stretto 0.6.1

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 (77) hide show
  1. data/CHANGELOG.markdown +0 -0
  2. data/README.markdown +67 -0
  3. data/Rakefile +9 -0
  4. data/lib/stretto.rb +14 -0
  5. data/lib/stretto/grammar/channel_pressure_grammar.treetop +9 -0
  6. data/lib/stretto/grammar/chord_grammar.treetop +60 -0
  7. data/lib/stretto/grammar/controller_change_grammar.treetop +9 -0
  8. data/lib/stretto/grammar/duration_grammar.treetop +60 -0
  9. data/lib/stretto/grammar/grammar_helper.rb +6 -0
  10. data/lib/stretto/grammar/harmonic_chord_grammar.treetop +15 -0
  11. data/lib/stretto/grammar/harmony_grammar.treetop +15 -0
  12. data/lib/stretto/grammar/instrument_grammar.treetop +9 -0
  13. data/lib/stretto/grammar/key_signature_grammar.treetop +10 -0
  14. data/lib/stretto/grammar/layer_change_grammar.treetop +9 -0
  15. data/lib/stretto/grammar/measure_grammar.treetop +7 -0
  16. data/lib/stretto/grammar/note_grammar.treetop +32 -0
  17. data/lib/stretto/grammar/pitch_bend_grammar.treetop +7 -0
  18. data/lib/stretto/grammar/polyphonic_pressure_grammar.treetop +9 -0
  19. data/lib/stretto/grammar/rest_grammar.treetop +9 -0
  20. data/lib/stretto/grammar/stretto_grammar.treetop +62 -0
  21. data/lib/stretto/grammar/tempo_grammar.treetop +9 -0
  22. data/lib/stretto/grammar/timing_grammar.treetop +9 -0
  23. data/lib/stretto/grammar/tokens/attack_decay_token.rb +42 -0
  24. data/lib/stretto/grammar/tokens/chord_token.rb +67 -0
  25. data/lib/stretto/grammar/tokens/controller_change_token.rb +26 -0
  26. data/lib/stretto/grammar/tokens/duration_token.rb +55 -0
  27. data/lib/stretto/grammar/tokens/harmonic_chord_token.rb +25 -0
  28. data/lib/stretto/grammar/tokens/harmony_with_melody_token.rb +66 -0
  29. data/lib/stretto/grammar/tokens/hash_token.rb +15 -0
  30. data/lib/stretto/grammar/tokens/key_signature_token.rb +30 -0
  31. data/lib/stretto/grammar/tokens/measure_token.rb +21 -0
  32. data/lib/stretto/grammar/tokens/modifier_token.rb +99 -0
  33. data/lib/stretto/grammar/tokens/note_string_token.rb +106 -0
  34. data/lib/stretto/grammar/tokens/note_token.rb +25 -0
  35. data/lib/stretto/grammar/tokens/pattern_token.rb +26 -0
  36. data/lib/stretto/grammar/tokens/polyphonic_pressure_token.rb +28 -0
  37. data/lib/stretto/grammar/tokens/rest_token.rb +21 -0
  38. data/lib/stretto/grammar/tokens/value_token.rb +28 -0
  39. data/lib/stretto/grammar/tokens/variable_definition_token.rb +30 -0
  40. data/lib/stretto/grammar/value_grammar.treetop +45 -0
  41. data/lib/stretto/grammar/variable_grammar.treetop +10 -0
  42. data/lib/stretto/grammar/voice_change_grammar.treetop +9 -0
  43. data/lib/stretto/music_elements/channel_pressure.rb +44 -0
  44. data/lib/stretto/music_elements/chord.rb +194 -0
  45. data/lib/stretto/music_elements/controller_change.rb +49 -0
  46. data/lib/stretto/music_elements/harmonic_chord.rb +56 -0
  47. data/lib/stretto/music_elements/harmony.rb +37 -0
  48. data/lib/stretto/music_elements/instrument.rb +57 -0
  49. data/lib/stretto/music_elements/key_signature.rb +110 -0
  50. data/lib/stretto/music_elements/layer.rb +22 -0
  51. data/lib/stretto/music_elements/layer_change.rb +39 -0
  52. data/lib/stretto/music_elements/measure.rb +38 -0
  53. data/lib/stretto/music_elements/melody.rb +72 -0
  54. data/lib/stretto/music_elements/modifiers/attack_decay.rb +55 -0
  55. data/lib/stretto/music_elements/modifiers/chord_intervals.rb +44 -0
  56. data/lib/stretto/music_elements/modifiers/duration.rb +180 -0
  57. data/lib/stretto/music_elements/modifiers/value.rb +172 -0
  58. data/lib/stretto/music_elements/modifiers/variables.rb +365 -0
  59. data/lib/stretto/music_elements/music_element.rb +88 -0
  60. data/lib/stretto/music_elements/note.rb +282 -0
  61. data/lib/stretto/music_elements/pattern.rb +100 -0
  62. data/lib/stretto/music_elements/pitch_bend.rb +46 -0
  63. data/lib/stretto/music_elements/polyphonic_pressure.rb +62 -0
  64. data/lib/stretto/music_elements/rest.rb +26 -0
  65. data/lib/stretto/music_elements/tempo.rb +42 -0
  66. data/lib/stretto/music_elements/timing.rb +33 -0
  67. data/lib/stretto/music_elements/variable.rb +49 -0
  68. data/lib/stretto/music_elements/voice.rb +49 -0
  69. data/lib/stretto/music_elements/voice_change.rb +39 -0
  70. data/lib/stretto/parsers/exceptions.rb +11 -0
  71. data/lib/stretto/parsers/parser.rb +89 -0
  72. data/lib/stretto/renderers/midiator/player.rb +200 -0
  73. data/lib/stretto/util/jfugue_format_parser.rb +20 -0
  74. data/lib/stretto/util/node.rb +5 -0
  75. data/lib/stretto/util/utils.rb +41 -0
  76. data/lib/stretto/version.rb +3 -0
  77. metadata +203 -0
@@ -0,0 +1,282 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+ require File.join(File.dirname(__FILE__), 'modifiers/attack_decay')
3
+
4
+ module Stretto
5
+ module MusicElements
6
+
7
+ # A note is one of the most basic elements in Stretto.
8
+ #
9
+ # It is composed of several elements:
10
+ #
11
+ # +key+:: Represents the note name (from A to G)
12
+ # +accidental+:: The modifier of the note, this is, flats, sharps or the natural indicator
13
+ # (bb, b, n, #, ##)
14
+ # +pitch+:: The actual numeric pitch of the note, according to MIDI specification (0 to 127)
15
+ # +octave+:: Octave in which the note is located. (0 to 10) The default octave for a note is 5
16
+ #
17
+ # Additionally, it holds a duration (see {Duration}), and attack and decay (see {AttackDecay})
18
+ #
19
+ # Example of valid notes are:
20
+ #
21
+ # +C+:: Returns the note C, with the default octave, duration, attack and decay
22
+ # +[60]+:: The same note, represented by its pitch value
23
+ # +D#7+:: The note D# in the seventh octave
24
+ # +Abwa80d100+:: The note Ab with a whole note duration, an attack of 80 and decay of 100
25
+ #
26
+ # Pitch values and octaves are indicated in the table below:
27
+ #
28
+ # Octave | C | C# | D | D# | E | F | F# | G | G# | A | A# | B
29
+ # 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
30
+ # 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23
31
+ # 2 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35
32
+ # 3 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47
33
+ # 4 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59
34
+ # 5 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71
35
+ # 6 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83
36
+ # 7 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95
37
+ # 8 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107
38
+ # 9 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119
39
+ # 10 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127
40
+ #
41
+ # Where pitch can go from 0 to 127.
42
+
43
+ class Note < MusicElement
44
+
45
+ include Duration
46
+ include AttackDecay
47
+
48
+ # @private
49
+ PITCHES = {
50
+ 'C' => 0,
51
+ 'D' => 2,
52
+ 'E' => 4,
53
+ 'F' => 5,
54
+ 'G' => 7,
55
+ 'A' => 9,
56
+ 'B' => 11
57
+ }
58
+
59
+ # @private
60
+ ACCIDENTALS = {
61
+ 'bb' => -2,
62
+ 'b' => -1,
63
+ '#' => 1,
64
+ '##' => 2
65
+ }
66
+
67
+ # @private
68
+ MAX_PITCH = 127
69
+
70
+ # @private
71
+ DEFAULT_OCTAVE = 5
72
+
73
+ attr_reader :original_key, :original_accidental, :original_duration, :original_octave
74
+ attr_reader :key_signature
75
+ attr_accessor :instrument
76
+
77
+ def initialize(string_or_options, pattern = nil)
78
+ token = case string_or_options
79
+ when String then Stretto::Parser.parse_note!(string_or_options)
80
+ else string_or_options
81
+ end
82
+ super(token[:text_value], pattern)
83
+ @original_key = token[:key]
84
+ @original_pitch = token[:pitch]
85
+ @original_accidental = token[:accidental]
86
+ @original_octave = token[:octave]
87
+ build_duration_from_token(token[:duration])
88
+ build_attack_and_decay(token[:attack], token[:decay])
89
+ end
90
+
91
+ # Gets pitch of the note.
92
+ # If no original pitch is passed, the pitch gets calculated from the key, accidental
93
+ # and octave values.
94
+ #
95
+ # @return [Number] The pitch of the note, from 0 to 127
96
+ def pitch
97
+ @pitch || build_pitch
98
+ end
99
+
100
+ # Gets the octave for the note.
101
+ # If the original string did not contain an oe, the octave for that pitch is returned
102
+ # (see octave table on {MusicElements::Note} documentation.)
103
+ #
104
+ # @return [Number] The octave for the pitch
105
+ def octave
106
+ build_pitch unless @octave
107
+ @octave
108
+ end
109
+
110
+ # Gets the key of the note.
111
+ # If the original string did not contain a key note, the key for that pitch is returned
112
+ # (see the pitch table on {MusicElements::Note} documentation.)
113
+ #
114
+ # @return [String] The note key, 'A' through 'G'
115
+ def key
116
+ build_pitch unless @key
117
+ @key
118
+ end
119
+
120
+ # Gets the accidental of the note.
121
+ # If the original string did not contain an accidental, the default accidental for the pitch
122
+ # is returned (refer to the pitch table on {MusicElement::Note} documentation), always using
123
+ # sharps for raised notes.
124
+ #
125
+ # @return [String] The accidental of the note
126
+ # @todo Return accidental according to the present key signature
127
+ def accidental
128
+ build_pitch unless @accidental
129
+ @accidental
130
+ end
131
+
132
+ # Returns a new note raised by `interval` semitones.
133
+ #
134
+ # @param interval The amount of semitones to raise the pitch.
135
+ # @return [MusicElements::Note] A new note with the raised pitch
136
+ def +(interval)
137
+ new_pitch = if @original_pitch
138
+ @original_pitch + interval
139
+ else
140
+ Stretto::Value.new(Stretto::Value::NumericValue.new(pitch + interval))
141
+ end
142
+ Note.new({
143
+ :text_value => "#{@original_string}+#{interval}",
144
+ :pitch => new_pitch,
145
+ :duration => @original_duration_token,
146
+ :attack => @original_attack,
147
+ :decay => @original_decay}, @pattern)
148
+ end
149
+
150
+ # @return [Boolean] Whether `other` is a note and has the same pitch
151
+ def ==(other)
152
+ # TODO: Revisit the semantics of ==
153
+ other.kind_of?(Note) && other.pitch == pitch
154
+ end
155
+
156
+ # @return (see #==)
157
+ def eql?(other)
158
+ # TODO: Revisit the semantics of eql?
159
+ other.kind_of?(Note) && other.pitch.eql?(pitch)
160
+ end
161
+
162
+ # @return (see #==)
163
+ def hash
164
+ @pitch.hash
165
+ end
166
+
167
+ # Assigns the key signature to this note, altering its pitch.
168
+ #
169
+ # It doesn't affect the note if it has accidental, or its pitch was explicitely
170
+ # given, either with a numeric or variable value
171
+ #
172
+ # @param key_signature [MusicElements::KeySignature]
173
+ def key_signature=(key_signature)
174
+ @key_signature = key_signature
175
+ increment = key_signature_increment
176
+ self.pitch += key_signature_increment if increment
177
+ end
178
+
179
+ # Text value of original pitch
180
+ def original_pitch
181
+ @original_pitch.to_s if @original_pitch
182
+ end
183
+
184
+ private
185
+
186
+ # Builds pitch, and calculates key, octave and accidental with it.
187
+ #-
188
+ # TODO: Refactor into a single method: build_attributes
189
+ def build_pitch
190
+ if @original_pitch
191
+ self.pitch = @original_pitch.to_i(@pattern)
192
+ @key = calculate_key_from_pitch(@pitch)
193
+ @octave = calculate_octave_from_pitch(@pitch)
194
+ @accidental = calculate_accidental_from_pitch(@pitch)
195
+ else
196
+ @key = @original_key.upcase
197
+ @octave = (@original_octave && @original_octave.to_i) || 5
198
+ @accidental = @original_accidental.downcase if @original_accidental
199
+ self.pitch = calculate_pitch_from_key_octave_and_accidental(@key, @octave, @accidental)
200
+ end
201
+ @pitch
202
+ end
203
+
204
+ # Returns the pitch value calculated from its key, pitch and accidental
205
+ #
206
+ # @return [Number]
207
+ # @example
208
+ # calculate_pitch_from_key_octave_and_accidental("C", "5", "#") # => 61
209
+ # calculate_pitch_from_key_octave_and_accidental("G", "4", "b") # => 56
210
+ def calculate_pitch_from_key_octave_and_accidental(key, octave, accidental)
211
+ pitch = 12 * octave
212
+ pitch += PITCHES[key]
213
+ pitch += ACCIDENTALS[accidental] || 0
214
+ pitch
215
+ end
216
+
217
+ # @private
218
+ KEYS_FOR_PITCHES = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B']
219
+
220
+ # Returns the key for the pitch
221
+ #
222
+ # @return [String]
223
+ # @example
224
+ # calculate_key_from_pitch(60) # => 'C', for 'C5'
225
+ # calculate_key_from_pitch(61) # => 'C', for 'C#5'
226
+ def calculate_key_from_pitch(pitch)
227
+ KEYS_FOR_PITCHES[pitch % 12]
228
+ end
229
+
230
+ # Returns the octave for pitch
231
+ #
232
+ # @return [Number]
233
+ # @example
234
+ # calculate_octave_from_pitch(59) # => 4, for "B4"
235
+ # calculate_octave_from_pitch(60) # => 5, for "C5"
236
+ def calculate_octave_from_pitch(pitch)
237
+ pitch / 12
238
+ end
239
+
240
+ # @private
241
+ ACCIDENTALS_FOR_PITCH = [nil, '#', nil, '#', nil, nil, '#', nil, '#', nil, '#', nil]
242
+
243
+ # Returns the accidental value for pitch
244
+ #
245
+ # @return [String]
246
+ # @example
247
+ # calculate_accidental_from_pitch(60) # => nil, pitch for "C"
248
+ # calculate_accidental_from_pitch(61) # => '#', pitch for "C#"
249
+ def calculate_accidental_from_pitch(value)
250
+ ACCIDENTALS_FOR_PITCH[value % 12]
251
+ end
252
+
253
+ # @param pitch [Number] The value for note's pitch
254
+ # @raise [Exceptions::NoteOutOfBoundsException] If the pitch is higher than 127
255
+ def pitch=(pitch)
256
+ raise Exceptions::NoteOutOfBoundsException if pitch < 0 or pitch > MAX_PITCH
257
+ @pitch = pitch
258
+ end
259
+
260
+ # @return [Number] The number of semitones the key signature raises
261
+ def key_signature_increment
262
+ if @key_signature and @original_key and !@accidental
263
+ @key_signature.modifier_for(@key)
264
+ else
265
+ nil
266
+ end
267
+ end
268
+
269
+
270
+ # @private
271
+ # @see MusicElements::MusicElement#substitute_variables!
272
+ def substitute_variables!
273
+ self.attack = attack
274
+ self.decay = decay
275
+ self.pitch = build_pitch
276
+ self.key_signature = @key_signature
277
+ end
278
+
279
+ end
280
+
281
+ end
282
+ end
@@ -0,0 +1,100 @@
1
+ require File.join(File.dirname(__FILE__), 'voice')
2
+ require File.join(File.dirname(__FILE__), '../util/jfugue_format_parser')
3
+
4
+ module Stretto
5
+
6
+ # Pattern is a series of MusicElements, that hold a context (like tied notes or key signature modifications)
7
+ # Is the equivalent of the JFugue implementation +Pattern+
8
+ #-
9
+ # NOTE: This class behavior is not definite, and may change during the development of Stretto
10
+ # until the first stable version
11
+ #+
12
+ class Pattern < Array
13
+
14
+ include Variables
15
+
16
+ DEFAULT_VOICE_INDEX = 0
17
+
18
+ attr_reader :voices, :variables # TODO: Limit access to variables
19
+
20
+ # Initializes a pattern
21
+ # @param music_string_or_file [String, File] Can be the music string directly, or
22
+ # a file in jfugue format.
23
+ def initialize(music_string_or_file = "")
24
+ @music_string = get_music_string_from(music_string_or_file)
25
+ @parser = Stretto::Parser.new(@music_string)
26
+ @voices = { }
27
+ @variables = { }
28
+ @__key_signature = { }
29
+ @__instruments = { }
30
+ if @parser.valid?
31
+ @parser.to_stretto(self).each { |music_element| self << music_element }
32
+ else
33
+ raise "Invalid music string \"#{@music_string}\" at character #{@parser.error_on}"
34
+ end
35
+ end
36
+
37
+ def elements
38
+ to_a
39
+ end
40
+
41
+ def to_s
42
+ @music_string
43
+ end
44
+
45
+ def <<(other)
46
+ other.pattern = self
47
+
48
+ if other.kind_of?(MusicElements::Variable)
49
+ @variables[other.name.upcase] = other.value
50
+ end
51
+
52
+ if other.kind_of?(MusicElements::VoiceChange)
53
+ @current_voice = (@voices[other.index] ||= Voice.new(other.index))
54
+ else
55
+ @voices[DEFAULT_VOICE_INDEX] = @current_voice = Voice.new(DEFAULT_VOICE_INDEX) unless @current_voice
56
+ @current_voice << other
57
+ if @current_voice.size > 1
58
+ @current_voice[-2].next = @current_voice[-1]
59
+ @current_voice[-1].prev = @current_voice[-2]
60
+ end
61
+ end
62
+
63
+ if other.kind_of?(MusicElements::KeySignature)
64
+ @__key_signature[@current_voice.index] = other
65
+ else
66
+ other.key_signature = @__key_signature[@current_voice.index] if other.respond_to?(:key_signature=)
67
+ end
68
+
69
+ if other.kind_of?(MusicElements::Instrument)
70
+ @__instruments[@current_voice.index] = other
71
+ else
72
+ @__instruments[@current_voice.index] ||= MusicElements::Instrument.default_instrument(self)
73
+ other.instrument = @__instruments[@current_voice.index] if other.respond_to?(:instrument=)
74
+ end
75
+
76
+ super(other)
77
+ end
78
+
79
+ def voice(index)
80
+ @voices[index]
81
+ end
82
+
83
+ def variable(name)
84
+ @variables[name.upcase] ||
85
+ (Value.new(Value::NumericValue.new(PREDEFINED_VARIABLES[name.upcase])) if PREDEFINED_VARIABLES[name.upcase]) ||
86
+ raise(Exceptions::VariableNotDefinedException.new("Variable '#{name}' not defined in pattern"))
87
+ end
88
+
89
+ private
90
+ def get_music_string_from(music_string_or_file)
91
+ case music_string_or_file
92
+ when String
93
+ music_string_or_file
94
+ when File
95
+ Stretto::JFugueFormatParser.parse(music_string_or_file)
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # Pitch bend, according to MIDI spacification, changes the tone of a note in steps of
7
+ # hundredth of a note. The full range can go from 0 to 16383, being 8192 the middle pitch,
8
+ # that is, no variation.
9
+ #
10
+ # Pitch bends are represented with a +&+ symbol before, for example +&16000+
11
+ class PitchBend < MusicElement
12
+
13
+ MAX_PITCH_BEND_VALUE = 16383
14
+
15
+ def initialize(string_or_options, pattern = nil)
16
+ token = case string_or_options
17
+ when String then Stretto::Parser.parse_pitch_bend!(string_or_options)
18
+ else string_or_options
19
+ end
20
+ super(token[:text_value], pattern)
21
+ @original_value = token[:value]
22
+ end
23
+
24
+ # Sets value and validates it is in range
25
+ def value=(value)
26
+ if value < 0 or value > MAX_PITCH_BEND_VALUE
27
+ raise Exceptions::ValueOutOfBoundsException.new("Pitch bend should be in range 0..#{MAX_PITCH_BEND_VALUE}")
28
+ end
29
+ @value = value
30
+ end
31
+
32
+ def value
33
+ @value || @original_value.to_i(@pattern)
34
+ end
35
+
36
+ private
37
+
38
+ # @private
39
+ def substitute_variables!
40
+ self.value = value
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # Polyphonic pressure is similar to channel pressure (see {ChannelPressure}) but
7
+ # it is applied to only a note. A polyphonic pressure token is specified by the notation
8
+ # +*+_key_,_value_, where key is the value for the pitch (0 to 127) and value is the
9
+ # applied pressure (0 to 127)
10
+ class PolyphonicPressure < MusicElement
11
+
12
+ MAX_PITCH_VALUE = 127
13
+ MAX_VALUE = 127
14
+
15
+ attr_reader :pitch, :value
16
+
17
+ def initialize(string_or_options, pattern = nil)
18
+ token = case string_or_options
19
+ when String then Stretto::Parser.parse_polyphonic_pressure!(string_or_options)
20
+ else string_or_options
21
+ end
22
+ super(token[:text_value], pattern)
23
+ @original_pitch = token[:pitch]
24
+ @original_value = token[:value]
25
+ end
26
+
27
+ def pitch
28
+ @pitch || @original_pitch.to_i(@pattern)
29
+ end
30
+
31
+ # Sets value and validates in range
32
+ def pitch=(pitch)
33
+ if pitch < 0 or pitch > MAX_PITCH_VALUE
34
+ raise Exceptions::ValueOutOfBoundsException.new("Pitch value for polyphonic pressure should be in range 0..#{MAX_PITCH_VALUE}")
35
+ end
36
+ @pitch = pitch
37
+ end
38
+
39
+ def value
40
+ @value || @original_value.to_i(@pattern)
41
+ end
42
+
43
+ # Sets value and validates in range
44
+ def value=(value)
45
+ if value < 0 or value > MAX_VALUE
46
+ raise Exceptions::ValueOutOfBoundsException.new("Value for polyphonic pressure should be in range 0..#{MAX_VALUE}")
47
+ end
48
+ @value = value
49
+ end
50
+
51
+ private
52
+
53
+ # @private
54
+ def substitute_variables!
55
+ self.pitch = pitch
56
+ self.value = value
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end