Just seen Extralife has done some revisions to the Big Button =) seems good!
Anybody managed to make it work? Mine is working all except the sync in (midi works well).
I havent modded mine⌠or built it yet, going to do that tonight =]
Literally got it in my vice ready to go.
ill add this after ive got it running tho i bet =D
Please let me know!! I have the midi part working nicely, but the Sync in is not working at all.
well Ive built my button at least, just working on a Teensy4 Wav player today for testing =)
You can also ask the person who published the layout yourself. I once asked him a question, he was friendly and was able to help with his answer!
Hi there,
Just compiled the script form the github page,
GitHub - matthewcieplak/hexdrum-midi: A MIDI-enabled fork of the LMNC big button
I got an error when compiling it , easy to fix (I could do it) so I am putting here in case:
Move that part of the code before the MIDI_CHANNEL assignement.
#include <Arduino.h>
#include <SoftwareSerial.h>
It now compiles fine.
This looked strange â why would moving the headers before that declaration fix anything? â so I took a look at this, I get no compilation error either way.
Ho, well thatâs strange.
I get the error
error: âbyteâ does not name a type; did you mean âbitâ?
Hm, thatâs odd. byte
is defined in stddef.h
, which is included by Arduino.h
. I have Arduino 1.8.16 here; I suppose different IDEs or different versions might or might not automatically include stddef.h
. If yours doesnât then including Arduino.h
first would indeed fix the error. But in mine it apparently is included implicitly.
I have made that one and I got the same issue as other people when using an arduino nano versus the nano every that Extralife used.
I have managed to fix the issues and mostly the clock input is also working (MIDI as well).
I am sharing the code here. I have annotated what I had to change inside the code. (Pins position for big button, LED, the step analog read).
If you only want the bit I had to change for the clock input here it is
updated code line:
attachInterrupt(digitalPinToInterrupt(2), onClockIn, RISING); // Updated line of Code from Bpbby
Full arduino nano code:
//////
///// H H EEEEE X X DDDD RRRR U U M M
///// H H E X X D DD R R U U M MM M
///// HHHHHH EEE X D D R RR U U M M M
///// H H E X X D DD R R U U M M
///// H H EEEEEE X X DD DD R R UU M M
/////
///// HEX DRUM is a midi-capable expanded port of the âbig buttonâ by
///// look mum no computer
///// modified by extralife in 2020
///// extralifedisco@gmail.com
///// no commercial use permitted
///// re-use and modification permitted with attribution
///// original âdont be a plebâ license included below
/////
// >>>>>>>>>>>> Edited by Bpbby to work on Arduino Nano (Main fixes are : pin output issue on Button LED and clock INPUT working)
//KOSMO BIG BUTTON 2016 SAM BATTLE
//email address is computer@lookmumnocomputer.com
//look mum no computer
//lookmumnocomputer@gmail.com
//www.facebook.com/lookmumnocomputer
//ITS AN ABSOLUTE MESS BUT IT WORKS⌠SORT OFâŚ
//YOU NEED QUITE A CLEAN CLOCK TRIGGER SIGNAL. QUITE A SHORT PULSE!
//To make it work better add this circuit to the clock input :-
//https://www.cgs.synth.net/modules/cgs24_gatetotrigger.html
//the premise of this is a simple performance sequencer.
//it is used in synth bike mk2 to sequencer the drums.
//i figured whats the point in not sharing it!!!
//dont be a pleb and steal it for a product or some shit. Build it and
//enjoy it as a musical instrument
// Clock in is pin 2
// Clear Button is pin 4 ⌠this clears the whole loop sequence
// Button Delete is pin 7 This deletes the step your on
// Bank select Pin 3 ⌠each channel has 2 banks of patterns, so you can record 2 alternative patterns and go between with this button
// BIG BUTTON is pin 19 (A5), this is the performance button! >>>>>>> Changed to pin A1
// Reset in is pin 6 you can add a button or a jack!
// FILL BUTTON pin 5, push this and it will continuously play the channel your on, doesnt record, push it and twist the select knob to make a fill!
// STEP LENGTH analog pin 1 (A1) >>>>>> changed to A6
// Channel select Analog pin (A0)
// SHIFT KNOB Analog pin (A2)
// CLOCK OUT (A3) (digital)
// LED (big button LED) pin 20 (A6) >>>>>>> changed to pin A1 analog/digitalread
#include <Arduino.h>
#include <SoftwareSerial.h>
const byte MIDI_CHANNEL = 16; //set this to receive midi note data on a specific channel
//NOTE: non-zero indexed, range is 1-16 (not 0-15)
SoftwareSerial midiSerial(0, 1); // RX, TX
// MIDI commands
#define MIDI_NOTE_ON 144
#define MIDI_NOTE_OFF 128
#define MIDI_CLOCK 0xF8
#define MIDI_CLOCK_START 0XFA
#define MIDI_CLOCK_CONTINUE 0xFB
#define MIDI_CLOCK_STOP 0xFC
#define MIDI_CLOCK_DIVISION 6 //MIDI uses 24 steps per quarter note, 6 steps is a sixteenth note. use smaller values for faster clock
//MIDI states
#define STATE_NONE 0
#define STATE_NOTE_ON 1
#define STATE_NOTE_PLAY 2
#define STATE_NOTE_OFF 3
int midiState = STATE_NONE;
int midiClocks = 0;
int multiplexor = 0;
const int midi_notes[6] = { //route input notes to output gates, standard GM midi drum mapping
36, //C1
38, //D1
40, //E1
41, //F1
43, //G1
45 //A2
};
int midiStates[6] = { 0, 0, 0, 0, 0, 0 };
const int OUT1 = 8;
const int OUT2 = 9;
const int OUT3 = 10;
const int OUT4 = 11;
const int OUT5 = 12;
const int OUT6 = 13;
const int outputs[6] = { OUT1, OUT2, OUT3, OUT4, OUT5, OUT6 };
const int BankLED = A5;//17 //changed to pin A5 digital output
const int ClockOut = A3;
const int ClockIn = 2;
const int BigButton = A1; // changed to A1 digitialread
const int ButtonBank = 3;
const int ButtonClear = 4; //reset button for the moment
const int FillButton = 5;
const int ResetButton = 6;
const int ButtonDelete = 7;
const int AltButton = A4;
const bool ALT_BUTTON_ENABLED = true; //disable (set false) to prevent weird behavior if you donât have this button!!!
#define TOLERANCE 0
#define CLOCK_PULSE_LENGTH 10 //milliseconds
long pulsekeeper;
//loop operators
int i;
int ii;
//Clock Reset Keepers
int ClockKeep = 0;
int looper = 0;
int step_duration_ms = 125; // 1/16th note at 120bpm is default - use for quantization
int step_duration_ms_half = 62;
long step_time = 0;
long button_time = 0;
//int buttonPushCounter = 0; // counter for the number of button presses
//Various button states
int BigButtonState = 0;
int prevBigButtonState = 0;
int DeleteButtonState = 0;
int prevDeleteButtonState = 0;
int ClearButtonState = 0;
int prevClearButtonState = 0;
int ResetButtonState = 0;
int prevResetButtonState = 0;
int FillButtonState = 0;
int AltButtonState = 0;
//Bank Button Latching states
long time = 0;
long debounce = 150;
int BankButtonState;
int BankButtonPrevState;
int bankChannelStates[6] = { 0, 0, 0, 0, 0, 0 };
//SHIFT KNOB
int shiftKnobVal = 0; //analog value
int shiftOldKnobVal = 0; //for detecting changes
int shiftState = 0; //integer state of knob as 0-8
int shiftChannelValues[6] = { 0, 0, 0, 0, 0, 0 }; //integer states to shift
int shiftKnobAnalogValueSteps[9] = { 0, 127, 254, 383, 511, 638, 767, 895, 1000 };
int Channel = 0; //active selected channel
int ClockState = 0; //whether or not clock input is high
int PulseState = 0; //whether or not clock/gate output is high
int HalfClockState = 0;
int steps = 0; //beginning number of the steps in the sequence adjusted by StepLength
bool Sequence[12][43]; //max length is really 32 but shift knob enables some overflow
bool DefaultSequence[12][43];
//mute/solo function replaces delete button
bool muteEnabled = false;
bool soloEnabled = false;
void setup()
{
pinMode(OUT1, OUTPUT);
pinMode(OUT2, OUTPUT);
pinMode(OUT3, OUTPUT);
pinMode(OUT4, OUTPUT);
pinMode(OUT5, OUTPUT);
pinMode(OUT6, OUTPUT);
pinMode(BankLED, OUTPUT);
pinMode(ClockOut, OUTPUT);
pinMode(ClockIn, INPUT);
pinMode(BigButton, INPUT);
pinMode(ButtonDelete, INPUT);
pinMode(ButtonClear, INPUT);
pinMode(ButtonBank, INPUT);
pinMode(ResetButton, INPUT);
pinMode(FillButton, INPUT);
pinMode(AltButton, INPUT);
for (i = 0; i < 14; i++) {
for (ii = 0; ii < 43; ii++) {
Sequence[i][ii] = 0;
DefaultSequence[i][ii] = 0;
}
}
//show startup sequence
blink(BankLED);
blink(OUT1);
blink(OUT2);
blink(OUT3);
blink(OUT4);
blink(OUT5);
blink(OUT6);
//initialize analog values
readStepsKnob();
readChannelSelectKnob();
readShiftKnob();
//attachInterrupt(ClockIn, onClockIn, RISING); //Original line of code
attachInterrupt(digitalPinToInterrupt(2), onClockIn, RISING); // Updated line of Code from Bpbby
midiSerial.begin(31250);
}
int StepLength = 0; //analog value storage
void readStepsKnob(){
StepLength = 1024 - analogRead(6); // changing analog read to pin A6 analog IN only on arduino nano
if (StepLength < 200) { steps = 2; }
else if (StepLength < 500) { steps = 4; }
else if (StepLength < 800) { steps = 8; }
else if (StepLength < 1000) { steps = 16;}
else { steps = 32;}
}
int CHANNELSELECT = 0; //analog value storage
void readChannelSelectKnob(){
CHANNELSELECT= analogRead(0);
if(CHANNELSELECT < 170) { Channel = 0; }
else if(CHANNELSELECT < 240) { Channel = 1; }
else if(CHANNELSELECT < 420) { Channel = 2; }
else if(CHANNELSELECT < 750) { Channel = 3; }
else if(CHANNELSELECT < 1000){ Channel = 4; }
else { Channel = 5; }
}
void readShiftKnob(){
//SHIFT knob - note swap with commented line for reversed pot pins
shiftKnobVal = 1024 - analogRead(2);
//KnobVal = analogRead(2);
int i = 0;
while (i <= 8) {
if (shiftKnobAnalogValueSteps[i] > shiftKnobVal) {
shiftState = i;
break;
}
i++;
}
int diff = abs(shiftKnobVal - shiftOldKnobVal);
if (diff > TOLERANCE) {
shiftChannelValues[Channel] = shiftState;
}
shiftOldKnobVal = shiftKnobVal;
}
void blink(int output) {
digitalWrite(output, HIGH);
delay(50);
digitalWrite(output, LOW);
}
void onClockIn()
{
ClockState = HIGH;
digitalWrite(ClockOut, HIGH);
//count tempo for quantization
step_duration_ms = millis() - step_time;
step_time = millis();
step_duration_ms_half = step_duration_ms / 2;
}
// cast true/false values to HIGH/LOW to simplify code
void digitalWriteCast(int pinNumber, bool pinState) {
digitalWrite(pinNumber, pinState ? HIGH : LOW);
}
void readButtons(){
BigButtonState = digitalRead(BigButton);
DeleteButtonState = digitalRead(ButtonDelete);
ClearButtonState = digitalRead(ButtonClear);
BankButtonState = digitalRead(ButtonBank);
ResetButtonState = digitalRead(ResetButton);
FillButtonState = digitalRead(FillButton);
if (ALT_BUTTON_ENABLED) {
AltButtonState = digitalRead(AltButton);
}
//Read big button input and write to nearest sequence step
if (BigButtonState != prevBigButtonState) {
prevBigButtonState = BigButtonState;
if (BigButtonState == HIGH) {
if (AltButtonState == HIGH) { //alt "listen mode" aka "audition" - play a sound without adding it to the sequence
if (!Sequence[Channel*2 + bankChannelStates[Channel]][(looper + shiftChannelValues[Channel]) % steps]) { //don't re-trigger if step is already active
digitalWrite(outputs[Channel], HIGH);
}
} else { //add a new step to the sequence
button_time = millis() - step_time; // get current step offset in ms
int step_quantize_advance = 0;
if (button_time > step_duration_ms_half) { //if we're closer to the current step, activate and trigger
step_quantize_advance = 1; //otherwise activate the next step
} else {
digitalWrite(outputs[Channel], HIGH);
}
Sequence[Channel*2 + bankChannelStates[Channel]][(looper + shiftChannelValues[Channel] + step_quantize_advance) % steps] = 1;
}
} else {
digitalWrite(outputs[Channel], LOW); //disable gate out on button release
}
}
if (ClearButtonState != prevClearButtonState) {
prevClearButtonState = ClearButtonState;
if (ClearButtonState == HIGH) {
if (AltButtonState == HIGH) { //shift clear + clear all active banks
for (ii = 0; ii < 6; ii++) {
for (i = 0; i < 32; i++) {
Sequence[ii2 + bankChannelStates[ii]][i] = 0;
}
}
} else { //clear current channel
for (i = 0; i < 32; i++) {
Sequence[Channel2 + bankChannelStates[Channel]][i] = 0;
}
}
}
}
//debounce bank button and latch state to channel
if (BankButtonState != BankButtonPrevState && millis() - time > debounce) {
BankButtonPrevState = BankButtonState;
time = millis();
if (BankButtonState == HIGH) {
if (AltButtonState == HIGH) {
for(i = 0; i < 6; i++) {
bankChannelStates[i] = bankChannelStates[i] == 1 ? 0 : 1; //flip bit and latch rather than assign from button state
}
} else {
bankChannelStates[Channel] = bankChannelStates[Channel] == 1 ? 0 : 1; //flip bit and latch rather than assign from button state
}
}
}
//MJC - 5/20/2020 - replacing delete function with mute/solo. uncomment next 3 lines for original delete
// if (DeleteButtonState == HIGH) {
// Sequence[Channel*2 + bankChannelStates[Channel]][looper + 1] = 0;
// } //This is the delete button
//MUTE/SOLO
if (DeleteButtonState != prevDeleteButtonState) {
if (DeleteButtonState == HIGH) {
if (AltButtonState) {
soloEnabled = true;
muteEnabled = false;
} else {
muteEnabled = true;
soloEnabled = false;
}
} else {
soloEnabled = false;
muteEnabled = false;
}
prevDeleteButtonState = DeleteButtonState;
}
//todo implement reset to default state with shift button
if (ResetButtonState != prevResetButtonState) {
if (ResetButtonState == HIGH) {
looper = 0;
ClockKeep = 0;
for (i = 0; i < 6; i++) {
bankChannelStates[i] = 0;
shiftChannelValues[i] = 0;
}
}
prevResetButtonState = ResetButtonState;
}
}
// received MIDI data
byte midiByte;
byte midiChannel;
byte midiCommand;
byte midiNote;
byte midiVelocity;
void receiveMIDI(){
if (midiSerial.available() > 0) {
// read MIDI byte
midiByte = midiSerial.read();
switch (midiState) {
case STATE_NONE:
// remove channel info
midiChannel = midiByte & B00001111;
midiCommand = midiByte & B11110000;
if (midiChannel == MIDI_CHANNEL - 1){
if (midiCommand == MIDI_NOTE_ON){
midiState = STATE_NOTE_ON;
} else if (midiCommand == MIDI_NOTE_OFF) {
midiState = STATE_NOTE_OFF;
}
} else if (midiByte == MIDI_CLOCK_START || midiByte == MIDI_CLOCK_CONTINUE) {
// midiState = STATE_NONE;
midiClocks = 0;
looper = 0;
ClockKeep = 0;
onClockIn();
} else if (midiByte == MIDI_CLOCK) {
midiClocks += 1;
// digitalWrite(BankLED, (midiClocks % 2) > 0 ? HIGH : LOW);
if (midiClocks == MIDI_CLOCK_DIVISION) {
onClockIn();
midiClocks = 0;
}
// midiState = STATE_CLOCK_START;
} else if (midiCommand == MIDI_CLOCK_STOP) {
midiClocks = 0;
// looper = 0;
// ClockKeep = 0;
}
break;
case STATE_NOTE_ON:
midiNote = midiByte;
midiState = STATE_NOTE_PLAY;
break;
case STATE_NOTE_OFF:
midiNote = midiByte;
case STATE_NOTE_PLAY:
midiVelocity = midiByte;
if (midiVelocity > 0 && midiState == STATE_NOTE_PLAY)
{
// digitalWrite(BankLED, HIGH);
for (i = 0; i < 6; i++) {
if (midiNote == midi_notes[i]) {
if (muteEnabled && i == Channel) {
// don't play muted channel
} else if (soloEnabled && i != Channel) {
//don't play un-soloed
} else {
digitalWrite(outputs[i], HIGH);
midiStates[i] = 1;
}
break;
}
}
} else { //receiving a note off message or zero velocity message
// digitalWrite(BankLED, LOW);
for (i = 0; i < 6; i++) {
if (midiNote == midi_notes[i]) {
digitalWrite(outputs[i], LOW);
midiStates[i] = 0;
break;
}
}
}
midiState = STATE_NONE;
break;
} // switch
} // mySerial.available()
}
void loop()
{
if (ClockState == HIGH && PulseState == LOW) {
ClockKeep += 1;
looper += 1;
// for (i = 0; i < 6; i++) { //track each channel independently?
// shiftChannelValues[i] += 1;
// }
digitalWrite(ClockOut, HIGH); //clock rising edge
for (i = 0; i < 6; i++) {
//set each channel output gate high or low per current step (or high if fill button is pressed)
if (muteEnabled && i == Channel) {
// donât play muted channel
} else {
if (soloEnabled && i != Channel) {
//donât play non soloed channel
} else if (midiStates[i]) {
//donât overlap midi notes
} else {
digitalWriteCast(
outputs[i],
Sequence[i*2 + bankChannelStates[i]][(looper + shiftChannelValues[i]) % steps] ||
(FillButtonState == HIGH && i == Channel));
}
}
}
if ((ClockKeep - 1) % 4 == 0) { //blink led every 4th step, typically quarter notes, invert for bank 2
digitalWrite(BankLED, bankChannelStates[Channel] == 1 ? LOW : HIGH);
} else {
digitalWrite(BankLED, bankChannelStates[Channel] == 1 ? HIGH : LOW);
}
ClockState = LOW;
HalfClockState = LOW;
PulseState = HIGH;
pulsekeeper = millis();
//delay(CLOCK_PULSE_LENGTH); //remove delay calls to fix serial input
} else if (FillButtonState == HIGH && AltButtonState == HIGH) { //ALT+FILL = 32nd note roll
if (HalfClockState == LOW) {
if (millis() - step_time >= step_duration_ms_half) {
HalfClockState = HIGH;
digitalWrite(outputs[Channel], HIGH);
pulsekeeper = millis();
PulseState = HIGH;
}
}
}
if (PulseState == HIGH && millis() - pulsekeeper > CLOCK_PULSE_LENGTH){
digitalWrite(ClockOut, LOW); //clock falling edge
for (i = 0; i < 6; i++) {
if (midiStates[i] == 0) { //don't overwrite midi notes
digitalWrite(outputs[i], LOW); //gate falling edge
}
}
PulseState = LOW; //ready for next clock pulse
}
multiplexor += 1;
if (multiplexor == 1) { //break up analogReads as they lag the clock and can cause dropped steps
readShiftKnob();
} else if (multiplexor == 2) {
readStepsKnob();
} else if (multiplexor == 3) {
readChannelSelectKnob();
} else if (multiplexor > 3) {
readButtons();
multiplexor = 0;
}
//always read midi or we could miss steps
receiveMIDI();
if (looper >= steps) {
looper = 0; //this bit starts the sequence over again
}
if (ClockKeep >= 32) {
looper = 0;
ClockKeep = 0;
}
}
Btw, I noticed that when copying this code directly, it does not compile. The errors are minors (missing brackets, space, etcâŚ)
I suspect this has to do with how the forum text editor tries to âreformatâ the text, because the original code works just fine. If I get a chance to go on a PC ill try to reupload it, if it is possible.
If posting code use the </>
icon in the post editor, or just enter three back ticks
```
then put the code
then three more
```
Resulting in:
/*
Blink
Turns an LED on for one second, then off for one second, repeatedly.
Most Arduinos have an on-board LED you can control. On the UNO, MEGA and ZERO
it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN is set to
the correct LED pin independent of which board is used.
If you want to know what pin the on-board LED is connected to on your Arduino
model, check the Technical Specs of your board at:
https://www.arduino.cc/en/Main/Products
modified 8 May 2014
by Scott Fitzgerald
modified 2 Sep 2016
by Arturo Guadalupi
modified 8 Sep 2016
by Colby Newman
This example code is in the public domain.
https://www.arduino.cc/en/Tutorial/BuiltInExamples/Blink
*/
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}