music-transcription 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +3 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.rdoc +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +28 -0
- data/Rakefile +54 -0
- data/bin/transcribe +176 -0
- data/lib/music-transcription.rb +20 -0
- data/lib/music-transcription/arrangement.rb +31 -0
- data/lib/music-transcription/instrument_config.rb +38 -0
- data/lib/music-transcription/interval.rb +66 -0
- data/lib/music-transcription/link.rb +115 -0
- data/lib/music-transcription/note.rb +156 -0
- data/lib/music-transcription/part.rb +128 -0
- data/lib/music-transcription/pitch.rb +297 -0
- data/lib/music-transcription/pitch_constants.rb +204 -0
- data/lib/music-transcription/profile.rb +105 -0
- data/lib/music-transcription/program.rb +136 -0
- data/lib/music-transcription/score.rb +122 -0
- data/lib/music-transcription/tempo.rb +44 -0
- data/lib/music-transcription/transition.rb +71 -0
- data/lib/music-transcription/value_change.rb +85 -0
- data/lib/music-transcription/version.rb +7 -0
- data/music-transcription.gemspec +36 -0
- data/samples/arrangements/glissando_test.yml +71 -0
- data/samples/arrangements/hip.yml +952 -0
- data/samples/arrangements/instrument_test.yml +119 -0
- data/samples/arrangements/legato_test.yml +237 -0
- data/samples/arrangements/make_glissando_test.rb +27 -0
- data/samples/arrangements/make_hip.rb +75 -0
- data/samples/arrangements/make_instrument_test.rb +34 -0
- data/samples/arrangements/make_legato_test.rb +37 -0
- data/samples/arrangements/make_missed_connection.rb +72 -0
- data/samples/arrangements/make_portamento_test.rb +27 -0
- data/samples/arrangements/make_slur_test.rb +37 -0
- data/samples/arrangements/make_song1.rb +84 -0
- data/samples/arrangements/make_song2.rb +69 -0
- data/samples/arrangements/missed_connection.yml +481 -0
- data/samples/arrangements/portamento_test.yml +71 -0
- data/samples/arrangements/slur_test.yml +237 -0
- data/samples/arrangements/song1.yml +640 -0
- data/samples/arrangements/song2.yml +429 -0
- data/spec/instrument_config_spec.rb +47 -0
- data/spec/interval_spec.rb +38 -0
- data/spec/link_spec.rb +22 -0
- data/spec/musicality_spec.rb +7 -0
- data/spec/note_spec.rb +65 -0
- data/spec/part_spec.rb +87 -0
- data/spec/pitch_spec.rb +139 -0
- data/spec/profile_spec.rb +24 -0
- data/spec/program_spec.rb +55 -0
- data/spec/score_spec.rb +55 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/transition_spec.rb +13 -0
- data/spec/value_change_spec.rb +19 -0
- metadata +239 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
|
4
|
+
# Define twelve pitch constants for each octave from octave 0 through 8.
|
5
|
+
|
6
|
+
# A pitch on octave 0
|
7
|
+
A0 = Pitch.new :octave => 0, :semitone => 9
|
8
|
+
# Bb pitch on octave
|
9
|
+
Bb0 = Pitch.new :octave => 0, :semitone => 10
|
10
|
+
# B pitch on octave 0
|
11
|
+
B0 = Pitch.new :octave => 0, :semitone => 11
|
12
|
+
|
13
|
+
# C pitch on octave 1
|
14
|
+
C1 = Pitch.new :octave => 1, :semitone => 0
|
15
|
+
# Db pitch on octave 1
|
16
|
+
Db1 = Pitch.new :octave => 1, :semitone => 1
|
17
|
+
# E pitch on octave 1
|
18
|
+
D1 = Pitch.new :octave => 1, :semitone => 2
|
19
|
+
# Eb pitch on octave 1
|
20
|
+
Eb1 = Pitch.new :octave => 1, :semitone => 3
|
21
|
+
# E pitch on octave 1
|
22
|
+
E1 = Pitch.new :octave => 1, :semitone => 4
|
23
|
+
# F pitch on octave 1
|
24
|
+
F1 = Pitch.new :octave => 1, :semitone => 5
|
25
|
+
# Gb pitch on octave 1
|
26
|
+
Gb1 = Pitch.new :octave => 1, :semitone => 6
|
27
|
+
# G pitch on octave 1
|
28
|
+
G1 = Pitch.new :octave => 1, :semitone => 7
|
29
|
+
# Ab pitch on octave 1
|
30
|
+
Ab1 = Pitch.new :octave => 1, :semitone => 8
|
31
|
+
# A pitch on octave 1
|
32
|
+
A1 = Pitch.new :octave => 1, :semitone => 9
|
33
|
+
# Bb pitch on octave
|
34
|
+
Bb1 = Pitch.new :octave => 1, :semitone => 10
|
35
|
+
# B pitch on octave 1
|
36
|
+
B1 = Pitch.new :octave => 1, :semitone => 11
|
37
|
+
|
38
|
+
# C pitch on octave 2
|
39
|
+
C2 = Pitch.new :octave => 2, :semitone => 0
|
40
|
+
# Db pitch on octave 2
|
41
|
+
Db2 = Pitch.new :octave => 2, :semitone => 1
|
42
|
+
# E pitch on octave 2
|
43
|
+
D2 = Pitch.new :octave => 2, :semitone => 2
|
44
|
+
# Eb pitch on octave 2
|
45
|
+
Eb2 = Pitch.new :octave => 2, :semitone => 3
|
46
|
+
# E pitch on octave 2
|
47
|
+
E2 = Pitch.new :octave => 2, :semitone => 4
|
48
|
+
# F pitch on octave 2
|
49
|
+
F2 = Pitch.new :octave => 2, :semitone => 5
|
50
|
+
# Gb pitch on octave 2
|
51
|
+
Gb2 = Pitch.new :octave => 2, :semitone => 6
|
52
|
+
# G pitch on octave 2
|
53
|
+
G2 = Pitch.new :octave => 2, :semitone => 7
|
54
|
+
# Ab pitch on octave 2
|
55
|
+
Ab2 = Pitch.new :octave => 2, :semitone => 8
|
56
|
+
# A pitch on octave 2
|
57
|
+
A2 = Pitch.new :octave => 2, :semitone => 9
|
58
|
+
# Bb pitch on octave
|
59
|
+
Bb2 = Pitch.new :octave => 2, :semitone => 10
|
60
|
+
# B pitch on octave 2
|
61
|
+
B2 = Pitch.new :octave => 2, :semitone => 11
|
62
|
+
|
63
|
+
# C pitch on octave 3
|
64
|
+
C3 = Pitch.new :octave => 3, :semitone => 0
|
65
|
+
# Db pitch on octave 3
|
66
|
+
Db3 = Pitch.new :octave => 3, :semitone => 1
|
67
|
+
# E pitch on octave 3
|
68
|
+
D3 = Pitch.new :octave => 3, :semitone => 2
|
69
|
+
# Eb pitch on octave 3
|
70
|
+
Eb3 = Pitch.new :octave => 3, :semitone => 3
|
71
|
+
# E pitch on octave 3
|
72
|
+
E3 = Pitch.new :octave => 3, :semitone => 4
|
73
|
+
# F pitch on octave 3
|
74
|
+
F3 = Pitch.new :octave => 3, :semitone => 5
|
75
|
+
# Gb pitch on octave 3
|
76
|
+
Gb3 = Pitch.new :octave => 3, :semitone => 6
|
77
|
+
# G pitch on octave 3
|
78
|
+
G3 = Pitch.new :octave => 3, :semitone => 7
|
79
|
+
# Ab pitch on octave 3
|
80
|
+
Ab3 = Pitch.new :octave => 3, :semitone => 8
|
81
|
+
# A pitch on octave 3
|
82
|
+
A3 = Pitch.new :octave => 3, :semitone => 9
|
83
|
+
# Bb pitch on octave
|
84
|
+
Bb3 = Pitch.new :octave => 3, :semitone => 10
|
85
|
+
# B pitch on octave 3
|
86
|
+
B3 = Pitch.new :octave => 3, :semitone => 11
|
87
|
+
|
88
|
+
# C pitch on octave 4
|
89
|
+
C4 = Pitch.new :octave => 4, :semitone => 0
|
90
|
+
# Db pitch on octave 4
|
91
|
+
Db4 = Pitch.new :octave => 4, :semitone => 1
|
92
|
+
# E pitch on octave 4
|
93
|
+
D4 = Pitch.new :octave => 4, :semitone => 2
|
94
|
+
# Eb pitch on octave 4
|
95
|
+
Eb4 = Pitch.new :octave => 4, :semitone => 3
|
96
|
+
# E pitch on octave 4
|
97
|
+
E4 = Pitch.new :octave => 4, :semitone => 4
|
98
|
+
# F pitch on octave 4
|
99
|
+
F4 = Pitch.new :octave => 4, :semitone => 5
|
100
|
+
# Gb pitch on octave 4
|
101
|
+
Gb4 = Pitch.new :octave => 4, :semitone => 6
|
102
|
+
# G pitch on octave 4
|
103
|
+
G4 = Pitch.new :octave => 4, :semitone => 7
|
104
|
+
# Ab pitch on octave 4
|
105
|
+
Ab4 = Pitch.new :octave => 4, :semitone => 8
|
106
|
+
# A pitch on octave 4
|
107
|
+
A4 = Pitch.new :octave => 4, :semitone => 9
|
108
|
+
# Bb pitch on octave
|
109
|
+
Bb4 = Pitch.new :octave => 4, :semitone => 10
|
110
|
+
# B pitch on octave 4
|
111
|
+
B4 = Pitch.new :octave => 4, :semitone => 11
|
112
|
+
|
113
|
+
# C pitch on octave 5
|
114
|
+
C5 = Pitch.new :octave => 5, :semitone => 0
|
115
|
+
# Db pitch on octave 5
|
116
|
+
Db5 = Pitch.new :octave => 5, :semitone => 1
|
117
|
+
# E pitch on octave 5
|
118
|
+
D5 = Pitch.new :octave => 5, :semitone => 2
|
119
|
+
# Eb pitch on octave 5
|
120
|
+
Eb5 = Pitch.new :octave => 5, :semitone => 3
|
121
|
+
# E pitch on octave 5
|
122
|
+
E5 = Pitch.new :octave => 5, :semitone => 4
|
123
|
+
# F pitch on octave 5
|
124
|
+
F5 = Pitch.new :octave => 5, :semitone => 5
|
125
|
+
# Gb pitch on octave 5
|
126
|
+
Gb5 = Pitch.new :octave => 5, :semitone => 6
|
127
|
+
# G pitch on octave 5
|
128
|
+
G5 = Pitch.new :octave => 5, :semitone => 7
|
129
|
+
# Ab pitch on octave 5
|
130
|
+
Ab5 = Pitch.new :octave => 5, :semitone => 8
|
131
|
+
# A pitch on octave 5
|
132
|
+
A5 = Pitch.new :octave => 5, :semitone => 9
|
133
|
+
# Bb pitch on octave
|
134
|
+
Bb5 = Pitch.new :octave => 5, :semitone => 10
|
135
|
+
# B pitch on octave 5
|
136
|
+
B5 = Pitch.new :octave => 5, :semitone => 11
|
137
|
+
|
138
|
+
# C pitch on octave 6
|
139
|
+
C6 = Pitch.new :octave => 6, :semitone => 0
|
140
|
+
# Db pitch on octave 6
|
141
|
+
Db6 = Pitch.new :octave => 6, :semitone => 1
|
142
|
+
# E pitch on octave 6
|
143
|
+
D6 = Pitch.new :octave => 6, :semitone => 2
|
144
|
+
# Eb pitch on octave 6
|
145
|
+
Eb6 = Pitch.new :octave => 6, :semitone => 3
|
146
|
+
# E pitch on octave 6
|
147
|
+
E6 = Pitch.new :octave => 6, :semitone => 4
|
148
|
+
# F pitch on octave 6
|
149
|
+
F6 = Pitch.new :octave => 6, :semitone => 5
|
150
|
+
# Gb pitch on octave 6
|
151
|
+
Gb6 = Pitch.new :octave => 6, :semitone => 6
|
152
|
+
# G pitch on octave 6
|
153
|
+
G6 = Pitch.new :octave => 6, :semitone => 7
|
154
|
+
# Ab pitch on octave 6
|
155
|
+
Ab6 = Pitch.new :octave => 6, :semitone => 8
|
156
|
+
# A pitch on octave 6
|
157
|
+
A6 = Pitch.new :octave => 6, :semitone => 9
|
158
|
+
# Bb pitch on octave
|
159
|
+
Bb6 = Pitch.new :octave => 6, :semitone => 10
|
160
|
+
# B pitch on octave 6
|
161
|
+
B6 = Pitch.new :octave => 6, :semitone => 11
|
162
|
+
|
163
|
+
# C pitch on octave 7
|
164
|
+
C7 = Pitch.new :octave => 7, :semitone => 0
|
165
|
+
# Db pitch on octave 7
|
166
|
+
Db7 = Pitch.new :octave => 7, :semitone => 1
|
167
|
+
# E pitch on octave 7
|
168
|
+
D7 = Pitch.new :octave => 7, :semitone => 2
|
169
|
+
# Eb pitch on octave 7
|
170
|
+
Eb7 = Pitch.new :octave => 7, :semitone => 3
|
171
|
+
# E pitch on octave 7
|
172
|
+
E7 = Pitch.new :octave => 7, :semitone => 4
|
173
|
+
# F pitch on octave 7
|
174
|
+
F7 = Pitch.new :octave => 7, :semitone => 5
|
175
|
+
# Gb pitch on octave 7
|
176
|
+
Gb7 = Pitch.new :octave => 7, :semitone => 6
|
177
|
+
# G pitch on octave 7
|
178
|
+
G7 = Pitch.new :octave => 7, :semitone => 7
|
179
|
+
# Ab pitch on octave 7
|
180
|
+
Ab7 = Pitch.new :octave => 7, :semitone => 8
|
181
|
+
# A pitch on octave 7
|
182
|
+
A7 = Pitch.new :octave => 7, :semitone => 9
|
183
|
+
# Bb pitch on octave
|
184
|
+
Bb7 = Pitch.new :octave => 7, :semitone => 10
|
185
|
+
# B pitch on octave 7
|
186
|
+
B7 = Pitch.new :octave => 7, :semitone => 11
|
187
|
+
|
188
|
+
# C pitch on octave 8
|
189
|
+
C8 = Pitch.new :octave => 8, :semitone => 0
|
190
|
+
|
191
|
+
# Contain pitch objects from A0 to C8
|
192
|
+
PITCHES = [
|
193
|
+
A0, Bb0, B0,
|
194
|
+
C1, Db1, D1, Eb1, E1, F1, Gb1, G1, Ab1, A1, Bb1, B1,
|
195
|
+
C2, Db2, D2, Eb2, E2, F2, Gb2, G2, Ab2, A2, Bb2, B2,
|
196
|
+
C3, Db3, D3, Eb3, E3, F3, Gb3, G3, Ab3, A3, Bb3, B3,
|
197
|
+
C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4, A4, Bb4, B4,
|
198
|
+
C5, Db5, D5, Eb5, E5, F5, Gb5, G5, Ab5, A5, Bb5, B5,
|
199
|
+
C6, Db6, D6, Eb6, E6, F6, Gb6, G6, Ab6, A6, Bb6, B6,
|
200
|
+
C7, Db7, D7, Eb7, E7, F7, Gb7, G7, Ab7, A7, Bb7, B7,
|
201
|
+
C8
|
202
|
+
]
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
|
4
|
+
# Represent a setting that can change over time.
|
5
|
+
#
|
6
|
+
# @author James Tunnell
|
7
|
+
#
|
8
|
+
class Profile
|
9
|
+
include Hashmake::HashMakeable
|
10
|
+
|
11
|
+
attr_accessor :start_value, :value_changes
|
12
|
+
|
13
|
+
# hashed-arg specs (for hash-makeable idiom)
|
14
|
+
ARG_SPECS = {
|
15
|
+
:start_value => arg_spec(:reqd => true),
|
16
|
+
:value_changes => arg_spec_hash(:reqd => false, :type => ValueChange)
|
17
|
+
}
|
18
|
+
|
19
|
+
# A new instance of Profile.
|
20
|
+
#
|
21
|
+
# @param [Hash] args Hashed args. Required key is :start_value. Optional key is :value_changes.
|
22
|
+
def initialize args
|
23
|
+
hash_make args, Profile::ARG_SPECS
|
24
|
+
end
|
25
|
+
|
26
|
+
# Compare to another Profile object.
|
27
|
+
def == other
|
28
|
+
(self.class == other.class) &&
|
29
|
+
(self.start_value == other.start_value) &&
|
30
|
+
(self.value_changes == other.value_changes)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Produce an identical Profile object.
|
34
|
+
def clone
|
35
|
+
Profile.new(:start_value => @start_value, :value_changes => @value_changes.clone)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if start value and value changes all are between given A and B.
|
39
|
+
def values_between? a, b
|
40
|
+
is_ok = self.start_value.between?(a,b)
|
41
|
+
|
42
|
+
if is_ok
|
43
|
+
self.value_changes.each do |offset, setting|
|
44
|
+
setting.value.between?(a,b)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
return is_ok
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns true if start value and value changes all are greater than zero.
|
51
|
+
def values_positive?
|
52
|
+
is_ok = self.start_value > 0.0
|
53
|
+
|
54
|
+
if is_ok
|
55
|
+
self.value_changes.each do |offset, setting|
|
56
|
+
setting.value > 0.0
|
57
|
+
end
|
58
|
+
end
|
59
|
+
return is_ok
|
60
|
+
end
|
61
|
+
|
62
|
+
def clone_and_collate computer_class, program_segments
|
63
|
+
new_profile = Profile.new :start_value => start_value
|
64
|
+
|
65
|
+
segment_start_offset = 0.0
|
66
|
+
comp = computer_class.new(self)
|
67
|
+
|
68
|
+
program_segments.each do |seg|
|
69
|
+
# figure which dynamics to keep/modify
|
70
|
+
changes = Marshal.load(Marshal.dump(value_changes))
|
71
|
+
changes.keep_if {|offset,change| seg.include?(offset) }
|
72
|
+
changes.each do |offset, change|
|
73
|
+
if(offset + change.transition.duration) > seg.last
|
74
|
+
change.transition.duration = seg.last - offset
|
75
|
+
change.value = comp.value_at seg.last
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# find & add segment start value first
|
80
|
+
value = comp.value_at seg.first
|
81
|
+
offset = segment_start_offset
|
82
|
+
new_profile.value_changes[offset] = value_change(value)
|
83
|
+
|
84
|
+
# add changes to part, adjusting for segment start offset
|
85
|
+
changes.each do |offset2, change|
|
86
|
+
offset3 = (offset2 - seg.first) + segment_start_offset
|
87
|
+
new_profile.value_changes[offset3] = change
|
88
|
+
end
|
89
|
+
|
90
|
+
segment_start_offset += (seg.last - seg.first)
|
91
|
+
end
|
92
|
+
|
93
|
+
return new_profile
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module_function
|
98
|
+
|
99
|
+
# Create a Profile object
|
100
|
+
def profile start_value, value_changes = {}
|
101
|
+
return Profile.new(:start_value => start_value, :value_changes => value_changes)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
|
4
|
+
# Program defines markers (by starting note offset) and subprograms (list which markers are played).
|
5
|
+
#
|
6
|
+
# @author James Tunnell
|
7
|
+
#
|
8
|
+
class Program
|
9
|
+
include Hashmake::HashMakeable
|
10
|
+
attr_reader :segments
|
11
|
+
|
12
|
+
# hashed-arg specs (for hash-makeable idiom)
|
13
|
+
ARG_SPECS = {
|
14
|
+
:segments => arg_spec_array(:reqd => false, :type => Range)
|
15
|
+
}
|
16
|
+
|
17
|
+
# A new instance of Program.
|
18
|
+
# @param [Hash] args Hashed arguments. Required key is :segments.
|
19
|
+
def initialize args={}
|
20
|
+
hash_make args
|
21
|
+
end
|
22
|
+
|
23
|
+
# Assign program segments. Each segment is a Range to specify which range of
|
24
|
+
# notes from a score should be played.
|
25
|
+
#
|
26
|
+
# @param [Array] segments An array of program segements. Each segment is a
|
27
|
+
# Range to specify which range of
|
28
|
+
# @raise [ArgumentError] if segments is not an Array.
|
29
|
+
# @raise [ArgumentError] if segments contains a non-Range
|
30
|
+
#
|
31
|
+
def segments= segments
|
32
|
+
ARG_SPECS[:segments].validate_value segments
|
33
|
+
@segments = segments
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Float] the starting note offset for the program
|
37
|
+
def start
|
38
|
+
@segments.first.first
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Float] the ending note offset for the program
|
42
|
+
def stop
|
43
|
+
@segments.last.last
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Float] the sum of all program segment lengths
|
47
|
+
def length
|
48
|
+
segments.inject(0.0) { |length, segment| length + (segment.last - segment.first) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# compare to another Program
|
52
|
+
def == other
|
53
|
+
# raise ArgumentError, "program is invalid" if !self.valid?
|
54
|
+
return @segments == other.segments
|
55
|
+
end
|
56
|
+
|
57
|
+
def include? offset
|
58
|
+
@segments.each do |segment|
|
59
|
+
if segment.include?(offset)
|
60
|
+
return true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
# For the given note elapsed, what will the note offset be?
|
67
|
+
#
|
68
|
+
def note_offset_for elapsed
|
69
|
+
raise ArgumentError, "elapsed #{elapsed} is less than 0.0" if elapsed < 0.0
|
70
|
+
raise ArgumentError, "elapsed #{elapsed} is greater than program length" if elapsed > self.length
|
71
|
+
|
72
|
+
so_far = 0.0
|
73
|
+
|
74
|
+
@segments.each do |segment|
|
75
|
+
segment_length = segment.last - segment.first
|
76
|
+
|
77
|
+
if (segment_length + so_far) > elapsed
|
78
|
+
return segment.first + (elapsed - so_far)
|
79
|
+
else
|
80
|
+
so_far += segment_length
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
raise "offset not determined even though the given elapsed is less than program length!"
|
85
|
+
end
|
86
|
+
|
87
|
+
# For the given note offset in the score, how much note will have elapsed to
|
88
|
+
# get there according to the program?
|
89
|
+
#
|
90
|
+
def note_elapsed_at offset
|
91
|
+
raise ArgumentError, "offset #{offset} is not included in program" if !self.include?(offset)
|
92
|
+
|
93
|
+
elapsed = 0.0
|
94
|
+
|
95
|
+
@segments.each do |segment|
|
96
|
+
if segment.include?(offset)
|
97
|
+
elapsed += (offset - segment.first)
|
98
|
+
break
|
99
|
+
else
|
100
|
+
elapsed += (segment.last - segment.first)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
return elapsed
|
105
|
+
end
|
106
|
+
|
107
|
+
# For the given note offset in the score, how much time will have elapsed to
|
108
|
+
# get there according to the program?
|
109
|
+
#
|
110
|
+
def time_elapsed_at offset, note_time_converter
|
111
|
+
raise ArgumentError, "offset #{offset} is not included in program" if !self.include?(offset)
|
112
|
+
|
113
|
+
elapsed = 0.0
|
114
|
+
|
115
|
+
@segments.each do |segment|
|
116
|
+
if segment.include?(offset)
|
117
|
+
elapsed += note_time_converter.time_elapsed(segment.first, offset)
|
118
|
+
break
|
119
|
+
else
|
120
|
+
elapsed += note_time_converter.time_elapsed(segment.first, segment.last)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
return elapsed
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
module_function
|
130
|
+
|
131
|
+
def program segments
|
132
|
+
Program.new(:segments => segments)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Music
|
2
|
+
module Transcription
|
3
|
+
|
4
|
+
# Abstraction of a musical score. Contains parts and a program.
|
5
|
+
#
|
6
|
+
# @author James Tunnell
|
7
|
+
#
|
8
|
+
# @!attribute [rw] parts
|
9
|
+
# @return [Array] Score parts.
|
10
|
+
#
|
11
|
+
# @!attribute [rw] program
|
12
|
+
# @return [Array] Score program.
|
13
|
+
#
|
14
|
+
class Score
|
15
|
+
include Hashmake::HashMakeable
|
16
|
+
attr_reader :parts, :program
|
17
|
+
|
18
|
+
# hashed-arg specs (for hash-makeable idiom)
|
19
|
+
ARG_SPECS = {
|
20
|
+
:parts => arg_spec_hash(:reqd => false, :type => Part),
|
21
|
+
:program => arg_spec(:reqd => false, :type => Program, :default => ->(){ Program.new }),
|
22
|
+
}
|
23
|
+
|
24
|
+
# A new instance of Score.
|
25
|
+
# @param [Hash] args Hashed arguments. Optional keys are :program and :parts.
|
26
|
+
def initialize args={}
|
27
|
+
hash_make args, ARG_SPECS
|
28
|
+
end
|
29
|
+
|
30
|
+
def clone
|
31
|
+
Marshal.load(Marshal.dump(self))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compare the equality of another Score object.
|
35
|
+
def ==(other)
|
36
|
+
return (@program == other.program) &&
|
37
|
+
(@parts == other.parts)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the score parts.
|
41
|
+
# @param [Hash] parts The score parts, mapped to IDs.
|
42
|
+
# @raise [ArgumentError] if notes is not a Hash.
|
43
|
+
# @raise [ArgumentError] if parts contain a non-Part object.
|
44
|
+
def parts= parts
|
45
|
+
Score::ARG_SPECS[:parts].validate_value parts
|
46
|
+
@parts = parts
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set the score program, which determines which defines sections and how they
|
50
|
+
# are played.
|
51
|
+
# @param [Program] program The score program.
|
52
|
+
# @raise [ArgumentError] if tempos is not a Program.
|
53
|
+
def program= program
|
54
|
+
Score::ARG_SPECS[:program].validate_value program
|
55
|
+
@program = program
|
56
|
+
end
|
57
|
+
|
58
|
+
# Find the start of a score. The start will be at then start of whichever part begins
|
59
|
+
# first, or 0 if no parts have been added.
|
60
|
+
def start
|
61
|
+
sos = 0.0
|
62
|
+
|
63
|
+
@parts.each do |id,part|
|
64
|
+
sop = part.start
|
65
|
+
sos = sop if sop > sos
|
66
|
+
end
|
67
|
+
|
68
|
+
return sos
|
69
|
+
end
|
70
|
+
|
71
|
+
# Find the end of a score. The end will be at then end of whichever part ends
|
72
|
+
# last, or 0 if no parts have been added.
|
73
|
+
def end
|
74
|
+
eos = 0.0
|
75
|
+
|
76
|
+
@parts.each do |id,part|
|
77
|
+
eop = part.end
|
78
|
+
eos = eop if eop > eos
|
79
|
+
end
|
80
|
+
|
81
|
+
return eos
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Score with a tempo profile.
|
86
|
+
#
|
87
|
+
# @author James Tunnell
|
88
|
+
#
|
89
|
+
# @!attribute [rw] tempo_profile
|
90
|
+
# @return [Profile] The tempo profile.
|
91
|
+
#
|
92
|
+
class TempoScore < Score
|
93
|
+
include Hashmake::HashMakeable
|
94
|
+
attr_reader :tempo_profile
|
95
|
+
|
96
|
+
# hashed-arg specs (for hash-makeable idiom)
|
97
|
+
ARG_SPECS = {
|
98
|
+
:tempo_profile => arg_spec(:reqd => true, :type => Profile, :validator => ->(a){ a.values_positive? }),
|
99
|
+
}
|
100
|
+
|
101
|
+
# A new instance of Score.
|
102
|
+
# @param [Hash] args Hashed arguments. Required key is :tempo_profile.
|
103
|
+
def initialize args={}
|
104
|
+
hash_make args
|
105
|
+
super(args)
|
106
|
+
end
|
107
|
+
|
108
|
+
def clone
|
109
|
+
Marshal.load(Marshal.dump(self))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Set the score tempo Profile.
|
113
|
+
# @param [Profile] tempo_profile The tempo profile for the score.
|
114
|
+
# @raise [ArgumentError] if tempo_profile is not a Profile.
|
115
|
+
def tempo_profile= tempo_profile
|
116
|
+
TempoScore::ARG_SPECS[:tempo_profile].validate_value tempo_profile
|
117
|
+
@tempo_profile = tempo_profile
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|