Clamps Packages
Example using Incudine responders
It is not required to rely on the infrastructure of Clamps to react to Midi input events. Incudine provides all necessary tools to set up your own MIDI input handlers, giving you even more flexibility1.
Reacting to MIDI input is done by defining responders. Responders are functions called whenever MIDI input is received. The function make-responder takes the MIDI input stream and a function to be called whenever MIDI input is received on the stream and returns a MIDI responder.
The function supplied to make-responder takes three arguments: The status byte, the data1 byte and the data2 byte. Each of these arguments takes an integer number in the range [0..127], the usual range for MIDI messages. The status byte contains the type of the MIDI message and the channel number. The information of the data1 and data2 parameters depends on the message type. In the case of MIDI CC messages, data1 is the CC number and data2 the CC value. In case of note-on or note-off messages, data1 represents the keynum and data2 the velocity.
As the status byte is not straightforward to decode, CM provides utility functions called status->channel and status->opcode to extract the channel and opcode information from the status byte. status->opcode returns ist results as a keyword. :cc for CC messages and :note-on or :note-off for note messages2.
recv-start and recv-stop are functions of Incudine which start or suspend receiving MIDI messages on a particular MIDI input stream aupplied as argument to these functions. Both functions can get called at any time and will also work in the two first examples3.
;; suspend receiving midi events (incudine:recv-stop *midi-in1*) ;; => #<RECEIVER jackmidi:input-stream stopped> ;; (re)start receiving midi events (recv-start *midi-in1*) ;; => #<RECEIVER jackmidi:input-stream running>
Below is an example how to achieve the behaviour of the first two examples.
;; define a variable to store all responders: (defvar *my-midi-responder* nil) ; => *my-midi-responder* (setf *my-midi-responder* (make-responder *midi-in1* (lambda (st d1 d2) (let ((channel (status->channel st)) (opcode (status->opcode st))) (case channel (1 (case opcode (:cc (let ((ccnum d1) (cc-val d2)) (case ccnum (1 (msg :warn "Received CC Value ~a on Midi Channel 1 and CC Number 1" cc-val)) (2 (msg :warn "Received CC Value ~a on Midi Channel 1 and CC Number 2" cc-val)) (3 (msg :warn "Received CC Value ~a on Midi Channel 1 and CC Number 3" cc-val)))))))))))) ;; => #S(#:responder ;; :receiver #<RECEIVER jackmidi:input-stream running> ;; :function #<function (lambda ;; (incudine::status incudine::data1 incudine::data2 ;; stream) ;; :in ;; incudine::midi-responder-wrapper) {10025E7C0B}>) ;; removing the responder (remove-responder *my-midi-responder*) ; => ; No value ;; If the message is always the same, the function can be simplified (setf *my-midi-responder* (make-responder *midi-in1* (lambda (st d1 d2) (let ((channel (status->channel st)) (opcode (status->opcode st))) (case opcode (:cc (let ((cc-num d1) (cc-val d2)) (msg :warn "Received CC Value ~a on Midi Channel ~a and CC Number ~a" cc-val channel cc-num)))))))) ;; => #S(#:responder ;; :receiver #<RECEIVER jackmidi:input-stream running> ;; :function #<function (lambda ;; (incudine::status incudine::data1 incudine::data2 ;; stream) ;; :in ;; incudine::midi-responder-wrapper) {1002D4D76B}>) ;; remove the midi responder (remove-responder *my-midi-responder*) ; => ; No value
All these methods have their advantages and disadvantages. The last example combines all MIDI input handling in one function which is compact and nice if there aren't too many responders to be used, but can become bloated if responding to many different messages. On the other hand, there is no need to just define one responder in Incudine. Different functionality can be distributed to different responders.
Footnotes:
After all the MIDI input infrastructure of Clamps was built on top of Incudine's infrastructure.
Be aware that most MIDI controllers and MIDI software don't send :note-off messages explicitely, but rather use a :note-on message with a velocity of 0 to denote a note off.
clamps' start-midi-receive and stop-midi-receive are in fact merely wrappers around recv-start and recv-stop.