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.
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