2026-02-22 12:39:27 +01:00
|
|
|
#ifndef __BREAKOR_AUDIO_APP_HPP__
|
|
|
|
|
#define __BREAKOR_AUDIO_APP_HPP__
|
|
|
|
|
|
2026-04-13 17:55:39 +02:00
|
|
|
#include "../../src/memllib/audio/AudioAppBase.hpp"
|
2026-02-22 12:39:27 +01:00
|
|
|
#include "../../src/memllib/synth/maximilian.h"
|
2026-02-22 13:41:09 +01:00
|
|
|
#include "../../src/memllib/interface/MIDIInOut.hpp"
|
|
|
|
|
|
2026-02-22 12:39:27 +01:00
|
|
|
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
#include <cstdint>
|
2026-04-13 17:55:39 +02:00
|
|
|
#include <memory>
|
2026-02-22 12:39:27 +01:00
|
|
|
|
2026-04-13 17:55:39 +02:00
|
|
|
#include "../../src/memllib/interface/InterfaceBase.hpp"
|
2026-02-22 12:39:27 +01:00
|
|
|
|
2026-03-05 23:25:21 +01:00
|
|
|
|
2026-02-22 12:39:27 +01:00
|
|
|
#include <span>
|
|
|
|
|
|
|
|
|
|
#include "../../voicespaces/VoiceSpaces.hpp"
|
|
|
|
|
|
2026-03-07 16:14:39 +01:00
|
|
|
#include "RatioSeq.hpp"
|
2026-04-13 17:55:39 +02:00
|
|
|
#include "RatioSeqEngine.hpp"
|
2026-02-22 12:39:27 +01:00
|
|
|
|
|
|
|
|
|
2026-03-05 23:25:21 +01:00
|
|
|
template<size_t NPARAMS=56, size_t NSEQUENCES=8>
|
2026-02-22 12:39:27 +01:00
|
|
|
class BreakOrAudioApp : public AudioAppBase<NPARAMS>
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static constexpr size_t kN_Params = NPARAMS;
|
|
|
|
|
static constexpr size_t nVoiceSpaces=0;
|
|
|
|
|
|
2026-03-03 12:53:50 +01:00
|
|
|
queue_t bpmQueue;
|
|
|
|
|
queue_t sequencerControlQueue;
|
2026-03-05 23:25:21 +01:00
|
|
|
queue_t i2cOutQueue;
|
2026-03-06 11:04:30 +01:00
|
|
|
queue_t barPhaseResetQueue;
|
|
|
|
|
|
|
|
|
|
enum SequencerClockModes {
|
|
|
|
|
INTERNAL,
|
|
|
|
|
MIDI_CLOCK
|
|
|
|
|
} sequencerClockMode = INTERNAL;
|
2026-03-05 23:25:21 +01:00
|
|
|
|
2026-03-03 12:53:50 +01:00
|
|
|
bool sequencerPlaying = false;
|
|
|
|
|
|
2026-02-22 13:41:09 +01:00
|
|
|
std::shared_ptr<MIDIInOut> midiIO;
|
|
|
|
|
|
2026-04-13 17:55:39 +02:00
|
|
|
RatioSeqEngine<NSEQUENCES> seqEngine;
|
|
|
|
|
|
2026-02-22 12:39:27 +01:00
|
|
|
std::array<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces;
|
2026-04-13 17:55:39 +02:00
|
|
|
|
2026-02-22 12:39:27 +01:00
|
|
|
VoiceSpaceFn<NPARAMS> currentVoiceSpace;
|
|
|
|
|
|
|
|
|
|
std::array<String, nVoiceSpaces> getVoiceSpaceNames() {
|
|
|
|
|
std::array<String, nVoiceSpaces> names;
|
|
|
|
|
for(size_t i=0; i < voiceSpaces.size(); i++) {
|
|
|
|
|
names[i] = voiceSpaces[i].name;
|
|
|
|
|
}
|
|
|
|
|
return names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setVoiceSpace(size_t i) {
|
|
|
|
|
if (i < voiceSpaces.size()) {
|
|
|
|
|
currentVoiceSpace = voiceSpaces[i].mappingFunction;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BreakOrAudioApp() : AudioAppBase<NPARAMS>() {
|
2026-03-03 12:53:50 +01:00
|
|
|
queue_init(&bpmQueue, sizeof(float), 1);
|
|
|
|
|
queue_init(&sequencerControlQueue, sizeof(int), 1);
|
2026-03-05 23:25:21 +01:00
|
|
|
queue_init(&i2cOutQueue, sizeof(float) * 8, 1);
|
2026-03-06 11:04:30 +01:00
|
|
|
queue_init(&barPhaseResetQueue, sizeof(int), 1);
|
2026-02-22 12:39:27 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool __force_inline euclidean(float phase, const size_t n, const size_t k, const size_t offset, const float pulseWidth)
|
|
|
|
|
{
|
|
|
|
|
const float fi = phase * n;
|
|
|
|
|
int i = static_cast<int>(fi);
|
|
|
|
|
const float rem = fi - i;
|
|
|
|
|
if (i == n)
|
|
|
|
|
{
|
|
|
|
|
i--;
|
|
|
|
|
}
|
|
|
|
|
const int idx = ((i + n - offset) * k) % n;
|
|
|
|
|
return (idx < k && rem < pulseWidth) ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stereosample_t __force_inline Process(const stereosample_t x) override
|
|
|
|
|
{
|
2026-03-03 12:53:50 +01:00
|
|
|
if (sequencerPlaying) {
|
2026-03-06 11:04:30 +01:00
|
|
|
if (sequencerClockMode == INTERNAL){
|
2026-04-13 17:55:39 +02:00
|
|
|
seqEngine.midiClockPhasor += seqEngine.midiClockPhasorInc;
|
|
|
|
|
if (seqEngine.midiClockPhasor >= 1.f) {
|
|
|
|
|
seqEngine.midiClockPhasor -= 1.f;
|
2026-03-06 11:04:30 +01:00
|
|
|
midiIO->queueClock();
|
|
|
|
|
midiIO->flushQueue();
|
|
|
|
|
}
|
2026-03-03 12:53:50 +01:00
|
|
|
}
|
2026-04-13 17:55:39 +02:00
|
|
|
if (sequencerClockMode == MIDI_CLOCK) {
|
|
|
|
|
int phasorResetValue=0;
|
|
|
|
|
if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) {
|
|
|
|
|
seqEngine.resetBar();
|
2026-03-06 11:04:30 +01:00
|
|
|
}
|
2026-04-13 17:55:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (seqEngine.tick()) {
|
|
|
|
|
// Capture trigger state for I2C after tick
|
|
|
|
|
for (size_t i = 0; i < NSEQUENCES; i++) {
|
|
|
|
|
i2cValues[i] = seqEngine.states[i].lastTrig;
|
2026-02-22 22:03:50 +01:00
|
|
|
}
|
2026-03-03 12:53:50 +01:00
|
|
|
midiIO->flushQueue();
|
2026-03-05 23:25:21 +01:00
|
|
|
queue_try_add(&i2cOutQueue, &i2cValues);
|
2026-02-22 22:03:50 +01:00
|
|
|
}
|
2026-03-03 12:53:50 +01:00
|
|
|
}
|
2026-04-13 17:55:39 +02:00
|
|
|
|
2026-02-22 12:39:27 +01:00
|
|
|
stereosample_t ret { 0.f,0.f };
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 17:55:39 +02:00
|
|
|
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface, SequencerClockModes clockMode)
|
2026-02-22 12:39:27 +01:00
|
|
|
{
|
|
|
|
|
AudioAppBase<NPARAMS>::Setup(sample_rate, interface);
|
|
|
|
|
maxiSettings::sampleRate = sample_rate;
|
|
|
|
|
sampleRatef = static_cast<float>(sample_rate);
|
2026-03-06 11:04:30 +01:00
|
|
|
sequencerClockMode = clockMode;
|
2026-04-13 17:55:39 +02:00
|
|
|
|
|
|
|
|
const size_t midiNotes[NSEQUENCES] = {36,37,38,39,40, 42,43,45};
|
|
|
|
|
for (size_t i = 0; i < NSEQUENCES; i++) {
|
|
|
|
|
seqEngine.states[i].midiNote = midiNotes[i];
|
2026-02-22 22:03:50 +01:00
|
|
|
}
|
2026-04-13 17:55:39 +02:00
|
|
|
|
|
|
|
|
seqEngine.setup(sample_rate);
|
|
|
|
|
seqEngine.updateBPM(90.f);
|
|
|
|
|
|
|
|
|
|
seqEngine.onNoteOn = [this](size_t seqIdx, int velocity) {
|
|
|
|
|
midiIO->queueNoteOn(seqEngine.states[seqIdx].midiNote, velocity);
|
|
|
|
|
};
|
|
|
|
|
seqEngine.onNoteOff = [this](size_t seqIdx) {
|
|
|
|
|
midiIO->queueNoteOff(seqEngine.states[seqIdx].midiNote, 0);
|
|
|
|
|
};
|
2026-02-22 12:39:27 +01:00
|
|
|
}
|
2026-02-22 22:03:50 +01:00
|
|
|
|
2026-02-22 13:41:09 +01:00
|
|
|
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
|
|
|
|
|
midiIO = new_midi_interf;
|
|
|
|
|
}
|
2026-02-22 12:39:27 +01:00
|
|
|
|
|
|
|
|
void loop() override {
|
|
|
|
|
AudioAppBase<NPARAMS>::loop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProcessParams(const std::array<float, NPARAMS>& params)
|
2026-04-13 17:55:39 +02:00
|
|
|
{
|
2026-02-22 12:39:27 +01:00
|
|
|
firstParamsReceived = true;
|
2026-04-13 17:55:39 +02:00
|
|
|
float newBPM;
|
|
|
|
|
if (queue_try_remove(&bpmQueue, &newBPM)) {
|
|
|
|
|
seqEngine.updateBPM(newBPM);
|
2026-03-03 12:53:50 +01:00
|
|
|
}
|
|
|
|
|
int sequencerControl;
|
|
|
|
|
if (queue_try_remove(&sequencerControlQueue, &sequencerControl)) {
|
|
|
|
|
sequencerPlaying = sequencerControl == 1;
|
2026-04-13 17:55:39 +02:00
|
|
|
seqEngine.setPlaying(sequencerPlaying);
|
2026-03-03 12:53:50 +01:00
|
|
|
if (!sequencerPlaying) {
|
|
|
|
|
midiIO->queueClockStop();
|
|
|
|
|
midiIO->flushQueue();
|
2026-04-13 17:55:39 +02:00
|
|
|
} else {
|
2026-03-03 12:53:50 +01:00
|
|
|
midiIO->queueClockStart();
|
|
|
|
|
midiIO->flushQueue();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-22 22:03:50 +01:00
|
|
|
|
2026-04-13 17:55:39 +02:00
|
|
|
seqEngine.updateParams(params, 0);
|
2026-02-22 22:03:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setTimeSignature(float beats, float division) {
|
2026-04-13 17:55:39 +02:00
|
|
|
seqEngine.setTimeSignature(beats, division);
|
2026-02-22 12:39:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
|
2026-03-05 23:25:21 +01:00
|
|
|
float i2cValues[8] = {0,0,0,0,0,0,0,0};
|
|
|
|
|
|
2026-02-22 12:39:27 +01:00
|
|
|
float sampleRatef;
|
|
|
|
|
bool firstParamsReceived = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif // __BREAKOR_AUDIO_APP_HPP__
|