stretto 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +0 -0
- data/README.markdown +67 -0
- data/Rakefile +9 -0
- data/lib/stretto.rb +14 -0
- data/lib/stretto/grammar/channel_pressure_grammar.treetop +9 -0
- data/lib/stretto/grammar/chord_grammar.treetop +60 -0
- data/lib/stretto/grammar/controller_change_grammar.treetop +9 -0
- data/lib/stretto/grammar/duration_grammar.treetop +60 -0
- data/lib/stretto/grammar/grammar_helper.rb +6 -0
- data/lib/stretto/grammar/harmonic_chord_grammar.treetop +15 -0
- data/lib/stretto/grammar/harmony_grammar.treetop +15 -0
- data/lib/stretto/grammar/instrument_grammar.treetop +9 -0
- data/lib/stretto/grammar/key_signature_grammar.treetop +10 -0
- data/lib/stretto/grammar/layer_change_grammar.treetop +9 -0
- data/lib/stretto/grammar/measure_grammar.treetop +7 -0
- data/lib/stretto/grammar/note_grammar.treetop +32 -0
- data/lib/stretto/grammar/pitch_bend_grammar.treetop +7 -0
- data/lib/stretto/grammar/polyphonic_pressure_grammar.treetop +9 -0
- data/lib/stretto/grammar/rest_grammar.treetop +9 -0
- data/lib/stretto/grammar/stretto_grammar.treetop +62 -0
- data/lib/stretto/grammar/tempo_grammar.treetop +9 -0
- data/lib/stretto/grammar/timing_grammar.treetop +9 -0
- data/lib/stretto/grammar/tokens/attack_decay_token.rb +42 -0
- data/lib/stretto/grammar/tokens/chord_token.rb +67 -0
- data/lib/stretto/grammar/tokens/controller_change_token.rb +26 -0
- data/lib/stretto/grammar/tokens/duration_token.rb +55 -0
- data/lib/stretto/grammar/tokens/harmonic_chord_token.rb +25 -0
- data/lib/stretto/grammar/tokens/harmony_with_melody_token.rb +66 -0
- data/lib/stretto/grammar/tokens/hash_token.rb +15 -0
- data/lib/stretto/grammar/tokens/key_signature_token.rb +30 -0
- data/lib/stretto/grammar/tokens/measure_token.rb +21 -0
- data/lib/stretto/grammar/tokens/modifier_token.rb +99 -0
- data/lib/stretto/grammar/tokens/note_string_token.rb +106 -0
- data/lib/stretto/grammar/tokens/note_token.rb +25 -0
- data/lib/stretto/grammar/tokens/pattern_token.rb +26 -0
- data/lib/stretto/grammar/tokens/polyphonic_pressure_token.rb +28 -0
- data/lib/stretto/grammar/tokens/rest_token.rb +21 -0
- data/lib/stretto/grammar/tokens/value_token.rb +28 -0
- data/lib/stretto/grammar/tokens/variable_definition_token.rb +30 -0
- data/lib/stretto/grammar/value_grammar.treetop +45 -0
- data/lib/stretto/grammar/variable_grammar.treetop +10 -0
- data/lib/stretto/grammar/voice_change_grammar.treetop +9 -0
- data/lib/stretto/music_elements/channel_pressure.rb +44 -0
- data/lib/stretto/music_elements/chord.rb +194 -0
- data/lib/stretto/music_elements/controller_change.rb +49 -0
- data/lib/stretto/music_elements/harmonic_chord.rb +56 -0
- data/lib/stretto/music_elements/harmony.rb +37 -0
- data/lib/stretto/music_elements/instrument.rb +57 -0
- data/lib/stretto/music_elements/key_signature.rb +110 -0
- data/lib/stretto/music_elements/layer.rb +22 -0
- data/lib/stretto/music_elements/layer_change.rb +39 -0
- data/lib/stretto/music_elements/measure.rb +38 -0
- data/lib/stretto/music_elements/melody.rb +72 -0
- data/lib/stretto/music_elements/modifiers/attack_decay.rb +55 -0
- data/lib/stretto/music_elements/modifiers/chord_intervals.rb +44 -0
- data/lib/stretto/music_elements/modifiers/duration.rb +180 -0
- data/lib/stretto/music_elements/modifiers/value.rb +172 -0
- data/lib/stretto/music_elements/modifiers/variables.rb +365 -0
- data/lib/stretto/music_elements/music_element.rb +88 -0
- data/lib/stretto/music_elements/note.rb +282 -0
- data/lib/stretto/music_elements/pattern.rb +100 -0
- data/lib/stretto/music_elements/pitch_bend.rb +46 -0
- data/lib/stretto/music_elements/polyphonic_pressure.rb +62 -0
- data/lib/stretto/music_elements/rest.rb +26 -0
- data/lib/stretto/music_elements/tempo.rb +42 -0
- data/lib/stretto/music_elements/timing.rb +33 -0
- data/lib/stretto/music_elements/variable.rb +49 -0
- data/lib/stretto/music_elements/voice.rb +49 -0
- data/lib/stretto/music_elements/voice_change.rb +39 -0
- data/lib/stretto/parsers/exceptions.rb +11 -0
- data/lib/stretto/parsers/parser.rb +89 -0
- data/lib/stretto/renderers/midiator/player.rb +200 -0
- data/lib/stretto/util/jfugue_format_parser.rb +20 -0
- data/lib/stretto/util/node.rb +5 -0
- data/lib/stretto/util/utils.rb +41 -0
- data/lib/stretto/version.rb +3 -0
- 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
|