Friday, December 15, 2017

Teensytune: A Teensy-based MIDI controller/keyboard


Teensytune is a homebuilt MIDI controller/keyboard built around an old broken keyboard using the Teensy microcontroller. It features 49 keys, a programmable 16 beat drum machine, pitch bend/modulation control and two recordable loop channels with controllable tempo. It outputs MIDI signals along a USB, so you can plug it into a laptop or any other MIDI synth to generate the actual sounds. It is constructed from a wooden frame with custom sideboards and control panels made using Carvey, a programmable 3D carving machine.

This post is a continuation of previous post on rebuilding an old broken electric piano. I last touched this project about 12 months ago, where I had a Teensy reading the keyboard state and passing MIDI messages to a Raspberry Pi which was running a synthesiser and outputting sound to an amplified speaker. I ended up having a lot of troubles getting the sound output from the RPi working reliably: I could never really find an acceptable balance between getting nice stutter-free sounds with low latency, even after trying custom firmware and playing with countless settings. I got frustrated and moved on to other projects.

12 months later I decided that it's time I moved on with this: I've dropped the RPi synth for now and have just focussed on getting a workable MIDI instrument up and running, leaving space inside the case for expanding the project to include a synth, amplifier and speaker at a later stage.


Implementing the Controls:

After I got the basic circuit and code setup on the Teensy for reading the keyboard state, I started to focus on developing some cool controls. I started by adding a simple drum accompaniment function using a single on/off switch and a potentiometer for controlling the tempo. The beat is run through an interrupt using a PIT timer on the Teensy that sends a MIDI note for the current beat in a static 16 beat pattern, that is looped on repeat. Changes in the tempo pot are used to reset the interrupt interval time. I added an old two axis joystick I had sitting around and read the values using two analog inputs on the Teensy and translated these into pitch bend and modulation MIDI messages.

In order to provide a bit of feedback to the player, I decided to try and add in some Neopixels for coloured, flashy fun and a two-line character display. I plugged in a Neopixel to test everything was working fine: all good. I got this two-line character display which interfaced to the Teensy via I2C to display out data, for example, on the instrument selection and provide feedback for the programable drum machine. The display is a 5V device, so I used a logic level convertor to convert to/from the 3.3V signals on the Teensy. I2C on the Teensy 3.1 requires that both SDA and SCL data lines be connected to 4.7KOhm pull-up resistors, which I did. I used this library to control the display: tested I could display some basic text and instrument number, and all is working well. I ended up using the character display to provide visual feedback for programming the 16 beat drum pattern: one line of the display shows the instrument number assigned to each beat, and the position of the current working beat flashing on the screen. I added two extra buttons for scrolling the working beat left or right, and setup the beat to be programmed to a new drum instrument by pressing one of the bottom 10 keys on the keyboard.

In order to add a few more buttons (running short on GPIO pins by this stage) I got a I2C port expander which provides an additional 8 digital inputs and communicates on I2C. I tested this on the same bus as the I2C character display and everything seemed to be working fine with a single button, but wouldn't work with the other seven. After a bit of debugging I realised that the default I2C for the expander and the display were the same: I changed the address for the expander using the external address pins and everything was working fine.


Finally, I implemented two recordable loop channels. The idea with this was that I could play an input sequence to the keyboard over the 16 beat period of the drum machine, and the loop channel would record and playback this sequence on repeat, while the drum machine was switched on. I could also playback these sequences at a variable tempo using the drum machine's tempo knob, providing the ability to record complex patterns at slow speeds, then ramp this up to a fast speed at playback which would otherwise be impossible to play manually. Each channel is controlled by a single button: when the button is initially pressed, the recording begins and starts to playback the input sequence in a loop. Subsequent button presses turn the playback for this loop on and off, and holding the button down for 1 second deletes the sequence, making the channel open for re-recording a new sequence. To implement the record and playback, I created two arrays in memory that contain the note pressed and the sampling time over the time period of the 16 beats. During recording, these arrays are written to using the main program loop with timing provided using Teensy's "elapsedMillis" type. Playback is achieved by using two PIT timers with interrupts to ensure playback timing is smooth regardless of what is happening in the main program.

I linked one of the two channels to have additional pitch control using the bottom octave of the keyboard during playback, gaining inspiration from this project. During recording, the first note of the pattern becomes the "root" note of the sequence. During playback, keys pressed in the bottom octave of the keyboard are used to re-assign the root note of the sequence, which acts to shift the pitch of the entire sequence up or down by a fixed amount. This provides that ability to make this sequence a "baseline" and have the player manually control a chord progression in a song with a single key press.

Designing the Case:

I bought some 19 mm thick dressed pine for building a new housing for the keyboard, because the old plastic one looked ugly, and I wasn't looking forward to 3D printing new panels to fit the controls I wanted. I put together the base and back board by hand: glued two bits of timber together and screwed them in for good measure. I then designed side panels and top panels to be cut out and decorated using Carvey. I designed the panel to hold all of the controls to be carved also using Carvey. I had to use two different milling bits to get the right combination of cutouts and fine detail in the lettering on the panels, and I had to carve first on the front and then on the back to complete the design/housing for the display, joystick and electronics PCBs.

Putting it all together:

I wired up the Teensy and connections to the keyboard on a perma-protoboard, tested this was working well leaving the other components on a breadboard. I connected up two neopixels to use as part of the visual feedback to the player: I mounted these to be facing up through the top control panel adjacent to each of the loop channel control buttons. These light turn yellow for standard operation, pink when the player is selecting a new instrument, blue to indicate a loop channel has a recorded sequence available, green to indicate this channel is currently playing and red to indicate the channel is currently being recorded. I then wired up and soldered on the remaining components to the back of the control panel, connected everything up to the keyboard and screwed it into the wooden frame.



This is a video of Teensytune in action playing an interpretation of "Rainbow Road" from Mario Kart 64 (I love that game). Starts off by programming the drum machine, then recording two loops: a baseline and a little flourish. The actual song starts about 1:27. When playing with the right hand, the left hand is controlling the chord progression by shifting the baseline. Sorry about my poor piano skills :).

I'm hoping to expand the project by using the available space to install a small embedded computer to perform the synthesis, and add in an amplifier and speaker, so that the Teensytune can operate independently of a laptop. Stay tuned!

Teensytune Code:

The code that runs on the Teensy can be found at:
https://github.com/mit-mit-randomprojectlab/teensytune

There are a few required libraries for the neopixels, two-line display and port expander, listed in the readme.