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,49 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# Represents a MIDI controller event.
|
7
|
+
#
|
8
|
+
# MIDI specification defines about 100 controller events, that are used by the
|
9
|
+
# instruments or synthesizer to perform a range of effects and control settings.
|
10
|
+
#
|
11
|
+
# JFugue defines the most common ones (See {Variables::CONTROLLER_VARIABLES}),
|
12
|
+
# and provides a shortcut for mixed (coarse and fine values) controllers.
|
13
|
+
#
|
14
|
+
# The syntax for a controller change is X_controller_=_value_, where controller is the
|
15
|
+
# number of the controller event, and value is the value that is going to be set.
|
16
|
+
class ControllerChange < MusicElement
|
17
|
+
|
18
|
+
attr_reader :controller, :value
|
19
|
+
|
20
|
+
def initialize(string_or_options, pattern = nil)
|
21
|
+
token = case string_or_options
|
22
|
+
when String then Stretto::Parser.parse_controller_change!(string_or_options)
|
23
|
+
else string_or_options
|
24
|
+
end
|
25
|
+
super(token[:text_value], pattern)
|
26
|
+
@original_controller = token[:controller]
|
27
|
+
@original_value = token[:value]
|
28
|
+
end
|
29
|
+
|
30
|
+
def controller
|
31
|
+
@controller || @original_controller.to_i(@pattern)
|
32
|
+
end
|
33
|
+
|
34
|
+
def value
|
35
|
+
@value || @original_value.to_i(@pattern)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# @private
|
41
|
+
def substitute_variables!
|
42
|
+
@controller = @original_controller.to_i(@pattern)
|
43
|
+
@value = @original_value.to_i(@pattern)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
require File.join(File.dirname(__FILE__), 'chord')
|
3
|
+
|
4
|
+
module Stretto
|
5
|
+
module MusicElements
|
6
|
+
|
7
|
+
# Represents a non-regular chord (see {Chord})
|
8
|
+
#
|
9
|
+
# A harmonic chord can be specied by joining notes, or even chords, together with
|
10
|
+
# the <tt>+</tt> symbol. For example, the chord <tt>C+E+G</tt> is the same as the chord
|
11
|
+
# +Cmaj+, and the chord <tt>C+D+E</tt> is an irregular chord (not included in the
|
12
|
+
# standard named chords) which will have the notes +C+, +D+ and +E+. Note that as
|
13
|
+
# the notes are specified by their note notation, the default octave is 5, instead of
|
14
|
+
# 3 as the normal chords.
|
15
|
+
class HarmonicChord < Chord
|
16
|
+
|
17
|
+
def initialize(string_or_options, pattern = nil)
|
18
|
+
token = case string_or_options
|
19
|
+
when String then Stretto::Parser.parse_harmonic_chord!(string_or_options)
|
20
|
+
else string_or_options
|
21
|
+
end
|
22
|
+
super(token, pattern)
|
23
|
+
@notes = normalize_notes(@notes)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return (see Chord#elements)
|
27
|
+
def elements
|
28
|
+
notes
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return (see Chord#elements)
|
32
|
+
def duration
|
33
|
+
@notes.map(&:duration).max
|
34
|
+
end
|
35
|
+
|
36
|
+
# @private
|
37
|
+
def substitute_variables!
|
38
|
+
@duration = @notes.map(&:duration).max
|
39
|
+
@notes.each{ |note| note.pattern = @pattern }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Builds the +@notes+ instance variable, flattening the notes
|
43
|
+
# and the notes in a chord into a single array of elements
|
44
|
+
def normalize_notes(base_notes)
|
45
|
+
base_notes.inject([]) do |notes, element|
|
46
|
+
element.pattern = @pattern
|
47
|
+
case element
|
48
|
+
when Note then notes << element
|
49
|
+
when Chord then notes + element.notes
|
50
|
+
end
|
51
|
+
end.uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# Represents a group of notes, chords and rests of different lengths
|
7
|
+
# that should play together.
|
8
|
+
#
|
9
|
+
# It is similar to a HarmonicChord (see {HarmonicChord} with the difference
|
10
|
+
# that it can also include rests and melodies (see {Melody})
|
11
|
+
class Harmony < MusicElement
|
12
|
+
|
13
|
+
attr_accessor :elements
|
14
|
+
|
15
|
+
def initialize(string_or_options, pattern = nil)
|
16
|
+
token = case string_or_options
|
17
|
+
when String then Stretto::Parser.parse_harmony!(string_or_options)
|
18
|
+
else string_or_options
|
19
|
+
end
|
20
|
+
super(token[:text_value], pattern)
|
21
|
+
@elements = token[:music_elements]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return (see HarmonicChord#duration)
|
25
|
+
def duration
|
26
|
+
@elements.map(&:duration).max
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return (see HarmonicChord#pattern=)
|
30
|
+
def pattern=(pattern)
|
31
|
+
@elements.each { |element| element.pattern = pattern }
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# Represent an instrument change by the MIDI specification.
|
7
|
+
#
|
8
|
+
# The sound played depends on the soundbank installed by
|
9
|
+
# the synthesizer, the MIDI standard defines 128 standard instruments
|
10
|
+
# (see {Variables::INSTRUMENT_VARIABLES}) that are reflected by
|
11
|
+
# JFugue with predefined variables.
|
12
|
+
class Instrument < MusicElement
|
13
|
+
|
14
|
+
MAX_INSTRUMENT_VALUE = 127
|
15
|
+
|
16
|
+
# Returns an instrument with value 0 (piano)
|
17
|
+
def self.default_instrument(pattern = nil)
|
18
|
+
params = {
|
19
|
+
:text_value => '',
|
20
|
+
:value => Value.new(Value::NumericValue.new(0))
|
21
|
+
}
|
22
|
+
new(params, pattern)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(string_or_options, pattern = nil)
|
26
|
+
token = case string_or_options
|
27
|
+
when String then Stretto::Parser.parse_instrument!(string_or_options)
|
28
|
+
else string_or_options
|
29
|
+
end
|
30
|
+
super(token[:text_value], pattern )
|
31
|
+
@original_value = token[:value]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets and validates value (0...127)
|
35
|
+
def value=(value)
|
36
|
+
if value < 0 or value > MAX_INSTRUMENT_VALUE
|
37
|
+
raise Exceptions::ValueOutOfBoundsException.new("Instrument value should be in range 0..#{MAX_INSTRUMENT_VALUE}")
|
38
|
+
end
|
39
|
+
@value = value
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Number] Returns or calculates the value for the
|
43
|
+
# instrument
|
44
|
+
def value
|
45
|
+
@value || @original_value.to_i(@pattern)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def substitute_variables!
|
51
|
+
self.value = value
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# A key signature indicates the channel to play in the indicated key or scale.
|
7
|
+
#
|
8
|
+
# This increases or decreases note values for certain notes, according to music theory
|
9
|
+
# (see http://en.wikipedia.org/wiki/Key_signature). The key signature is specified by the
|
10
|
+
# letter +K+, followed by a key (for example +C+, +D#+ or +Eb+) and the scale (+maj+ or
|
11
|
+
# +min+)
|
12
|
+
#
|
13
|
+
# Note that notes and chords specified with a pitch value will not be affected. That is,
|
14
|
+
# given the following pattern "KGmaj F [65]", the first note will raise its pitch to
|
15
|
+
# 66 (due to the sharp accidental in the F notes), while the second note will keep its
|
16
|
+
# given value of 65
|
17
|
+
class KeySignature < MusicElement
|
18
|
+
|
19
|
+
attr_reader :key, :scale
|
20
|
+
|
21
|
+
def initialize(string_or_options, pattern = nil)
|
22
|
+
token = case string_or_options
|
23
|
+
when String then Stretto::Parser.parse_key_signature!(string_or_options)
|
24
|
+
else string_or_options
|
25
|
+
end
|
26
|
+
super(token[:text_value], pattern)
|
27
|
+
@key = normalize_keysig(token[:key])
|
28
|
+
@scale = SCALES[token[:scale].downcase]
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Number] +1, 0 or -1, if the note key has a flat, none or sharp accidental
|
32
|
+
# respectively for the given +note_key+
|
33
|
+
def modifier_for(note_key)
|
34
|
+
MODIFIERS[@scale][@key][note_key]
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
MODIFIERS = {
|
40
|
+
:major => {
|
41
|
+
'C' => {},
|
42
|
+
'G' => { 'F' => +1 },
|
43
|
+
'D' => { 'F' => +1, 'C' => +1 },
|
44
|
+
'A' => { 'F' => +1, 'C' => +1, 'G' => +1 },
|
45
|
+
'E' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1 },
|
46
|
+
'B' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1, 'A' => +1 },
|
47
|
+
'F#' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1, 'A' => +1, 'E' => +1},
|
48
|
+
'C#' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1, 'A' => +1, 'E' => +1, 'B' => +1 },
|
49
|
+
|
50
|
+
'F' => { 'B' => -1 },
|
51
|
+
'Bb' => { 'B' => -1, 'E' => -1 },
|
52
|
+
'Eb' => { 'B' => -1, 'E' => -1, 'A' => -1 },
|
53
|
+
'Ab' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1 },
|
54
|
+
'Db' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1, 'G' => -1 },
|
55
|
+
'Gb' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1, 'G' => -1, 'C' => -1 },
|
56
|
+
'Cb' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1, 'G' => -1, 'C' => -1, 'F' => -1},
|
57
|
+
},
|
58
|
+
:minor => {
|
59
|
+
'A' => {},
|
60
|
+
'E' => { 'F' => +1 },
|
61
|
+
'B' => { 'F' => +1, 'C' => +1 },
|
62
|
+
'F#' => { 'F' => +1, 'C' => +1, 'G' => +1 },
|
63
|
+
'C#' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1 },
|
64
|
+
'G#' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1, 'A' => +1 },
|
65
|
+
'D#' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1, 'A' => +1, 'E' => +1},
|
66
|
+
'A#' => { 'F' => +1, 'C' => +1, 'G' => +1, 'D' => +1, 'A' => +1, 'E' => +1, 'B' => +1 },
|
67
|
+
'D' => { 'B' => -1 },
|
68
|
+
'G' => { 'B' => -1, 'E' => -1 },
|
69
|
+
'C' => { 'B' => -1, 'E' => -1, 'A' => -1 },
|
70
|
+
'F' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1 },
|
71
|
+
'Bb' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1, 'G' => -1 },
|
72
|
+
'Eb' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1, 'G' => -1, 'C' => -1 },
|
73
|
+
'Ab' => { 'B' => -1, 'E' => -1, 'A' => -1, 'D' => -1, 'G' => -1, 'C' => -1, 'F' => -1},
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
# ALIASES IN MAJOR SCALE
|
78
|
+
MODIFIERS[:major]['Fb'] = MODIFIERS[:major]['E' ]
|
79
|
+
MODIFIERS[:major]['Cb'] = MODIFIERS[:major]['B' ]
|
80
|
+
MODIFIERS[:major]['Gb'] = MODIFIERS[:major]['F#']
|
81
|
+
MODIFIERS[:major]['Db'] = MODIFIERS[:major]['C#']
|
82
|
+
MODIFIERS[:major]['B#'] = MODIFIERS[:major]['C' ]
|
83
|
+
|
84
|
+
# EQUIVALENCES FROM MAJOR SCALE TO MINOR SCALE
|
85
|
+
MODIFIERS[:major]['G#'] = MODIFIERS[:major]['Ab'] = MODIFIERS[:minor]['F' ]
|
86
|
+
MODIFIERS[:major]['D#'] = MODIFIERS[:major]['Eb'] = MODIFIERS[:minor]['C' ]
|
87
|
+
MODIFIERS[:major]['A#'] = MODIFIERS[:major]['Bb'] = MODIFIERS[:minor]['G' ]
|
88
|
+
MODIFIERS[:major]['E#'] = MODIFIERS[:major]['F' ] = MODIFIERS[:minor]['D' ]
|
89
|
+
|
90
|
+
# ALIASES IN MINOR SCALE
|
91
|
+
MODIFIERS[:minor]['B#'] = MODIFIERS[:minor]['C' ]
|
92
|
+
MODIFIERS[:minor]['E#'] = MODIFIERS[:minor]['F' ]
|
93
|
+
MODIFIERS[:minor]['A#'] = MODIFIERS[:minor]['Bb']
|
94
|
+
MODIFIERS[:minor]['D#'] = MODIFIERS[:minor]['Eb']
|
95
|
+
MODIFIERS[:minor]['G#'] = MODIFIERS[:minor]['Ab']
|
96
|
+
|
97
|
+
# EQUIVALENCES FROM MINOR SCALE TO MAJOR SCALE
|
98
|
+
MODIFIERS[:minor]['Db'] = MODIFIERS[:minor]['C#'] = MODIFIERS[:major]['E' ]
|
99
|
+
MODIFIERS[:minor]['Gb'] = MODIFIERS[:minor]['F#'] = MODIFIERS[:major]['A' ]
|
100
|
+
MODIFIERS[:minor]['Cb'] = MODIFIERS[:minor]['B' ] = MODIFIERS[:major]['D' ]
|
101
|
+
MODIFIERS[:minor]['Fb'] = MODIFIERS[:minor]['E' ] = MODIFIERS[:major]['G' ]
|
102
|
+
|
103
|
+
SCALES = { 'maj' => :major, 'min' => :minor }
|
104
|
+
|
105
|
+
def normalize_keysig(string)
|
106
|
+
string.downcase.sub(/\w/){ |initial| initial.upcase }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Stretto
|
2
|
+
|
3
|
+
# Wrapper for a layer: set of elements inside a voice
|
4
|
+
#
|
5
|
+
# Layers are not part of the MIDI specification, but are
|
6
|
+
# a resource to separate multiple notes that should be played
|
7
|
+
# together in a voice.
|
8
|
+
#
|
9
|
+
# This is most useful in the voice 9 (see {Voice}), which is
|
10
|
+
# the percussion layer. Different parts of the percussion track
|
11
|
+
# can be separated in different layers, and they all play together
|
12
|
+
# in the same track.
|
13
|
+
class Layer < Array
|
14
|
+
|
15
|
+
# @return [Array(MusicElement)] Its elements as an array
|
16
|
+
def elements
|
17
|
+
to_a
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
class LayerChange < MusicElement
|
7
|
+
|
8
|
+
MAX_LAYERS = 15
|
9
|
+
|
10
|
+
attr_reader :index
|
11
|
+
|
12
|
+
def initialize(string_or_options, pattern = nil)
|
13
|
+
token = case string_or_options
|
14
|
+
when String then Stretto::Parser.parse_layer_change!(string_or_options)
|
15
|
+
else string_or_options
|
16
|
+
end
|
17
|
+
super(token[:text_value], pattern)
|
18
|
+
@original_value = token[:value]
|
19
|
+
end
|
20
|
+
|
21
|
+
def index
|
22
|
+
@index || @original_value.to_i(@pattern)
|
23
|
+
end
|
24
|
+
|
25
|
+
def index=(index)
|
26
|
+
if index < 0 or index > MAX_LAYERS
|
27
|
+
raise Exceptions::ValueOutOfBoundsException.new("Layer value should be in range 0..#{MAX_LAYERS}")
|
28
|
+
end
|
29
|
+
@index = index
|
30
|
+
end
|
31
|
+
|
32
|
+
def substitute_variables!
|
33
|
+
self.index = index
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# Does not have effect in the playback, it represents a measure
|
7
|
+
# for readability of the music texts
|
8
|
+
class Measure < MusicElement
|
9
|
+
|
10
|
+
def initialize(string_or_options, pattern = nil)
|
11
|
+
token = case string_or_options
|
12
|
+
when String then Stretto::Parser.parse_measure!(string_or_options)
|
13
|
+
else string_or_options
|
14
|
+
end
|
15
|
+
super(token[:text_value], pattern)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @private
|
19
|
+
# TODO: Make tests for other elements inside ties, move this to MusicElement
|
20
|
+
def tied_elements
|
21
|
+
next_element = self.next
|
22
|
+
if next_element
|
23
|
+
next_element.tied_elements
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
# TODO: Make tests for other elements inside ties, move this to MusicElement
|
31
|
+
def tied_duration
|
32
|
+
tied_elements.inject(0){|sum, element| sum + element.duration}
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'music_element')
|
2
|
+
|
3
|
+
module Stretto
|
4
|
+
module MusicElements
|
5
|
+
|
6
|
+
# A set of elements that should play sequentially. The elements are separated
|
7
|
+
# by underscores, for example <tt>C_D_E</tt>
|
8
|
+
#
|
9
|
+
# A melody alone would be the equivalent to play the elements of it separately,
|
10
|
+
# but it is useful to indicate explicitely a melody when used in harmonies
|
11
|
+
# (see {Harmony}). For example, a harmony (C_D+E_Fmaj) will play at the same time
|
12
|
+
# +C+ + +E+ for one quarter of a whole note, then +D+ and +Fmaj+ will play together.
|
13
|
+
class Melody < MusicElement
|
14
|
+
|
15
|
+
attr_reader :elements
|
16
|
+
|
17
|
+
def initialize(array_or_hash, pattern = nil)
|
18
|
+
options = _handle_initial_argument(array_or_hash)
|
19
|
+
_verify_is_pattern(pattern)
|
20
|
+
@elements = options[:elements]
|
21
|
+
super(options[:original_string], pattern)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the sum of the duration of its elements
|
25
|
+
def duration
|
26
|
+
@elements.map(&:duration).sum
|
27
|
+
end
|
28
|
+
|
29
|
+
# Adds an element to the melody, additionally setting its pattern
|
30
|
+
# (see {MusicElement::pattern=)
|
31
|
+
def <<(element)
|
32
|
+
@elements << element
|
33
|
+
element.pattern = @pattern if @pattern
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @private
|
39
|
+
# Builds the set of elements based on what the constructor is (a token,
|
40
|
+
# an array of elements or a single MusicElement (in which case, a melody
|
41
|
+
# with just one element is created.)
|
42
|
+
def _handle_initial_argument(array_hash_or_music_element)
|
43
|
+
arg = array_hash_or_music_element
|
44
|
+
case arg
|
45
|
+
when Array
|
46
|
+
unless arg.present? and arg.all? { |element| element.kind_of?(MusicElement) }
|
47
|
+
raise ArgumentError.new("First argument should be either a MusicElement or an array of at least one MusicElement")
|
48
|
+
end
|
49
|
+
{ :elements => arg,
|
50
|
+
:original_string => arg.map(&:original_string).join('_')
|
51
|
+
}
|
52
|
+
when Hash
|
53
|
+
arg
|
54
|
+
when MusicElement
|
55
|
+
{ :elements => [arg],
|
56
|
+
:original_string => arg.original_string
|
57
|
+
}
|
58
|
+
else raise ArgumentError.new("First argument should be either a MusicElement or an array of MusicElements")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @private
|
63
|
+
# Verifies that the second parameter in initializer is a {Pattern}
|
64
|
+
def _verify_is_pattern(pattern)
|
65
|
+
if pattern and !pattern.kind_of?(Pattern)
|
66
|
+
raise ArgumentError.new("Second argument should be a Pattern object or nil")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|