Create Register | Login
 
  Latest from concentration studios - Home

Musical Live Coding in Ruby? 

  June 3rd, 2007 20:29

I am working on a project of turning History into Song. Sounds exciting, huh? When we were recording our Watergate song, we decided to spice it up with a lot of synth. My companion broke out his MIDI controller, and a bunch of cables, and I grabbed a 1/8” jack, and hooked it up to my macbook. First I started my JRuby synth, JTalk.

JTalk

JTalk is a really simple script. It starts a MIDI synthesizer in Java, and exposes a few convenience methods via DRb. Very simple stuff, but lets me play some notes pretty easily. The script is about 150 lines long, because I include a complete MIDI value => Note/Octave mapping hash, so I am going to drop it on pastie. You can find it here: http://pastie.caboo.se/67444.

Now this script works pretty, I just open up an irb session, make the connection, and start playing notes. But my friend already has those musical notes covered with all his keyboards and cords and things I only pretend to understand. But his magic devices can’t get a womans robotic voice saying “Watergate”. That’s my domain.

VoiceSynth

So now I fire up my next Synth script, this one all about voice. This lovely script started out as just a couple of system() calls to the say command, but I felt that was too simple. _why posted a great, very obfuscated way of accessing the voice stuff in OS X, and after a little while, I was able to abstract it out into something useful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'dl/import'
require 'drb'

module Talk
  include DRbUndumped 
  extend DL::Importable
  dlload "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/SpeechSynthesis.framework/SpeechSynthesis"
  
  %w[SpeechBusy() SpeakString(void*)].each do |function|
    extern "int #{function}"
  end
  
  def self.speak(string)
    speakString [string.size,string].pack("Ca*")
    sleep(0.1) until speechBusy == 0
  end
end

if __FILE__ == $0
DRb.start_service('druby://0.0.0.0:9001', Talk)
puts "Server started on #{DRb.uri}"
DRb.thread.join
end

It is a pretty simple [ab]use of the DL library in the Ruby stdlib, and is quite fast. The use is also very simple.

1
2
3
4
5
6
7
>> require 'drb'
=> true
>> DRbObject.new(nil,"druby://localhost:9001")
=> #<DRb::DRbObject:0x157546c @ref=nil, @uri="druby://localhost:9001">
>> Talk = _
=> #<DRb::DRbObject:0x157546c @ref=nil, @uri="druby://localhost:9001">
>> Talk.speak "Hello, World!"

Tada! irb can talk! But I need to do more than just saying “Hello, World!” once. We need a lot of Watergate, and a lot of Nixon.

1
2
3
4
5
6
7
8
>> Thread.new { loop do
?> Talk.speak "Watergate"
>> Talk.speak "Nixon"
>> end
>> }
=> #<Thread:0x1563a3c run>
>> _.kill
=> #<Thread:0x1563a3c dead>

With a little bit of work, we can make a simple server out of it. But first, we need the Ruby/EventMachine gem. sudo gem install eventmachine Then pop back into our irb session, and lets code up a server!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>> require 'eventmachine'
=> true
>> module TalkServer
>>   def receive_data(data)
>>     Talk.speak data.to_str
>>     send_data ">> #{data}"
>>     close_connection if data =~ /quit|exit/i
>>   end
>> end
=> nil

>> EventMachine::run {
?> EventMachine.start_server "127.0.0.1", 8081, TalkServer
>> }

Now open up a new Terminal window, and telnet into your server!

saurasaurusrex:~ cdcarter$ telnet localhost 8081
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hi
>> Hi
quit
Connection closed by foreign host.

You now have a TalkServer! Pretty easy huh? You can do a lot in irb, not just testing things. I am going to start playing around more with EventMachine, and DL. Ruby has a lot of power in its libraries!

Bookmark this page on del.icio.us. Search Technorati for links to this page