Module: AMS::MIDI

Defined in:
midi.rb

Constant Summary

Class Method Summary (collapse)

Class Method Details

+ (Boolean) change_channel_controller(channel, cnum, cval)

Set note controller.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

  • cnum (Fixnum)

    between 0 and 127. Controller number.

  • cval (Fixnum)

    between 0 and 127. Controller value.

Returns:

  • (Boolean)

    success

See Also:

Since:

  • 2.0.0



201
202
203
204
205
206
207
208
209
210
211
# File 'midi.rb', line 201

def change_channel_controller(channel, cnum, cval)
  open_device
  channel = AMS.clamp(channel.to_i, 0, 15)
  cnum = AMS.clamp(cnum.to_i, 0, 127)
  cval = AMS.clamp(cval.to_i, 0, 127)
  # Change note controller.
  h = cval
  l =  cnum << 8 | (0xB0 | channel)
  msg = h << 16 | l
  C.midiOutShortMsg( @handle.read_int, msg ) == 0
end

+ (Boolean) change_channel_expression(channel, expression = 127)

Set channel expression. This is used for dynamics within a single track.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

  • expression (Fixnum) (defaults to: 127)

    between 0 and 127.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



237
238
239
# File 'midi.rb', line 237

def change_channel_expression(channel, expression = 127)
  change_channel_controller(channel, 0x0B, expression)
end

+ (Boolean) change_channel_stereo_pan(channel, pan = 64)

Distribute channel volume between left and right speakers.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

  • pan (Fixnum) (defaults to: 64)

    between 0 and 127. 0 is far left, 64 is center, 127 is far right.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



228
229
230
# File 'midi.rb', line 228

def change_channel_stereo_pan(channel, pan = 64)
  change_channel_controller(channel, 0x0A, pan)
end

+ (Boolean) change_channel_volume(channel, volume = 127)

Set channel volume.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

  • volume (Fixnum) (defaults to: 127)

    between 0 and 127. Soft to loud.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



218
219
220
# File 'midi.rb', line 218

def change_channel_volume(channel, volume = 127)
  change_channel_controller(channel, 0x07, volume)
end

+ (Boolean) channel_sustain_pedal(channel, state = true)

Control channel pedal. Message must be sent prior to the note it effects.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

  • state (Boolean) (defaults to: true)

    true for pedal down, false for pedal up.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



247
248
249
# File 'midi.rb', line 247

def channel_sustain_pedal(channel, state = true)
  change_channel_controller(channel, 0x40, state ? 127 : 0)
end

+ (Boolean) close_device

Close device.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



72
73
74
75
76
77
# File 'midi.rb', line 72

def close_device
  return false unless @handle
  handle = @handle
  @handle = nil
  C.midiOutClose(handle.read_int) == 0
end

+ (Array<Numeric>) get_volume

Get volume of both channels.

Returns:

  • (Array<Numeric>)

    [left, right] Each value is between 0.00 and 1.00.

Since:

  • 2.0.0



83
84
85
86
87
88
89
90
91
92
# File 'midi.rb', line 83

def get_volume
  open_device
  volume = FFI::MemoryPointer.new(FFI.type_size(:ulong))
  C.midiOutGetVolume(@handle.read_int, volume)
  left = volume.read_ulong & 0xFFFF
  right = volume.read_ulong >> 16
  lr = (left*100.0/0xFFFF).round * 0.01
  rr = (right*100.0/0xFFFF).round * 0.01
  [lr, rr]
end

+ (Boolean) open_device

Open device.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



63
64
65
66
67
# File 'midi.rb', line 63

def open_device
  return false if @handle
  @handle = AMS::FFI::MemoryPointer.new(AMS::FFI.type_size(:int))
  C.midiOutOpen(@handle, 0, nil, nil, 0) == 0
end

+ (Boolean) play_3d_note(pos, range, instrument, note, channel = 0, max_volume = 127, duration = nil)

Play a midi note at position.

Parameters:

  • pos (Geom::Point3d)

    Note position.

  • range (Numeric)

    Note hearing radius in inches.

  • instrument (Fixnum)

    between 0 and 127.

  • note (Fixnum)

    between 0 and 127. Each instrument has a maximum of 127 notes.

  • channel (Fixnum) (defaults to: 0)

    between 0 and 15. Each note has a maximum of 16 channels. To play a variety of same notes at the same time, you must set an unused channel.

  • max_volume (Fixnum) (defaults to: 127)

    between 0 and 127.

  • duration (Numeric, NilClass) (defaults to: nil)

    Note duration in seconds. Pass nil to have sound play continuously.

Returns:

  • (Boolean)

    success

See Also:

Since:

  • 2.0.0



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'midi.rb', line 274

def play_3d_note(pos, range, instrument, note, channel = 0, max_volume = 127, duration = nil)
  pos = Geom::Point3d.new(pos.to_a)
  range = range.to_f
  return false if range < 1
  instrument = AMS.clamp(instrument.to_i, 0, 127)
  note = AMS.clamp(note.to_i, 0, 127)
  channel = AMS.clamp(channel.to_i, 0, 15)
  max_volume = AMS.clamp(max_volume.to_i, 0, 127)
  if @notes[channel]
    stop_note(@notes[channel][:instrument], @notes[channel][:note], channel)
  end
  res = play_note(instrument, note, channel, max_volume, duration)
  return false unless res
  @notes[channel] = {
    :pos => pos,
    :range => range,
    :instrument => instrument,
    :note => note,
    :max_volume => max_volume
  }
  unless @timer
    @timer = AMS::Timer.start(1/20.0, true){
      NOTES_UPDATE_PROC.call
    }
  end
  true
end

+ (Boolean) play_note(instrument, note, channel = 0, volume = 127, duration = nil)

Play a midi note.

Parameters:

  • instrument (Fixnum)

    between 0 and 127.

  • note (Fixnum)

    between 0 and 127. Each instrument has a maximum of 127 notes.

  • channel (Fixnum) (defaults to: 0)

    between 0 and 15. Each note has a maximum of 16 channels. To play a variety of same notes at the same time, you must set an unused channel.

  • volume (Fixnum) (defaults to: 127)

    between 0 and 127.

  • duration (Numeric, NilClass) (defaults to: nil)

    Note duration in seconds. Pass nil to have sound play continuously.

Returns:

  • (Boolean)

    success

See Also:

Since:

  • 2.0.0



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'midi.rb', line 142

def play_note(instrument, note, channel = 0, volume = 127, duration = nil)
  open_device
  instrument = AMS.clamp(instrument.to_i, 0, 127)
  note = AMS.clamp(note.to_i, 0, 127)
  channel = AMS.clamp(channel.to_i, 0, 15)
  volume = AMS.clamp(volume.to_i, 0, 127)
  # Set program or instrument.
  msg = instrument << 8 | (0xC0 | channel)
  C.midiOutShortMsg( @handle.read_int, msg )
  # Add timer to stop the note.
  if duration.is_a?(Numeric)
    AMS::Timer.start(duration, false){
      stop_note(instrument, note, channel)
    }
  end
  # Play note.
  h = volume
  l =  note << 8 | (0x90 | channel)
  msg = h << 16 | l
  C.midiOutShortMsg( @handle.read_int, msg ) == 0
end

+ (Boolean) reset

Stop all notes.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



121
122
123
124
125
126
127
# File 'midi.rb', line 121

def reset
  open_device
  C.midiOutReset(@handle.read_int) == 0
  @notes.clear
  AMS::Timer.stop(@timer) if @timer
  @timer = nil
end

+ (Boolean) reset_channel_controllers(channel)

Reset channel controllers.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



255
256
257
# File 'midi.rb', line 255

def reset_channel_controllers(channel)
  change_channel_controller(channel, 0x79, 0)
end

+ (Boolean) set_3d_note_position(channel, pos)

Change 3d note position.

Parameters:

  • channel (Fixnum)

    between 0 and 15.

  • pos (Geom::Point3d)

    Note position.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



307
308
309
310
311
# File 'midi.rb', line 307

def set_3d_note_position(channel, pos)
  return false unless @notes[channel]
  @notes[channel][:pos] = Geom::Point3d.new(pos.to_a)
  true
end

+ (Boolean) set_volume(left, right) + (Boolean) set_volume(vol)

Overloads:

  • + (Boolean) set_volume(left, right)

    Set volume of each channel.

    Parameters:

    • left (Numeric)

      This value is clamped between 0.0 and 1.0.

    • right (Numeric)

      This value is clamped between 0.0 and 1.0.

    Returns:

    • (Boolean)

      success

    Since:

    • 2.0.0

  • + (Boolean) set_volume(vol)

    Apply same volume to both channels.

    Parameters:

    • vol (Numeric)

      This value is clamped between 0.0 and 1.0.

    Returns:

    • (Boolean)

      success

    Since:

    • 2.0.0



105
106
107
108
109
110
111
112
113
114
115
116
# File 'midi.rb', line 105

def set_volume(*args)
  if args.size.between?(1,2)
    open_device
    left = AMS.clamp(args[0], 0, 1)
    right = (args.size == 1) ? left : AMS.clamp(args[1], 0, 1)
    r = (right*0xFFFF).to_i
    l = (left*0xFFFF).to_i
    C.midiOutSetVolume(@handle.read_int, r<<16|l) == 0
  else
    raise ArgumentError, "Expected 1 or 2 parameters, but got #{args.size}."
  end
end

+ (Boolean) stop_note(instrument, note, channel = 0)

Stop a midi note.

Parameters:

  • instrument (Fixnum)

    between 0 and 127.

  • note (Fixnum)

    between 0 and 127.

  • channel (Fixnum) (defaults to: 0)

    between 0 and 15.

Returns:

  • (Boolean)

    success

Since:

  • 2.0.0



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'midi.rb', line 170

def stop_note(instrument, note, channel = 0)
  open_device
  instrument = AMS.clamp(instrument.to_i, 0, 127)
  note = AMS.clamp(note.to_i, 0, 127)
  channel = AMS.clamp(channel.to_i, 0, 15)
  # Update 3d notes queue.
  @notes.keys.each { |ch|
    next if ch != channel
    next if @notes[ch][:instrument] != instrument
    next if @notes[ch][:note] != note
    @notes.delete(ch)
  }
  if @notes.empty? and @timer
    AMS::Timer.stop(@timer)
    @timer = nil
  end
  # Set program or instrument.
  msg = instrument << 8 | (0xC0 | channel)
  C.midiOutShortMsg( @handle.read_int, msg )
  # Stop note.
  msg = note << 8 | (0x80 | channel)
  C.midiOutShortMsg( @handle.read_int, msg ) == 0
end