This is cl-alsa-midi, a Common Lisp library for MIDI in Linux via ALSA. With it, you can send and receive MIDI messages to and from any program or device that is supported by ALSA.
Currently, this library is in the process of being refactored:
- Remove extraneous/unneeded features.
- Reduce dependencies - in particular
optima
andoptima.extra
. - Improve/clean up the code style (remove unnecessary macros, fix indentation, etc).
- Add more docstrings/documentation.
- Simplify and stabilize the API - integrate
cl-alsa-midi/quick
andmidihelper
into the maincl-alsa-midi
package. - Add more tests.
- Submit to Quicklisp (after most of the above is complete–in particular, API improvement/stabilization).
This library is based off of cl-alsaseq, originally written by Richard Venn. It cleans up the library, removing the sequencer functionality and other non-essential parts, and simplifying the interface.
Here is a simple example of how to use cl-alsa-midi.
Note that in the future cl-alsa-midi’s API will change and this example will stop working. If you just need a stable API that you can rely on, you should use cl-alsaseq for now instead.
(ql:quickload :cl-alsa-midi) ; Load the system.
;; Define a basic handler function for MIDI input.
(defun midi-map (messages)
(dolist (message messages)
(let* ((event-type (getf message :event-type))
(event-data (getf message :event-data))
(source (car (getf message :source)))
(destination (car (getf message :dest))))
(declare (ignorable source destination))
(format t "~A: ~S~%"
(case event-type
(:snd_seq_event_noteon "Note on")
(:snd_seq_event_noteoff "Note off")
(:snd_seq_event_controller "CC")
(t event-type))
event-data))))
;; Start the ALSA MIDI client with midi-map function as input handler.
(cl-alsa-midi/midihelper:midihelper-start :master 96 'midi-map)
;; ...Then connect the "CL" ALSA MIDI source to the destination of your choice.
;; I usually use Qjackctl to manage MIDI connections.
(defparameter *midi-channel* 0)
(cl-alsa-midi/midihelper:send-event (cl-alsa-midi/midihelper:ev-noteon *midi-channel* 69 127)) ; Send a MIDI note on event. 69 is the note number, 127 is the velocity
(cl-alsa-midi/midihelper:send-event (cl-alsa-midi/midihelper:ev-noteoff *midi-channel* 69 127)) ; Send a MIDI note off to stop the previous note.
(cl-alsa-midi/midihelper:send-event (cl-alsa-midi/midihelper:ev-pgmchange *midi-channel* 2)) ; Send a program change message to switch to program #2.
To use cl-alsa-midi to trigger synths with cl-collider, you can do something like the following:
(ql:quickload '(:cl-alsa-midi :cl-collider)) ; Load the systems.
(defvar *midi-map-synths-playing* (make-hash-table)
"Hash table mapping MIDI note numbers to the synth/node that they triggered.")
(defparameter *midi-map-synth* 'default
"Name of the synth that should be triggered by incoming MIDI notes.")
;; Define a basic handler function for MIDI input.
(defun midi-map (messages)
(dolist (message messages)
(let* ((event-type (getf message :event-type))
(event-data (getf message :event-data))
(source (car (getf message :source)))
(destination (car (getf message :dest))))
(declare (ignorable source destination))
(case event-type
(:snd_seq_event_noteon
(let ((note (getf event-data 'cl-alsa-midi::note)))
(setf (gethash note *midi-map-synths-playing*) (sc:synth *midi-map-synth* :freq (sc:midicps note)))))
(:snd_seq_event_noteoff
(let* ((note (getf event-data 'cl-alsa-midi::note))
(synth (gethash note *midi-map-synths-playing*)))
(when synth
(sc:release synth)
(setf (gethash note *midi-map-synths-playing*) nil))))
(:snd_seq_event_controller nil)
(t event-type)))))
;; Start the ALSA MIDI client with midi-map function as input handler.
(cl-alsa-midi/midihelper:midihelper-start :master 96 'midi-map)
The original library that cl-alsa-midi is based off of.
Portmidi bindings for Common Lisp.