#ifndef __BREAKOR_AUDIO_APP_HPP__ #define __BREAKOR_AUDIO_APP_HPP__ #include "../../src/memllib/audio/AudioAppBase.hpp" #include "../../src/memllib/synth/maximilian.h" #include "../../src/memllib/interface/MIDIInOut.hpp" #include #include #include // Added for std::shared_ptr #include "../../src/memllib/interface/InterfaceBase.hpp" #include #include "../../voicespaces/VoiceSpaces.hpp" struct ratioSeqState { std::array ratios{1.f}; float phasor=0.f; float phasorInc=0.f; float phaseOffset = 0.f; bool lastTrig = false; float phasorMul = 1.f; float ratioSum=1.f; int midiNote = 36; float pulseWidth = 0.5f; std::array ampRatios{1.f}; float ampRatioSum=1.f; }; template inline bool __not_in_flash_func(ratioSeq)(float phasor, float phaseOffset, float ratioSum, const std::array &ratios, float pulseWidth) { bool trig = 0; float offsetPhase = phaseOffset + phasor; if (offsetPhase >= 1.f) { offsetPhase -= 1.f; } float phaseAdj = ratioSum * offsetPhase; float accumulatedSum = 0; float lastAccumulatedSum = 0; for (size_t v : ratios) { accumulatedSum += v; if (phaseAdj <= accumulatedSum) { // check pulse width float beatPhase = (phaseAdj - lastAccumulatedSum) / (accumulatedSum - lastAccumulatedSum); trig = beatPhase <= pulseWidth; break; } lastAccumulatedSum = accumulatedSum; } return trig; } template class BreakOrAudioApp : public AudioAppBase { public: static constexpr size_t kN_Params = NPARAMS; static constexpr size_t nVoiceSpaces=0; queue_t bpmQueue; queue_t sequencerControlQueue; bool sequencerPlaying = false; std::shared_ptr midiIO; std::array, nVoiceSpaces> voiceSpaces; VoiceSpaceFn currentVoiceSpace; std::array getVoiceSpaceNames() { std::array 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() { // currentVoiceSpace = voiceSpaces[0].mappingFunction; queue_init(&bpmQueue, sizeof(float), 1); queue_init(&sequencerControlQueue, sizeof(int), 1); }; bool __force_inline euclidean(float phase, const size_t n, const size_t k, const size_t offset, const float pulseWidth) { // Euclidean function const float fi = phase * n; int i = static_cast(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 { if (sequencerPlaying) { midiClockPhasor += midiClockPhasorInc; if (midiClockPhasor >= 1.f) { midiClockPhasor -= 1.f; midiIO->queueClock(); midiIO->flushQueue(); } if (sequencingSampleCounter==0) { for(auto &seq: ratioSeqStates) { //update phasor seq.phasor += (seq.phasorInc * seq.phasorMul); if (seq.phasor >= 1.f) { seq.phasor -= 1.f; } bool trig = ratioSeq<3>(seq.phasor, seq.phaseOffset, seq.ratioSum, seq.ratios, seq.pulseWidth); bool highAmp = ratioSeq<2>(seq.phasor, seq.phaseOffset, seq.ampRatioSum, seq.ampRatios, 0.5f); if (trig && !seq.lastTrig) { if (trig) { midiIO->queueNoteOn(seq.midiNote, highAmp ? 127 : 64); }else{ midiIO->queueNoteOff(seq.midiNote, 0); } } seq.lastTrig = trig; } midiIO->flushQueue(); } sequencingSampleCounter ++; if (sequencingSampleCounter >= sequencingSampleDiv) { sequencingSampleCounter = 0; } } stereosample_t ret { 0.f,0.f }; return ret; } void Setup(float sample_rate, std::shared_ptr interface) override { AudioAppBase::Setup(sample_rate, interface); maxiSettings::sampleRate = sample_rate; sampleRatef = static_cast(sample_rate); updateBPM(90.f); const size_t midiNotes[NSEQUENCES] = {36,37,38,39,40, 42,43,45,47,48}; size_t midiNote = 0; for(auto &seq: ratioSeqStates) { seq.midiNote = midiNotes[midiNote++]; } } void setupMIDI(std::shared_ptr new_midi_interf) { midiIO = new_midi_interf; } void loop() override { AudioAppBase::loop(); } void ProcessParams(const std::array& params) { firstParamsReceived = true; if (queue_try_remove(&bpmQueue, &bpm)) { bpm = 30.f + (bpm * 200.f); // Scale BPM from [0,1] to [30,230] updateBPM(bpm); } int sequencerControl; if (queue_try_remove(&sequencerControlQueue, &sequencerControl)) { sequencerPlaying = sequencerControl == 1; if (!sequencerPlaying) { midiIO->queueClockStop(); // Send note offs for all sequences when stopping for(auto &seq: ratioSeqStates) { midiIO->queueNoteOff(seq.midiNote, 0); seq.phasor = 0.f; // Reset phasor to start when stopping } midiIO->flushQueue(); midiClockPhasor = 0.f; // Reset MIDI clock phasor when stopping sequencingSampleCounter = 0; // Reset sequencing sample counter }else{ midiIO->queueClockStart(); midiIO->flushQueue(); } } // currentVoiceSpace(params); size_t paramIdx = 0; for(auto &v: ratioSeqStates) { float sum=0.f; for(size_t i=0; i < v.ratios.size(); i++) { v.ratios[i] = (float)(int)(params[paramIdx++] * 3.f) + 1.f; sum += v.ratios[i]; } v.ratioSum = sum; // static float muls[7] = {0.25f, 0.33f, 0.5f, 1.f, 1.5f, 2.f, 3.f}; // v.phasorMul = muls[(int)(params[paramIdx++] * 6.999999f)]; static float muls[4] = {0.5f, 1.f, 2.f, 4.f}; v.phasorMul = muls[(int)(params[paramIdx++] * 3.999999f)]; v.phaseOffset = ((int)(params[paramIdx++] * timeSigBeats)) * timeSigBeatsInv; sum=0.f; for(size_t i=0; i < v.ampRatios.size(); i++) { v.ampRatios[i] = (float)(int)(params[paramIdx++] * 3.f) + 1.f; sum += v.ampRatios[i]; } v.ampRatioSum = sum; } // Serial.printf("pm: %f", ratioSeqStates[0].phasorMul); } void updateBPM(float newBPM) { bpm = newBPM; float beatLengthInSeconds = 60.f / bpm; float barLengthInSeconds = beatLengthInSeconds * timeSigBeats; float barLengthInSamples = barLengthInSeconds * (sampleRatef/ sequencingSampleDiv); float barPhasorInc = 1.f/ barLengthInSamples; for(auto &v: ratioSeqStates) { v.phasorInc = barPhasorInc; } float midiClockLengthInSeconds = beatLengthInSeconds / 24.f; float midiClockLengthInSamples = midiClockLengthInSeconds * sampleRatef; midiClockPhasorInc = 1.f / midiClockLengthInSamples; } void setTimeSignature(float beats, float division) { timeSigBeats = beats; timeSigDivision = division; updateBPM(bpm); // Recalculate phasor increment with new time signature } protected: float sampleRatef; bool firstParamsReceived = false; float bpm=90.f; float timeSigBeats=4.f; float timeSigBeatsInv=1.f/timeSigBeats; float timeSigDivision=4.f; size_t sequencingSampleDiv = 50; size_t sequencingSampleCounter = 0; std::array ratioSeqStates; float midiClockPhasor=0; float midiClockPhasorInc=0; }; #endif // __BREAKOR_AUDIO_APP_HPP__