Overview
The goal of this project for me is to learn more about midi protocol, and venture into ableton live remote midi scripts.This is a basic transport controller for ableton live, but it can be used for any DAW if you manually map the controls to the CC numbers sent by the arduino.
The downside of using the arduino nano is that it uses a usb to serial chip to be able to program the atmega328p. As a result of this it requires the use of a serial to midi bridge to be able to communicate with midi devices. I used Hairless MIDI < - > Serial bridge to do this, which can be found here https://projectgus.github.io/hairless-midiserial/.
If using windows you will also need some sort of virtual midi port to be able to route the midi messages to ableton, the one I use can be found here http://www.tobias-erichsen.de/software/loopmidi.html. I only use windows so I'm not sure what needs to be done for mac or linux.
The next version I work on will use atmega328u4 which has native usb, this allows the unit to be connected as a usb midi device, which eliminates the need for the hairless midi bridge. The aim is to also have the microcontroller incorporated into the same circuit as the other components rather than having the pcb as an arduino hat like it currently is.
Bill of materials
- 6 x Momentary buttons
- Arduino nano
- 7 x Leds
- 10 x 10k resistors (button pull down resistors)
- 7 x 1k resistors (led current limiting resistors)
- Optional dip switch (sets midi channel)
Code
The code is relatively simple, with push button input being translated into serial midi data. The leds are essentially driven by a simple state machine. The loop, and record arms can be independently set from all the others, but the play and stop leds can only have one lit at any one time. The master midi channel is determined by the binary value set on the dip switches. This can easily be hardcoded if you would prefer not to include the dip switches.
Stemming from the fact that this is only a small project, and requires only cc midi messages I decided to forgo the inclusion of a whole midi library. Instead of using a library I wrote a function that would send basic cc messages.
CC numbers
- 0 : Stop
- 1 : Play
- 2 : Record arm
- 3 : Toggle looping
- 4 : Rewind
- 5 : Fast fwd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "dip_switch.h" | |
/* MIDI command defines */ | |
#define CC_MESSAGE (uint16_t)0xB0 | |
#define NUM_BUTT0NS 6 | |
/* States for leds. | |
Note: RECORDING and LOOPING can be active at the | |
same time as PLAYING and STOPPED | |
*/ | |
enum class State | |
{ | |
STOPPED = 0, | |
PLAYING, | |
RECORDING, | |
LOOPING | |
}; | |
/* Pin defines for buttons */ | |
enum class BtnNum | |
{ | |
STOP = 2, | |
PLAY = 3, | |
REC = 4, | |
LOOP = 5, | |
RWD = 6, | |
FFWD = 7 | |
}; | |
const BtnNum button_pins[] = {BtnNum::STOP, BtnNum::PLAY, BtnNum::REC, | |
BtnNum::LOOP, BtnNum::RWD, BtnNum::FFWD}; | |
/* Button cc message numbers */ | |
enum class CCNum | |
{ | |
STOP = 0, | |
PLAY = 1, | |
REC = 2, | |
LOOP = 3, | |
RWD = 4, | |
FFWD = 5 | |
}; | |
const CCNum cc_numbers[] = {CCNum::STOP, CCNum::PLAY, CCNum::REC, | |
CCNum::LOOP, CCNum::RWD, CCNum::FFWD}; | |
uint8_t led_pins[NUM_BUTT0NS] = {A1, A0, A5, A4, A3, A2}; | |
/* Master midi channel */ | |
DipSwitch dip_switch = DipSwitch(8, 9, 10, 11); | |
uint8_t midi_channel = 0; | |
/* Led state booleans */ | |
bool is_playing = false; | |
bool is_looping = false; | |
bool rec_armed = false; | |
uint8_t curr_state = 0; | |
uint8_t prev_state = 0; | |
void setup() | |
{ | |
midi_channel = dip_switch.get_bin_state(); | |
Serial.begin(115200); | |
for(uint8_t i = 0; i < NUM_BUTT0NS; i++) | |
{ | |
pinMode((uint8_t)button_pins[i], INPUT); | |
pinMode(led_pins[i], OUTPUT); | |
} | |
} | |
void loop() | |
{ | |
poll_buttons(); | |
if(prev_state != curr_state) | |
{ | |
set_leds(); | |
} | |
} | |
void poll_buttons(void) | |
{ | |
for(uint8_t btn = 0; btn < NUM_BUTT0NS; btn++) | |
{ | |
if(digitalRead((uint8_t)button_pins[btn]) == HIGH) | |
{ | |
update_state(button_pins[btn]); | |
send_cc((uint8_t)cc_numbers[btn], 127, midi_channel); | |
delay(250); | |
} | |
} | |
} | |
void update_state(BtnNum btn) | |
{ | |
prev_state = curr_state; | |
switch(btn) | |
{ | |
case BtnNum::STOP : | |
if(is_playing) | |
{ | |
is_playing = false; | |
clear_state(State::PLAYING); | |
set_state(State::STOPPED); | |
} | |
break; | |
case BtnNum::PLAY : | |
if(!is_playing) | |
{ | |
is_playing = true; | |
set_state(State::PLAYING); | |
clear_state(State::STOPPED); | |
} | |
break; | |
case BtnNum::REC : | |
if(!rec_armed) | |
{ | |
rec_armed = true; | |
set_state(State::RECORDING); | |
} | |
else | |
{ | |
rec_armed = false; | |
clear_state(State::RECORDING); | |
} | |
break; | |
case BtnNum::LOOP : | |
if(!is_looping) | |
{ | |
is_looping = true; | |
set_state(State::LOOPING); | |
} | |
else | |
{ | |
is_looping = false; | |
clear_state(State::LOOPING); | |
} | |
break; | |
} | |
} | |
void set_state(State st) | |
{ | |
curr_state |= (0x01 << (uint8_t)st); | |
} | |
void clear_state(State st) | |
{ | |
curr_state &= ~(0x01 << (uint8_t)st); | |
} | |
void set_leds(void) | |
{ | |
for(int led = 0; led < NUM_BUTT0NS; led++) | |
{ | |
if(curr_state & (0x01 << led)) | |
{ | |
digitalWrite(led_pins[led], HIGH); | |
} | |
else | |
{ | |
digitalWrite(led_pins[led], LOW); | |
} | |
} | |
} | |
void send_cc(uint8_t cc_num, uint8_t value, uint8_t channel) | |
{ | |
Serial.write(CC_MESSAGE | channel); | |
Serial.write(cc_num); | |
Serial.write(value); | |
} |
Ableton User Configuration Script
Premade ableton user configuration script is in the main github repository, but I've added a link below to instructions if you want to make your own.https://help.ableton.com/hc/en-us/articles/206240184-Creating-your-own-Control-Surface-script
If you want to make your own user configuration script, these are the changes you would have to make to interface with this particular arduino script.
[TransportControls]
# The transport buttons are also expected not to be toggles.
StopButton: 0
PlayButton: 1
RecButton: 2
LoopButton: 3
RwdButton: 4
FfwdButton: 5