Consistent clock behavior for ext cv and midi.

This commit is contained in:
2025-06-02 21:56:17 -07:00
parent 8cf64fefca
commit 01bf09d4f5
4 changed files with 79 additions and 51 deletions

58
clock.h
View File

@ -24,7 +24,10 @@
#define MIDI_CONTINUE 0xFB
const int DEFAULT_TEMPO = 120;
static bool MIDI_ENABLED = false;
typedef void (*ExtCallback)(void);
static ExtCallback extUserCallback = nullptr;
static void serialEventNoop(uint8_t msg, uint8_t status) {}
enum Source {
SOURCE_INTERNAL,
@ -38,12 +41,12 @@ class Clock {
public:
void Init() {
NeoSerial.begin(31250);
NeoSerial.attachInterrupt(onSerialEvent);
// Static pin definition for pulse out.
pinMode(PULSE_OUT_PIN, OUTPUT);
// Initialize the clock library
uClock.setExtIntervalBuffer(32);
uClock.init();
uClock.setClockMode(uClock.INTERNAL_CLOCK);
uClock.setOutputPPQN(uClock.PPQN_96);
@ -52,14 +55,15 @@ class Clock {
// MIDI events.
uClock.setOnClockStart(sendMIDIStart);
uClock.setOnClockStop(sendMIDIStop);
uClock.setOnSync24(sendMidiClock);
uClock.setOnSync24(sendMIDIClock);
uClock.setOnSync48(sendPulseOut);
uClock.start();
}
// Handler for receiving clock trigger(PPQN_4 or PPQN_24).
void AttachExtHandler(void (*callback)(void)) {
// Handle external clock tick and call user callback when receiving clock trigger (PPQN_4, PPQN_24, or MIDI).
void AttachExtHandler(void (*callback)()) {
extUserCallback = callback;
attachInterrupt(digitalPinToInterrupt(EXT_PIN), callback, RISING);
}
@ -70,8 +74,13 @@ class Clock {
// Set the source of the clock mode.
void SetSource(Source source) {
bool was_playing = !IsPaused();
uClock.stop();
MIDI_ENABLED = false;
// If source is currently MIDI, disable the serial interrupt handler.
if (source_ == SOURCE_EXTERNAL_MIDI) {
NeoSerial.attachInterrupt(serialEventNoop);
}
source_ = source;
switch (source) {
case SOURCE_INTERNAL:
uClock.setClockMode(uClock.INTERNAL_CLOCK);
@ -87,52 +96,64 @@ class Clock {
case SOURCE_EXTERNAL_MIDI:
uClock.setClockMode(uClock.EXTERNAL_CLOCK);
uClock.setInputPPQN(uClock.PPQN_24);
MIDI_ENABLED = true;
NeoSerial.attachInterrupt(onSerialEvent);
break;
}
uClock.start();
if (was_playing) {
uClock.start();
}
}
// Return true if the current selected source is externl (PPQN_4, PPQN_24, or MIDI).
bool ExternalSource() {
return uClock.getClockMode() == uClock.EXTERNAL_CLOCK;
}
// Return true if the current selected source is the internal master clock.
bool InternalSource() {
return uClock.getClockMode() == uClock.INTERNAL_CLOCK;
}
// Returns the current BPM tempo.
int Tempo() {
return uClock.getTempo();
}
// Set the clock tempo to a int between 1 and 400.
void SetTempo(int tempo) {
return uClock.setTempo(tempo);
}
// Record an external clock tick received to process external/internal syncronization.
void Tick() {
uClock.clockMe();
}
void Pause() {
uClock.pause();
}
void Reset() {
// Start the internal clock.
void Start() {
uClock.start();
}
// Stop internal clock clock.
void Stop() {
uClock.stop();
}
// Returns true if the clock is not running.
bool IsPaused() {
return uClock.clock_state == uClock.PAUSED;
}
private:
Source source_ = SOURCE_INTERNAL;
static void onSerialEvent(uint8_t msg, uint8_t status) {
// if (!MIDI_ENABLED) {
// return;
// }
// Note: uClock start and stop will echo to MIDI.
switch (msg) {
case MIDI_CLOCK:
uClock.clockMe();
if (extUserCallback) {
extUserCallback();
}
break;
case MIDI_STOP:
uClock.stop();
@ -141,7 +162,6 @@ class Clock {
case MIDI_CONTINUE:
uClock.start();
break;
}
}
@ -153,7 +173,7 @@ class Clock {
NeoSerial.write(MIDI_STOP);
}
static void sendMidiClock(uint32_t tick) {
static void sendMIDIClock(uint32_t tick) {
NeoSerial.write(MIDI_CLOCK);
}

View File

@ -13,7 +13,7 @@
*
* BTN1: Play/pause the internal clock.
*
* BTN2: Reset all clocks.
* BTN2: Stop all clocks.
*
*/
@ -68,8 +68,8 @@ void setup() {
gravity.Init();
// Clock handlers.
gravity.clock.AttachExtHandler(ExtClock);
gravity.clock.AttachIntHandler(IntClock);
gravity.clock.AttachIntHandler(HandleIntClockTick);
gravity.clock.AttachExtHandler(HandleExtClockTick);
// Encoder rotate and press handlers.
gravity.encoder.AttachPressHandler(HandleEncoderPressed);
@ -95,16 +95,9 @@ void loop() {
// Firmware handlers.
//
void ExtClock() {
if (gravity.clock.ExternalSource()) {
gravity.clock.Tick();
app.refresh_screen = true;
}
}
void IntClock(uint32_t tick) {
void HandleIntClockTick(uint32_t tick) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
const auto& channel = app.channel[i];
auto& channel = app.channel[i];
auto& output = gravity.outputs[i];
const uint32_t mod_pulses = clock_mod_pulses[channel.clock_mod_index];
@ -126,18 +119,27 @@ void IntClock(uint32_t tick) {
}
}
void HandlePlayPressed() {
gravity.clock.Pause();
if (gravity.clock.IsPaused()) {
for (int i = 0; i < OUTPUT_COUNT; i++) {
gravity.outputs[i].Low();
}
void HandleExtClockTick() {
// Ignore tick if not using external source.
if (!gravity.clock.ExternalSource()) {
return;
}
gravity.clock.Tick();
app.refresh_screen = true;
}
void HandlePlayPressed() {
gravity.clock.IsPaused()
? gravity.clock.Start()
: gravity.clock.Stop();
ResetOutputs();
app.refresh_screen = true;
}
void HandleShiftPressed() {
gravity.clock.Reset();
gravity.clock.Stop();
ResetOutputs();
app.refresh_screen = true;
}
void HandleEncoderPressed() {
@ -228,6 +230,12 @@ Channel& GetSelectedChannel() {
return app.channel[app.selected_channel - 1];
}
void ResetOutputs() {
for (int i = 0; i < OUTPUT_COUNT; i++) {
gravity.outputs[i].Low();
}
}
//
// UI Display functions.
//
@ -352,7 +360,7 @@ void DisplayChannelPage() {
gravity.display.setFontMode(1);
gravity.display.setDrawColor(2);
gravity.display.setFont(LARGE_FONT);
int textWidth;
int textY = 26;
int subTextY = 42;

View File

@ -12,17 +12,17 @@
#include "gravity.h"
void Gravity::Init() {
InitClock();
InitInputs();
InitOutputs();
InitDisplay();
initClock();
initInputs();
initOutputs();
initDisplay();
}
void Gravity::InitClock() {
void Gravity::initClock() {
clock.Init();
}
void Gravity::InitInputs() {
void Gravity::initInputs() {
shift_button.Init(SHIFT_BTN_PIN);
play_button.Init(PLAY_BTN_PIN);
@ -40,7 +40,7 @@ void Gravity::InitInputs() {
PCMSK1 |= B00001000;
}
void Gravity::InitOutputs() {
void Gravity::initOutputs() {
// Initialize each of the outputs with it's GPIO pins and probability.
outputs[0].Init(OUT_CH1);
outputs[1].Init(OUT_CH2);
@ -49,7 +49,7 @@ void Gravity::InitOutputs() {
outputs[4].Init(OUT_CH5);
outputs[5].Init(OUT_CH6);
}
void Gravity::InitDisplay() {
void Gravity::initDisplay() {
// OLED Display configuration.
display.begin();
}

View File

@ -37,10 +37,10 @@ class Gravity {
AnalogInput cv2;
private:
void InitClock();
void InitDisplay();
void InitInputs();
void InitOutputs();
void initClock();
void initDisplay();
void initInputs();
void initOutputs();
};
extern Gravity gravity;