mtk 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. data/.yardopts +3 -2
  2. data/DEVELOPMENT_NOTES.md +114 -0
  3. data/INTRO.md +64 -8
  4. data/LICENSE.txt +1 -1
  5. data/README.md +31 -102
  6. data/Rakefile +56 -18
  7. data/bin/mtk +215 -0
  8. data/examples/crescendo.rb +5 -5
  9. data/examples/drum_pattern1.rb +23 -0
  10. data/examples/dynamic_pattern.rb +8 -11
  11. data/examples/gets_and_play.rb +26 -0
  12. data/examples/notation.rb +22 -0
  13. data/examples/play_midi.rb +8 -10
  14. data/examples/random_tone_row.rb +2 -2
  15. data/examples/syntax_to_midi.rb +28 -0
  16. data/examples/test_output.rb +8 -0
  17. data/examples/tone_row_melody.rb +6 -6
  18. data/lib/mtk.rb +52 -40
  19. data/lib/mtk/chord.rb +55 -0
  20. data/lib/mtk/constants/durations.rb +57 -0
  21. data/lib/mtk/constants/intensities.rb +61 -0
  22. data/lib/mtk/constants/intervals.rb +73 -0
  23. data/lib/mtk/constants/pitch_classes.rb +29 -0
  24. data/lib/mtk/constants/pitches.rb +52 -0
  25. data/lib/mtk/duration.rb +211 -0
  26. data/lib/mtk/events/event.rb +119 -0
  27. data/lib/mtk/events/note.rb +112 -0
  28. data/lib/mtk/events/parameter.rb +54 -0
  29. data/lib/mtk/helpers/collection.rb +164 -0
  30. data/lib/mtk/helpers/convert.rb +36 -0
  31. data/lib/mtk/helpers/lilypond.rb +162 -0
  32. data/lib/mtk/helpers/output_selector.rb +67 -0
  33. data/lib/mtk/helpers/pitch_collection.rb +23 -0
  34. data/lib/mtk/helpers/pseudo_constants.rb +26 -0
  35. data/lib/mtk/intensity.rb +156 -0
  36. data/lib/mtk/interval.rb +155 -0
  37. data/lib/mtk/lang/mtk_grammar.citrus +190 -13
  38. data/lib/mtk/lang/parser.rb +29 -0
  39. data/lib/mtk/melody.rb +94 -0
  40. data/lib/mtk/midi/dls_synth_device.rb +144 -0
  41. data/lib/mtk/midi/dls_synth_output.rb +62 -0
  42. data/lib/mtk/midi/file.rb +67 -32
  43. data/lib/mtk/midi/input.rb +97 -0
  44. data/lib/mtk/midi/jsound_input.rb +36 -17
  45. data/lib/mtk/midi/jsound_output.rb +48 -46
  46. data/lib/mtk/midi/output.rb +195 -0
  47. data/lib/mtk/midi/unimidi_input.rb +117 -0
  48. data/lib/mtk/midi/unimidi_output.rb +121 -0
  49. data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
  50. data/lib/mtk/patterns/chain.rb +49 -0
  51. data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
  52. data/lib/mtk/patterns/cycle.rb +18 -0
  53. data/lib/mtk/patterns/for_each.rb +71 -0
  54. data/lib/mtk/patterns/function.rb +39 -0
  55. data/lib/mtk/{pattern → patterns}/lines.rb +11 -17
  56. data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
  57. data/lib/mtk/patterns/pattern.rb +171 -0
  58. data/lib/mtk/patterns/sequence.rb +20 -0
  59. data/lib/mtk/pitch.rb +7 -6
  60. data/lib/mtk/pitch_class.rb +124 -46
  61. data/lib/mtk/pitch_class_set.rb +58 -35
  62. data/lib/mtk/sequencers/event_builder.rb +131 -0
  63. data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
  64. data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
  65. data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
  66. data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
  67. data/lib/mtk/timeline.rb +39 -22
  68. data/lib/mtk/variable.rb +32 -0
  69. data/spec/mtk/chord_spec.rb +83 -0
  70. data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
  71. data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
  72. data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
  73. data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
  74. data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
  75. data/spec/mtk/duration_spec.rb +372 -0
  76. data/spec/mtk/events/event_spec.rb +234 -0
  77. data/spec/mtk/events/note_spec.rb +174 -0
  78. data/spec/mtk/events/parameter_spec.rb +220 -0
  79. data/spec/mtk/{helper → helpers}/collection_spec.rb +86 -3
  80. data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
  81. data/spec/mtk/intensity_spec.rb +289 -0
  82. data/spec/mtk/interval_spec.rb +265 -0
  83. data/spec/mtk/lang/parser_spec.rb +597 -0
  84. data/spec/mtk/melody_spec.rb +223 -0
  85. data/spec/mtk/midi/file_spec.rb +16 -16
  86. data/spec/mtk/midi/jsound_input_spec.rb +11 -0
  87. data/spec/mtk/midi/jsound_output_spec.rb +11 -0
  88. data/spec/mtk/midi/output_spec.rb +102 -0
  89. data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
  90. data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
  91. data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
  92. data/spec/mtk/patterns/chain_spec.rb +110 -0
  93. data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
  94. data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
  95. data/spec/mtk/patterns/for_each_spec.rb +136 -0
  96. data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
  97. data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
  98. data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
  99. data/spec/mtk/patterns/pattern_spec.rb +132 -0
  100. data/spec/mtk/patterns/sequence_spec.rb +203 -0
  101. data/spec/mtk/pitch_class_set_spec.rb +23 -21
  102. data/spec/mtk/pitch_class_spec.rb +151 -39
  103. data/spec/mtk/pitch_spec.rb +22 -1
  104. data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
  105. data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
  106. data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
  107. data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
  108. data/spec/mtk/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
  109. data/spec/mtk/timeline_spec.rb +109 -16
  110. data/spec/mtk/variable_spec.rb +52 -0
  111. data/spec/spec_coverage.rb +2 -0
  112. data/spec/spec_helper.rb +3 -0
  113. metadata +188 -91
  114. data/lib/mtk/_constants/durations.rb +0 -80
  115. data/lib/mtk/_constants/intensities.rb +0 -81
  116. data/lib/mtk/_constants/intervals.rb +0 -85
  117. data/lib/mtk/_constants/pitch_classes.rb +0 -35
  118. data/lib/mtk/_constants/pitches.rb +0 -49
  119. data/lib/mtk/event.rb +0 -70
  120. data/lib/mtk/helper/collection.rb +0 -114
  121. data/lib/mtk/helper/event_builder.rb +0 -85
  122. data/lib/mtk/helper/pseudo_constants.rb +0 -26
  123. data/lib/mtk/lang/grammar.rb +0 -17
  124. data/lib/mtk/note.rb +0 -63
  125. data/lib/mtk/pattern/abstract_pattern.rb +0 -132
  126. data/lib/mtk/pattern/cycle.rb +0 -51
  127. data/lib/mtk/pattern/enumerator.rb +0 -26
  128. data/lib/mtk/pattern/function.rb +0 -46
  129. data/lib/mtk/pattern/sequence.rb +0 -30
  130. data/lib/mtk/pitch_set.rb +0 -84
  131. data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
  132. data/lib/mtk/transform/invertible.rb +0 -15
  133. data/lib/mtk/transform/mappable.rb +0 -18
  134. data/lib/mtk/transform/set_theory_operations.rb +0 -34
  135. data/lib/mtk/transform/transposable.rb +0 -14
  136. data/spec/mtk/event_spec.rb +0 -139
  137. data/spec/mtk/helper/event_builder_spec.rb +0 -92
  138. data/spec/mtk/lang/grammar_spec.rb +0 -100
  139. data/spec/mtk/note_spec.rb +0 -115
  140. data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
  141. data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
  142. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
  143. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
  144. data/spec/mtk/pattern/sequence_spec.rb +0 -151
  145. data/spec/mtk/pitch_set_spec.rb +0 -198
  146. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
  147. 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 JSoundInput
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
- def initialize(input_device)
11
- if input_device.is_a? ::JSound::Midi::Device
12
- @input = input_device
13
- else
14
- @input = ::JSound::Midi::INPUTS.send input_device
15
- end
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
- @input >> @recorder
27
+ @device.open
28
+ end
29
+
30
+ def name
31
+ @device.description
18
32
  end
19
33
 
20
- def record
21
- @input.open
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
- for message,time in @recorder.messages_with_timestamps
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[message.pitch]
68
+ note_on, start_time = note_ons.delete(message.pitch)
50
69
  duration = time - start_time
51
- note = Note.from_midi note_on.pitch, note_on.velocity, duration
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 gem
9
- class JSoundOutput
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
- attr_reader :device
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 play(timeline, options={})
31
- scheduler_rate = options.fetch :scheduler_rate, 500 # default: 500 Hz
32
- trailing_buffer = options.fetch :trailing_buffer, 2 # default: continue playing for 2 beats after the end of the timeline
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
- end_time = timeline.times.last + trailing_buffer
51
- @scheduler.at(end_time) { @scheduler.stop }
41
+ ######################
42
+ protected
52
43
 
53
- thread = @scheduler.run
54
- thread.join if not in_background
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
- private
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
- # It's necessary to generate the events through methods and lambdas like this to create closures.
61
- # Otherwise when the @generator methods are called, they might not be passed the values you expected.
62
- # I suspect this may not a problem in MRI ruby, but I'm having trouble in JRuby
63
- # (pitch and velocity were always the last scheduled values)
59
+ # (see Output#channel_pressure)
60
+ def channel_pressure(midi_value, channel)
61
+ @generator.channel_pressure(midi_value, channel)
62
+ end
64
63
 
65
- def note_on(pitch, velocity)
66
- lambda { @generator.note_on(pitch, velocity) }
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
- def note_off(pitch, velocity)
70
- lambda { @generator.note_off(pitch, velocity) }
69
+ # (see Output#bend)
70
+ def bend(midi_value, channel)
71
+ @generator.pitch_bend(midi_value, channel)
71
72
  end
72
73
 
73
- def at time, block
74
- @scheduler.at(time) { block.call }
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