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