Showing posts with label event-driven. Show all posts
Showing posts with label event-driven. Show all posts

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

Monday, June 27, 2011

High-level realtime MIDI IO with Ruby

Update (9/8/2011): I've created another library that wraps all of the concepts of this post in a Ruby DSL and adds shorthand notation and some other fun things. Read about MicroMIDI here


Understandably, a few people have asked me for advice on how to input and output MIDI in a human friendly way with unimidi so I've decided to put together a quick tutorial. I'll be focusing on two gem libraries that I wrote: midi-message, which deals soley with MIDI message objects, and midi-eye, a library for reacting to MIDI input. Of course, it should be mentioned that there's no one way to do this with unimidi.  You can use whatever MIDI objects you like or create your own classes-- unimidi just deals in raw low-level bytes.  There are other libraries such as midilib that provide an intriguing alternative and could work pretty easily with unimidi.  Or one could get creative and go off with a totally unconventional approach as well. For the examples that follow, I'm using a MIDI input and output that I specify with unimidi.
require 'unimidi'

@input = UniMIDI::Input.use(:first)
@output = UniMIDI::Output.use(:first)

If you copy and paste these, they will just open the first MIDI devices available on your computer. You should determine which MIDI devices you want to use and edit these statements to suit your setup. (here's a blog post that goes into more detail on this)

Dealing with MIDI input using midi-eye

My preferred way of dealing with MIDI input is to react to arriving messages with an event listener.  Midi-eye makes this easy and its constructor accepts a unimidi input to attach to.  Here is an example that will react to all incoming messages in the same way by printing them to the screen

require 'midi-eye'

listener = MIDIEye::Listener.new(@input)
listener.on_message do |event|
  puts event[:timestamp]
  puts event[:message]
end

listener.start
Chances are if you're working with MIDI input that you will want to cherry-pick certain messages, or at least react in a different way depending on what type of message you've received eg. a note message, control change, etc.  To accomplish this, arguments can be passed to the Listener#listen_for method which will match against properties of the incoming messages
listener.listen_for(:class => [MIDIMessage::NoteOn, MIDIMessage::NoteOff]) do |event|

  # raise the note value by an octave
  event[:message].note += 12

  # send the altered note message to the output you chose earlier
  @output.puts(event[:message])

end

listener.start
In this example, I take all note messages (identified by their class), transpose them up one octave and send them to my MIDI output. You can add as many of these callbacks as you like, just keep calling Listener#listen_for. While that type of matching will be useful in a lot of cases, it is limited by the fact that it only matches positively against the properties and values you pass in. If you need more complex matching, I recommend putting a conditional statement within the callback.
listener.listen do |event|

  # is this a note above C3?
  if event[:message].respond_to?(:note) && event[:message].note > 48

    # if so, lower the note value by a fifth
    event[:message].note -= 7

  end

  # and send the message to the unimidi output
  @output.puts(event[:message])

end

listener.start

(listen and listen_for are actually the same method, I just think it looks cleaner to call listen when there is no matching happening)

Threading

Pass :background => true to listener.start to have the listener work only in a background thread. This will allow you to run other listeners or foreground threads while that particular listener is running in the background.

Output MIDI using midi-message

In those examples, I sent messages to an output-- but I didn't create those messages myself. The midi-message library allows you to create messages like that yourself in a flexible way.

require 'midi-message'
include MIDIMessage
Here are three different MIDI note-on messages created using three different methods.
messages = []

messages << NoteOn.new(0, 48, 64) # C3

messages << NoteOn["E3"].new(0, 100)

with(:channel => 0, :velocity => 100) do
  messages << note_on("G3")
end
With those message objects in hand, I pass each to UniMIDI::Output#puts the same way you saw earlier.
messages.each { |message| @output.puts(message) }
That's it... and it works the same for all types of MIDI messages including sysex. You can find some info on creating sysex messages here.

http://github.com/arirusso/midi-eye
http://github.com/arirusso/midi-message
http://github.com/arirusso/unimidi