AudioUnit MIDI
Love it or hate it MIDI is still the primary means for communicating between musical hardware/software and Impromptu includes support for MIDI I/O. In fact if you have run the first Impromptu tutorial you have already been using MIDI behind the scenes. The Impromptu play-note function is actually a wrapper for the au:midi-out function. au:midi-out passes MIDI data to AudioUnits. Evaluate the following expression for a description of au:midi-out.
(help au:midi-out #t)
We could implement our own play-note function like this.
(define piano (au:make-node "aumu" "dls " "appl"))
(au:connect-node piano 0 *au:output-node* 0)
(define my-play-note
   (lambda (time au pitch velocity duration channel)
      (au:midi-out time au *io:midi-on* channel pitch velocity)
      (au:midi-out (+ time duration) au *io:midi-off* channel pitch 0)))
(my-play-note (now) piano 60 80 10000 0)
As this example demonstrates, au:midi-out passes MIDI event messages through to the provided AudioUnit. You can pass any MIDI event message type including note-on (*io:midi-on*), note-off (*io:midi-off*), control change (*io:midi-cc*) and program change (*io:midi-pc*) - of course you can supply your own hex number for message types that have no constant (i.e. pitch bend messages are #xE). Let's send a program change to the dls on midi-channel 2 (i.e. 1).
(au:midi-out (now) piano *io:midi-pc* 1 70 0)
(my-play-note (now) piano 60 80 10000 1)
(my-play-note (now) piano 60 80 10000 0)
MIDI channels 1 and 2 should now play with different timbres when we call my-play-note with different midi-channels. Probably the most useful purpose for au:midi-out is for sending control change messages to AudioUnits. Decimal 10 (or hex #xA) is the GM control change number for Panning. You can find a list of standard control change numbers here. Let's pan channel 0 full left and channel 1 full right - where 0 is hard left, 64 is centre and 127 is hard right.
(au:midi-out (now) piano *io:midi-cc* 0 10 0)
(au:midi-out (now) piano *io:midi-cc* 1 10 127)
(my-play-note (now) piano 60 80 10000 0)
(my-play-note (now) piano 60 80 10000 1)
Most AudioUnit's will allow you to assign Control Change numbers to any AudioUnit parameter that you may wish to control - a very handy option if you cannot use au-set-param for some reason.
External MIDI
As well as being able to send MIDI events to AudioUnits, Impromptu can send/receive MIDI events to/from external devices. Impromptu uses the io:midi-in and io:midi-out functions for this purpose.
(help io:midi-in #t)
(help io:midi-out #t)
However, before we can send and receive events we first need to define a source and/or destination for our MIDI messages. Here is the list of available sources and destinations on my machine.
MIDI Sources
0: name=BCF2000 Port 1
1: name=BCF2000 Port 2
MIDI Destinations
0: name=BCF2000 Port 1
1: name=BCF2000 Port 2
The source/destination number is indicated first followed by the name of the source/destination port. We can define as many sources/destinations as we like by calling io:midi-destination and io:midi-source.
(define dest (io:midi-destination 0))
(define src (io:midi-source 0))
Here we define dest to be the first available destination and src to be the first available src. We can now send MIDI events to dest like so:
(io:midi-out (now) dest *io:midi-cc* 0 1 64)
Notice that the object defined by calling (io:midi-destination 0) (i.e. dest) specifies which device (and port) the io:midi-out message should be sent to. If we defined dest1 to be (io:midi-destination 1) we could send messages to BCF2000 Port 2 instead of dest which is BCF2000 Port 1. To reiterate, we can define as many sources and destinations as we have available on our system.
io:midi-in, once defined receives messages from ALL midi devices. We can identify which device any given message arrives from by checking the device number (the io:midi-source function returns a given devices number to us - BCF2000 Port 1 in this example).
(define io:midi-in
   (lambda (device type channel a b)
      (if (= src device)
          (print device type channel a b)
          (print "wrong device"))))
By checking the device number we can reveal the source of any incoming message. It is trivial to define a straight pass-through from an external MIDI keyboard to an AudioUnit. In this example we pass through messages from any incoming device.
(define io:midi-in
   (lambda (device type channel a b)
      (au:midi-out (now) piano type channel a b)))
In recent years OSC or Open Sound Control has been asserting itself as a dominant new communications standard for Audio applications. Impromptu has basic support for sending and receiving OSC messages. I should begin by saying that unlike MIDI, OSC is a very open specification, you will not find tables listing standards for various event types. It is up to each application to specify the message types that it expects to receive.
An OSC message is made up of an address and arguments. The address specifies to the server the type of call being made - kind of like a method name. A variable number of arguments (of type float, int or string) can be passed.
Let's start by seeing how we can register to receive OSC events. First we need to call io:osc-register-events. This will open a UDP socket on port 7009 for incoming OSC messages (make sure you poke a UDP hole through your firewall). Then we need to define the io:osc-receive function to start receiving incoming messages.
; Register this process to receive OSC events
; Receives "/piano/start" and "/piano/stop" messages
; Prints all other messages to the log
(define (io:osc-receive timestamp address . args)
   (cond ((string=? address "/piano/start")
          (start-sound (now) piano (car args) (cadr args)))
         ((string=? address "/piano/stop")
          (stop-sound (now) piano (car args)))
         (else (print address '-> args))))
This example uses the piano we defined for our MIDI examples above. All messages sent using the OSC address /piano/start will start the dls. All messages sent using the OSC address /piano/stop will stop the dls. All other messages will print the address and any arguments to the log view.
To send a message we first need to specify the hostname (or IP-address) and the port of the OSC server we want to send a message to. If we are sending the message to another Impromptu host we know the port number will be 7009. Other OSC host applications may use different port numbers. We cons the hostname (or IP-address) together with the port number to define a host-address (as opposed to an OSC address string). Then we can call io:osc-send with a time, a host-address, an OSC-address and any required arguments.
(define *host-address* (cons "localhost" 7009))
(io:osc-send (now) *host-address* "/piano/start" 60 80)
(io:osc-send (now) *host-address* "/piano/stop" 60)
If we pass a message to our server and io:osc-receive does not match the osc-address (i.e. it falls through to the cond's else expression) our server will print the message to the log view. Let's send a /test/msg with strings, ints and floats. Notice that because /test/msg is not caught by the cond if falls through to the else and is printed to the log view.
(io:osc-send (now) *host-address* "/test/msg" "Hello" 500 6.6 "World")
You can of course send messages to external applications just as easily once you know the osc-address's required and the arguments expected (as well as the hostname/IP address and the UDP port number).
Have Fun!
Or In and Out