music-transcription 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby-version +1 -0
- data/bin/transcribe +0 -1
- data/lib/music-transcription.rb +1 -5
- data/lib/music-transcription/accent.rb +43 -0
- data/lib/music-transcription/errors.rb +5 -0
- data/lib/music-transcription/link.rb +46 -89
- data/lib/music-transcription/note.rb +66 -110
- data/lib/music-transcription/part.rb +14 -88
- data/lib/music-transcription/pitch.rb +39 -41
- data/lib/music-transcription/pitch_constants.rb +88 -88
- data/lib/music-transcription/profile.rb +5 -20
- data/lib/music-transcription/program.rb +2 -31
- data/lib/music-transcription/score.rb +28 -65
- data/lib/music-transcription/tempo.rb +3 -15
- data/lib/music-transcription/transition.rb +37 -53
- data/lib/music-transcription/value_change.rb +9 -35
- data/lib/music-transcription/version.rb +1 -1
- data/music-transcription.gemspec +5 -10
- data/spec/link_spec.rb +14 -10
- data/spec/note_spec.rb +13 -40
- data/spec/part_spec.rb +4 -72
- data/spec/pitch_spec.rb +39 -39
- data/spec/profile_spec.rb +4 -7
- data/spec/program_spec.rb +6 -6
- data/spec/score_spec.rb +9 -14
- data/spec/spec_helper.rb +2 -14
- data/spec/transition_spec.rb +25 -7
- data/spec/value_change_spec.rb +4 -4
- metadata +21 -25
- data/lib/music-transcription/arrangement.rb +0 -31
- data/lib/music-transcription/instrument_config.rb +0 -38
- data/lib/music-transcription/interval.rb +0 -66
- data/spec/instrument_config_spec.rb +0 -47
- data/spec/interval_spec.rb +0 -38
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/bin/transcribe
CHANGED
data/lib/music-transcription.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'hashmake'
|
2
|
-
|
3
1
|
# basic core classes
|
4
2
|
require 'music-transcription/version'
|
5
3
|
|
@@ -7,7 +5,7 @@ require 'music-transcription/version'
|
|
7
5
|
require 'music-transcription/pitch'
|
8
6
|
require 'music-transcription/pitch_constants'
|
9
7
|
require 'music-transcription/link'
|
10
|
-
require 'music-transcription/
|
8
|
+
require 'music-transcription/accent'
|
11
9
|
require 'music-transcription/note'
|
12
10
|
require 'music-transcription/transition'
|
13
11
|
require 'music-transcription/value_change'
|
@@ -16,5 +14,3 @@ require 'music-transcription/part'
|
|
16
14
|
require 'music-transcription/program'
|
17
15
|
require 'music-transcription/tempo'
|
18
16
|
require 'music-transcription/score'
|
19
|
-
require 'music-transcription/instrument_config'
|
20
|
-
require 'music-transcription/arrangement'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
|
4
|
+
# Defines a note accent (stacatto, tenuto, etc.)
|
5
|
+
#
|
6
|
+
# @author James Tunnell
|
7
|
+
#
|
8
|
+
class Accent
|
9
|
+
def ==(other)
|
10
|
+
self.class == other.class
|
11
|
+
end
|
12
|
+
|
13
|
+
def clone
|
14
|
+
self.class.new
|
15
|
+
end
|
16
|
+
|
17
|
+
class Stacatto < Accent
|
18
|
+
def to_s
|
19
|
+
return "."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Stacattissimo < Accent
|
24
|
+
def to_s
|
25
|
+
return "'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Martellato < Accent
|
30
|
+
def to_s
|
31
|
+
return "^"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Tenuto < Accent
|
36
|
+
def to_s
|
37
|
+
return "_"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -8,107 +8,64 @@ module Transcription
|
|
8
8
|
# @!attribute [rw] target_pitch
|
9
9
|
# @return [Pitch] The pitch of the note which is being connected to.
|
10
10
|
#
|
11
|
-
# @!attribute [rw] relationship
|
12
|
-
# @return [Symbol] The relationship between the current note and a consecutive
|
13
|
-
# note. Valid values are RELATIONSHIP_NONE, RELATIONSHIP_TIE,
|
14
|
-
# RELATIONSHIP_SLUR, RELATIONSHIP_LEGATO, RELATIONSHIP_GLISSANDO,
|
15
|
-
# and RELATIONSHIP_PORTAMENTO.
|
16
|
-
#
|
17
11
|
class Link
|
18
|
-
|
19
|
-
|
20
|
-
# no relationship with the following note
|
21
|
-
RELATIONSHIP_NONE = :none
|
22
|
-
# tie to the following note
|
23
|
-
RELATIONSHIP_TIE = :tie
|
24
|
-
# play notes continuously and don't rearticulate
|
25
|
-
RELATIONSHIP_SLUR = :slur
|
26
|
-
# play notes continuously and do rearticulate
|
27
|
-
RELATIONSHIP_LEGATO = :legato
|
28
|
-
# play an uninterrupted slide through a series of consecutive tones to the next note.
|
29
|
-
RELATIONSHIP_GLISSANDO = :glissando
|
30
|
-
# play an uninterrupted glide to the next note.
|
31
|
-
RELATIONSHIP_PORTAMENTO = :portamento
|
12
|
+
attr_accessor :target_pitch
|
32
13
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
RELATIONSHIP_TIE,
|
37
|
-
RELATIONSHIP_SLUR,
|
38
|
-
RELATIONSHIP_LEGATO,
|
39
|
-
RELATIONSHIP_GLISSANDO,
|
40
|
-
RELATIONSHIP_PORTAMENTO
|
41
|
-
]
|
42
|
-
|
43
|
-
# hashed-arg specs (for hash-makeable idiom)
|
44
|
-
ARG_SPECS = {
|
45
|
-
:target_pitch => arg_spec(:reqd => false, :type => Pitch, :default => ->(){ Pitch.new }),
|
46
|
-
:relationship => arg_spec(:reqd => false, :type => Symbol, :default => RELATIONSHIP_NONE, :validator => ->(a){ RELATIONSHIPS.include?(a)}),
|
47
|
-
}
|
14
|
+
def initialize target_pitch
|
15
|
+
@target_pitch = target_pitch
|
16
|
+
end
|
48
17
|
|
49
|
-
|
50
|
-
|
51
|
-
# A new instance of Link.
|
52
|
-
# @param [Hash] args Hashed arguments. See ARG_SPECS for details about valid keys.
|
53
|
-
def initialize args={}
|
54
|
-
hash_make args
|
18
|
+
def ==(other)
|
19
|
+
self.class == other.class && self.target_pitch == other.target_pitch
|
55
20
|
end
|
56
21
|
|
57
|
-
# Produce an identical Link object.
|
58
22
|
def clone
|
59
|
-
|
23
|
+
self.class.new @target_pitch.clone
|
60
24
|
end
|
61
25
|
|
62
|
-
|
63
|
-
|
64
|
-
return (@target_pitch == other.target_pitch) && (@relationship == other.relationship)
|
26
|
+
def to_s
|
27
|
+
return @target_pitch
|
65
28
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
29
|
+
|
30
|
+
class Slur < Link
|
31
|
+
def initialize target_pitch
|
32
|
+
super(target_pitch)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
return "=" + super()
|
37
|
+
end
|
73
38
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
39
|
+
|
40
|
+
class Legato < Link
|
41
|
+
def initialize target_pitch
|
42
|
+
super(target_pitch)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
return "-" + super()
|
47
|
+
end
|
83
48
|
end
|
84
49
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
def
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
def
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
# helper method to create a Link object with SLUR relationship.
|
105
|
-
def slur pitch
|
106
|
-
Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_SLUR)
|
107
|
-
end
|
108
|
-
|
109
|
-
# helper method to create a Link object with TIE relationship.
|
110
|
-
def tie pitch
|
111
|
-
Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_TIE)
|
50
|
+
class Glissando < Link
|
51
|
+
def initialize target_pitch
|
52
|
+
super(target_pitch)
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
return "~" + super()
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Portamento < Link
|
61
|
+
def initialize target_pitch
|
62
|
+
super(target_pitch)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
return "/" + super()
|
67
|
+
end
|
68
|
+
end
|
112
69
|
end
|
113
70
|
|
114
71
|
end
|
@@ -1,156 +1,112 @@
|
|
1
1
|
module Music
|
2
2
|
module Transcription
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
# Abstraction of a musical note. The note can contain zero or more pitches,
|
7
|
+
# with links to a pitches in a following note. The note also has an accent,
|
8
|
+
# which must be one of Note::ACCENTS.
|
8
9
|
#
|
9
10
|
# @author James Tunnell
|
10
11
|
#
|
11
12
|
# @!attribute [rw] duration
|
12
13
|
# @return [Numeric] The duration (in, say note length or time), greater than 0.0.
|
13
14
|
#
|
14
|
-
# @!attribute [
|
15
|
-
# @return [
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# @!attribute [rw] attack
|
19
|
-
# @return [Numeric] The amount of attack, from 0.0 (less) to 1.0 (more).
|
20
|
-
# Attack controls how quickly a note's loudness increases
|
21
|
-
# at the start.
|
22
|
-
#
|
23
|
-
# @!attribute [rw] sustain
|
24
|
-
# @return [Numeric] The amount of sustain, from 0.0 (less) to 1.0 (more).
|
25
|
-
# Sustain controls how much the note's loudness is
|
26
|
-
# sustained after the attack.
|
15
|
+
# @!attribute [r] pitches
|
16
|
+
# @return [Set] The pitches that are part of the note and can link to
|
17
|
+
# pitches in a following note.
|
27
18
|
#
|
28
|
-
# @!attribute [
|
29
|
-
# @return [
|
30
|
-
#
|
31
|
-
#
|
19
|
+
# @!attribute [r] links
|
20
|
+
# @return [Hash] Maps pitches in the current note to pitches in the following
|
21
|
+
# note, by some link class, like Link::Slur.
|
22
|
+
#
|
23
|
+
# @!attribute [rw] accent
|
24
|
+
# @return [Accent] The accent type, which must be one of Note::ACCENTS.
|
32
25
|
#
|
33
26
|
class Note
|
34
|
-
|
35
|
-
|
27
|
+
attr_reader :duration, :pitches, :links
|
28
|
+
attr_accessor :accent
|
36
29
|
|
37
|
-
# hashed-arg specs (for hash-makeable idiom)
|
38
|
-
ARG_SPECS = {
|
39
|
-
:duration => arg_spec(:type => Numeric, :reqd => true, :validator => ->(a){ a > 0 } ),
|
40
|
-
:intervals => arg_spec_array(:type => Interval, :reqd => false),
|
41
|
-
:sustain => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5),
|
42
|
-
:attack => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5),
|
43
|
-
:separation => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5),
|
44
|
-
}
|
45
|
-
|
46
30
|
# A new instance of Note.
|
47
|
-
|
48
|
-
|
49
|
-
|
31
|
+
def initialize duration, pitches = [], links: {}, accent: nil
|
32
|
+
self.duration = duration
|
33
|
+
@pitches = Set.new(pitches)
|
34
|
+
@links = links
|
35
|
+
self.accent = accent
|
50
36
|
end
|
51
37
|
|
52
38
|
# Compare the equality of another Note object.
|
53
39
|
def == other
|
54
40
|
return (@duration == other.duration) &&
|
55
|
-
(
|
56
|
-
(@
|
57
|
-
(@
|
58
|
-
(@separation == other.separation)
|
41
|
+
(self.pitches == other.pitches) &&
|
42
|
+
(@links == other.links) &&
|
43
|
+
(@accent == other.accent)
|
59
44
|
end
|
60
45
|
|
61
46
|
# Set the note duration.
|
62
47
|
# @param [Numeric] duration The duration to use.
|
63
48
|
# @raise [ArgumentError] if duration is not greater than 0.
|
64
49
|
def duration= duration
|
65
|
-
|
50
|
+
raise ValueNotPositiveError if duration <= 0
|
66
51
|
@duration = duration
|
67
|
-
end
|
68
|
-
|
69
|
-
# Set the note sustain.
|
70
|
-
# @param [Numeric] sustain The sustain of the note.
|
71
|
-
# @raise [ArgumentError] if sustain is not a Numeric.
|
72
|
-
# @raise [RangeError] if sustain is outside the range 0.0..1.0.
|
73
|
-
def sustain= sustain
|
74
|
-
ARG_SPECS[:sustain].validate_value sustain
|
75
|
-
@sustain = sustain
|
76
|
-
end
|
77
|
-
|
78
|
-
# Set the note attack.
|
79
|
-
# @param [Numeric] attack The attack of the note.
|
80
|
-
# @raise [ArgumentError] if attack is not a Numeric.
|
81
|
-
# @raise [RangeError] if attack is outside the range 0.0..1.0.
|
82
|
-
def attack= attack
|
83
|
-
ARG_SPECS[:attack].validate_value attack
|
84
|
-
@attack = attack
|
85
|
-
end
|
86
|
-
|
87
|
-
# Set the note separation.
|
88
|
-
# @param [Numeric] separation The separation of the note.
|
89
|
-
# @raise [ArgumentError] if separation is not a Numeric.
|
90
|
-
# @raise [RangeError] if separation is outside the range 0.0..1.0.
|
91
|
-
def separation= separation
|
92
|
-
ARG_SPECS[:separation].validate_value separation
|
93
|
-
@separation = separation
|
94
52
|
end
|
95
53
|
|
96
54
|
# Produce an identical Note object.
|
97
55
|
def clone
|
98
56
|
Marshal.load(Marshal.dump(self))
|
99
57
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
# same interval), removing all but the last occurance.
|
104
|
-
def remove_duplicates
|
105
|
-
# in case of duplicate notes
|
106
|
-
intervals_to_remove = Set.new
|
107
|
-
for i in (0...@intervals.count).entries.reverse
|
108
|
-
@intervals.each_index do |j|
|
109
|
-
if j < i
|
110
|
-
if @intervals[i].pitch == @intervals[j].pitch
|
111
|
-
intervals_to_remove.add @intervals[j]
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
@intervals.delete_if { |interval| intervals_to_remove.include? interval}
|
117
|
-
|
118
|
-
# in case of duplicate links
|
119
|
-
for i in (0...@intervals.count).entries.reverse
|
120
|
-
@intervals.each_index do |j|
|
121
|
-
if j < i
|
122
|
-
if @intervals[i].linked? && @intervals[j].linked? && @intervals[i].link.target_pitch == @intervals[j].link.target_pitch
|
123
|
-
@intervals[j].link = Link.new
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
58
|
+
|
59
|
+
def transpose_pitches_only pitch_diff
|
60
|
+
self.clone.transpose_pitches! pitch_diff, transpose_link
|
128
61
|
end
|
129
62
|
|
130
|
-
def
|
131
|
-
|
63
|
+
def transpose_pitches_only! pitch_diff
|
64
|
+
@pitches = @pitches.map {|pitch| pitch + pitch_diff}
|
65
|
+
new_links = {}
|
66
|
+
@links.each_pair do |k,v|
|
67
|
+
new_links[k + pitch_diff] = v
|
68
|
+
end
|
69
|
+
@links = new_links
|
70
|
+
return self
|
71
|
+
end
|
72
|
+
|
73
|
+
def transpose_pitches_and_links pitch_diff
|
74
|
+
self.clone.transpose_pitches_and_links! pitch_diff
|
132
75
|
end
|
133
76
|
|
134
|
-
def
|
135
|
-
@
|
136
|
-
|
137
|
-
|
77
|
+
def transpose_pitches_and_links! pitch_diff
|
78
|
+
@pitches = @pitches.map {|pitch| pitch + pitch_diff}
|
79
|
+
new_links = {}
|
80
|
+
@links.each_pair do |k,v|
|
81
|
+
v.target_pitch += pitch_diff
|
82
|
+
new_links[k + pitch_diff] = v
|
138
83
|
end
|
84
|
+
@links = new_links
|
139
85
|
return self
|
140
86
|
end
|
141
87
|
|
142
88
|
def to_s
|
143
|
-
|
89
|
+
output = @duration.to_s
|
90
|
+
if @pitches.any?
|
91
|
+
output += "@"
|
92
|
+
@pitches[0...-1].each do |pitch|
|
93
|
+
output += pitch.to_s
|
94
|
+
if @links.has_key? pitch
|
95
|
+
output += @links[pitch].to_s
|
96
|
+
end
|
97
|
+
output += ","
|
98
|
+
end
|
99
|
+
|
100
|
+
last_pitch = @pitches[-1]
|
101
|
+
output += last_pitch.to_s
|
102
|
+
if @links.has_key? last_pitch
|
103
|
+
output += @links[last_pitch].to_s
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
return output
|
144
108
|
end
|
145
109
|
end
|
146
110
|
|
147
|
-
module_function
|
148
|
-
|
149
|
-
def note duration, intervals = [], other_args = {}
|
150
|
-
Note.new(
|
151
|
-
{ :duration => duration, :intervals => intervals }.merge other_args
|
152
|
-
)
|
153
|
-
end
|
154
|
-
|
155
111
|
end
|
156
112
|
end
|