Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Akai Force Rec Pedal w/ 8x8 Matrix Display (USB MIDI) - for Teensy LC
// ===============================================================
// Akai Force Rec Pedal w/ 8x8 Matrix Display (USB MIDI) - for Teensy LC
// "Cowboy" Ben Alman, 2021
// https://gist.github.com/cowboy/6c2230d54aea58577c5b7953ea86aebe
// ===============================================================
// Why does this exist?
//
// I designed this to circumvent an issue with the Akai Force where receiving
// an MMC Rec message signals that the loop should end at the end of the
// current measure BUT it also stops recording the current clip at the moment
// MMC Rec is received, NOT at the end of the measure, which makes it very hard
// to end loop recording via foot pedal in a musical way.
//
// Usage
//
// While recording:
// - Press the pedal to queue an MMC Rec message to be sent at the end of the
// current measure. This will a) tell Force that it should set the loop length
// to the end of the measure and b) turn recording mode off at the end of the
// measure.
// - If you hold the pedal past the end of the measure, it will do "a" above
// but keep recording mode on, instead of turing it off.
// - If you press the pedal again before the end of the current measure, it
// will cancel the previous queued behavior.
//
// While not recording:
// - Press the pedal to queue an MMC Rec message to be sent at the end of
// the current measure.
// - If you press the pedal again before the end of the current measure, it
// will cancel the previous queued behavior.
//
// In order for this to work, you need to ensure the Force is set to send "MIDI
// Clock" and "Receive MMC" and be sure to enable "Sync" on the "Cowboy Akai
// Force Rec Pedal" Output Port. Also, note that because MIDI clock is dumb,
// this device can't know about any other time signature than what is hard-coded
// into it, which in this case is 4. I don't think Force supports anything other
// than 4/4 time, but if you want to change this, see BEATS_PER_MEASURE.
//
// How do I build this?
//
// This requires a momentary (normally open) foot pedal, like the Yamaha FC5, as
// well as an MAX7219-controlled 8x8 LED Matrix. See the photos in the comments
// to get an idea of how I built it. Here are the parts I used:
//
// * Teensy LC https://www.pjrc.com/store/teensylc.html
// * 1/4" Mono Jack https://www.switchcraft.com/Category_Multi.aspx?Parent=952
// * Yamaha FC5 https://smile.amazon.com/gp/product/B00005ML71
// * LED Matrix https://smile.amazon.com/gp/product/B07VM6HXN5
// * Project Box https://smile.amazon.com/gp/product/B0895HPW1T
// * USB adapter https://www.amazon.com/gp/product/B07FL3MKLK
//
// How do I program this?
//
// Program with the Arduino IDE and Teensyduino. Their instructions are pretty
// comprehensive, but you may also want to read some guides or tutorials.
//
// * Arduino IDE https://www.arduino.cc/en/software
// * Teensyduino https://www.pjrc.com/teensy/teensyduino.html
// * MAX7219 Lib https://github.com/GyverLibs/GyverMAX7219
// * (in English) https://translate.google.com/translate?hl=en&sl=en&tl=en&u=https%3A%2F%2Fgithub.com%2FGyverLibs%2FGyverMAX7219
// * Bitmaps https://bitmap-code-generator.benalman.com/
//
// Have fun!
#include <Bounce.h>
#include <GyverMAX7219.h>
// Uncomment the next line to simulate pressing play + running on boot
//#define RUN_TEST
// Change these pins if desired
const int SWITCH_PIN = 0;
const int LED_PIN = LED_BUILTIN;
const int MATRIX_DATA_PIN = 23;
const int MATRIX_CLK_PIN = 21;
const int MATRIX_CS_PIN = 22;
const int MATRIX_WIDTH = 8;
const int MATRIX_HEIGHT = 8;
const int MATRIX_BRIGHTNESS = 8;
MAX7219 < MATRIX_WIDTH, MATRIX_HEIGHT, MATRIX_CS_PIN, MATRIX_DATA_PIN, MATRIX_CLK_PIN > mtrx;
const uint8_t number_1_icon[] PROGMEM = {0x06, 0x0e, 0x16, 0x06, 0x06, 0x06, 0x06, 0x1f};
const uint8_t number_2_icon[] PROGMEM = {0x0e, 0x1b, 0x03, 0x03, 0x06, 0x0c, 0x18, 0x1f};
const uint8_t number_3_icon[] PROGMEM = {0x1e, 0x03, 0x03, 0x0e, 0x03, 0x03, 0x03, 0x1e};
const uint8_t number_4_icon[] PROGMEM = {0x03, 0x07, 0x0b, 0x1b, 0x1f, 0x03, 0x03, 0x03};
const uint8_t* const numbers[] PROGMEM = {number_1_icon, number_2_icon, number_3_icon, number_4_icon};
const uint8_t play_icon[] PROGMEM = {0xff, 0x81, 0xb1, 0xbd, 0xbd, 0xb1, 0x81, 0xff};
const uint8_t stop_icon[] PROGMEM = {0xff, 0x81, 0xbd, 0xbd, 0xbd, 0xbd, 0x81, 0xff};
const uint8_t ok_icon[] PROGMEM = {0x65, 0x95, 0x95, 0x96, 0x96, 0x95, 0x95, 0x65};
const uint8_t err_icon[] PROGMEM = {0x3d, 0x42, 0x85, 0x89, 0x91, 0xa1, 0x42, 0xbc};
const uint8_t robot_0_icon[] PROGMEM = {0x42, 0x7e, 0x81, 0xa5, 0x81, 0x7e, 0x3c, 0xff};
const uint8_t robot_1_icon[] PROGMEM = {0x42, 0x7e, 0x81, 0x81, 0x81, 0x7e, 0x3c, 0xff};
const uint8_t* const idle_screen_icons[] PROGMEM = {
robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon,
robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon,
robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon, robot_0_icon,
robot_1_icon, robot_0_icon, robot_1_icon, robot_0_icon,
};
const int idle_screen_icon_count = 28;
const int idle_screen_frame_delay = 20000;
const int stop_icon_frame_delay = 100000;
// Assume 4/4 time
int BEATS_PER_MEASURE = 4;
int PPQN = 24;
int PPSN = PPQN / 4;
int MAX_PULSES = PPQN * BEATS_PER_MEASURE;
Bounce button0 = Bounce(SWITCH_PIN, 5);
int counter = 0;
int error = 0;
int idle_screen_counter = 0;
int button_pressed = 0;
int rec_toggle_pending = 0;
int clock_running = 0;
int led_lit = 0;
int just_started = 0;
int just_stopped = 0;
void setup() {
Serial.begin(115200);
// Setup pins
pinMode(SWITCH_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
// MIDI message handlers
usbMIDI.setHandleClock(onClock);
usbMIDI.setHandleStart(onStart);
usbMIDI.setHandleContinue(onContinue);
usbMIDI.setHandleStop(onStop);
mtrx.begin();
mtrx.setBright(MATRIX_BRIGHTNESS);
mtrx.setRotation(3);
draw_icon(ok_icon);
blink(5);
init_idle_screen();
#ifdef RUN_TEST
onStart();
#endif
}
void led_on() {
led_lit = 1;
digitalWrite(LED_PIN, HIGH);
}
void led_off() {
led_lit = 0;
digitalWrite(LED_PIN, LOW);
}
void led_toggle() {
if (led_lit) {
led_off();
} else {
led_on();
}
}
void blink(int count) {
for (int i = 0; i < count; i++) {
led_on();
delay(50);
led_off();
delay(50);
}
}
void test_matrix() {
mtrx.clear();
byte x, y;
// light one LED at a time, scanning left to right
// and top to bottom... useful for testing the matrix
for (y = 0; y < MATRIX_HEIGHT; y++) {
for (x = 0; x < MATRIX_WIDTH; x++) {
mtrx.dot(x, y);
mtrx.update();
delay(100);
}
}
}
void loop() {
if (just_stopped) {
just_stopped++;
if (just_stopped == stop_icon_frame_delay) {
just_stopped = 0;
init_idle_screen();
}
} else if (!clock_running && !error) {
idle_screen();
}
button0.update();
// Pedal pressed
if (button0.risingEdge()) {
button_pressed = 1;
if (clock_running) {
// Toggle pending state
rec_toggle_pending = 1 - rec_toggle_pending;
// Turn the LED on or off accordingly
if (rec_toggle_pending) {
led_on();
} else {
led_off();
}
} else {
// Complain if pressed when the clock is not running
blink(2);
}
}
// Pedal released
else if (button0.fallingEdge()) {
button_pressed = 0;
}
usbMIDI.read();
#ifdef RUN_TEST
onClock();
delay(50);
#endif
}
void send_rec() {
static uint8_t mmc_rec[6] = {0xF0, 0x7F, 0x7F, 0x06, 0x06, 0xF7};
usbMIDI.sendSysEx(6, mmc_rec);
}
void clear_matrix() {
mtrx.clear();
mtrx.update();
}
void draw_icon(uint8_t *icon) {
mtrx.clear();
mtrx.drawBitmap(0, 0, icon, 8, 8);
mtrx.update();
}
void draw_number(int i) {
mtrx.clear();
uint16_t ptr = pgm_read_word(&(numbers[i]));
mtrx.drawBitmap(-3, 0, ptr, 8, 8);
}
void update_matrix() {
int beat = counter / PPQN;
int sixteenth = counter / PPSN;
if (counter % PPQN == 0) {
draw_number(beat);
}
for (int i = 0; i <= sixteenth % 4; i++) {
int y1 = i * 2;
int y2 = y1 + 1;
if (rec_toggle_pending) {
mtrx.fastLineH(y1, 6, 7);
mtrx.fastLineH(y2, 6, 7);
} else {
int x1 = beat % 2 ? 6 : 7;
int x2 = beat % 2 ? 7 : 6;
mtrx.dot(x1, y1, 1);
mtrx.dot(x2, y1, 0);
mtrx.dot(x1, y2, 0);
mtrx.dot(x2, y2, 1);
}
}
mtrx.update();
}
void init_idle_screen() {
idle_screen_counter = 0;
}
void idle_screen() {
if (++idle_screen_counter % idle_screen_frame_delay == 0) {
int i = idle_screen_counter / idle_screen_frame_delay;
if (i == idle_screen_icon_count) {
i = 0;
idle_screen_counter = 0;
}
draw_icon(pgm_read_word(&(idle_screen_icons[i])));
}
}
void onClock() {
if (!clock_running) {
if (!error) {
error = 1;
draw_icon(err_icon);
}
return;
}
if (++counter == MAX_PULSES) {
counter = 0;
// Send rec message if pending
if (rec_toggle_pending) {
rec_toggle_pending = 0;
send_rec();
// Since this is the beginning of the measure, send again
// if the pedal is held down
if (button_pressed) {
send_rec();
}
}
}
if (counter % PPQN == 0 && just_started) {
just_started = 0;
}
if (!just_started && counter % PPSN == 0) {
update_matrix();
}
if (counter == 0) {
led_on();
} else if (counter % PPQN == 0 || counter == 2) {
led_toggle();
} else if (counter % PPQN == 1 || counter == 3) {
led_toggle();
} else if (counter == MAX_PULSES - 2) {
led_off();
}
}
void onStart() {
Serial.println("Start");
led_on();
counter = 0;
clock_running = 1;
rec_toggle_pending = 0;
just_started = 1;
just_stopped = 0;
draw_icon(play_icon);
error = 0;
}
void onContinue() {
Serial.println("Continue");
clock_running = 1;
error = 0;
}
void onStop() {
Serial.println("Stop");
led_off();
clock_running = 0;
rec_toggle_pending = 0;
just_started = 0;
just_stopped = 1;
error = 0;
draw_icon(stop_icon);
}
// To give your project a unique name, this code must be
// placed into a .c file (its own tab). It can not be in
// a .cpp file or your main sketch (the .ino file).
#include "usb_names.h"
// Edit these lines to create your own name. The length must
// match the number of characters in your custom name.
#define MIDI_NAME {'C', 'o', 'w', 'b', 'o', 'y', ' ', 'A', 'k', 'a', 'i', ' ', 'F', 'o', 'r', 'c', 'e', ' ', 'R', 'e', 'c', ' ', 'P', 'e', 'd', 'a', 'l'}
#define MIDI_NAME_LEN 27
// Do not change this part. This exact format is required by USB.
struct usb_string_descriptor_struct usb_string_product_name = {
2 + MIDI_NAME_LEN * 2,
3,
MIDI_NAME
};
@cowboy
Copy link
Author

cowboy commented Sep 28, 2021

Photos

Inside

2021-09-27 19 05 23

Front

2021-09-27 19 08 18

Back

2021-09-27 19 08 23

Video

https://www.youtube.com/watch?v=iSE7Q92W-eg

@biano22
Copy link

biano22 commented Sep 29, 2022

Your tutorials are amazing, it saves a lot of time learning about them. Thank you for sharing it is really valuable driving directions mapquest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment