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