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