Wednesday, October 19, 2011

OSC Access: Build OSC into Ruby objects

I've created a Ruby library called OSC Access for binding OSC directly into Ruby classes and objects.

It conveniently wraps a lot of functionality from osc-ruby, handling server/client sharing and management as well as other tasks commonly associated with OSC.

gem install osc-access

All of OSC Access' functionality is available by including the OSCAccessible module into a class. The module gives you a lot of functionality but you'll want to know about these three methods in particular to get up and running


osc_receive

All OSC input is handled by using the osc_receive method. Here's an example of using osc_receive in a simple way:

class Instrument

  include OSCAccessible

  osc_receive("/1/fader1") do |instance, val|
    instance.velocity = val
  end

  def velocity=(val)
    puts "setting velocity to #{val}"
    ...
  end

end

i = Instrument.new
i.osc_start(:input_port => 8000).join

When this example is run, the method velocity= is called on all instances of the Instrument class whenever OSC messages for the address /1/fader1 are received.

A couple of things to note here...

In order to enable OSC input, an input port must be specified for each instance. I've done that in this example using the osc_start method but there is also a method osc_input which just takes a port number. You can also add multiple input ports and share ports across various objects. (see example...)

Another thing to note is that val is, by default, the value of the first argument of the received OSC message. (OSC messages can have an unlimited number of arguments). You can modify which arg is used, or pass in all of them, by setting the :arg option on osc_receive.

You can also use osc_receive as an instance method. (see example...) However, more usefully, you can create a Hash map spec of osc_receive calls and pass it to an instance like this:

map = {
  "/1/fader1" => { 
    :translate => { :remote => 0..1, :local => 0..127 }
    :action => Proc.new { |instance, val| instance.pitch = val }
  }
}

class Instrument

  include OSCAccessible

  def pitch=(val)
    puts "setting pitch to #{val}"
    ...
  end

end

i = Instrument.new
i.osc_start(:map => map, :input_port => 8000).join

This kind of approach gives you more flexibility by decoupling the OSC spec for your object from the class -- like a controller and model in MVC.

Osc_receive has a few options:

:translate

There's another difference between those two examples: the :translate option means that val will be translated from a number between 0 to 1 to the analogous value between 0 and 127 before being passed to the code block. So for example if the first argument of the received OSC message is equal to 0.5, val will be equal to 63.

:thru

By setting the :thru option to true, any messages that are received for /1/fader1 are sent immediately to the output (as well as calling the :action block). For example, using the Instrument class from the last example:

map = {
  "/1/fader1" => { 
    :thru => true
    :translate => { :remote => 0..1, :local => 0..127 }
    :action => Proc.new { |instance, val| instance.pitch = val }
  }
}

i = Instrument.new
i.osc_start(:map => map, :input_port => 8000, :output => { :host => "192.168.1.9", :port => 9000 }).join

As you can see, I also specified an OSC output host and port for this example. If you're ever missing input or output port or host info, your object simply will not perform IO -- it won't raise any kind of exception.

osc_send

Osc_send gives you the ability to output arbitrary OSC messages. The first argument is the address of the message and any arguments after that are the content. Here is an example of our class definition from this first example with output added

class Instrument

  include OSCAccessible

  osc_receive("/1/fader1") do |instance, val|
    instance.velocity = val
    instance.osc_send("/velocity", val)
  end

  def velocity=(val)
    puts "setting velocity to #{val}"
    ...
  end

end

i = Instrument.new
i.osc_start(:map => map, :input_port => 8000, :output => { :host => "192.168.1.9", :port => 9000 }).join
i.osc_send("/greeting", "hi!")

In this example, I'm sending a message from both osc_receive's action block and in the main program block after i is instantiated.

osc_start

Osc_start starts all of the OSC servers that are connected to your objects. You must call it on an instance before osc_receive will function.

I'll be adding OSC Access to Diamond and coming up with a way to use it with MicroMIDI in the next few days. Thanks for reading.

http://github.com/arirusso/osc-access

2 comments:

  1. october 19th?? where are you? come back to us.

    ReplyDelete
  2. Ha! Oh, I'm comin' back. Just started a new job so I needed to refocus a little bit...

    ReplyDelete