Skip to content

Instantly share code, notes, and snippets.

@adamjmurray
Last active April 26, 2017 13:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamjmurray/5667817 to your computer and use it in GitHub Desktop.
Save adamjmurray/5667817 to your computer and use it in GitHub Desktop.
How to play MIDI using the OS X built-in DLS synth using Ruby & FFI
require 'ffi'
# A MIDI driver to play MIDI using OSX's built in DLS synthesizer.
#
# == Authors
#
# * Adam Murray <adam@compusition.com>
#
# == Copyright
#
# Copyright (c) 2013 Adam Murray
#
# This code released under the terms of the MIT license.
#
class DLSSynthDriver
attr_accessor :synth
module AudioToolbox
extend FFI::Library
ffi_lib '/System/Library/Frameworks/AudioToolbox.framework/Versions/Current/AudioToolbox'
ffi_lib '/System/Library/Frameworks/AudioUnit.framework/Versions/Current/AudioUnit'
class ComponentDescription < FFI::Struct
layout :componentType, :int,
:componentSubType, :int,
:componentManufacturer, :int,
:componentFlags, :int,
:componentFlagsMask, :int
end
def self.to_bytes(s)
bytes = 0
s.each_byte do |byte|
bytes <<= 8
bytes += byte
end
return bytes
end
AUDIO_UNIT_MANUFACTURER_APPLE = to_bytes('appl') # to_bytes may not be strictly necessary but these are supposed to be 4 byte numbers
AUDIO_UNIT_TYPE_MUSIC_DEVICE = to_bytes('aumu')
AUDIO_UNIT_SUBTYPE_DLS_SYNTH = to_bytes('dls ')
AUDIO_UNIT_TYPE_OUTPUT = to_bytes('auou')
AUDIO_UNIT_SUBTYPE_DEFAULT_OUTPUT = to_bytes('def ')
# int NewAUGraph(void *)
attach_function :NewAUGraph, [:pointer], :int
# int AUGraphAddNode(void *, ComponentDescription *, void *)
attach_function :AUGraphAddNode, [:pointer, :pointer, :pointer], :int
# int AUGraphOpen(void *)
attach_function :AUGraphOpen, [:pointer], :int
# int AUGraphConnectNodeInput(void *, void *, int, void *, int)
attach_function :AUGraphConnectNodeInput, [:pointer, :pointer, :int, :pointer, :int], :int
# int AUGraphNodeInfo(void *, void *, ComponentDescription *, void *)
attach_function :AUGraphNodeInfo, [:pointer, :pointer, :pointer, :pointer], :int
# int AUGraphInitialize(void *)
attach_function :AUGraphInitialize, [:pointer], :int
# int AUGraphStart(void *)
attach_function :AUGraphStart, [:pointer], :int
# int AUGraphStop(void *)
attach_function :AUGraphStop, [:pointer], :int
# int DisposeAUGraph(void *)
attach_function :DisposeAUGraph, [:pointer], :int
# void * CAShow(void *)
attach_function :CAShow, [:pointer], :void
# void * MusicDeviceMIDIEvent(void *, int, int, int, int)
attach_function :MusicDeviceMIDIEvent, [:pointer, :int, :int, :int, :int], :void
end
def require_noerr(action_description, &block)
if block.call != 0
fail "Failed to #{action_description}"
end
end
def open
synth_pointer = FFI::MemoryPointer.new(:pointer)
graph_pointer = FFI::MemoryPointer.new(:pointer)
synth_node_pointer = FFI::MemoryPointer.new(:pointer)
out_node_pointer = FFI::MemoryPointer.new(:pointer)
cd = AudioToolbox::ComponentDescription.new
cd[:componentManufacturer] = AudioToolbox::AUDIO_UNIT_MANUFACTURER_APPLE
cd[:componentFlags] = 0
cd[:componentFlagsMask] = 0
require_noerr('create AUGraph') { AudioToolbox.NewAUGraph(graph_pointer) }
@graph = graph_pointer.get_pointer(0)
cd[:componentType] = AudioToolbox::AUDIO_UNIT_TYPE_MUSIC_DEVICE
cd[:componentSubType] = AudioToolbox::AUDIO_UNIT_SUBTYPE_DLS_SYNTH
require_noerr('add synthNode') { AudioToolbox.AUGraphAddNode(@graph, cd, synth_node_pointer) }
synth_node = synth_node_pointer.get_pointer(0)
cd[:componentType] = AudioToolbox::AUDIO_UNIT_TYPE_OUTPUT
cd[:componentSubType] = AudioToolbox::AUDIO_UNIT_SUBTYPE_DEFAULT_OUTPUT
require_noerr('add outNode') { AudioToolbox.AUGraphAddNode(@graph, cd, out_node_pointer) }
out_node = out_node_pointer.get_pointer(0)
require_noerr('open graph') { AudioToolbox.AUGraphOpen(@graph) }
require_noerr('connect synth to out') { AudioToolbox.AUGraphConnectNodeInput(@graph, synth_node, 0, out_node, 0) }
require_noerr('graph info') { AudioToolbox.AUGraphNodeInfo(@graph, synth_node, nil, synth_pointer) }
@synth = synth_pointer.get_pointer(0)
require_noerr('init graph') { AudioToolbox.AUGraphInitialize(@graph) }
require_noerr('start graph') { AudioToolbox.AUGraphStart(@graph) }
AudioToolbox.CAShow(@graph) # for debugging
end
def message(*args)
arg0 = args[0] || 0
arg1 = args[1] || 0
arg2 = args[2] || 0
AudioToolbox.MusicDeviceMIDIEvent(@synth, arg0, arg1, arg2, 0)
end
def close
if @graph
AudioToolbox.AUGraphStop(@graph)
AudioToolbox.DisposeAUGraph(@graph)
end
end
end
########################
# A quick test
ON = 0x90
OFF = 0x80
PC = 0xc0
channel = 0
driver = DLSSynthDriver.new
driver.open
driver.message(ON | channel, 60, 127)
sleep(1)
driver.message(OFF | channel, 60, 127)
sleep(1)
driver.message(PC | channel, 5)
driver.message(ON | channel, 62, 127)
sleep(1)
driver.message(OFF | channel, 62, 127)
sleep(1)
driver.message(PC | channel, 20)
driver.message(ON | channel, 64, 127)
sleep(1)
driver.message(OFF | channel, 64, 127)
sleep(1)
driver.close
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment