LEARNING MIDI WITH THE NOVATION LAUNCHPAD IMG Launchpad lit up showing a happy face Novation Launchpad (NOVLPD01) is a MIDI device. It features a grid of 70 soft LED buttons capable of being independently addressed and illuminated in various brightness of green, red, yellow, and amber. When pressed, a button will transmit a message for the initial strike (press) and its termination (release). I bought this device when it was first released around 2010. At the time, I was making music with Ableton Live and wanted a keyboard-less way of triggering clips. Later, I would use the Launchpad as an interface and/or display for projects (e.g.: Conway's Game of Life implementation) built using Processing, a programming language and environment built on top of Java. Eventually, it was put in a box as my other interests emerged. I recently resurfaced the device. It is very pretty, fun to touch, and certainly can have some use in my life today. So I'm endeavoring to discover some yet-to-be-defined new purpose for the Launchpad. I have a few ideas so far: weather display, tamagatchi-like pet, temperature display, controller and partial display for a 8-bit-ish paint program. The Launchpad is capable of much and I have hardly scratched the surface for what it can do or how it can be used. This phlog covers my attempt and success to get setup with documentation, programming and debugging tools for working with the Launchpad. To my surprise and delight, the success of this attempt came quick and easy. I expected headaches, confusion, and the usual tremors of incapability. But it took little more than a lazy Sunday afternoon to feel like this project will work out ok! MIDI communication ---------------------------------------------------------------------- In this section I captured in brief what I used to discover and begin using the MIDI device. The short of it is: I needed ALSA and its development libraries. That's it! :) I didn't have to install any USB drivers or weird bits of firmware. The Launchpad was immediately recognized by `dbus' when plugged in, and my probes with ALSA's `amidi' tool worked once I found the right messages to send. Anyways, read on to learn the intricacies. Over command line ...................................................................... `lsusb' shows the file descriptor corresponding to the device. This wasn't important for sending messages or getting them, but I found it helpful to verify and understand the device's location. The output: ,---- | Bus 001 Device 015: ID 1235:000e Focusrite-Novation Launchpad `---- This means that the device's file node (is that the right word?) is `/dev/bus/usb/001/015'. `amidi -l' shows the MIDI device name for the device. This was crucial for revealing the name needed to send and receive communications. Its output: ,---- | Dir Device Name | IO hw:2,0,0 Launchpad MIDI 1 `---- `amidi -p hw:2,0,0 -d' dumps all MIDI messages from the device in hex. This was helpful for verifying that the buttons worked. `amidi -p hw:2,0,0 -S "B0 00 7D"' sends a MIDI message to the device (hw:2,0,0) in hex. Sending this message and seeing the buttons all light up was a happy moment. More MIDI messages can be inferred or copied from the Launchpad programmer's reference (linked at end). Here are some examples: - "B0 00 7D": Reset device and turn all LEDs on amber high. - "90 01 3C": Turn second LED to green high. - "80 00 00": Turn first LED to off. These messages can be tested using `amidi', or used in a program, which I illustrate in the next section. Using <alsa/asoundlib.h> ...................................................................... `alsa-lib-devel' library needs to be installed. C programs using library are compiled with `-lasound' flag. A very basic program to send a single message is illustrated below: ,---- | #include <alsa/asoundlib.h> | int main() { | int err; | snd_rawmidi_t *handle_out = 0; | err = snd_rawmidi_open(NULL, &handle_out, "hw:2,0,0", SND_RAWMIDI_SYNC); | if (err) { | printf("Error opening ALSA sequencer: %s\n", snd_strerror(err)); | return 1; | } | uint8_t buffer[] = {0xB0, 0x00, 0x7D}; | snd_rawmidi_write(handle_out, buffer, sizeof(buffer)); | return 1; | } `---- After the program successfully compiled and ran, and the button lit up, I knew I had cracked this project open. Misdirections and lessons learned ---------------------------------------------------------------------- ChatGPT seemed to hallucinate a CommonLisp library called `cl-midi' that could interface with a MIDI device. After looking around, I saw no such library capable of bi-directional communication to a device. There is a library called `midi', but that is for reading and writing MIDI files. Clojure has a MIDI library for talking to devices. I briefly considered using that library, since a friendly tutorial was available for MIDI (). But I haven't learned much about Clojure yet, so thought best to avoid. Novation has legacy documentation available for their Launchpad devices. At first, I read and referenced the programmer's guide for the Launchpad S. This was a mistake I only caught and corrected (crucially) later. The Launchpad needs three byte messages, and always (mostly) listens on MIDI channel 1. At first, I was including the channel number in my messages, resulting in four byte messages. Reading the manual straightened this out for me. Like I paraphrased, it states: ,---- | Launchpad transmits and receives on MIDI channel 1. There is one exception to | this, which will be covered later, but it is not essential to learn it. | Hence a Launchpad MIDI message is always three bytes long. `---- Example: paint program ---------------------------------------------------------------------- Below is the code to a very primitive paint program. Requires `alsa-lib-devel', compiles with `-l lasound' flag. ,---- | #include <alsa/asoundlib.h> | int main() { | int err_in, err_out; | snd_rawmidi_t *handle_out, *handle_in = 0; | err_out = snd_rawmidi_open(NULL, &handle_out, "hw:2,0,0", SND_RAWMIDI_SYNC); | err_in = snd_rawmidi_open(&handle_in, NULL, "hw:2,0,0", SND_RAWMIDI_SYNC); // check: not sure last arg is needed? | if (err_out || err_in) { | printf("Error opening ALSA sequencer: %s\n", snd_strerror(err_out || err_in)); // fix: won't show both errors Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! | } | int i; | ssize_t ret; | unsigned char buf[1024]; | int selected_color = 0; | int colors[] = {0x00, 0x7D, 0x1D, 0x3F, 0x0D, 0x0F, 0x3C, 0x1C, 0x3C}; | | while (1) { | ret = snd_rawmidi_read(handle_in, buf, sizeof(buf)); | if (ret < 0) { Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! | } | fprintf(stderr, "%02x %02x %02x\n", buf[0], buf[1], buf[2]); | // button: erase all | if (buf[0] == 0x68 && buf[1] == 0x7f) { Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! | // button: change color | if (buf[0] == 0x78 && buf[1] == 0x7f) { Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! | } | if (selected_color >= sizeof(colors)/sizeof(int)) { Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! | } | uint8_t out_buffer[] = {0x90, buf[0], colors[selected_color]}; | snd_rawmidi_write(handle_out, out_buffer, sizeof(out_buffer)); | } | return 1; | } `---- References ----------------------------------------------------------------------