Simple MIDI Clock Example for pod

This is inspired by the Simple Tap Tempo Example.

Instead of tapping a switch, it uses MIDI Clock to set the LFO’s frequency. Acts as a basic MIDI-controlled tremolo effect. Has been tested with Arturia KeyStep.

#include "daisy_pod.h"
#include "daisysp.h"

using namespace daisy;
using namespace daisysp;

DaisyPod   hw;
Oscillator lfo;

// BPM range: 30 - 240
#define TTEMPO_MIN 30
#define TTEMPO_MAX 240

static uint32_t prev_ms = 0;
static float    lfo_out;
static uint16_t tt_count = 0;

// Prototypes
float    bpm_to_freq(uint32_t tempo);
uint32_t ms_to_bpm(uint32_t ms);
void HandleSystemRealTime(uint8_t srt_type);
void     AudioCallback(AudioHandle::InputBuffer  in, AudioHandle::OutputBuffer out, size_t size);

float bpm_to_freq(uint32_t tempo)
{
    return tempo / 60.0f;
}

uint32_t ms_to_bpm(uint32_t ms)
{
    return 60000 / ms;
}

void AudioCallback(AudioHandle::InputBuffer  in, AudioHandle::OutputBuffer out, size_t size)
{
    for(size_t i = 0; i < size; i++)
    {
        lfo_out   = lfo.Process();
        out[0][i] = lfo_out * in[0][i];
        out[1][i] = lfo_out * in[1][i];
    }
}

void HandleSystemRealTime(uint8_t srt_type)
{
    switch(srt_type)
    {
        // 0xFA - start
        case Start: break;

        // 0xFC - stop
        case Stop: break;

#ifdef DEBUG
        default:
            hw.seed.PrintLine("MIDI SystemRealTime: %x", m.srt_type);
            break;
#endif

        // MIDI Clock -  24 clicks per quarter note
        case TimingClock:
            tt_count++;
            if(tt_count == 24)
            {
                uint32_t ms   = System::GetNow();
                uint32_t diff = ms - prev_ms;
                uint32_t bpm  = ms_to_bpm(diff);
#ifdef DEBUG
                hw.seed.PrintLine("msec=%d, diff=%d, BPM=%d", ms, diff, bpm);
#endif
                if(bpm >= TTEMPO_MIN && bpm <= TTEMPO_MAX)
                {
                    lfo.SetFreq(bpm_to_freq(bpm));
                }

                prev_ms = ms;
                tt_count = 0;
            }
            break;
    }
}

int main(void)
{
    hw.Init();
    hw.SetAudioBlockSize(4); // number of samples handled per callback
    hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);

    // LFO Settings
    lfo.Init(hw.AudioSampleRate());
    lfo.SetWaveform(lfo.WAVE_SIN);
    lfo.SetFreq(1.0f);
    lfo.SetAmp(1.0f);
#ifdef DEBUG
    hw.seed.StartLog(false);
    System::Delay(250);
#endif
    hw.StartAdc();
    hw.StartAudio(AudioCallback);
    hw.midi.StartReceive();

    while(true)
    {
        hw.midi.Listen();
        while(hw.midi.HasEvents())
        {
            MidiEvent m = hw.midi.PopEvent();
            if(m.type == SystemRealTime)
            {
                HandleSystemRealTime(m.srt_type);
            }
        }
    }
}
5 Likes

Nice!! This will be super useful and fun.
Thank you for sharing!

thank you so much for posting this

Any time. BTW, it is better to use usec calls for better precision:

  uint32_t now = System::GetUs();
  uint32_t diff = now - prev_timestamp;
  uint32_t bpm = fus_to_bpm(diff) / 2;
 static uint32_t fus_to_bpm(uint32_t us)
  {
    float fus = static_cast<float>(us);
    float val = roundf(60000000.0f / fus);
    return static_cast<uint32_t>(val);
  }
1 Like