I’ve been having some issues with late and missing MIDI events and tracked it down to slow QSPI storage saves.
As I understand it both the MIDI handling and the PersistantStorage
saves should be in the main loop, but while the storage is saving it’s blocking the MIDI listen so that events are either missed or delayed.
I created a simple reproduction that saves every two seconds and you can really clearly hear the long or missed notes (all the notes should have been about the same length and rhythm):
Am I doing something wrong? How are others handling MIDI so they don’t have this issue?
Here’s my reproduction, using a Patch Submodule:
#include "daisy_patch_sm.h"
#include "daisysp.h"
using namespace daisy;
using namespace patch_sm;
using namespace daisysp;
struct Settings
{
uint32_t something;
bool operator==(const Settings &rhs) { return something == rhs.something; }
bool operator!=(const Settings &rhs) { return !operator==(rhs); }
};
DaisyPatchSM hw;
Adsr env;
MidiUartHandler midi;
Oscillator osc;
PersistentStorage<Settings> storage(hw.qspi);
uint32_t last_save;
bool gate;
void AudioCallback(AudioHandle::InputBuffer in,
AudioHandle::OutputBuffer out,
size_t size)
{
float sig;
for (size_t i = 0; i < size; i++)
{
sig = osc.Process() * env.Process(gate);
OUT_L[i] = sig;
OUT_R[i] = sig;
}
}
// Very simple MIDI event handling.
void HandleMidiMessage(MidiEvent m)
{
switch (m.type)
{
case NoteOn:
{
NoteOnEvent p = m.AsNoteOn();
if (m.data[1] != 0)
{
p = m.AsNoteOn();
osc.SetFreq(mtof(p.note));
osc.SetAmp((p.velocity / 127.0f));
gate = true;
}
}
break;
case NoteOff:
{
gate = false;
}
break;
default:
break;
}
}
int main(void)
{
float samplerate;
hw.Init();
samplerate = hw.AudioSampleRate();
storage.Init({0});
// Synthesis
osc.Init(samplerate);
osc.SetWaveform(Oscillator::WAVE_POLYBLEP_SAW);
env.Init(samplerate);
env.SetSustainLevel(0.5f);
env.SetAttackTime(0.005f);
env.SetDecayTime(0.005f);
env.SetReleaseTime(0.2f);
// I think this MIDI setup would not be not needed if trying this on Daisy
// hardware with inbuilt MIDI.
MidiUartHandler::Config midi_config;
midi_config.transport_config.periph = UartHandler::Config::Peripheral::USART_1;
midi_config.transport_config.rx = hw.A9;
midi_config.transport_config.tx = hw.A8;
midi.Init(midi_config);
midi.StartReceive();
hw.StartAudio(AudioCallback);
while (1)
{
midi.Listen();
while (midi.HasEvents())
{
HandleMidiMessage(midi.PopEvent());
}
Settings &settings = storage.GetSettings();
// Change the storage so that it will actually save:
settings.something = System::GetNow();
// Throttle the saves to once every 2 seconds:
if (System::GetNow() - last_save >= 2000)
{
storage.Save();
last_save = System::GetNow();
}
}
}