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,55 @@
1
+ module Stretto
2
+ module MusicElements
3
+
4
+ # This module encapsulates behavior for all elements that specify attack and decay values
5
+ #
6
+ # Attack represents the "warm up" of the note, and decay its "cool down", that is, the
7
+ # times it takes to the note to reach its main volume form 0 when building, and the
8
+ # inverse when going into silence. The default value for both is 0
9
+ module AttackDecay
10
+
11
+ DEFAULT_ATTACK = 64
12
+ DEFAULT_DECAY = 64
13
+
14
+ attr_reader :original_attack, :original_decay
15
+
16
+ # Sets up the original attack and decay tokens
17
+ def build_attack_and_decay(original_attack, original_decay)
18
+ @original_attack = original_attack
19
+ @original_decay = original_decay
20
+ end
21
+
22
+ # Sets the instance variable `@attack` and performs validation in range
23
+ #
24
+ # @raise [Exceptions::InvalidValueException] if the value is greater than 127
25
+ def attack=(attack)
26
+ @attack = attack || DEFAULT_ATTACK
27
+ if @attack < 0 or @attack > 127
28
+ raise Exceptions::InvalidValueException.new("Attack should be in the range 0..127")
29
+ end
30
+ end
31
+
32
+ # Sets the instance variable `@decay` and performs validation in range
33
+ #
34
+ # @raise [Exceptions::InvalidValueException] if the value is greater than 127
35
+ def decay=(decay)
36
+ @decay = decay || DEFAULT_DECAY
37
+ if @decay < 0 or @decay > 127
38
+ raise Exceptions::InvalidValueException.new("Decay should be in the range 0..127")
39
+ end
40
+ end
41
+
42
+ # @return [Number] Numeric value for attack, or the default one
43
+ def attack
44
+ @original_attack.to_i(@pattern) || DEFAULT_ATTACK
45
+ end
46
+
47
+ # @return [Number] Numeric value for delay, or the default one
48
+ def decay
49
+ @original_decay.to_i(@pattern) || DEFAULT_DECAY
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ module Stretto
2
+ module MusicElements
3
+
4
+ class Chord < MusicElement
5
+
6
+ # The literal names of chord intervals.
7
+ # Values represent the semitones from the base note of the notes that form the chord.
8
+ CHORD_INTERVALS = {
9
+ 'maj' => [4, 7],
10
+ 'min' => [3, 7],
11
+ 'aug' => [4, 8],
12
+ 'dim' => [3, 6],
13
+ 'dom7' => [4, 7, 10],
14
+ 'maj7' => [4, 7, 11],
15
+ 'min7' => [3, 7, 10],
16
+ 'sus4' => [5, 7],
17
+ 'sus2' => [2, 7],
18
+ 'maj6' => [4, 7, 9],
19
+ 'min6' => [3, 7, 9],
20
+ 'dom9' => [4, 7, 10, 14],
21
+ 'maj9' => [4, 7, 11, 14],
22
+ 'min9' => [3, 7, 10, 14],
23
+ 'dim7' => [3, 6, 9],
24
+ 'add9' => [4, 7, 14],
25
+ 'min11' => [7, 10, 14, 15, 17],
26
+ 'dom11' => [7, 10, 14, 17],
27
+ 'dom13' => [7, 10, 14, 16, 21],
28
+ 'min13' => [7, 10, 14, 15, 21],
29
+ 'maj13' => [7, 11, 14, 16, 21],
30
+ 'dom7<5' => [4, 6, 10],
31
+ 'dom7>5' => [4, 8, 10],
32
+ 'maj7<5' => [4, 6, 11],
33
+ 'maj7>5' => [4, 8, 11],
34
+ 'minmaj7' => [3, 7, 11],
35
+ 'dom7<5<9' => [4, 6, 10, 13],
36
+ 'dom7<5>9' => [4, 6, 10, 15],
37
+ 'dom7>5<9' => [4, 8, 10, 13],
38
+ 'dom7>5>9' => [4, 8, 10, 15]
39
+ }
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,180 @@
1
+ require 'rational'
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # This module encapsulates behavior for elements that specify a duration.
7
+ #
8
+ # Duration can modify notes, chords and rests. It is specified after the note string,
9
+ # and its relative to the duration of the whole note (see {Tempo}). That means that a
10
+ # duration of 1.0 represents a whole note, 0.5 a half note, and 1.5 a whole and a half
11
+ # note, for instance.
12
+ #
13
+ # A duration may be indicated by the duration letters, or literally as a numeric
14
+ # value.
15
+ #
16
+ # In the first way, the letter for the duration is indicated, with the following values:
17
+ #
18
+ # +whole+:: w
19
+ # +half+:: h
20
+ # +quarter+:: q
21
+ # +eighth+:: i
22
+ # +sixteenth+:: s
23
+ # +thity-second+:: t
24
+ # +sixty-fourth+:: x
25
+ # +one-twenty-eighth+:: o
26
+ #
27
+ # The duration can be concatenated (so a duration of +hq+ qould be three quarters), and
28
+ # you may append one or more dot modifiers, that add half the value of the note succesively
29
+ # (see http://en.wikipedia.org/wiki/Dotted_note).
30
+ #
31
+ # Additionally, tuplets can be built by appending a tuplet notation, specified by the
32
+ # syntax +*+_numerator_+:+_denominator_ (see http://en.wikipedia.org/wiki/Tuplet for more
33
+ # information about tuplets). If omitter, the numerator and denominator will default to
34
+ # 3 and 2, respectively (most commonly called a triplet).
35
+ #
36
+ # The other way to specify a duration is to indicate its numeric value after a slash.
37
+ # For example, a C note with half duration can be expressed as +C/0.5+
38
+ # This notation does not accept dots or tuplets.
39
+ #
40
+ # The duration of a note can be tied, so its {#tied_duration tied duration} will be
41
+ # added to the next {#tied_elements tied elements}. To indicate the start or end of a tie,
42
+ # add a dash after or before the duration (For example, +Cw- C-w- C-h will tie two
43
+ # whole notes and a half note together). When added to a pattern, one can get the
44
+ # total duration of a note by calling {tied_duration}. All elements other than notes, chords
45
+ # or rests are ignored within tied notes (most notably {Measure measures})
46
+ module Duration
47
+
48
+ DURATIONS = { 'w' => Rational(1),
49
+ 'h' => Rational(1, 2),
50
+ 'q' => Rational(1, 4),
51
+ 'i' => Rational(1, 8),
52
+ 's' => Rational(1, 16),
53
+ 't' => Rational(1, 32),
54
+ 'x' => Rational(1, 64),
55
+ 'o' => Rational(1, 128) }
56
+
57
+ DEFAULT_DURATION = DURATIONS['q']
58
+ DEFAULT_TUPLET_NUMERATOR = 3
59
+ DEFAULT_TUPLET_DENOMINATOR = 2
60
+
61
+ attr_reader :original_duration
62
+ attr_reader :start_tie, :end_tie
63
+
64
+ # @param [Tokens::DurationToken, nil] duration_token
65
+ # @param [Rational, Number] default_duration The default duration if there's no token
66
+ def build_duration_from_token(duration_token, default_duration = DEFAULT_DURATION)
67
+ @original_duration_token = duration_token
68
+ @original_duration = duration_token ? duration_token.text_value : ''
69
+ @processed_value = Duration.parse_duration(@original_duration_token, default_duration)
70
+ if @original_duration_token
71
+ @start_of_tie = @original_duration_token.start_of_tie?
72
+ @end_of_tie = @original_duration_token.end_of_tie?
73
+ end
74
+ end
75
+
76
+ # Duration value is proportional to a whole note (one whole note = 1.0 duration).
77
+ #
78
+ # This method will coerce the duration to a float, but internally the elements can
79
+ # handle it as a rational number, to perform calculations more accurately.
80
+ #
81
+ # @return [Number] The value of the duration
82
+ def duration
83
+ case @processed_value
84
+ when Stretto::Value then @processed_value.to_f(@pattern)
85
+ else @processed_value
86
+ end
87
+ end
88
+
89
+ # @return [Boolean] If the note starts a tie, indicating the duration should continue
90
+ # onto the next element
91
+ def start_of_tie?
92
+ @start_of_tie.present?
93
+ end
94
+
95
+ # @return [Boolean] If the note ends a tie, indicating the duration should extend
96
+ # the duration of the previous element
97
+ def end_of_tie?
98
+ @end_of_tie.present?
99
+ end
100
+
101
+ # Returns an array containing all the elements that are tied to the right
102
+ #
103
+ # It can return an array consisiting on just the current element, if it is not tied
104
+ # or simply it doesn't belong to a pattern
105
+ #
106
+ # @return [Array(MusicElements::MusicElement)] Array of tied elements
107
+ def tied_elements
108
+ current = self
109
+ elements = [current]
110
+ while next_is_tied?(current)
111
+ current = current.next
112
+ elements << current unless current.kind_of?(Measure)
113
+ end
114
+ elements
115
+ end
116
+
117
+ # Returns the total duration of the tied elements
118
+ #
119
+ # @return [Number]
120
+ # @see #tied_elements
121
+ def tied_duration
122
+ tied_elements.inject(0){|sum, element| sum + element.duration}
123
+ end
124
+
125
+ # Parses duration from a token, or returning the default duration
126
+ #
127
+ # @return [Rational, Number]
128
+ def self.parse_duration(duration_token, default_duration = DEFAULT_DURATION)
129
+ if !duration_token
130
+ default_duration
131
+ elsif duration_token.value
132
+ duration_token.value
133
+ else
134
+ parse_duration_token(duration_token)
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ # Returns whether this note is start of tie, and the next one has end of tie.
141
+ # Ignores Measures, so it will return true even when there's one in between
142
+ #
143
+ # @return [Boolean]
144
+ def next_is_tied?(note)
145
+ note.start_of_tie? &&
146
+ note.next &&
147
+ note.next.end_of_tie? &&
148
+ (note.class == note.next.class || note.next.kind_of?(Measure) || note.kind_of?(Measure))
149
+ end
150
+
151
+ class << self
152
+
153
+ private
154
+
155
+ # Parses the duration when it comes as characters + modifiers
156
+ # (e.g. "wh.", "q*3:5")
157
+ #
158
+ # @return [Rational, Number]
159
+ def parse_duration_token(duration_token)
160
+ duration = duration_token.duration_character.split('').map do |char_duration| # TODO: deprecation. each_char is only 1.9+
161
+ DURATIONS[char_duration.downcase]
162
+ end.sum
163
+ original_duration = duration
164
+ duration_token.dots.times do |dot_duration|
165
+ duration += (original_duration * Rational(1, 2 ** (dot_duration + 1)))
166
+ end
167
+ if duration_token.tuplet
168
+ numerator = duration_token.tuplet[:numerator] || DEFAULT_TUPLET_NUMERATOR
169
+ denominator = duration_token.tuplet[:denominator] || DEFAULT_TUPLET_DENOMINATOR
170
+ duration *= Rational(denominator.to_i, numerator.to_i)
171
+ end
172
+ duration
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,172 @@
1
+ require File.dirname(__FILE__) + '/variables'
2
+
3
+ module Stretto
4
+
5
+ # This class acts as a placeholder for values contained by musical elements.
6
+ #
7
+ # It is needed in order to retrieve values held by a variable until evaluation,
8
+ # looking for them in the pattern the element belongs, or one of the predefined
9
+ # variables.
10
+ class Value
11
+
12
+ # Wraps a numeric value
13
+ # @see Value
14
+ class NumericValue
15
+
16
+ attr_reader :numeric
17
+
18
+ # @param [Number] The numeric value held
19
+ def initialize(numeric)
20
+ @numeric = numeric
21
+ end
22
+
23
+ # @return [Integer] The numeric value coerced to an integer
24
+ def to_i(pattern)
25
+ @numeric.to_i
26
+ end
27
+
28
+ # @return [Float] The numeric value coerced to a float
29
+ def to_f(pattern)
30
+ @numeric.to_f
31
+ end
32
+
33
+ # @return [Boolean] True if the value it is holding is equal to other's value
34
+ def ==(other)
35
+ other.kind_of?(NumericValue) && other.numeric == @numeric
36
+ end
37
+
38
+ # @return [String] The string representation of the numeric value
39
+ def to_s
40
+ @numeric.to_s
41
+ end
42
+ end
43
+
44
+ #--------------------------------------------------------------
45
+
46
+ # Wraps a variable value, that is, holds a reference to a value that is going
47
+ # to be evaluated until requested.
48
+ #
49
+ # @see Value
50
+ class VariableValue
51
+
52
+ include Variables
53
+
54
+ attr_reader :name
55
+
56
+ # @param [String] The name that references the variable
57
+ def initialize(name)
58
+ @name = name
59
+ end
60
+
61
+ # Returns the value of the variable as an integer
62
+ #
63
+ # @return [Integer]
64
+ # @raise (see #get_numeric_value)
65
+ def to_i(pattern)
66
+ get_numeric_value(pattern){ |value| value.to_i(pattern) }
67
+ end
68
+
69
+ # Returns the value of the variable as a float
70
+ #
71
+ # @return [Float]
72
+ # @raise (see #get_numeric_value)
73
+ def to_f(pattern)
74
+ get_numeric_value(pattern){ |value| value.to_f(pattern) }
75
+ end
76
+
77
+ # Returns either the variable value if attached to a pattern, or raises an exception
78
+ # if there is no pattern attached and the variable is not one of the predefined ones.
79
+ def value(pattern)
80
+ if pattern
81
+ pattern.variable(@name)
82
+ else
83
+ predefined = PREDEFINED_VARIABLES[name.upcase]
84
+ unless predefined
85
+ raise Stretto::Exceptions::VariableContextException.new(
86
+ "A pattern is needed to access variable #{@name}")
87
+ end
88
+ Value.new(Value::NumericValue.new(predefined))
89
+ end
90
+ end
91
+
92
+ # Returns whether the other object is the same class and holds the same variable name
93
+ def ==(other)
94
+ other.kind_of?(VariableValue) && other.name == @name
95
+ end
96
+
97
+ # Name of the variable, enclosed in brackets
98
+ #
99
+ # @return [String]
100
+ def to_s
101
+ "[#{@name}]"
102
+ end
103
+
104
+ private
105
+
106
+ # Gets the numeric value of the variable.
107
+ #
108
+ # It does indirection, that is, if the value holding is a variable itself,
109
+ # looks up recursively.
110
+ #
111
+ # @raise [VariableContextException] if the variable is not one of the predefined values
112
+ # and there is no pattern attached
113
+ # @raise [VariableNotDefinedException] if the variable is not defined in the pattern
114
+ def get_numeric_value(pattern)
115
+ variable = self
116
+ variable = yield variable.value(pattern) until variable.kind_of?(Numeric)
117
+ variable
118
+ end
119
+ end
120
+
121
+ #--------------------------------------------------------------
122
+
123
+ # Initializes with the value passed in, and an optional variation.
124
+ #
125
+ # @param value [NumericValue, VariableValue]
126
+ # @param variation [Number] Works as a delayed addition, so you can do things like
127
+ # `Value.new(VariableValue.new("SOME_VAR")) + 1`
128
+ # and have it evaluated until requested
129
+ def initialize(value, variation = nil)
130
+ @value = value
131
+ @variation = variation
132
+ end
133
+
134
+ # Converts the value to integer
135
+ #
136
+ # @return [Integer, nil]
137
+ def to_i(pattern)
138
+ if @value
139
+ result = @value.to_i(pattern)
140
+ result += @variation if @variation
141
+ result
142
+ end
143
+ end
144
+
145
+ # Converts the value to float
146
+ #
147
+ # @return [Float, nil]
148
+ def to_f(pattern)
149
+ if @value
150
+ result = @value.to_f(pattern)
151
+ result += @variation if @variation
152
+ result
153
+ end
154
+ end
155
+
156
+ # Adds a variation to the Value, returning a new one with the sum of variations
157
+ #
158
+ # @return [Value]
159
+ def +(variation)
160
+ new_variation = [@variation, variation].compact.sum
161
+ self.class.new(@value, new_variation)
162
+ end
163
+
164
+ # String representation for this value, either a number or the name of the variable.
165
+ def to_s
166
+ output = @value.to_s
167
+ output += "+#{@variation}" if @variation
168
+ output
169
+ end
170
+
171
+ end
172
+ end