Created
February 14, 2009 04:45
-
-
Save bryanwoods/64252 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# If you're on a Mac, download SimpleSynth: | |
# http://notahat.com/simplesynth | |
# Just open SimpleSynth up and leave it running. | |
# | |
# Then open a terminal window and enter: | |
# ruby bit_phase.rb | |
# | |
# It should work on Windows and Linux as well | |
# But hasn't yet been tested | |
# | |
# "Bit Phase" | |
# (c) 2009 Bryan Woods / The Bird Cage Theater | |
# | |
# http://myspace.com/thebirdcagetheater | |
# Permission granted to act freely | |
require 'dl/import' | |
class LiveMIDI | |
ON = 0x90 | |
OFF = 0X80 | |
PC = 0XC0 | |
def initialize | |
open | |
end | |
# Full velocity because duh | |
def note_on(channel, note, velocity=128) | |
message(ON | channel, note, velocity) | |
end | |
# See above | |
def note_off(channel, note, velocity=128) | |
message(OFF | channel, note, velocity) | |
end | |
def program_change(channel, preset) | |
message(PC | channel, preset) | |
end | |
end | |
# Windows deets | |
if RUBY_PLATFORM.include?('mswin') | |
class LiveMIDI | |
module C | |
extend DL::Importable | |
dlload 'winmm' | |
extern "int midiOutOpen(HMIDIOUT*, int, int, int, int)" | |
extern "int midiOutClose(int)" | |
extern "int midiOutShortMsg(int, int)" | |
end | |
def open | |
@device = DL.malloc(DL.sizeof('I')) | |
C.midiOutOpen(@device, -1, 0, 0, 0) | |
end | |
def close | |
C.midiOutClose(@device.ptr.to_i) | |
end | |
def message(one, two=0, three=0) | |
message = one + (two << 8) + (three << 16) | |
C.midiOutShortMsg(@device.ptr.to_i, message) | |
end | |
end | |
# Mac deets | |
elsif RUBY_PLATFORM.include?('darwin') | |
class NoMIDIDestinations < Exception ; end | |
class LiveMIDI | |
module C | |
extend DL::Importable | |
dlload '/System/Library/Frameworks/CoreMIDI.framework/Versions/Current/CoreMIDI' | |
extern "int MIDIClientCreate(void *, void *, void *, void *)" | |
extern "int MIDIClientDispose(void *)" | |
extern "int MIDIGetNumberOfDestinations()" | |
extern "void * MIDIGetDestination(int)" | |
extern "int MIDIOutputPortCreate(void *, void *, void *)" | |
extern "void * MIDIPacketListInit(void *)" | |
extern "void * MIDIPacketListAdd(void *, int, void *, int, int, int, void *)" | |
extern "int MIDISend(void *, void *, void *)" | |
end | |
module CF | |
extend DL::Importable | |
dlload '/System/Library/Frameworks/CoreFoundation.framework/Versions/Current/CoreFoundation' | |
extern "void * CFStringCreateWithCString (void *, char *, int)" | |
end | |
def open | |
client_name = CF.cFStringCreateWithCString(nil, "RubyMIDI", 0) | |
@client = DL::PtrData.new(nil) | |
C.mIDIClientCreate(client_name, nil, nil, @client.ref); | |
port_name = CF.cFStringCreateWithCString(nil, "Output", 0) | |
@outport = DL::PtrData.new(nil) | |
C.mIDIOutputPortCreate(@client, port_name, @outport.ref); | |
num = C.mIDIGetNumberOfDestinations() | |
raise NoMIDIDestinations if num < 1 | |
@destination = C.mIDIGetDestination(0) | |
end | |
def close | |
C.mIDIClientDispose(@client) | |
end | |
def message(*args) | |
format = "C" * args.size | |
bytes = args.pack(format).to_ptr | |
packet_list = DL.malloc(256) | |
packet_ptr = C.mIDIPacketListInit(packet_list) | |
packet_ptr = C.mIDIPacketListAdd(packet_list, 256, packet_ptr, 0, 0, args.size, bytes) | |
C.mIDISend(@outport, @destination, packet_list) | |
end | |
end | |
# Linux deets | |
elsif RUBY_PLATFORM.include?('linux') | |
class LiveMIDI | |
module C | |
extend DL::Importable | |
dlload 'libasound.so' | |
extern "int snd_rawmidi_open(void*, void*, char*, int)" | |
extern "int snd_rawmidi_close(void*)" | |
extern "int snd_rawmidi_write(void*, void*, int)" | |
extern "int snd_rawmidi_drain(void*)" | |
end | |
def open | |
@ouput = DL::PtrData.new(nil) | |
C.snd_rawmidi_open(nil, @output.ref, "virtual", 0) | |
end | |
def close | |
C.snd_rawmidi_close(@output) | |
end | |
def message(*args) | |
format = "C" * args.size | |
bytes = args.pack(format).to_ptr | |
C.snd_rawmidi_write(@output, bytes, args.size) | |
C.snd_rawmidi_drain(@output) | |
end | |
end | |
else | |
raise "Couldn't find a LiveMIDI implementation for your platform" | |
end | |
# Timer class for the metronome | |
class Timer | |
def initialize(resolution) | |
@resolution = resolution | |
@queue = [] | |
Thread.new do | |
while true | |
dispatch | |
sleep(@resolution) | |
end | |
end | |
end | |
public | |
def at(time, &block) | |
time = time.to_f if time.kind_of?(Time) | |
@queue.push [time, block] | |
end | |
private | |
def dispatch | |
now = Time.now.to_f | |
ready, @queue = @queue.partition{|time, lambda| time <= now } | |
ready.each {|time, lambda| lambda.call(time) } | |
end | |
end | |
# It's a metronome, but set to "Punch," so... | |
class Metronome | |
def initialize(bpm) | |
@midi = LiveMIDI.new | |
@midi.program_change(0, 127) | |
@interval = 60.0 / bpm | |
@timer = Timer.new(@interval/10) | |
now = Time.now.to_f | |
register_next_bang(now) | |
end | |
def register_next_bang(time) | |
@timer.at(time) do | |
now = Time.now.to_f | |
register_next_bang(now + @interval) | |
bang | |
end | |
end | |
def bang | |
@midi.note_on(0, 84, 100) | |
sleep(0.1) | |
@midi.note_off(0, 84, 100) | |
end | |
end | |
# We want to phase but we don't want to cheat | |
# The machine is the slave | |
# You are the master | |
loop do | |
10.times do | |
m = Metronome.new(60) | |
sleep(10) | |
loop do | |
m = Metronome.new(60) | |
sleep(10) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment