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