mtk 0.0.2 → 0.0.3
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/.yardopts +3 -2
- data/DEVELOPMENT_NOTES.md +114 -0
- data/INTRO.md +64 -8
- data/LICENSE.txt +1 -1
- data/README.md +31 -102
- data/Rakefile +56 -18
- data/bin/mtk +215 -0
- data/examples/crescendo.rb +5 -5
- data/examples/drum_pattern1.rb +23 -0
- data/examples/dynamic_pattern.rb +8 -11
- data/examples/gets_and_play.rb +26 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +8 -10
- data/examples/random_tone_row.rb +2 -2
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +8 -0
- data/examples/tone_row_melody.rb +6 -6
- data/lib/mtk.rb +52 -40
- data/lib/mtk/chord.rb +55 -0
- data/lib/mtk/constants/durations.rb +57 -0
- data/lib/mtk/constants/intensities.rb +61 -0
- data/lib/mtk/constants/intervals.rb +73 -0
- data/lib/mtk/constants/pitch_classes.rb +29 -0
- data/lib/mtk/constants/pitches.rb +52 -0
- data/lib/mtk/duration.rb +211 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/helpers/collection.rb +164 -0
- data/lib/mtk/helpers/convert.rb +36 -0
- data/lib/mtk/helpers/lilypond.rb +162 -0
- data/lib/mtk/helpers/output_selector.rb +67 -0
- data/lib/mtk/helpers/pitch_collection.rb +23 -0
- data/lib/mtk/helpers/pseudo_constants.rb +26 -0
- data/lib/mtk/intensity.rb +156 -0
- data/lib/mtk/interval.rb +155 -0
- data/lib/mtk/lang/mtk_grammar.citrus +190 -13
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/melody.rb +94 -0
- data/lib/mtk/midi/dls_synth_device.rb +144 -0
- data/lib/mtk/midi/dls_synth_output.rb +62 -0
- data/lib/mtk/midi/file.rb +67 -32
- data/lib/mtk/midi/input.rb +97 -0
- data/lib/mtk/midi/jsound_input.rb +36 -17
- data/lib/mtk/midi/jsound_output.rb +48 -46
- data/lib/mtk/midi/output.rb +195 -0
- data/lib/mtk/midi/unimidi_input.rb +117 -0
- data/lib/mtk/midi/unimidi_output.rb +121 -0
- data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
- data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/pitch.rb +7 -6
- data/lib/mtk/pitch_class.rb +124 -46
- data/lib/mtk/pitch_class_set.rb +58 -35
- data/lib/mtk/sequencers/event_builder.rb +131 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
- data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
- data/lib/mtk/timeline.rb +39 -22
- data/lib/mtk/variable.rb +32 -0
- data/spec/mtk/chord_spec.rb +83 -0
- data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
- data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
- data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
- data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
- data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
- data/spec/mtk/duration_spec.rb +372 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
- data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
- data/spec/mtk/intensity_spec.rb +289 -0
- data/spec/mtk/interval_spec.rb +265 -0
- data/spec/mtk/lang/parser_spec.rb +597 -0
- data/spec/mtk/melody_spec.rb +223 -0
- data/spec/mtk/midi/file_spec.rb +16 -16
- data/spec/mtk/midi/jsound_input_spec.rb +11 -0
- data/spec/mtk/midi/jsound_output_spec.rb +11 -0
- data/spec/mtk/midi/output_spec.rb +102 -0
- data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
- data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
- data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
- data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
- data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
- data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/pitch_class_set_spec.rb +23 -21
- data/spec/mtk/pitch_class_spec.rb +151 -39
- data/spec/mtk/pitch_spec.rb +22 -1
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
- data/spec/mtk/timeline_spec.rb +109 -16
- data/spec/mtk/variable_spec.rb +52 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- metadata +188 -91
- data/lib/mtk/_constants/durations.rb +0 -80
- data/lib/mtk/_constants/intensities.rb +0 -81
- data/lib/mtk/_constants/intervals.rb +0 -85
- data/lib/mtk/_constants/pitch_classes.rb +0 -35
- data/lib/mtk/_constants/pitches.rb +0 -49
- data/lib/mtk/event.rb +0 -70
- data/lib/mtk/helper/collection.rb +0 -114
- data/lib/mtk/helper/event_builder.rb +0 -85
- data/lib/mtk/helper/pseudo_constants.rb +0 -26
- data/lib/mtk/lang/grammar.rb +0 -17
- data/lib/mtk/note.rb +0 -63
- data/lib/mtk/pattern/abstract_pattern.rb +0 -132
- data/lib/mtk/pattern/cycle.rb +0 -51
- data/lib/mtk/pattern/enumerator.rb +0 -26
- data/lib/mtk/pattern/function.rb +0 -46
- data/lib/mtk/pattern/sequence.rb +0 -30
- data/lib/mtk/pitch_set.rb +0 -84
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
- data/lib/mtk/transform/invertible.rb +0 -15
- data/lib/mtk/transform/mappable.rb +0 -18
- data/lib/mtk/transform/set_theory_operations.rb +0 -34
- data/lib/mtk/transform/transposable.rb +0 -14
- data/spec/mtk/event_spec.rb +0 -139
- data/spec/mtk/helper/event_builder_spec.rb +0 -92
- data/spec/mtk/lang/grammar_spec.rb +0 -100
- data/spec/mtk/note_spec.rb +0 -115
- data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
- data/spec/mtk/pattern/sequence_spec.rb +0 -151
- data/spec/mtk/pitch_set_spec.rb +0 -198
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -0,0 +1,97 @@
|
|
1
|
+
module MTK
|
2
|
+
module MIDI
|
3
|
+
|
4
|
+
# Common behavior for realtime MIDI input.
|
5
|
+
#
|
6
|
+
class Input
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def inherited(subclass)
|
11
|
+
available_input_types << subclass
|
12
|
+
end
|
13
|
+
|
14
|
+
def available_input_types
|
15
|
+
@available_input_types ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def input_types_by_device
|
19
|
+
@input_types_by_device ||= (
|
20
|
+
available_input_types.each_with_object( Hash.new ) do |input_type,hash|
|
21
|
+
input_type.devices.each{|device| hash[device] = input_type }
|
22
|
+
end
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# All available input devices.
|
27
|
+
def devices
|
28
|
+
@devices ||= available_input_types.map{|input_type| input_type.devices }.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
# Maps input device names to the input device.
|
32
|
+
def devices_by_name
|
33
|
+
@devices_by_name ||= (
|
34
|
+
available_input_types.each_with_object( Hash.new ) do |input_type,hash|
|
35
|
+
hash.merge!( input_type.devices_by_name )
|
36
|
+
end
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_by_name(name)
|
41
|
+
if name.is_a? Regexp
|
42
|
+
matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
|
43
|
+
device = devices_by_name[matching_name]
|
44
|
+
else
|
45
|
+
device = devices_by_name[name.to_s]
|
46
|
+
end
|
47
|
+
open(device) if device
|
48
|
+
end
|
49
|
+
|
50
|
+
def open(device)
|
51
|
+
input_type = input_types_by_device[device]
|
52
|
+
input_type.new(device) if input_type
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def initialize(input_device, options={})
|
58
|
+
@device = input_device
|
59
|
+
@device.open
|
60
|
+
@options = options
|
61
|
+
end
|
62
|
+
private_class_method :new
|
63
|
+
|
64
|
+
|
65
|
+
########################
|
66
|
+
public
|
67
|
+
|
68
|
+
# The underlying output device implementation wrapped by this class.
|
69
|
+
# The device type depends on the platform.
|
70
|
+
attr_reader :device
|
71
|
+
|
72
|
+
def name
|
73
|
+
@device.name
|
74
|
+
end
|
75
|
+
|
76
|
+
def record
|
77
|
+
end
|
78
|
+
|
79
|
+
def stop
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_timeline
|
83
|
+
Timeline.new
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
|
92
|
+
if RUBY_PLATFORM == 'java'
|
93
|
+
require 'mtk/midi/jsound_input'
|
94
|
+
else
|
95
|
+
require 'mtk/midi/unimidi_input'
|
96
|
+
end
|
97
|
+
end
|
@@ -1,31 +1,50 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'jsound'
|
3
2
|
|
4
3
|
module MTK
|
5
4
|
module MIDI
|
6
5
|
|
7
|
-
# Provides MIDI input for JRuby via the jsound gem
|
8
|
-
class
|
6
|
+
# Provides realtime MIDI input for JRuby via the jsound gem.
|
7
|
+
# @note This class is optional and only available if you require 'mtk/midi/jsound_input'.
|
8
|
+
# It depends on the 'jsound' gem.
|
9
|
+
class JSoundInput < Input
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
public_class_method :new
|
12
|
+
|
13
|
+
def self.devices
|
14
|
+
@devices ||= ::JSound::Midi::INPUTS.devices
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.devices_by_name
|
18
|
+
@devices_by_name ||= devices.each_with_object( Hash.new ){|device,hash| hash[device.description] = device }
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
attr_reader :device
|
23
|
+
|
24
|
+
def initialize(input_device, options={})
|
25
|
+
@device = input_device
|
16
26
|
@recorder = ::JSound::Midi::Devices::Recorder.new(false)
|
17
|
-
@
|
27
|
+
@device.open
|
28
|
+
end
|
29
|
+
|
30
|
+
def name
|
31
|
+
@device.description
|
18
32
|
end
|
19
33
|
|
20
|
-
def record
|
21
|
-
|
34
|
+
def record(options={})
|
35
|
+
if options[:monitor]
|
36
|
+
@monitor = ::JSound::Midi::Devices::Monitor.new
|
37
|
+
@device >> [@monitor, @recorder]
|
38
|
+
else
|
39
|
+
@device >> @recorder
|
40
|
+
end
|
41
|
+
|
22
42
|
@recorder.clear
|
23
43
|
@recorder.start
|
24
44
|
end
|
25
45
|
|
26
46
|
def stop
|
27
47
|
@recorder.stop
|
28
|
-
@input.close
|
29
48
|
end
|
30
49
|
|
31
50
|
def to_timeline(options={})
|
@@ -35,7 +54,7 @@ module MTK
|
|
35
54
|
note_ons = {}
|
36
55
|
start = nil
|
37
56
|
|
38
|
-
|
57
|
+
@recorder.messages_with_timestamps.each do |message,time|
|
39
58
|
start = time unless start
|
40
59
|
time -= start
|
41
60
|
time /= beats_per_second
|
@@ -46,13 +65,13 @@ module MTK
|
|
46
65
|
|
47
66
|
when :note_off
|
48
67
|
if note_ons.has_key? message.pitch
|
49
|
-
note_on, start_time = note_ons
|
68
|
+
note_on, start_time = note_ons.delete(message.pitch)
|
50
69
|
duration = time - start_time
|
51
|
-
note = Note.from_midi
|
70
|
+
note = MTK::Events::Note.from_midi(note_on.pitch, note_on.velocity, duration, message.channel)
|
52
71
|
timeline.add time,note
|
53
72
|
end
|
54
73
|
|
55
|
-
else timeline.add time,message
|
74
|
+
else timeline.add time, MTK::Events::Parameter.from_midi([message.type, message.channel], message.data1, message.data2)
|
56
75
|
end
|
57
76
|
end
|
58
77
|
|
@@ -1,23 +1,30 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'jsound'
|
3
|
-
require 'gamelan'
|
4
2
|
|
5
3
|
module MTK
|
6
4
|
module MIDI
|
7
5
|
|
8
|
-
# Provides MIDI output for JRuby via the jsound
|
9
|
-
class
|
6
|
+
# Provides realtime MIDI output for JRuby via the jsound and gamelan gems.
|
7
|
+
# @note This class is optional and only available if you require 'mtk/midi/jsound_output'.
|
8
|
+
# It depends on the 'jsound' and 'gamelan' gems.
|
9
|
+
class JSoundOutput < Output
|
10
10
|
|
11
|
-
|
11
|
+
public_class_method :new
|
12
|
+
|
13
|
+
def self.devices
|
14
|
+
@devices ||= ::JSound::Midi::OUTPUTS.devices
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.devices_by_name
|
18
|
+
@devices_by_name ||= devices.each_with_object( Hash.new ){|device,hash| hash[device.description] = device }
|
19
|
+
end
|
12
20
|
|
13
|
-
def initialize(output, options={})
|
14
|
-
if output.is_a? ::JSound::Midi::Device
|
15
|
-
@device = output
|
16
|
-
else
|
17
|
-
@device = ::JSound::Midi::OUTPUTS.send output
|
18
|
-
end
|
19
21
|
|
22
|
+
def initialize(device, options={})
|
23
|
+
@device = device
|
24
|
+
|
25
|
+
# and create an object for generating MIDI message to send to the output:
|
20
26
|
@generator = ::JSound::Midi::Devices::Generator.new
|
27
|
+
|
21
28
|
if options[:monitor]
|
22
29
|
@monitor = ::JSound::Midi::Devices::Monitor.new
|
23
30
|
@generator >> [@monitor, @device]
|
@@ -27,51 +34,46 @@ module MTK
|
|
27
34
|
@device.open
|
28
35
|
end
|
29
36
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
in_background = options.fetch :background, false # default: don't run in background Thread
|
34
|
-
bpm = options.fetch :bmp, 120 # default: 120 beats per minute
|
35
|
-
|
36
|
-
@scheduler = Gamelan::Scheduler.new :tempo => bpm, :rate => scheduler_rate
|
37
|
-
|
38
|
-
for time,events in timeline
|
39
|
-
for event in events
|
40
|
-
case event
|
41
|
-
when Note
|
42
|
-
pitch, velocity, duration = event.to_midi
|
43
|
-
at time, note_on(pitch,velocity)
|
44
|
-
time += duration
|
45
|
-
at time, note_off(pitch,velocity)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
37
|
+
def name
|
38
|
+
@device.description
|
39
|
+
end
|
49
40
|
|
50
|
-
|
51
|
-
|
41
|
+
######################
|
42
|
+
protected
|
52
43
|
|
53
|
-
|
54
|
-
|
44
|
+
# (see Output#note_on)
|
45
|
+
def note_on(pitch, velocity, channel)
|
46
|
+
@generator.note_on(pitch, velocity, channel)
|
55
47
|
end
|
56
48
|
|
57
|
-
|
58
|
-
|
49
|
+
# (see Output#note_off)
|
50
|
+
def note_off(pitch, velocity, channel)
|
51
|
+
@generator.note_off(pitch, velocity, channel)
|
52
|
+
end
|
53
|
+
|
54
|
+
# (see Output#control)
|
55
|
+
def control(number, midi_value, channel)
|
56
|
+
@generator.control_change(number, midi_value, channel)
|
57
|
+
end
|
59
58
|
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
# (see Output#channel_pressure)
|
60
|
+
def channel_pressure(midi_value, channel)
|
61
|
+
@generator.channel_pressure(midi_value, channel)
|
62
|
+
end
|
64
63
|
|
65
|
-
|
66
|
-
|
64
|
+
# (see Output#poly_pressure)
|
65
|
+
def poly_pressure(pitch, midi_value, channel)
|
66
|
+
@generator.poly_pressure(pitch, midi_value, channel)
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
69
|
+
# (see Output#bend)
|
70
|
+
def bend(midi_value, channel)
|
71
|
+
@generator.pitch_bend(midi_value, channel)
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
|
-
|
74
|
+
# (see Output#program)
|
75
|
+
def program(number, channel)
|
76
|
+
@generator.program_change(number, channel)
|
75
77
|
end
|
76
78
|
|
77
79
|
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'gamelan'
|
3
|
+
|
4
|
+
module MTK
|
5
|
+
module MIDI
|
6
|
+
|
7
|
+
# Provides a scheduler and common behavior for realtime MIDI output, using the gamelan gem for scheduling.
|
8
|
+
#
|
9
|
+
# @abstract Subclasses must provide {.devices}, {.devices_by_name}, {#note_on}, {#note_off}, {#control}, {#channel_pressure}, {#poly_pressure}, {#bend}, and {#program} to implement a MIDI output.
|
10
|
+
#
|
11
|
+
class Output
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def inherited(subclass)
|
16
|
+
available_output_types << subclass
|
17
|
+
end
|
18
|
+
|
19
|
+
def available_output_types
|
20
|
+
@available_output_types ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def output_types_by_device
|
24
|
+
@output_types_by_device ||= (
|
25
|
+
available_output_types.each_with_object( Hash.new ) do |output_type,hash|
|
26
|
+
output_type.devices.each{|device| hash[device] = output_type }
|
27
|
+
end
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
# All available output devices.
|
32
|
+
def devices
|
33
|
+
@devices ||= available_output_types.map{|output_type| output_type.devices }.flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
# Maps output device names to the output device.
|
37
|
+
def devices_by_name
|
38
|
+
@devices_by_name ||= (
|
39
|
+
available_output_types.each_with_object( Hash.new ) do |output_type,hash|
|
40
|
+
hash.merge!( output_type.devices_by_name )
|
41
|
+
end
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_by_name(name)
|
46
|
+
if name.is_a? Regexp
|
47
|
+
matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
|
48
|
+
device = devices_by_name[matching_name]
|
49
|
+
else
|
50
|
+
device = devices_by_name[name.to_s]
|
51
|
+
end
|
52
|
+
open(device) if device
|
53
|
+
end
|
54
|
+
|
55
|
+
def open(device)
|
56
|
+
output_type = output_types_by_device[device]
|
57
|
+
output_type.new(device) if output_type
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def initialize(output_device, options={})
|
63
|
+
@device = output_device
|
64
|
+
@device.open
|
65
|
+
@options = options
|
66
|
+
end
|
67
|
+
private_class_method :new
|
68
|
+
|
69
|
+
|
70
|
+
########################
|
71
|
+
public
|
72
|
+
|
73
|
+
# The underlying output device implementation wrapped by this class.
|
74
|
+
# The device type depends on the platform.
|
75
|
+
attr_reader :device
|
76
|
+
|
77
|
+
def name
|
78
|
+
@device.name
|
79
|
+
end
|
80
|
+
|
81
|
+
def play(anything, options={})
|
82
|
+
timeline = case anything
|
83
|
+
when MTK::Timeline then anything
|
84
|
+
when Hash then MTK::Timeline.from_hash anything
|
85
|
+
when Enumerable,MTK::Events::Event then MTK::Timeline.from_hash(0 => anything)
|
86
|
+
else raise "#{self.class}.play() doesn't understand #{anything} (#{anything.class})"
|
87
|
+
end
|
88
|
+
timeline = timeline.flatten
|
89
|
+
|
90
|
+
scheduler_rate = options.fetch :scheduler_rate, 500 # default: 500 Hz
|
91
|
+
trailing_buffer = options.fetch :trailing_buffer, 2 # default: continue playing for 2 beats after the end of the timeline
|
92
|
+
in_background = options.fetch :background, false # default: don't run in background Thread
|
93
|
+
bpm = options.fetch :bmp, 120 # default: 120 beats per minute
|
94
|
+
|
95
|
+
@scheduler = Gamelan::Scheduler.new :tempo => bpm, :rate => scheduler_rate
|
96
|
+
|
97
|
+
timeline.each do |time,events|
|
98
|
+
events.each do |event|
|
99
|
+
next if event.rest?
|
100
|
+
|
101
|
+
channel = event.channel || 0
|
102
|
+
|
103
|
+
case event.type
|
104
|
+
when :note
|
105
|
+
pitch = event.midi_pitch
|
106
|
+
velocity = event.velocity
|
107
|
+
duration = event.duration.to_f
|
108
|
+
@scheduler.at(time) { note_on(pitch,velocity,channel) }
|
109
|
+
@scheduler.at(time + duration) { note_off(pitch,velocity,channel) }
|
110
|
+
|
111
|
+
when :control
|
112
|
+
@scheduler.at(time) { control(event.number, event.midi_value, channel) }
|
113
|
+
|
114
|
+
when :pressure
|
115
|
+
if event.number
|
116
|
+
@scheduler.at(time) { poly_pressure(event.number, event.midi_value, channel) }
|
117
|
+
else
|
118
|
+
@scheduler.at(time) { channel_pressure(event.midi_value, channel) }
|
119
|
+
end
|
120
|
+
|
121
|
+
when :bend
|
122
|
+
@scheduler.at(time) { bend(event.midi_value, channel) }
|
123
|
+
|
124
|
+
when :program
|
125
|
+
@scheduler.at(time) { program(event.number, channel) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end_time = timeline.times.last + trailing_buffer
|
131
|
+
@scheduler.at(end_time) { @scheduler.stop }
|
132
|
+
|
133
|
+
thread = @scheduler.run
|
134
|
+
thread.join if not in_background
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
########################
|
139
|
+
protected
|
140
|
+
|
141
|
+
# these all return stubbed data for testing purposes
|
142
|
+
|
143
|
+
# Send a note on event to the MIDI output
|
144
|
+
def note_on(midi_pitch, velocity, channel)
|
145
|
+
[:note_on, midi_pitch, velocity, channel] # stubbed data for testing purposes
|
146
|
+
end
|
147
|
+
|
148
|
+
# Send a note off event to the MIDI output
|
149
|
+
def note_off(midi_pitch, velocity, channel)
|
150
|
+
[:note_off, midi_pitch, velocity, channel]
|
151
|
+
end
|
152
|
+
|
153
|
+
# Send a control change event to the MIDI output
|
154
|
+
def control(number, midi_value, channel)
|
155
|
+
[:control, number, midi_value, channel]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Send a poly pressure event to the MIDI output.
|
159
|
+
def poly_pressure(midi_pitch, midi_value, channel)
|
160
|
+
[:poly_pressure, midi_pitch, midi_value, channel]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Send a channel pressure event to the MIDI output.
|
164
|
+
def channel_pressure(midi_value, channel)
|
165
|
+
[:channel_pressure, midi_value, channel]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Send a pitch bend event to the MIDI output.
|
169
|
+
def bend(midi_value, channel)
|
170
|
+
[:bend, midi_value, channel]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Send a program change event to the MIDI output.
|
174
|
+
def program(number, channel)
|
175
|
+
[:program, number, channel]
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
|
185
|
+
if RbConfig::CONFIG['host_os'] =~ /darwin/
|
186
|
+
# We're running on OS X
|
187
|
+
require 'mtk/midi/dls_synth_output'
|
188
|
+
end
|
189
|
+
|
190
|
+
if RUBY_PLATFORM == 'java'
|
191
|
+
require 'mtk/midi/jsound_output'
|
192
|
+
else
|
193
|
+
require 'mtk/midi/unimidi_output'
|
194
|
+
end
|
195
|
+
end
|