A CV clock with multiple divisions is a great tool to use with your analogue synths and drum machines, whether Eurorack, Volcas, Moog…
In this project, you’ll learn how to make a clock which outputs your BPM together with /2, /4 and /8. The BPM is set via tapping or through an encoder and it has variable pulse length, making it an extremely versatile device which is way cheaper than getting a ready-made one plus you can adapt the code to your needs any time!
Now we’ll connect the 4 outputs with their respective LEDs. Repeat the instructions below 4 times, using pins D2, D3, D4 and D5 on the Arduino.
Be aware that once this is up and running, D5 will output the current BPM, D4 will output 1/2 of that, D3 1/4 and D2 1/8.
This is the only SimpleTimer library that will work with this project, do NOT USE the one that you’ll find through the Arduino IDE.
/*********************This sketch contains parts Programmed by SyntheMafia**********************/
/********************Encoder implementation based on
* Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
* Contact: bb@cactii.net
* More info: http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
*/
/******************Final implementation by Mowgli***********************************************/
#include <SimpleTimer.h>
SimpleTimer timer;
int count = 0;
int encoderClockPin = 7;
int encoderDataPin = 6;
int encoderCount = 0;
/*************************ENCODER *********************************/
// Half-step mode?
#define HALF_STEP
// Arduino pins the encoder is attached to. Attach the center to ground.
#define ROTARY_PIN1 7
#define ROTARY_PIN2 6
// define to enable weak pullups.
#define ENABLE_PULLUPS
#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11)
const char ttable[6][4] = {
{0x3 , 0x2, 0x1, 0x0}, {0x83, 0x0, 0x1, 0x0},
{0x43, 0x2, 0x0, 0x0}, {0x3 , 0x5, 0x4, 0x0},
{0x3 , 0x3, 0x4, 0x40}, {0x3 , 0x5, 0x3, 0x80}
};
#else
// Use the full-step state table (emits a code at 00 only)
const char ttable[7][4] = {
{0x0, 0x2, 0x4, 0x0}, {0x3, 0x0, 0x1, 0x40},
{0x3, 0x2, 0x0, 0x0}, {0x3, 0x2, 0x1, 0x0},
{0x6, 0x0, 0x4, 0x0}, {0x6, 0x5, 0x0, 0x80},
{0x6, 0x5, 0x4, 0x0},
};
#endif
volatile char state = 0;
/* Call this once in setup(). */
void rotary_init() {
pinMode(encoderClockPin, INPUT);
pinMode(encoderDataPin, INPUT);
#ifdef ENABLE_PULLUPS
digitalWrite(encoderClockPin, HIGH);
digitalWrite(encoderDataPin, HIGH);
#endif
}
/* Read input pins and process for events. Call this either from a
* loop or an interrupt (eg pin change or timer).
*
* Returns 0 on no event, otherwise 0x80 or 0x40 depending on the direction.
*/
char rotary_process() {
char pinstate = (digitalRead(encoderDataPin) << 1) | digitalRead(encoderClockPin);
state = ttable[state & 0xf][pinstate];
return (state & 0xc0);
}
void setup() {
Serial.begin(9600);
rotary_init();
pinMode(2, OUTPUT);//LED
pinMode(3, OUTPUT);//LED
pinMode(4, OUTPUT);//LED
pinMode(5, OUTPUT);//LED
pinMode(10, INPUT);//encoder button input
//pinMode (encoderClockPin,INPUT_PULLUP);
//pinMode (encoderDataPin,INPUT_PULLUP);
//pinMode(9, INPUT);//encoder A input
//pinMode(8, INPUT);//encoder B input
}
bool started = false;
int priority = 0;
int tapX = 0;
int tapactual;
int tap_time;
int time_actual;
int input1X = 0;
float BPM = 60;
int max_BPM = 500; /******************************************** max BPM that you want */
int min_BPM = 60; /******************************************** min BPM that you want */
int max_time = ((1/(min_BPM/60)) * 1000);
int min_time = ((1/(max_BPM/60)) * 1000);
void loop() {
if (!started) {
cycle_on();
started = true;
}
timer.run();
//**************ENCODER BUTTON INPUT******************
if (digitalRead(10) == HIGH && tapX ==0){
tapX = millis ();
while (digitalRead (10) == HIGH){
delay(1);
}
}
if (digitalRead (10) == HIGH && tapX !=0 ){
tapactual = millis ();
tap_time = (tapactual - tapX);
if (tap_time > max_time){
tap_time = max_time;
}
if (tap_time < min_time){
tap_time = min_time;
}
tapX = tapactual;
priority = 1;
while (digitalRead (10) == HIGH){
delay(1);
}
}
time_actual = millis ();
if ((time_actual - tapX) > 4000){
tapX = 0;
}
//encoder*******************************************
char result = rotary_process();
if (result)
Serial.println(result == 0x40 ? "LEFT" : "RIGHT");
if (result == 0x40){
encoderCount = -1;
}else if (result == 0){
encoderCount = 0;
}
else{encoderCount = 1;}
BPM = BPM + encoderCount;
// Serial.print(" encoder count: ");
// Serial.println (encoderCount);
Serial.print(" BPM: ");
Serial.println(BPM);
}
void cycle_off() {
digitalWrite(2, LOW);
digitalWrite(3, LOW);
digitalWrite(4, LOW);
digitalWrite(5, LOW);
count++;
if (count == 8) {
count = 0;
}
}
void cycle_on() {
/* duty duration knob*****************************************************************/
int input2 = analogRead(A1);
float duration_percentage = map(input2, 0, 1023, 1, 90);
int cycletime = (60000/BPM); // works out millisecond gap between beats
float cycle_start = cycletime;
float cycle_stop = (cycletime * (duration_percentage/100)); // works out gate length based on percentage of millisecond gap between beats
Serial.print(" Duration percentage: ");
Serial.println(duration_percentage);
Serial.print(" Cycletime: ");
Serial.println(cycletime);
Serial.print(" Input2: ");
Serial.println(input2);
//**************************************************
switch (count) {
case 0:
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
digitalWrite(5, HIGH);
break;
case 1:
digitalWrite(2, HIGH);
break;
case 2:
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
break;
case 3:
digitalWrite(2, HIGH);
break;
case 4:
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
break;
case 5:
digitalWrite(2, HIGH);
break;
case 6:
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
break;
case 7:
digitalWrite(2, HIGH);
break;
}
if (priority == 1){
BPM = (60000 / tap_time);
}
timer.setTimeout(cycle_start, cycle_on);
timer.setTimeout(cycle_stop, cycle_off);
}
/*********************************************************************/
That’s it! Your clock should now be cycling through the LEDs and outputting clock signals through the jack sockets. You can change the tempo by tapping a beat on the encoder’s push action, you can also use the encoder’s rotation to slow down and speed up the tempo. You can adjust the duration of the signal using the potentiometer. The button will reset the Arduino, you won’t need this while your project is breadboarded but it will be useful if you decide to finalise the clock and put it in an enclosure.
Now that you now have the clock up and running you can dive deeper into the code and tweak it to your liking. You can start by changing the maximum and minimum BPM or the default BPM which is set to 60.
You could easily turn it into a Eurorack module by incorporating the appropriate power connections. Alternatively, you could make an enclosure for it and include battery power or simply power it through USB.
From a quick tap to smashing that love button and show how much you enjoyed this project.