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