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,106 @@
1
+ module Stretto
2
+ module Tokens
3
+
4
+
5
+ # Token from parsing a note pitch, when an explicit pitch is given.
6
+ #
7
+ # @example "[60]". "[SOME_NOTE]"
8
+ module NotePitchToken
9
+
10
+ # Always return nil
11
+ # @return nil
12
+ def accidental
13
+ nil
14
+ end
15
+
16
+ # Always return nil
17
+ # @return nil
18
+ def octave
19
+ nil
20
+ end
21
+
22
+ # Always return nil
23
+ # @return nil
24
+ def key
25
+ nil
26
+ end
27
+
28
+ # Wraps the value into a Value object, either a numeric or a variable value
29
+ # @return [Value] The wrapped value for the pitch
30
+ def pitch
31
+ klass = if _pitch.is_numeric?
32
+ Value::NumericValue
33
+ else
34
+ Value::VariableValue
35
+ end
36
+ Value.new(klass.new(_pitch.text_value))
37
+ end
38
+ end
39
+
40
+ #------------------------------------------------------------------
41
+
42
+ # Token result from parsing the part of a note that contains key, and optional
43
+ # accidental and octave values
44
+ #
45
+ # @example "C#7"
46
+ module NoteKeyAccidentalOctaveToken
47
+
48
+ # @return [String] The key of the note ('A' through 'G')
49
+ # @example "C"
50
+ def key
51
+ note_key.key.text_value
52
+ end
53
+
54
+ # @return [String, nil] The accidental of the note, if present (one of 'bb', 'b', 'n', '#', '##')
55
+ # @example "#"
56
+ def accidental
57
+ note_key.accidental.text_value if note_key.accidental and note_key.accidental.text_value.present?
58
+ end
59
+
60
+ # Always return nil, as this is used to differentiate a note with a given pitch from one with key,
61
+ # octave and accidental whose pitch has to be calculated
62
+ # @return [nil]
63
+ def pitch
64
+ nil
65
+ end
66
+
67
+ # @return [String, nil] The octave of the note, if present
68
+ # @example "5"
69
+ def octave
70
+ _octave.text_value if _octave and _octave.text_value.present?
71
+ end
72
+ end
73
+
74
+ #------------------------------------------------------------------
75
+
76
+ # Include this module to include note string functionality, that is, to provide
77
+ # key, accidental, octave and/or pitch
78
+ module WithNoteStringToken
79
+
80
+ # @return (see NoteKeyAccidentalOctaveToken#octave)
81
+ # @return (see NotePitchToken#octave)
82
+ def octave
83
+ note_string.octave
84
+ end
85
+
86
+ # @return (see NoteKeyAccidentalOctaveToken#accidental)
87
+ # @return (see NotePitchToken#accidental)
88
+ def accidental
89
+ note_string.accidental
90
+ end
91
+
92
+ # @return (see NoteKeyAccidentalOctaveToken#key)
93
+ # @return (see NotePitchToken#key)
94
+ def key
95
+ note_string.key
96
+ end
97
+
98
+ # @return (see NoteKeyAccidentalOctaveToken#pitch)
99
+ # @return (see NotePitchToken#pitch)
100
+ def pitch
101
+ note_string.pitch
102
+ end
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), 'duration_token')
2
+ require File.join(File.dirname(__FILE__), 'note_string_token')
3
+ require File.join(File.dirname(__FILE__), 'attack_decay_token')
4
+ require File.join(File.dirname(__FILE__), '../../music_elements/note')
5
+
6
+ module Stretto
7
+ module Tokens
8
+
9
+ # Token result from parsing a note element. It includes the note string, attack, decay and duration
10
+ #
11
+ # @example "C", "F#6", "[60]w.", "Gb/3.0a100d100"
12
+ class NoteToken < HashToken
13
+
14
+ include WithDurationToken
15
+ include WithNoteStringToken
16
+ include WithAttackDecayToken
17
+
18
+ # @return [MusicElements::Note] The constructed Note element
19
+ def to_stretto(pattern = nil)
20
+ Stretto::MusicElements::Note.new(self, pattern)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Stretto
2
+ module Tokens
3
+
4
+ # This is the token that parses a pattern, that is, a collection of music elements
5
+ #
6
+ # @example "T[ALLEGRO] I[PIANO] | C C D E | Rw. | Dmin"
7
+ module PatternToken
8
+
9
+ # @return [Array(MusicElements::MusicElement)] An array containing all the elements of
10
+ # the pattern
11
+ #-
12
+ # TODO: This should return a Pattern object directly
13
+ def to_stretto(pattern = nil)
14
+ unless head.text_value.empty?
15
+ [head.to_stretto(pattern)] + more_elements.elements.map do |element|
16
+ element.music_element.to_stretto(pattern)
17
+ end
18
+ else
19
+ []
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), '../../music_elements/polyphonic_pressure')
2
+
3
+ module Stretto
4
+ module Tokens
5
+
6
+ # Token from parsing a polyphonic pressure element
7
+ #
8
+ # @example "*80,100"
9
+ class PolyphonicPressureToken < HashToken
10
+
11
+ # @return [MusicElements::PolyphonicPressure] The PolyphonicPressure element constructed
12
+ def to_stretto(pattern = nil)
13
+ Stretto::MusicElements::PolyphonicPressure.new(self, pattern)
14
+ end
15
+
16
+ # @return [Value] Value of the pitch to apply pressure
17
+ def pitch
18
+ Stretto::Value.new(__pitch.wrap)
19
+ end
20
+
21
+ # @return [Value] Value of the pressure applied
22
+ def value
23
+ Stretto::Value.new(__value.wrap)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'duration_token')
2
+ require File.join(File.dirname(__FILE__), '../../music_elements/rest')
3
+
4
+ module Stretto
5
+ module Tokens
6
+
7
+ # Token from parsing a rest element
8
+ #
9
+ # @example "R", "Rq"
10
+ class RestToken < HashToken
11
+
12
+ include WithDurationToken
13
+
14
+ # @return [MusicElement::Rest] The Rest element constructed
15
+ def to_stretto(pattern = nil)
16
+ Stretto::MusicElements::Rest.new(self, pattern)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ module Stretto
2
+ module Tokens
3
+
4
+ # Represents a numeric value (either decimal or integer) parsed from a music string
5
+ # @example "10.5"
6
+ # @see VariableToken
7
+ module NumericToken
8
+
9
+ # @return [Value::NumericValue] A NumericValue with the parsed text as a float number
10
+ def wrap
11
+ Stretto::Value::NumericValue.new(text_value.to_f)
12
+ end
13
+ end
14
+
15
+ # Represents a variable value parsed from a music string
16
+ # @example "[SOME_VARIABLE]"
17
+ # @see NumericToken
18
+ module VariableToken
19
+
20
+ # @return [Value::VariableValue] A VariableValue with the parsed text as the name of
21
+ # the variable it references
22
+ def wrap
23
+ Stretto::Value::VariableValue.new(name.text_value)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), '../../music_elements/variable')
2
+
3
+ module Stretto
4
+ module Tokens
5
+
6
+ # Token from parsing a variable definition
7
+ #
8
+ # @example "$MY_VAR=80", "$OTHER_VAR=[SOME_NOTE_VAR]"
9
+ class VariableDefinitionToken < HashToken
10
+
11
+ # @return [MusicElements::Variable] The constructed Variable element
12
+ def to_stretto(pattern = nil)
13
+ Stretto::MusicElements::Variable.new(self, pattern)
14
+ end
15
+
16
+ # @return [String] The name of the variable
17
+ def name
18
+ __name.text_value
19
+ end
20
+
21
+ # @return [Value] The value of the variable, a Value object
22
+ # wrapping either a numeric or another variable value
23
+ def value
24
+ Stretto::Value.new(__value.wrap)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ grammar ValueGrammar
2
+
3
+ rule variable_or_integer
4
+ variable <Stretto::Tokens::VariableToken>
5
+ /
6
+ integer <Stretto::Tokens::NumericToken>
7
+ end
8
+
9
+ rule variable_or_decimal
10
+ variable <Stretto::Tokens::VariableToken>
11
+ /
12
+ decimal <Stretto::Tokens::NumericToken>
13
+ end
14
+
15
+ rule integer
16
+ "0" / [1-9] [0-9]*
17
+ end
18
+
19
+ rule decimal
20
+ integer ('.' [0-9]+)?
21
+ end
22
+
23
+ rule variable
24
+ '[' name:variable_name ']'
25
+ end
26
+
27
+ rule variable_name
28
+ [A-Za-z_] [A-Za-z0-9_]+
29
+ end
30
+
31
+ rule integer_or_variable_name
32
+ integer {
33
+ def is_numeric?
34
+ true
35
+ end
36
+ }
37
+ /
38
+ variable_name {
39
+ def is_numeric?
40
+ false
41
+ end
42
+ }
43
+ end
44
+
45
+ end
@@ -0,0 +1,10 @@
1
+ grammar VariableGrammar
2
+
3
+ include ValueGrammar
4
+
5
+ rule variable_definition
6
+ # It can be set to a integer also, but decimal handles that case
7
+ '$' __name:variable_name '=' __value:variable_or_decimal <Stretto::Tokens::VariableDefinitionToken>
8
+ end
9
+
10
+ end
@@ -0,0 +1,9 @@
1
+ grammar VoiceChangeGrammar
2
+
3
+ include ValueGrammar
4
+
5
+ rule voice
6
+ kind:'V' __value:variable_or_integer <Stretto::Tokens::VoiceChangeToken>
7
+ end
8
+
9
+ end
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # ChannelPressure sends a MIDI message similar to applying pressure to an
7
+ # electronic keyboard. This event applies pressure to the whole channel, and its
8
+ # value can rango from 0 to 127
9
+ class ChannelPressure < MusicElement
10
+
11
+ MAX_CHANNEL_PRESSURE_VALUE = 127
12
+
13
+ def initialize(string_or_options, pattern = nil)
14
+ token = case string_or_options
15
+ when String then Stretto::Parser.parse_channel_pressure!(string_or_options)
16
+ else string_or_options
17
+ end
18
+ super(token[:text_value], pattern)
19
+ @original_value = token[:value]
20
+ end
21
+
22
+ # Sets value and validates in range
23
+ def value=(value)
24
+ if value < 0 or value > MAX_CHANNEL_PRESSURE_VALUE
25
+ raise Exceptions::ValueOutOfBoundsException.new("Channel pressure should be in range 0..#{MAX_CHANNEL_PRESSURE_VALUE}")
26
+ end
27
+ @value = value
28
+ end
29
+
30
+ def value
31
+ @value || @original_value.to_i(@pattern)
32
+ end
33
+
34
+ private
35
+
36
+ # @private
37
+ def substitute_variables!
38
+ self.value = value
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,194 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+ require File.join(File.dirname(__FILE__), 'modifiers/attack_decay')
3
+ require File.join(File.dirname(__FILE__), 'modifiers/chord_intervals')
4
+ require 'forwardable'
5
+
6
+ module Stretto
7
+ module MusicElements
8
+
9
+ # A chord indicates a group of notes that can be played together.
10
+ #
11
+ # The most common chord is a named chord. This is indicated by a note string
12
+ # (see {Note}) and the name of the chord. For example, chord C major is
13
+ # represented by +Cmaj+, and it will consist on the notes C, E and G (for definition,
14
+ # according to the musical theory).
15
+ #
16
+ # The chord can also indicate a duration (see {Duration}) and attack and decay (see
17
+ # {AttackDecay}), which have to be inserted after the named chord. For example, the chord
18
+ # +C#5maj7wa80d100+ has a base note of +C#5+, is the major 7th chord (+maj7+) with a
19
+ # whole duration (+w+), and has attack 80 and decay 100 (+a80d100+). The default octave
20
+ # for chords is 3
21
+ #
22
+ # For a list of supported chords see the JFugue reference guide, or the source code
23
+ # for {CHORD_INTERVALS}
24
+ #
25
+ # A chord can be inverted; that is, some of its notes is raised or lowered an octave, also
26
+ # called as the voicing of a chord. Chord inversions can be specified by two ways.
27
+ # The first one is to indicate with the symbol +^+ the number of notes that will be raised
28
+ # a whole octave (for example, the chord +C5maj+ will have notes C5, E5 and G5, its
29
+ # inversion +C5maj^+ will have E5, G5 and E6, and +C5maj^^+ notes G5, C6 and E6). The other
30
+ # way is to indicate explicitely the pivot note to invert from (the note +C5maj^E5+ is
31
+ # equivalent to the first inversion). It will raise an error if a chord is tried to
32
+ # be inverted by a note that is not part of the chord.
33
+ #
34
+ # This class can also hold non-regular chords, called harmonic chords. See {HarmonicChord}
35
+ class Chord < MusicElement
36
+
37
+ include Duration
38
+ include AttackDecay
39
+
40
+ DEFAULT_OCTAVE = 3
41
+
42
+ attr_reader :original_named_chord, :named_chord
43
+ attr_reader :key_signature
44
+ attr_accessor :instrument
45
+
46
+ extend Forwardable
47
+ def_delegators :@base_note, :original_accidental, :accidental,
48
+ :original_pitch, :pitch,
49
+ :original_key, :key,
50
+ :original_octave, :octave,
51
+ :original_attack, :attack,
52
+ :original_decay, :decay
53
+
54
+ def initialize(string_or_options, pattern = nil)
55
+ token = case string_or_options
56
+ when String then Stretto::Parser.parse_chord!(string_or_options)
57
+ else string_or_options
58
+ end
59
+ super(token[:text_value], pattern)
60
+ unless @notes = token[:notes]
61
+ build_duration_from_token(token[:duration])
62
+ @original_base_note = token[:base_note]
63
+ @original_named_chord = token[:named_chord]
64
+ @named_chord = @original_named_chord.downcase
65
+ @original_inversions = token[:inversions]
66
+ @base_note = base_note
67
+ build_attack_and_decay(token[:attack], token[:decay])
68
+ end
69
+ end
70
+
71
+ # @return [Array(MusicElements::Note)] The array of notes generated by this chord
72
+ def notes
73
+ unless @notes
74
+ build_chord_notes(@named_chord)
75
+ build_inversions
76
+ end
77
+ @notes
78
+ end
79
+
80
+ # Returns the base note of the chord, that is, the note the chord was specified with.
81
+ #
82
+ # Note that this is not necessarily equal to the first note in the chord. If it has
83
+ # inversions, the base note will be kept as the original note, but the
84
+ # +notes+ construct will be effectively transposed.
85
+ def base_note
86
+ @base_note || build_base_note(@original_base_note)
87
+ end
88
+
89
+ # @return [Boolean] True if all the notes of the chord are equal
90
+ # @see MusicElements::Note#==
91
+ def ==(other)
92
+ notes && notes == other.notes
93
+ end
94
+
95
+ # @return [Number] The number of inversions of the chord
96
+ # @example
97
+ # Chord.new("Cmaj^^").inversions # => 2
98
+ def inversions
99
+ build_inversions unless @inversions
100
+ @inversions
101
+ end
102
+
103
+ # @return [MusicElements::Note] The pivot note in which do the inversion, if specified
104
+ # @example
105
+ # Chord.new("Cmaj^E").pivot_note # => Note<@key="E">
106
+ def pivot_note
107
+ build_inversions unless @pivot_note
108
+ @pivot_note
109
+ end
110
+
111
+ # Assigns the key signature for the chord, and for all of its notes.
112
+ #
113
+ # @see MusicElements::Note#key_signature=
114
+ def key_signature=(key_signature)
115
+ @key_signature = key_signature
116
+ if @base_note
117
+ @base_note.key_signature = key_signature
118
+ build_chord_notes(@named_chord)
119
+ build_inversions
120
+ end
121
+ @notes.each{ |note| note.pattern = @pattern }
122
+ end
123
+
124
+ private
125
+
126
+ # Builds the base_note according to the parsed token
127
+ def build_base_note(base_note_options)
128
+ Note.new({
129
+ :text_value => base_note_options[:text_value],
130
+ :octave => base_note_options[:octave] || DEFAULT_OCTAVE,
131
+ :accidental => base_note_options[:accidental],
132
+ :key => base_note_options[:key],
133
+ :pitch => base_note_options[:pitch],
134
+ :attack => base_note_options[:attack],
135
+ :decay => base_note_options[:decay],
136
+ :duration => base_note_options[:duration] },
137
+ @pattern
138
+ )
139
+ end
140
+
141
+ # When a named chord is specified, returns an array of the notes consisting
142
+ # on the base note plus the note with the intervals applied
143
+ def build_chord_notes(named_chord)
144
+ @named_chord = named_chord
145
+ intervals = CHORD_INTERVALS[@named_chord]
146
+ @notes = [@base_note] + intervals.map{|interval| @base_note + interval}
147
+ end
148
+
149
+ # Builds either implicit (+^+) or explicit (+^+_note_) inversions
150
+ def build_inversions
151
+ if @original_inversions
152
+ @inversions = @original_inversions[:inversions]
153
+ pivot_note = @original_inversions[:pivot_note]
154
+ if pivot_note
155
+ @pivot_note = Note.new({
156
+ :text_value => pivot_note.text_value,
157
+ :key => pivot_note.key,
158
+ :pitch => pivot_note.pitch,
159
+ :accidental => pivot_note.accidental,
160
+ :octave => pivot_note.octave || DEFAULT_OCTAVE,
161
+ :duration => @original_duration_token,
162
+ :attack => @original_attack,
163
+ :decay => @original_decay
164
+ }, @pattern)
165
+ @pivot_note.pattern = @pattern
166
+ end
167
+ else
168
+ @inversions = 0
169
+ end
170
+ raise Exceptions::ChordInversionsException.new("Number of inversions (#{@inversions}) is greater than chord size (#{notes.size})") if @inversions >= notes.size
171
+
172
+ notes.each{ |note| note.pattern = @pattern }
173
+ if @pivot_note
174
+ actual_pivot = notes.index(notes.find { |note| @pivot_note.pitch == note.pitch }) # TODO: Dropped notes.index(&block) for compatibility with <1.8.7
175
+ raise Exceptions::ChordInversionsException.new("Note #{@pivot_note.original_string}(#{@pivot_note.pitch}) does not belong to chord #{@original_string}") unless actual_pivot
176
+ actual_pivot.times { notes << notes.shift + 12 }
177
+ else
178
+ @inversions.times { notes << notes.shift + 12 }
179
+ end
180
+ end
181
+
182
+
183
+ # @private
184
+ # @see MusicElements::MusicElement#substitute_variables!
185
+ def substitute_variables!
186
+ @base_note.pattern = @pattern
187
+ build_chord_notes(@named_chord)
188
+ build_inversions
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+ end