Showing posts with label sysex. Show all posts
Showing posts with label sysex. Show all posts

Saturday, September 10, 2011

Generating Sysex Messages with MicroMIDI

Recently, the idea of converting MIDI Control Change messages to Sysex on the fly has come up a couple of times. One could use this to control a synth such as a Roland MKS or Yamaha DX7 that only accepts Sysex for control with a regular MIDI knob controller.

The following is a simplified example of doing this with MicroMIDI.
@i = UniMIDI::Input.use(:first)
@o = UniMIDI::Output.use(:first)
  
MIDI.using(@i, @o) do
  
  node :roland, :model_id => 0x42, :device_id => 0x10
  
  *@my_map =
    [0x40, 0x7F, 0x00],   
    [0x41, 0x7F, 0x00],
    [0x42, 0x7F, 0x00]
  
  receive :cc do |message|
      
    command @my_map[message.index - 1], message.value
      
  end
  
  join
  
end
Defining a Node

I won't get into too much background on Sysex but there are two concepts that one must understand in order to generate Sysex with MicroMIDI.

The first concept is what I call a Sysex Node: there are up to three bytes of data used in each Sysex message to identify the synth/module/destination/node/etc where the message is intended to be sent. This is not unlike the MIDI channel in a regular short message except that it's three bytes. Two of those bytes pinpoint the make and model of the synth while the third byte identifies the individual synth (device ID) in case you have multiple Yamaha DX7's or whatever the case.

MicroMIDI allows you to define these bytes as a sticky value using the node function.

Since I'm only using one synth for the entire example, I call the node function before setting up the input event to catch Control Change messages. (If you are using multiple synths and multiple events you would call node in each event block). The arguments represent the Manufacturer ID and the optional Model ID and Device ID. The Manufacturer can be referred to by a symbol (as above) or a string if its name is found in the manufacturer constants in midi.yml (by all means, add yours and do a pull request).

Now here's the annoying part: different brands and synth models use this Node data differently. For instance, I believe some devices don't understand messages with a model ID in them. In those cases just leave out whatever needs to be omitted from your messages. As I learn more about this myself, perhaps I can have this function streamline accepting the proper data for major synth types.

Command vs Request

The other concept to understand is that Sysex messages can (in theory) either be a command or request. This is pretty simple, and if you are creating a controller program you'll deal mostly in commands. In the case of the example above, we send a command
command @my_map[message.index - 1], message.value
When using the command function, the first argument is the sysex address and the second is the value to assign. The value can either be a number, as in this case, or an array of bytes. When making a request, the first argument is also the address but the second argument is the size of the response (in bytes) that you expect to receive
request 0x43, 43
If all else fails...

Due to the fact that Sysex hasn't had a truly concrete spec, some devices will use messages that don't really adhere to the command/request format. In those cases, you can just use the generic sysex command like this
sysex 0x1, 0x2, 0x3, 0x4
With no node specified, this will give you a message composed of
F0 01 02 03 04 F7
In other words, sysex will create a message and not perform any validation or add a checksum to it. You can still use node with these message-- it will append those bytes immediately after the F0 start byte as it would with a command or request.

tldr, Sysex is tricky to to objectify

If you'd like to gain a deeper understanding of how sysex messages work, this is a good tutorial (if Roland-centric) and I often referred back to it while creating MicroMIDI and the libraries that support it.

http://github.com/arirusso/micromidi

Next: More MicroMIDI Tricks

Wednesday, August 31, 2011

MicroMIDI: a Ruby DSL for MIDI

After a month of moving I'm finally getting back into some music projects. I've got some more flashy things in the works but first here is a Ruby DSL called MicroMIDI that brings all of the MIDI projects mentioned in this post together along with some new tricks in to a package a bit more suited for live coding and one-off scripts.

Being that it's an interface for four libraries, there's a lot of functionality. I break down each concept in the following posts:

http://github.com/arirusso/micromidi

Tuesday, June 7, 2011

Unimidi: Platform independent realtime MIDI IO in Ruby

Unimidi is a universal Ruby library for realtime MIDI IO.

It currently works with MRI 1.9.2 on Linux, OSX, Windows/cygwin and under JRuby in 1.9 mode on any platform.

gem install unimidi

No compilation is required, install the gem and start playing.

Unimidi deals in raw bytes rather than high level message objects with the intention of allowing people to use whichever message objects or helpers they choose. There's a few libraries out there for that, including one by me.

Under the hood, unimidi is essentially linkage to a set of platform specific C bindings (that use ruby-ffi).  Or in the case of JRuby, some code that wraps javax.sound.   These platform specific libraries are broken out in to their own gems and the appropriate one should install automatically when the unimidi gem is installed. In unusual cases where your platform isn't recognized, all of those gems will be installed.  You can see which library goes with which platform on the unimidi github page. It is possible to use those gems on their own, but currently none of them contain any features that aren't also made available through unimidi.

Here's a couple of quick examples to get started

Sending notes to a MIDI output

First, load unimidi and then define a set of note values and a duration value for how long the notes will be held out.

require 'unimidi'

notes = [36, 40, 43, 48, 52, 55, 60, 64, 67] # C E G arpeggios
duration = 0.1

Next, select an output.  To list all available outputs, you can do unimidi list from the command line or UniMIDI::Device.list in irb.  In this case, the first output is selected and enabled. (here is another post with more information about selecting a device)

output = UniMIDI::Output.open(:first)

Now open that output.  Passing a block to open is optional but it ensures that the device is closed when the program is finished.  This can alleviate some headaches when multitasking MIDI/audio programs.  Note that it's also possible to select, open and use multiple inputs and outputs concurrently.

output.open do |output|

  notes.each do |note|
    output.puts(0x90, note, 100) # note on message
    sleep(duration)  # wait
    output.puts(0x80, note, 100) # note off message
  end

end

Output#puts can also be used for sysex messages in the same manner as note messages, a la:
output.puts(0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7)
Note that some OS's will silently reject invalid sysex or short messages.

You can also Output#puts a string of hex bytes if you prefer to work that way
output.puts("904040")
output.puts("F04110421240007F41F7")

Working with input

Input can be collected two ways. First, unimidi has a method Input#gets which waits for input before returning. It works exactly the way Ruby's $stdin.gets works when waiting for keyboard input.

Here is a demonstration of Input#gets

Select an input and open it...

input = UniMIDI::Input.first
input.open do |input|

  $stdout.puts "send some MIDI to your input now..."

  loop do
    m = input.gets
    $stdout.puts(m)
  end

end

When MIDI input is received, you'll see inspected Hash objects like this:

{ :data => [144, 60, 100], :timestamp => 1024 }

In this case [144, 60, 100] is a note-on message for channel 0, note C4 (aka 60), with velocity 100.  The timestamp is the number of milliseconds since the input was opened.

The other more advanced way to receive input is by polling Input#buffer manually.  As new messages are received, they are added to that buffer array.  I normally use this with a pointer to keep track of the index of the last message seen.  Polling this way would for example, allow you to create your own background thread to collect input while your program does other things.  The library Topaz, which I wrote about in my last post, collects clock messages from unimidi that way.

Your computer should be able to receive data with 1-2 millisecond accuracy

http://github.com/arirusso/unimidi