stretto 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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