Class: MTK::IO::MIDIOutput Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/mtk/io/midi_output.rb

Overview

This class is abstract.

Provides a scheduler and common behavior for realtime MIDI output, using the gamelan gem for scheduling.

Direct Known Subclasses

DLSSynthOutput, JSoundOutput, UniMIDIOutput

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output_device, options = {}) ⇒ MIDIOutput

Returns a new instance of MIDIOutput.



62
63
64
65
66
# File 'lib/mtk/io/midi_output.rb', line 62

def initialize(output_device, options={})
  @device = output_device
  @device.open
  @options = options
end

Instance Attribute Details

#deviceObject (readonly)

The underlying output device implementation wrapped by this class. The device type depends on the platform.



75
76
77
# File 'lib/mtk/io/midi_output.rb', line 75

def device
  @device
end

Class Method Details

.available_output_typesObject



19
20
21
# File 'lib/mtk/io/midi_output.rb', line 19

def available_output_types
  @available_output_types ||= []
end

.devicesObject

All available output devices.



32
33
34
# File 'lib/mtk/io/midi_output.rb', line 32

def devices
  @devices ||= available_output_types.map{|output_type| output_type.devices }.flatten
end

.devices_by_nameObject

Maps output device names to the output device.



37
38
39
40
41
42
43
# File 'lib/mtk/io/midi_output.rb', line 37

def devices_by_name
  @devices_by_name ||= (
    available_output_types.each_with_object( Hash.new ) do |output_type,hash|
      hash.merge!( output_type.devices_by_name )
    end
  )
end

.find_by_name(name) ⇒ Object



45
46
47
48
49
50
51
52
53
# File 'lib/mtk/io/midi_output.rb', line 45

def find_by_name(name)
  if name.is_a? Regexp
    matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
    device = devices_by_name[matching_name]
  else
    device = devices_by_name[name.to_s]
  end
  open(device) if device
end

.open(device) ⇒ Object



55
56
57
58
# File 'lib/mtk/io/midi_output.rb', line 55

def open(device)
  output_type = output_types_by_device[device]
  output_type.new(device) if output_type
end

.output_types_by_deviceObject



23
24
25
26
27
28
29
# File 'lib/mtk/io/midi_output.rb', line 23

def output_types_by_device
  @output_types_by_device ||= (
    available_output_types.each_with_object( Hash.new ) do |output_type,hash|
      output_type.devices.each{|device| hash[device] = output_type }
    end
  )
end

Instance Method Details

#bend(midi_value, channel) ⇒ Object (protected)

Send a pitch bend event to the MIDI output.



176
177
178
# File 'lib/mtk/io/midi_output.rb', line 176

def bend(midi_value, channel)
  [:bend, midi_value, channel]
end

#channel_pressure(midi_value, channel) ⇒ Object (protected)

Send a channel pressure event to the MIDI output.



171
172
173
# File 'lib/mtk/io/midi_output.rb', line 171

def channel_pressure(midi_value, channel)
  [:channel_pressure, midi_value, channel]
end

#control(number, midi_value, channel) ⇒ Object (protected)

Send a control change event to the MIDI output



161
162
163
# File 'lib/mtk/io/midi_output.rb', line 161

def control(number, midi_value, channel)
  [:control, number, midi_value, channel]
end

#nameObject



77
78
79
# File 'lib/mtk/io/midi_output.rb', line 77

def name
  @device.name
end

#note_off(midi_pitch, velocity, channel) ⇒ Object (protected)

Send a note off event to the MIDI output



156
157
158
# File 'lib/mtk/io/midi_output.rb', line 156

def note_off(midi_pitch, velocity, channel)
  [:note_off, midi_pitch, velocity, channel]
end

#note_on(midi_pitch, velocity, channel) ⇒ Object (protected)

Send a note on event to the MIDI output



151
152
153
# File 'lib/mtk/io/midi_output.rb', line 151

def note_on(midi_pitch, velocity, channel)
  [:note_on, midi_pitch, velocity, channel] # stubbed data for testing purposes
end

#play(anything, options = {}) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/mtk/io/midi_output.rb', line 81

def play(anything, options={})
  timeline = case anything
    when MTK::Events::Timeline then anything
    when Hash then  MTK::Events::Timeline.from_h anything
    when Enumerable,MTK::Events::Event then  MTK::Events::Timeline.from_h(0 => anything)
    else raise "#{self.class}.play() doesn't understand #{anything} (#{anything.class})"
  end
  timeline = timeline.flatten

  scheduler_rate = options.fetch :scheduler_rate, 500 # default: 500 Hz
  trailing_buffer = options.fetch :trailing_buffer, 2 # default: continue playing for 2 beats after the end of the timeline
  in_background = options.fetch :background, false # default: don't run in background Thread
  bpm = options.fetch :bmp, 120 # default: 120 beats per minute

  @scheduler = Gamelan::Scheduler.new :tempo => bpm, :rate => scheduler_rate

  timeline.each do |time,events|
    events.each do |event|
      next if event.rest?

      channel = event.channel || 0

      case event.type
        when :note
          pitch = event.midi_pitch
          velocity = event.velocity
          duration = event.duration.to_f
          # Set a lower priority (via level:1) for note-ons, so legato notes at the same pitch don't
          # prematurely chop off the next note, by ensuring all note-offs at the same timepoint occur first.
          @scheduler.at(time, level: 1) { note_on(pitch,velocity,channel) }
          @scheduler.at(time + duration) { note_on(pitch,0,channel) }
          # TODO: use a proper note off message whenever we support off velocities
          #@scheduler.at(time + duration) { note_off(pitch,velocity,channel) }

        when :control
          @scheduler.at(time) { control(event.number, event.midi_value, channel) }

        when :pressure
          if event.number
            @scheduler.at(time) { poly_pressure(event.number, event.midi_value, channel) }
          else
            @scheduler.at(time) { channel_pressure(event.midi_value, channel) }
          end

        when :bend
          @scheduler.at(time) { bend(event.midi_value, channel) }

        when :program
          @scheduler.at(time) { program(event.number, channel) }
      end
    end
  end

  end_time = timeline.times.last
  final_events = timeline[end_time]
  max_length = final_events.inject(0) {|max,event| len = event.length; max > len ? max : len } || 0
  end_time += max_length + trailing_buffer
  @scheduler.at(end_time) { @scheduler.stop }

  thread = @scheduler.run
  thread.join if not in_background
end

#poly_pressure(midi_pitch, midi_value, channel) ⇒ Object (protected)

Send a poly pressure event to the MIDI output.



166
167
168
# File 'lib/mtk/io/midi_output.rb', line 166

def poly_pressure(midi_pitch, midi_value, channel)
  [:poly_pressure, midi_pitch, midi_value, channel]
end

#program(number, channel) ⇒ Object (protected)

Send a program change event to the MIDI output.



181
182
183
# File 'lib/mtk/io/midi_output.rb', line 181

def program(number, channel)
  [:program, number, channel]
end