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