Output Devices
Files: midi_test.c
This week we’re exploring output devices. I need to send MIDI messages for my final project, so my goal for the week is to figure out this subsystem. (Edit: in the end I simplified things and didn’t use MIDI for my final project.)
Background
MIDI is a relatively simple protocol, built on top of a standard 8-N-1 serial connection. There are two types of bytes: status bytes and data bytes. All status bytes have a 1 in the MSB, while all data bytes have a 0. Every message begins with a status byte, which may then be followed by data bytes. The number of data bytes that appear is determined by the specific status byte that is sent.
The two most important status bytes are note on (144) and note off (128). Each is followed by two data bytes: note number and note velocity. Valid notes range from 0 to 127, with middle C being 60. Velocities are encoded in the same range, with 0 usually being inaudibly soft and 127 as hard as possible. It may seem strange that note off events have velocity, but this has a number of uses. Instruments like harpsichords that have an audible note off “clunk” that can be varied by the performer. And many modern synthesizers will vary the decay rate of the filter frequency (or other parameters) based on the note off velocity. Even if the instrument you’re controlling won’t use it, it’s important to send both data bytes since otherwise the MIDI message is incomplete.
MIDI on an ATTINY44
The ATTINY44 doesn’t have a UART or USART, so I’ll use Neil’s serial bit-banging code (see hello.ftdi.44.echo.c). (Another strategy would be to use the ATTINY44’s universal serial interface in three wire mode as discussed in datasheet section 14.4.1, but I’ll save this for another day.)
To send a note on command, we should only have to do the following:
put_char(&serial_port, serial_pin_out, 144);
put_char(&serial_port, serial_pin_out, 60);
put_char(&serial_port, serial_pin_out, 100);
This will play middle C with a velocity of 100. To turn it off, we do this:
put_char(&serial_port, serial_pin_out, 100);
put_char(&serial_port, serial_pin_out, 60);
put_char(&serial_port, serial_pin_out, 100);
To test it, I sent the serial data to my computer using an FTDI friend. I made use of Hairless MIDI to interpret the serial signals and transform them into inputs that the music production software on my laptop can understand. Sure enough the notes are coming through!
Here is my first “hello world” MIDI recording (rendered to audio in Logic Pro).
Unfortunately it doesn’t work flawlessly. When I try to play longer sequences of notes, some note off messages stop making it through to my computer, resulting in stuck notes in Logic. For a given program, which notes hang seems to be consistent. But when I add new notes to a program, sometimes that will break notes that worked previously. This is a very strange problem and I’m not sure what’s causing it at this time.