music-transcription 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.document +3 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/ChangeLog.rdoc +4 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.rdoc +28 -0
  9. data/Rakefile +54 -0
  10. data/bin/transcribe +176 -0
  11. data/lib/music-transcription.rb +20 -0
  12. data/lib/music-transcription/arrangement.rb +31 -0
  13. data/lib/music-transcription/instrument_config.rb +38 -0
  14. data/lib/music-transcription/interval.rb +66 -0
  15. data/lib/music-transcription/link.rb +115 -0
  16. data/lib/music-transcription/note.rb +156 -0
  17. data/lib/music-transcription/part.rb +128 -0
  18. data/lib/music-transcription/pitch.rb +297 -0
  19. data/lib/music-transcription/pitch_constants.rb +204 -0
  20. data/lib/music-transcription/profile.rb +105 -0
  21. data/lib/music-transcription/program.rb +136 -0
  22. data/lib/music-transcription/score.rb +122 -0
  23. data/lib/music-transcription/tempo.rb +44 -0
  24. data/lib/music-transcription/transition.rb +71 -0
  25. data/lib/music-transcription/value_change.rb +85 -0
  26. data/lib/music-transcription/version.rb +7 -0
  27. data/music-transcription.gemspec +36 -0
  28. data/samples/arrangements/glissando_test.yml +71 -0
  29. data/samples/arrangements/hip.yml +952 -0
  30. data/samples/arrangements/instrument_test.yml +119 -0
  31. data/samples/arrangements/legato_test.yml +237 -0
  32. data/samples/arrangements/make_glissando_test.rb +27 -0
  33. data/samples/arrangements/make_hip.rb +75 -0
  34. data/samples/arrangements/make_instrument_test.rb +34 -0
  35. data/samples/arrangements/make_legato_test.rb +37 -0
  36. data/samples/arrangements/make_missed_connection.rb +72 -0
  37. data/samples/arrangements/make_portamento_test.rb +27 -0
  38. data/samples/arrangements/make_slur_test.rb +37 -0
  39. data/samples/arrangements/make_song1.rb +84 -0
  40. data/samples/arrangements/make_song2.rb +69 -0
  41. data/samples/arrangements/missed_connection.yml +481 -0
  42. data/samples/arrangements/portamento_test.yml +71 -0
  43. data/samples/arrangements/slur_test.yml +237 -0
  44. data/samples/arrangements/song1.yml +640 -0
  45. data/samples/arrangements/song2.yml +429 -0
  46. data/spec/instrument_config_spec.rb +47 -0
  47. data/spec/interval_spec.rb +38 -0
  48. data/spec/link_spec.rb +22 -0
  49. data/spec/musicality_spec.rb +7 -0
  50. data/spec/note_spec.rb +65 -0
  51. data/spec/part_spec.rb +87 -0
  52. data/spec/pitch_spec.rb +139 -0
  53. data/spec/profile_spec.rb +24 -0
  54. data/spec/program_spec.rb +55 -0
  55. data/spec/score_spec.rb +55 -0
  56. data/spec/spec_helper.rb +23 -0
  57. data/spec/transition_spec.rb +13 -0
  58. data/spec/value_change_spec.rb +19 -0
  59. 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