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,26 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # Represents a rest.
7
+ #
8
+ # Is represented by an "R", followed by a duration modifier (see {Duration})
9
+ class Rest < MusicElement
10
+
11
+ include Duration
12
+
13
+ attr_reader :original_duration
14
+
15
+ def initialize(string_or_options, pattern = nil)
16
+ token = case string_or_options
17
+ when String then Stretto::Parser.parse_rest!(string_or_options)
18
+ else string_or_options
19
+ end
20
+ super(token[:text_value], pattern)
21
+ build_duration_from_token(token[:duration])
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # Tempo represents the bpm of the song.
7
+ #
8
+ # It is indicated by a +T+ followed by the value of the tempo.
9
+ # There are some predefined variables that define the most common tempos
10
+ # (see {Variables::TEMPO_VARIABLES}), and the default is _allegro_, that is,
11
+ # a tempo of +T120+ bpm
12
+ class Tempo < MusicElement
13
+
14
+ def initialize(string_or_options, pattern = nil)
15
+ token = case string_or_options
16
+ when String then Stretto::Parser.parse_tempo!(string_or_options)
17
+ else string_or_options
18
+ end
19
+ super(token[:text_value], pattern)
20
+ @original_value = token[:value]
21
+ end
22
+
23
+ def value=(value)
24
+ @value = value
25
+ end
26
+
27
+ def bpm
28
+ value
29
+ end
30
+
31
+ def value
32
+ @value || @original_value.to_i(@pattern)
33
+ end
34
+
35
+ def substitute_variables!
36
+ self.value = value
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ # Timing information places the elements at the specified position in time,
7
+ #
8
+ # This information is mostly used when reading a MIDI file or using it live,
9
+ # to synchronize with playback. The quantity is expressed in miliseconds.
10
+ class Timing < MusicElement
11
+
12
+ def initialize(string_or_options, pattern = nil)
13
+ token = case string_or_options
14
+ when String then Stretto::Parser.parse_timing!(string_or_options)
15
+ else string_or_options
16
+ end
17
+ super(token[:text_value], pattern)
18
+ @original_value = token[:value]
19
+ end
20
+
21
+ def value
22
+ @value || @original_value.to_i(@pattern)
23
+ end
24
+
25
+ # private
26
+ def substitute_variables!
27
+ @value = self.value
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+
4
+ module Stretto
5
+ module MusicElements
6
+
7
+ # Holds a value, identified by a name, that can be used in some elements in the pattern.
8
+ #
9
+ # The syntax for setting a value is $_name_=_vaue_
10
+ #
11
+ # For example, in a pattern
12
+ #
13
+ # $SOME_VARIABLE=80 [SOME_VARIABLE]
14
+ #
15
+ # the note will have a picth of 80, as defined by the variable _SOME_VARIABLE_
16
+ #
17
+ # Value can be either a numeric value or another variable. Note that predefined variables are
18
+ # not constants; they can be overriden at any time, and the elements after the new definition
19
+ # will take its most recent value.
20
+ #
21
+ # @see Value
22
+ class Variable < MusicElement
23
+
24
+ attr_reader :name, :value
25
+
26
+ def initialize(string_or_options, pattern = nil)
27
+ token = case string_or_options
28
+ when String then Stretto::Parser.parse_variable!(string_or_options)
29
+ else string_or_options
30
+ end
31
+ super(token[:text_value], pattern)
32
+ @value = token[:value]
33
+ @name = token[:name]
34
+ end
35
+
36
+ # @return (see Value#to_i)
37
+ def to_i
38
+ @value.to_i(@pattern)
39
+ end
40
+
41
+ # @return (see Value#to_f)
42
+ def to_f
43
+ @value.to_f(@pattern)
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), 'layer')
2
+
3
+ module Stretto
4
+
5
+ # Represent a _channel_ or _track_, according to the MIDI
6
+ # specification. Each voice has an individual key signature,
7
+ # instrument, and for some values, different controller variables
8
+ #
9
+ # The MIDI specification allows a pattern to have up to 16
10
+ # channels, being the channel 9 the percussion 1.
11
+ class Voice < Array
12
+
13
+ DEFAULT_LAYER_INDEX = 0
14
+
15
+ attr_reader :layers
16
+ attr_reader :index
17
+
18
+ def initialize(index, *args)
19
+ @layers = {}
20
+ @index = index
21
+ super(*args)
22
+ end
23
+
24
+ # Method intended to keep consistency between array-like structures
25
+ # @return [Array(MusicElement)] Its elements as an array
26
+ def elements
27
+ to_a
28
+ end
29
+
30
+ # Appends an element to the voice, creating an additional layer
31
+ # if necessary.
32
+ def <<(element)
33
+ if element.kind_of?(Stretto::MusicElements::LayerChange)
34
+ @current_layer = (@layers[element.index] ||= Layer.new)
35
+ else
36
+ @layers[DEFAULT_LAYER_INDEX] = @current_layer = Layer.new unless @current_layer
37
+ @current_layer << element
38
+ end
39
+ super(element)
40
+ end
41
+
42
+ # Accesor for the {Layer layer} in the specified +index+
43
+ def layer(index)
44
+ @layers[index]
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), 'music_element')
2
+
3
+ module Stretto
4
+ module MusicElements
5
+
6
+ class VoiceChange < MusicElement
7
+
8
+ MAX_VOICES = 15
9
+
10
+ attr_reader :index
11
+
12
+ def initialize(string_or_options, pattern = nil)
13
+ token = case string_or_options
14
+ when String then Stretto::Parser.parse_voice_change!(string_or_options)
15
+ else string_or_options
16
+ end
17
+ super(token[:text_value], pattern)
18
+ @original_value = token[:value]
19
+ end
20
+
21
+ def index
22
+ @index || @original_value.to_i(pattern)
23
+ end
24
+
25
+ def index=(index)
26
+ if index < 0 or index > MAX_VOICES
27
+ raise Exceptions::ValueOutOfBoundsException.new("Voice value should be in range 0..#{MAX_VOICES}")
28
+ end
29
+ @index = index
30
+ end
31
+
32
+ def substitute_variables!
33
+ self.index = index
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ module Stretto
2
+ module Exceptions
3
+ class InvalidValueException < StandardError; end
4
+ class ValueOutOfBoundsException < StandardError; end
5
+ class NoteOutOfBoundsException < StandardError; end
6
+ class ChordInversionsException < StandardError; end
7
+ class VariableNotDefinedException < StandardError; end
8
+ class VariableContextException < StandardError; end
9
+ class ParseError < StandardError; end
10
+ end
11
+ end
@@ -0,0 +1,89 @@
1
+ require File.dirname(__FILE__) + '/exceptions'
2
+
3
+ Treetop.load File.join(File.dirname(__FILE__), "../grammar/stretto_grammar")
4
+
5
+ module Stretto
6
+ class Parser
7
+
8
+ attr_reader :parser, :parsed_elements
9
+
10
+ def initialize(music_string)
11
+ @music_string = music_string
12
+ @parser = StrettoGrammarParser.new
13
+ end
14
+
15
+ def to_stretto(pattern = nil)
16
+ parsed_string.to_stretto(pattern)
17
+ end
18
+
19
+ def valid?
20
+ not parsed_string.nil?
21
+ end
22
+
23
+ def error_on
24
+ @last_error_on
25
+ end
26
+
27
+ private
28
+
29
+ def parsed_string
30
+ parsed_string = @parser.parse(@music_string)
31
+ if parsed_string
32
+ @last_error_on = nil
33
+ else
34
+ @last_error_on = @parser.max_terminal_failure_index
35
+ end
36
+ parsed_string
37
+ end
38
+
39
+ class << self
40
+
41
+ private
42
+ def parse_music_element!(klass, music_string, expected_element)
43
+ klass.new.parse(music_string) ||
44
+ raise(Exceptions::ParseError.new("Invalid #{expected_element}: #{music_string}"))
45
+ end
46
+
47
+ public
48
+
49
+ elements = {
50
+ :channel_pressure => ChannelPressureGrammarParser,
51
+ :chord => ChordGrammarParser,
52
+ :controller_change => ControllerChangeGrammarParser,
53
+ :harmony => HarmonyGrammarParser,
54
+ :harmonic_chord => HarmonicChordGrammarParser,
55
+ :instrument => InstrumentGrammarParser,
56
+ :key_signature => KeySignatureGrammarParser,
57
+ :layer_change => LayerChangeGrammarParser,
58
+ :measure => MeasureGrammarParser,
59
+ :note => NoteGrammarParser,
60
+ :pitch_bend => PitchBendGrammarParser,
61
+ :polyphonic_pressure => PolyphonicPressureGrammarParser,
62
+ :rest => RestGrammarParser,
63
+ :timing => TimingGrammarParser,
64
+ :tempo => TempoGrammarParser,
65
+ :variable => VariableGrammarParser,
66
+ :voice_change => VoiceChangeGrammarParser
67
+ }
68
+ elements.each do |element, klass|
69
+ define_method "parse_#{element}!" do |music_element|
70
+ parse_music_element!(klass, music_element, element.to_s.gsub('_', ' '))
71
+ end
72
+ end
73
+
74
+ def parse_element!(element, klass)
75
+ send(:"parse_#{elementize(klass)}!", element)
76
+ end
77
+
78
+ def elementize(str)
79
+ str.to_s.split('::').last.
80
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
81
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
82
+ tr("-", "_").
83
+ downcase
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,200 @@
1
+ module Stretto
2
+
3
+ # Stretto default Player uses MIDIator for MIDI output.
4
+ class Player
5
+ attr_reader :midi
6
+ attr_accessor :bpm
7
+
8
+ #TODO: can time signature be set?
9
+ DEFAULT_BEAT = 4 # each beat is a quarter note
10
+
11
+ # @param options [Hash] An array of options to initialize the MIDI driver
12
+ # Pass `:driver => :autodetect` to let MIDIator select the most appropiate driver based on the OS
13
+ # @example
14
+ # Stretto::Player.new(:driver => :dls_synth)
15
+ def initialize(options = {:driver => :autodetect})
16
+ @midi = ::MIDIator::Interface.new
17
+ if options[:driver] == :autodetect
18
+ @midi.autodetect_driver
19
+ else
20
+ # TODO: exceptions for unhandled drivers
21
+ @midi.use options[:driver]
22
+ end
23
+ end
24
+
25
+ # Plays the passed music string (or a File object containing a music string)
26
+ #
27
+ # @param music_string_or_file [String, File] The stretto music string
28
+ # @example
29
+ # Stretto::Player.new.play("C D E F G A B C6")
30
+ #
31
+ def play(music_string_or_file)
32
+ @pattern = Stretto::Pattern.new(music_string_or_file)
33
+
34
+ set_default_tempo
35
+ layer_threads = []
36
+ @pattern.voices.each_value do |voice|
37
+ voice.layers.each_value do |layer|
38
+ layer_threads << Thread.new(layer) do |l|
39
+ l.each { |e| play_element(e, voice.index) }
40
+ end
41
+
42
+ end
43
+ end
44
+ layer_threads.each { |t| t.join}
45
+ end
46
+
47
+ private
48
+
49
+ def play_element(element, channel = 0)
50
+ case element
51
+ when Stretto::MusicElements::Note
52
+ play_note(element, channel)
53
+ when Stretto::MusicElements::Rest
54
+ play_rest(element)
55
+ when Stretto::MusicElements::Chord
56
+ play_chord(element, channel)
57
+ when Stretto::MusicElements::Melody
58
+ play_melody(element, channel)
59
+ when Stretto::MusicElements::Measure
60
+ play_measure(element)
61
+ when Stretto::MusicElements::Variable
62
+ play_variable(element)
63
+ when Stretto::MusicElements::VoiceChange
64
+ play_voice_change(element)
65
+ when Stretto::MusicElements::LayerChange
66
+ play_layer_change(element)
67
+ when Stretto::MusicElements::KeySignature
68
+ play_key_signature(element)
69
+ when Stretto::MusicElements::Tempo
70
+ play_tempo(element)
71
+ when Stretto::MusicElements::Harmony
72
+ play_harmony(element, channel)
73
+ when Stretto::MusicElements::ChannelPressure
74
+ play_channel_pressure(element, channel)
75
+ when Stretto::MusicElements::PolyphonicPressure
76
+ play_polyphonic_pressure(element, channel)
77
+ when Stretto::MusicElements::Instrument
78
+ play_instrument(element, channel)
79
+ when Stretto::MusicElements::PitchBend
80
+ play_pitch_bend(element, channel)
81
+ when Stretto::MusicElements::ControllerChange
82
+ play_controller_change(element, channel)
83
+ when Stretto::MusicElements::Timing
84
+ play_timing(element, channel)
85
+ else
86
+ raise "element of class #{element.class} not yet handled by player"
87
+ end
88
+ end
89
+
90
+ def play_note(note, channel)
91
+ unless note.end_of_tie?
92
+ duration = 60.0 / bpm * note.tied_duration * DEFAULT_BEAT
93
+ @midi.note_on(note.pitch, channel, note.attack)
94
+ @midi.rest(duration)
95
+ @midi.note_off(note.pitch, channel, note.decay)
96
+ end
97
+ end
98
+
99
+ def play_rest(rest)
100
+ unless rest.end_of_tie?
101
+ duration = 60.0 / bpm * rest.tied_duration * DEFAULT_BEAT
102
+ @midi.rest(duration)
103
+ end
104
+ end
105
+
106
+ def play_chord(chord, channel)
107
+ unless chord.end_of_tie?
108
+ duration = 60.0 / bpm * chord.tied_duration * DEFAULT_BEAT
109
+ chord.notes.each do |note|
110
+ @midi.note_on(note.pitch, channel, note.attack)
111
+ end
112
+ @midi.rest(duration)
113
+ chord.notes.each do |note|
114
+ @midi.note_off(note.pitch, channel, note.decay)
115
+ end
116
+ end
117
+ end
118
+
119
+ def play_melody(melody, channel)
120
+ melody.elements.each do |element|
121
+ case element
122
+ when Stretto::MusicElements::Note
123
+ play_note(element, channel)
124
+ when Stretto::MusicElements::Rest
125
+ play_rest(element)
126
+ when Stretto::MusicElements::Chord
127
+ play_chord(element, channel)
128
+ end
129
+ end
130
+ end
131
+
132
+ def play_harmony(harmony, channel)
133
+ harmony_threads = []
134
+ harmony.elements.each do |element|
135
+ harmony_threads << Thread.new(element) { |e| play_element(e, channel) }
136
+ end
137
+ harmony_threads.each { |t| t.join }
138
+ end
139
+
140
+ def play_tempo(tempo)
141
+ @bpm = tempo.bpm
142
+ end
143
+
144
+ def play_channel_pressure(channel_pressure, channel)
145
+ @midi.channel_aftertouch(channel, channel_pressure.value)
146
+ end
147
+
148
+ def play_polyphonic_pressure(polyphonic_pressure, channel)
149
+ @midi.aftertouch(polyphonic_pressure.pitch, channel, polyphonic_pressure.value)
150
+ end
151
+
152
+ def set_default_tempo
153
+ unless @pattern.first.is_a?(Stretto::MusicElements::Tempo)
154
+ play_element(Stretto::MusicElements::Tempo.new("T[Allegro]"))
155
+ end
156
+ end
157
+
158
+ def play_instrument(instrument, channel)
159
+ @midi.program_change(channel, instrument.value)
160
+ end
161
+
162
+ def play_pitch_bend(pitch_bend, channel)
163
+ @midi.pitch_bend(channel, pitch_bend.value)
164
+ end
165
+
166
+ def play_key_signature(key_signature)
167
+ # noop
168
+ # TODO, NOTE:
169
+ # MIDI specification allows for a meta message key-signature event.
170
+ # While this is useful for some tools, it does not affect the playback.
171
+ # TODO: Add meta-event key signature
172
+ end
173
+
174
+ def play_measure(measure)
175
+ # noop
176
+ end
177
+
178
+ def play_variable(variable)
179
+ # noop
180
+ end
181
+
182
+ def play_voice_change(voice_change)
183
+ # noop
184
+ end
185
+
186
+ def play_layer_change(layer_change)
187
+ # noop
188
+ end
189
+
190
+ def play_controller_change(controller_change, channel)
191
+ @midi.control_change(channel, controller_change.controller, controller_change.value)
192
+ end
193
+
194
+ def play_timing(timing, channel)
195
+ # TODO: Implement
196
+ end
197
+
198
+ end
199
+ end
200
+