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