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