diff --git a/MEMLNaut-NISPS.ino b/MEMLNaut-NISPS.ino index 3b7c537..b50b9c0 100644 --- a/MEMLNaut-NISPS.ino +++ b/MEMLNaut-NISPS.ino @@ -1,3 +1,8 @@ +//machine config + +#define JOYSTICK_IS_4D false +#define MEMLNAUT_ANALOG_INPUTS 3 + (JOYSTICK_IS_4D ? 1 : 0) +#define MEMLNAUT_INPUT_MODE InterfaceRL::INPUT_MODES::JOYSTICK //hardware #include "src/memllib/utils/perf.hpp" @@ -23,12 +28,18 @@ #include "modes/MEMLNautModeChannelStrip.hpp" #include "modes/MEMLNautModeSoundAnalysisMIDI.hpp" #include "modes/MEMLNautModeXIASRI.hpp" +#include "modes/MEMLNautModeBreakOr.hpp" +#include "modes/MEMLNautModeVerbFX.hpp" +#include "modes/MEMLNautModeElysiamorfs.hpp" -//hook up the memlnaut mode +//hook up the memlnaut mode // #define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI // #define MEMLNAUT_MODE_TYPE MEMLNautModeXIASRI -#define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip +#define MEMLNAUT_MODE_TYPE MEMLNautModeVerbFX +// #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr +// #define MEMLNAUT_MODE_TYPE MEMLNautModeElysiamorfs +// #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip // #define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth MEMLNAUT_MODE_TYPE AUDIO_MEM MEMLNautModeHub; @@ -86,6 +97,9 @@ void setup() { uint32_t seed = get_rosc_entropy_seed(32); srand(seed); + midi_interf = std::make_shared(); + // Serial.println("MIDI setup complete."); + Serial.begin(115200); // while (!Serial) {} Serial.println("Serial initialised."); @@ -101,11 +115,6 @@ void setup() { WRITE_VOLATILE(interface_ready, true); Serial.println("Bound interface to MEMLNaut."); - midi_interf = std::make_shared(); - Serial.println("MIDI setup complete."); - if (midi_interf) { - currentMode->setupMIDI(midi_interf); - } WRITE_VOLATILE(core_0_ready, true); while (!READ_VOLATILE(core_1_ready)) { @@ -162,6 +171,8 @@ void loop() { digitalWrite(33, LOW); }, 100000) + + currentMode->loopCore0(); } @@ -200,6 +211,9 @@ void setup1() { delay(1); } + if (midi_interf) { + currentMode->setupMIDI(midi_interf); + } currentMode->setupAudio(AudioDriver::GetSampleRate()); @@ -226,5 +240,5 @@ void loop1() { PERIODIC_RUN_US( midi_interf->Poll(); - , 10000) + , 1000) } diff --git a/modes/AudioApps/BreakOrAudioApp.hpp b/modes/AudioApps/BreakOrAudioApp.hpp new file mode 100644 index 0000000..9d96a40 --- /dev/null +++ b/modes/AudioApps/BreakOrAudioApp.hpp @@ -0,0 +1,266 @@ +#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" + +#include "RatioSeq.hpp" + + +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; + queue_t i2cOutQueue; + queue_t barPhaseResetQueue; + + enum SequencerClockModes { + INTERNAL, + MIDI_CLOCK + } sequencerClockMode = INTERNAL; + + + 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); + queue_init(&i2cOutQueue, sizeof(float) * 8, 1); + queue_init(&barPhaseResetQueue, 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) { + if (sequencerClockMode == INTERNAL){ + midiClockPhasor += midiClockPhasorInc; + if (midiClockPhasor >= 1.f) { + midiClockPhasor -= 1.f; + midiIO->queueClock(); + midiIO->flushQueue(); + } + } + if (sequencingSampleCounter==0) { + + barPhasor += barPhasorInc; + if (barPhasor >= 1.f) { + barPhasor -= 1.f; + } + if (sequencerClockMode == MIDI_CLOCK) { + int phasorResetValue=0; + if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) { + barPhasor = 0.f; + } + } + int i2cIdx=0; + for(auto &seq: ratioSeqStates) { + //update phasor + float seqPhasor = barPhasor * seq.phasorMul; + seqPhasor = fmodf(seqPhasor + seq.phaseOffset, 1.f); // Wrap phasor to [0,1] + + bool trig = ratioSeq<3>(seqPhasor, seq.phaseOffset, seq.ratioSum, seq.ratios, seq.pulseWidth); + bool highAmp = ratioSeq<2>(seqPhasor, 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; + i2cValues[i2cIdx++] = trig; + } + midiIO->flushQueue(); + queue_try_add(&i2cOutQueue, &i2cValues); + } + + sequencingSampleCounter ++; + if (sequencingSampleCounter >= sequencingSampleDiv) { + sequencingSampleCounter = 0; + } + + + } + stereosample_t ret { 0.f,0.f }; + return ret; + } + + void Setup(float sample_rate, std::shared_ptr interface, SequencerClockModes clockMode) + { + AudioAppBase::Setup(sample_rate, interface); + maxiSettings::sampleRate = sample_rate; + sampleRatef = static_cast(sample_rate); + sequencerClockMode = clockMode; + 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) + { + // if (sequencerPlaying) { + // } + 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(); + } + Serial.printf("Sequencer %s\n", sequencerPlaying ? "Playing" : "Stopped"); + } + // 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] = {1.f, 2.f, 4.f, 8.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); + 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 i2cValues[8] = {0,0,0,0,0,0,0,0}; + + float sampleRatef; + bool firstParamsReceived = false; + + float bpm=90.f; + float timeSigBeats=4.f; + float timeSigBeatsInv=1.f/timeSigBeats; + float timeSigDivision=4.f; + + float barPhasorInc=0.f; + float barPhasor; + + size_t sequencingSampleDiv = 400; + size_t sequencingSampleCounter = 0; + + std::array ratioSeqStates; + + float midiClockPhasor=0; + float midiClockPhasorInc=0; + +}; + +#endif // __BREAKOR_AUDIO_APP_HPP__ + diff --git a/modes/AudioApps/ElysiamorfAudioApp.hpp b/modes/AudioApps/ElysiamorfAudioApp.hpp new file mode 100644 index 0000000..d43cbd4 --- /dev/null +++ b/modes/AudioApps/ElysiamorfAudioApp.hpp @@ -0,0 +1,324 @@ +#ifndef __ELYSIAMORF_AUDIO_APP_HPP__ +#define __ELYSIAMORF_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 FMOp { + float prevOutput = 0.f; + + // phase: external phasor 0-1 (e.g. barPhasor * phasorMul) + // modIn: signal from another operator [-1, 1], 0 = none + // freqMul: cycles per phase period (1 = once per bar, 2 = twice, 0.5 = half, etc.) + // modIndex: depth of modulation — scales how much modIn shifts the phase + // fbLevel: feedback 0-1 — feeds prevOutput back into own phase + float __force_inline process(float phase, float modIn, + float freqMul, float modIndex, + float fbLevel) { + float p = fmodf(phase * freqMul + modIndex * modIn + fbLevel * prevOutput, 1.f); + if (p < 0.f) p += 1.f; + float out = sinf(TWO_PI * p); + prevOutput = out; + return out; + } +}; + +// Convenience: 2-op FM pair matching the (phase, carrierFreq, modFreq, modIndex, fbLevel) signature. +// fbLevel sits on the modulator (DX7 style). carrier and modulator are caller-owned FMOp instances. +float __force_inline fmPair(float phase, + float carrierFreq, float modFreq, + float modIndex, float fbLevel, + FMOp& carrier, FMOp& modulator) { + float modOut = modulator.process(phase, 0.f, modFreq, 0.f, fbLevel); + return carrier.process(phase, modOut, carrierFreq, modIndex, 0.f); +} + +struct fmSeqState { + float phaseOffset = 0.f; + float phasorMul = 1.f; + float ratio=1.f; + float carrierFreq=1.f; + float modFreq = 2.f; + float modIndex = 2.f; + float fbLevel = 0.f; + FMOp carrier; + FMOp modulator; +}; + + +#include "RatioSeq.hpp" + +template +class ElysiamorfAudioApp : public AudioAppBase +{ +public: + static constexpr size_t kN_Params = NPARAMS; + static constexpr size_t nVoiceSpaces=0; + + queue_t bpmQueue; + queue_t sequencerControlQueue; + queue_t i2cOutQueue; + queue_t barPhaseResetQueue; + + enum SequencerClockModes { + INTERNAL, + MIDI_CLOCK + } sequencerClockMode = INTERNAL; + + + 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; + } + } + + ElysiamorfAudioApp() : AudioAppBase() { + // currentVoiceSpace = voiceSpaces[0].mappingFunction; + queue_init(&bpmQueue, sizeof(float), 1); + queue_init(&sequencerControlQueue, sizeof(int), 1); + queue_init(&i2cOutQueue, sizeof(float) * 8, 1); + queue_init(&barPhaseResetQueue, 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) { + if (sequencerClockMode == INTERNAL){ + midiClockPhasor += midiClockPhasorInc; + if (midiClockPhasor >= 1.f) { + midiClockPhasor -= 1.f; + midiIO->queueClock(); + midiIO->flushQueue(); + } + } + if (sequencingSampleCounter==0) { + + barPhasor += barPhasorInc; + if (barPhasor >= 1.f) { + barPhasor -= 1.f; + } + if (sequencerClockMode == MIDI_CLOCK) { + int phasorResetValue=0; + if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) { + barPhasor = 0.f; + } + } + // int i2cIdx=0; + int ccIndex=0; + static int ccNumbers[8] = {1,2,3,4,5,9,11,12}; + // for(auto &seq: ratioSeqStates) { + // //update phasor + // float seqPhasor = barPhasor * seq.phasorMul; + // seqPhasor = fmodf(seqPhasor + seq.phaseOffset, 1.f); // Wrap phasor to [0,1] + + // bool trig = ratioSeq<3>(seqPhasor, seq.phaseOffset, seq.ratioSum, seq.ratios, seq.pulseWidth); + // bool highAmp = ratioSeq<2>(seqPhasor, seq.phaseOffset, seq.ampRatioSum, seq.ampRatios, 0.5f); + // if (trig != seq.lastTrig) { + // midiIO->queueCC(ccNumbers[ccIndex++], trig ? highAmp ? 127 : 64 : 0); + // // Serial.printf("Seq %d - Phasor: %f, Trig: %d, HighAmp: %d\n", &seq - &ratioSeqStates[0], seqPhasor, trig, highAmp); + // } + // seq.lastTrig = trig; + // // i2cValues[i2cIdx++] = trig; + // } + for(auto &seq: fmSeqStates) { + //update phasor + float seqPhasor = barPhasor * seq.phasorMul; + seqPhasor = fmodf(seqPhasor + seq.phaseOffset, 1.f); // Wrap phasor to [0,1] + float fmVal = fmPair(seqPhasor, seq.carrierFreq, seq.modFreq, seq.modIndex, seq.fbLevel, seq.carrier, seq.modulator); + fmVal = (fmVal + 1.f) * 0.5f; // Normalize to [0,1] + fmVal *= 127.f; // Scale to MIDI range + // if (ccIndex==0) { + // Serial.printf("Seq %d - Phasor: %f, FM Val: %f\n", &seq - &fmSeqStates[0], seqPhasor, fmVal); + // } + midiIO->queueCC(ccNumbers[ccIndex++], static_cast(fmVal)); + } + midiIO->flushQueue(); + // queue_try_add(&i2cOutQueue, &i2cValues); + } + + sequencingSampleCounter ++; + if (sequencingSampleCounter >= sequencingSampleDiv) { + sequencingSampleCounter = 0; + } + + + } + stereosample_t ret { 0.f,0.f }; + return ret; + } + + void Setup(float sample_rate, std::shared_ptr interface, SequencerClockModes clockMode) + { + AudioAppBase::Setup(sample_rate, interface); + maxiSettings::sampleRate = sample_rate; + sampleRatef = static_cast(sample_rate); + sequencerClockMode = clockMode; + updateBPM(90.f); + } + + 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)) { + updateBPM(bpm); + // Serial.printf("BPM updated: %f\n", 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(); + } + Serial.printf("Sequencer %s\n", sequencerPlaying ? "Playing" : "Stopped"); + } + // 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] = {1.f, 2.f, 4.f, 8.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; + // } + size_t paramIdx = 0; + for(auto &v: fmSeqStates) { + v.carrierFreq = (0.25f + params[paramIdx++] * 0.75f) * 0.125f; // 1 to 10 + v.modFreq = (0.25f + params[paramIdx++] * 0.75f) * 0.25f; // 1 to 10 + v.modIndex = params[paramIdx++] * 4.f; // 0 to 10 + v.fbLevel = 0.f;//params[paramIdx++] ; // 0 to 1 + static float muls[4] = {1.f,2.f, 3.f,4.f}; + v.phasorMul = muls[(int)(params[paramIdx++]* 3.999f) ]; + v.phaseOffset = ((int)(params[paramIdx++] * timeSigBeats)) * timeSigBeatsInv; + } + // 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); + 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 i2cValues[8] = {0,0,0,0,0,0,0,0}; + + float sampleRatef; + bool firstParamsReceived = false; + + float bpm=90.f; + float timeSigBeats=4.f; + float timeSigBeatsInv=1.f/timeSigBeats; + float timeSigDivision=4.f; + + float barPhasorInc=0.f; + float barPhasor; + + size_t sequencingSampleDiv = 500; + size_t sequencingSampleCounter = 0; + + std::array ratioSeqStates; + std::array fmSeqStates; + + + float midiClockPhasor=0; + float midiClockPhasorInc=0; + +}; + +#endif // __ELYSIAMORF_AUDIO_APP_HPP__ + diff --git a/modes/AudioApps/RatioSeq.hpp b/modes/AudioApps/RatioSeq.hpp new file mode 100644 index 0000000..c522764 --- /dev/null +++ b/modes/AudioApps/RatioSeq.hpp @@ -0,0 +1,49 @@ +#ifndef __RATIO_SEQ_HPP__ +#define __RATIO_SEQ_HPP__ + +#include +#include + +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; +} + +#endif // __RATIO_SEQ_HPP__ diff --git a/modes/AudioApps/VerbFXAudioApp.hpp b/modes/AudioApps/VerbFXAudioApp.hpp new file mode 100644 index 0000000..eb4f0d2 --- /dev/null +++ b/modes/AudioApps/VerbFXAudioApp.hpp @@ -0,0 +1,173 @@ +#ifndef __VERBFX_AUDIO_APP_HPP__ +#define __VERBFX_AUDIO_APP_HPP__ + +#include "../../src/memllib/audio/AudioAppBase.hpp" // Added missing include + +#include +#include +#include + +//#include "src/memllib/interface/InterfaceBase.hpp" // Added missing include + +#include +#include "../../voicespaces/VoiceSpaces.hpp" +#include "../../src/memllib/synth/OnePoleSmoother.hpp" +#include "../../src/memllib/synth/maximilian.h" +#include "../../src/daisysp/Effects/pitchshifter.h" + + + + + +template +class VerbFXAudioApp : public AudioAppBase +{ +public: + static constexpr size_t kN_Params = NPARAMS; + static constexpr size_t nVoiceSpaces=1; + + + 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; + } + } + + VerbFXAudioApp() : AudioAppBase() { + }; + + + __attribute__((hot)) stereosample_t __force_inline Process(const stereosample_t x) override + { + float mix = x.L + x.R; + + smoother.Process(neuralNetOutputs.data(), smoothParams.data()); + + + // float allp1fb = smoothParams[4] * 0.99f; + // float allp2fb = smoothParams[5] * 0.99f; + // float allp3fb = smoothParams[14] * 0.99f; + + wetdry_mix_ = (smoothParams[0] * 0.9f) + 0.1f; + const float lp0fb = smoothParams[1] * 0.98f; + const float lp0cutoff = (smoothParams[2] * 0.5f) + 0.05f; + + const float lp1fb = smoothParams[3] * 0.98f; + const float lp1cutoff = (smoothParams[4] * 0.5f) + 0.05f; + + const float lp2fb = smoothParams[5] * 0.98f; + const float lp2cutoff = (smoothParams[6] * 0.5f) + 0.05f; + + const float lp3fb = smoothParams[7] * 0.98f; + const float lp3cutoff = (smoothParams[8] * 0.5f) + 0.05f; + + const float lp4fb = smoothParams[9] * 0.98f; + const float lp4cutoff = (smoothParams[10] * 0.5f) + 0.05f; + + const float lp5fb = smoothParams[11] * 0.98f; + const float lp5cutoff = (smoothParams[12] * 0.5f) + 0.05f; + + const float lp6fb = smoothParams[13] * 0.98f; + const float lp6cutoff = (smoothParams[14] * 0.5f) + 0.05f; + + const float lp7fb = smoothParams[15] * 0.98f; + const float lp7cutoff = (smoothParams[16] * 0.5f) + 0.05f; + + float lp0 = lpcomb0.lpcombfb(mix, SIZE_comb0, lp0fb, lp0cutoff); + float lp1 = lpcomb1.lpcombfb(mix, SIZE_comb1, lp1fb, lp1cutoff); + float lp2 = lpcomb2.lpcombfb(mix, SIZE_comb2, lp2fb, lp2cutoff); + float lp3 = lpcomb3.lpcombfb(mix, SIZE_comb3, lp3fb, lp3cutoff); + float lp4 = lpcomb4.lpcombfb(mix, SIZE_comb4, lp4fb, lp4cutoff); + float lp5 = lpcomb5.lpcombfb(mix, SIZE_comb5, lp5fb, lp5cutoff); + float lp6 = lpcomb6.lpcombfb(mix, SIZE_comb6, lp6fb, lp6cutoff); + float lp7 = lpcomb7.lpcombfb(mix, SIZE_comb7, lp7fb, lp7cutoff); + + float y = lp0 + lp1 + lp2 + lp3 + lp4 + lp5 + lp6 + lp7; + + + const float allp0fb = smoothParams[17] * 0.98f; + const float allp1fb = smoothParams[18] * 0.98f; + const float allp2fb = smoothParams[19] * 0.98f; + const float allp3fb = smoothParams[20] * 0.98f; + + y = allp0.allpass(y, SIZE_allp0, allp0fb); + y = allp1.allpass(y, SIZE_allp1, allp1fb); + y = allp2.allpass(y, SIZE_allp2, allp2fb); + y = allp3.allpass(y, SIZE_allp3, allp3fb); + + + // Mix dry + y = (y * wetdry_mix_) + (mix * (1.f - wetdry_mix_)); + + + stereosample_t ret { y, y }; + return ret; + } + + void Setup(float sample_rate, std::shared_ptr interface) override + { + AudioAppBase::Setup(sample_rate, interface); + maxiSettings::sampleRate = sample_rate; + } + + __attribute__((always_inline)) void ProcessParams(const std::array& params) + { + // currentVoiceSpace(params); + neuralNetOutputs = params; + } + + +protected: + + std::array neuralNetOutputs{0}, smoothParams{0}; + + // https://ccrma.stanford.edu/~jos/pasp/Freeverb.html + static constexpr size_t SIZE_allp0=244; + static constexpr size_t SIZE_allp1=605; + static constexpr size_t SIZE_allp2=479; + static constexpr size_t SIZE_allp3=371; + + maxiReverbFilters allp0; + maxiReverbFilters allp1; + maxiReverbFilters allp2; + maxiReverbFilters allp3; + + static constexpr size_t SIZE_comb0=1694; + static constexpr size_t SIZE_comb1=1759; + static constexpr size_t SIZE_comb2=1622; + static constexpr size_t SIZE_comb3=1547; + static constexpr size_t SIZE_comb4=1379; + static constexpr size_t SIZE_comb5=1464; + static constexpr size_t SIZE_comb6=1283; + static constexpr size_t SIZE_comb7=1205; + + maxiReverbFilters lpcomb0; + maxiReverbFilters lpcomb1; + maxiReverbFilters lpcomb2; + maxiReverbFilters lpcomb3; + maxiReverbFilters lpcomb4; + maxiReverbFilters lpcomb5; + maxiReverbFilters lpcomb6; + maxiReverbFilters lpcomb7; + + maxiDCBlocker dcb; + + float wetdry_mix_{0.5f}; + + OnePoleSmoother smoother{150.f, kSampleRate}; + +}; + +#endif diff --git a/modes/MEMLNautMode.hpp b/modes/MEMLNautMode.hpp index ad8c52a..5581259 100644 --- a/modes/MEMLNautMode.hpp +++ b/modes/MEMLNautMode.hpp @@ -23,5 +23,6 @@ concept MEMLNautMode = requires(T proc) { {proc.processAnalysisParams()} -> std::same_as; // {proc.getNMIDICtrlOutputs()} -> std::same_as; {proc.setupInterface()} -> std::same_as; + {proc.loopCore0()} -> std::same_as; requires std::same_as; }; diff --git a/modes/MEMLNautModeBreakOr.hpp b/modes/MEMLNautModeBreakOr.hpp new file mode 100644 index 0000000..32c6b4c --- /dev/null +++ b/modes/MEMLNautModeBreakOr.hpp @@ -0,0 +1,132 @@ +#ifndef MEMLNAUT_MODE_RHYTHM_BOX_HPP +#define MEMLNAUT_MODE_RHYTHM_BOX_HPP + +//Break|| + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp" +#include "./AudioApps/BreakOrAudioApp.hpp" +#include "../src/memllib/examples/InterfaceRL.hpp" +#include "../src/memllib/PicoDefs.hpp" +#include "MEMLNautMode.hpp" +#include +#include +#include "../src/memllib/interface/USeqI2C.hpp" + + +class MEMLNautModeBreakOr { +public: + constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate + + USeqI2C i2cOut; + inline static BreakOrAudioApp<> audioAppBreakOr; + std::array::nVoiceSpaces> voiceSpaceList; + + InterfaceRL interface; + std::shared_ptr interfacePtr; + + BreakOrAudioApp<>::SequencerClockModes clockMode = BreakOrAudioApp<>::MIDI_CLOCK; + + bool sequencerPlaying = false; + + void setupInterface() { + interface.setup(kN_InputParams, BreakOrAudioApp<>::kN_Params); + interface.bindInterface(InterfaceRL::INPUT_MODES::JOYSTICK, true); + interfacePtr = make_non_owning(interface); + + MEMLNaut::Instance()->setTogA2Callback([this](bool state) { // scr_ref no longer captured directly + Serial.println(state ? "TogA2 ON" : "TogA2 OFF"); + if (state) { + sequencerPlaying = !sequencerPlaying; + queue_try_add(&audioAppBreakOr.sequencerControlQueue, &sequencerPlaying); + } + + }); + + i2cOut.begin(); + } + + String getHelpTitle() { + return "Break||"; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppBreakOr.Process(x); + } + + void setupAudio(float sample_rate) { + audioAppBreakOr.Setup(sample_rate, interfacePtr, clockMode); + voiceSpaceList = audioAppBreakOr.getVoiceSpaceNames(); + audioAppBreakOr.setupMIDI(midi_interf); + MEMLNaut::Instance()->setRVGain1Callback([this](float value) { + if (clockMode == BreakOrAudioApp<>::INTERNAL) { + queue_try_add(&audioAppBreakOr.bpmQueue, &value); + } + }, 0); // Set threshold to 0 to trigger on any change + + } + + __force_inline void loop() { + audioAppBreakOr.loop(); + } + + std::shared_ptr midi_interf; + + void setupMIDI(std::shared_ptr new_midi_interf) { + midi_interf = new_midi_interf; + midi_interf->Setup(0); + midi_interf->SetMIDISendChannel(1); + midi_interf->SetMIDINoteChannel(10); + midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) { + Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon); + if (note_number==0) { + int resetTrigger=1; + queue_try_add(&audioAppBreakOr.barPhaseResetQueue, &resetTrigger); // value doesn't matter, just a trigger + Serial.println("Bar phase reset triggered by MIDI note 0"); + + } + }); + midi_interf->SetBPMCallback([this](float bpm) { + // Serial.printf("Estimated BPM: %.2f\n", bpm); + if (clockMode == BreakOrAudioApp<>::MIDI_CLOCK) { + queue_try_add(&audioAppBreakOr.bpmQueue, &bpm); + } + + }); + interface.bindMIDI(midi_interf); + } + + void addViews() { + // std::shared_ptr voiceSpaceSelectView; + // voiceSpaceSelectView = std::make_shared("Voice Spaces"); + + // MEMLNaut::Instance()->disp->InsertViewAfter(interface.rlStatsView, voiceSpaceSelectView); + // voiceSpaceSelectView->setOptions(voiceSpaceList); //set by core 1 on startup + // voiceSpaceSelectView->setNewVoiceCallback( + // [this](size_t idx) { + // audioAppPAFSynth.setVoiceSpace(idx); + // }); + + + }; + + inline void processAnalysisParams() {} + + void analyse(stereosample_t) {} + + float i2cValues[8]; + void loopCore0() { + PERIODIC_RUN_US( + if (queue_try_remove(&audioAppBreakOr.i2cOutQueue, &i2cValues)) { + // Serial.printf("i2c received: %f %f %f %f %f %f %f %f\n", i2cValues[0], i2cValues[1], i2cValues[2], i2cValues[3], i2cValues[4], i2cValues[5], i2cValues[6], i2cValues[7]); + bool i2cSuccess = i2cOut.sendValues(i2cValues, 8); + } + , 5000) + + } + +}; + + + +#endif // MEMLNAUT_MODE_RHYTHM_BOX_HPP \ No newline at end of file diff --git a/modes/MEMLNautModeChannelStrip.hpp b/modes/MEMLNautModeChannelStrip.hpp index da9caeb..593b2a1 100644 --- a/modes/MEMLNautModeChannelStrip.hpp +++ b/modes/MEMLNautModeChannelStrip.hpp @@ -26,17 +26,6 @@ public: String getHelpTitle() { return "Channel Strip Mode"; } - // size_t getNParams() { - // return ChannelStripAudioApp<>::kN_Params; - // } - - // void setVoiceSpace(size_t i) { - // audioAppChannelStrip.setVoiceSpace(i); - // } - - // std::span getVoiceSpaceList() { - // return voiceSpaceList; - // } __force_inline stereosample_t process(stereosample_t x) { return audioAppChannelStrip.Process(x); @@ -116,5 +105,6 @@ public: void analyse(stereosample_t) {} + void loopCore0() {} }; diff --git a/modes/MEMLNautModeElysiamorfs.hpp b/modes/MEMLNautModeElysiamorfs.hpp new file mode 100644 index 0000000..60b6a7c --- /dev/null +++ b/modes/MEMLNautModeElysiamorfs.hpp @@ -0,0 +1,121 @@ +#ifndef MEMLNAUT_MODE_ELYSIAMORFS_HPP +#define MEMLNAUT_MODE_ELYSIAMORFS_HPP + +//Elysiamorfs + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp" +#include "./AudioApps/ElysiamorfAudioApp.hpp" +#include "../src/memllib/examples/InterfaceRL.hpp" +#include "../src/memllib/PicoDefs.hpp" +#include "MEMLNautMode.hpp" +#include +#include +#include "../src/memllib/interface/USeqI2C.hpp" + + +class MEMLNautModeElysiamorfs { +public: + constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate + + USeqI2C i2cOut; + inline static ElysiamorfAudioApp<> audioAppElysiamorfs; + std::array::nVoiceSpaces> voiceSpaceList; + + InterfaceRL interface; + std::shared_ptr interfacePtr; + + ElysiamorfAudioApp<>::SequencerClockModes clockMode = ElysiamorfAudioApp<>::INTERNAL; + + bool sequencerPlaying = false; + + void setupInterface() { + interface.setup(kN_InputParams, ElysiamorfAudioApp<>::kN_Params); + interface.bindInterface(InterfaceRL::INPUT_MODES::JOYSTICK, true); + interfacePtr = make_non_owning(interface); + + MEMLNaut::Instance()->setTogA2Callback([this](bool state) { // scr_ref no longer captured directly + Serial.println(state ? "TogA2 ON" : "TogA2 OFF"); + if (state) { + sequencerPlaying = !sequencerPlaying; + queue_try_add(&audioAppElysiamorfs.sequencerControlQueue, &sequencerPlaying); + } + + }); + + i2cOut.begin(); + } + + String getHelpTitle() { + return "Elysiamorfs"; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppElysiamorfs.Process(x); + } + + void setupAudio(float sample_rate) { + audioAppElysiamorfs.Setup(sample_rate, interfacePtr, clockMode); + voiceSpaceList = audioAppElysiamorfs.getVoiceSpaceNames(); + audioAppElysiamorfs.setupMIDI(midi_interf); + MEMLNaut::Instance()->setRVGain1Callback([this](float value) { + if (clockMode == ElysiamorfAudioApp<>::INTERNAL) { + float bpm = 30.f + (value * 200.f); // Scale BPM from [0,1] to [30,230] + queue_try_add(&audioAppElysiamorfs.bpmQueue, &bpm); + } + }, 0); // Set threshold to 0 to trigger on any change + + } + + __force_inline void loop() { + audioAppElysiamorfs.loop(); + } + + std::shared_ptr midi_interf; + + void setupMIDI(std::shared_ptr new_midi_interf) { + midi_interf = new_midi_interf; + midi_interf->Setup(0); + // midi_interf->SetMIDISendChannel(1); + // midi_interf->SetMIDINoteChannel(10); + midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) { + // Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon); + if (note_number==0) { + int resetTrigger=1; + queue_try_add(&audioAppElysiamorfs.barPhaseResetQueue, &resetTrigger); // value doesn't matter, just a trigger + Serial.println("Bar phase reset triggered by MIDI note 0"); + + } + }); + midi_interf->SetBPMCallback([this](float bpm) { + if (clockMode == ElysiamorfAudioApp<>::MIDI_CLOCK) { + queue_try_add(&audioAppElysiamorfs.bpmQueue, &bpm); + } + + }); + interface.bindMIDI(midi_interf); + } + + void addViews() { + }; + + inline void processAnalysisParams() {} + + void analyse(stereosample_t) {} + + float i2cValues[8]; + void loopCore0() { + // PERIODIC_RUN_US( + // if (queue_try_remove(&audioAppElysiamorfs.i2cOutQueue, &i2cValues)) { + // // Serial.printf("i2c received: %f %f %f %f %f %f %f %f\n", i2cValues[0], i2cValues[1], i2cValues[2], i2cValues[3], i2cValues[4], i2cValues[5], i2cValues[6], i2cValues[7]); + // bool i2cSuccess = i2cOut.sendValues(i2cValues, 8); + // } + // , 5000) + + } + +}; + + + +#endif // MEMLNAUT_MODE_ELYSIAMORFS_HPP diff --git a/modes/MEMLNautModePAFSynth.hpp b/modes/MEMLNautModePAFSynth.hpp index c0f6606..3fd7152 100644 --- a/modes/MEMLNautModePAFSynth.hpp +++ b/modes/MEMLNautModePAFSynth.hpp @@ -12,7 +12,7 @@ class MEMLNautModePAFSynth { public: - constexpr static size_t kN_InputParams = 3; // joystick x, y, rotate + constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate inline static PAFSynthAudioApp<> audioAppPAFSynth; std::array::nVoiceSpaces> voiceSpaceList; @@ -22,24 +22,13 @@ public: void setupInterface() { interface.setup(kN_InputParams, PAFSynthAudioApp<>::kN_Params); - interface.bindInterface(InterfaceRL::INPUT_MODES::JOYSTICK); + interface.bindInterface(InterfaceRL::INPUT_MODES::JOYSTICK, true); interfacePtr = make_non_owning(interface); } String getHelpTitle() { return "PAF Synth Mode"; } - // size_t getNParams() { - // return PAFSynthAudioApp<>::kN_Params; - // } - - // void setVoiceSpace(size_t i) { - // audioAppPAFSynth.setVoiceSpace(i); - // } - - // std::span getVoiceSpaceList() { - // return voiceSpaceList; - // } __force_inline stereosample_t process(stereosample_t x) { return audioAppPAFSynth.Process(x); @@ -126,4 +115,6 @@ public: void analyse(stereosample_t) {} + void loopCore0() {} + }; diff --git a/modes/MEMLNautModeSoundAnalysisMIDI.hpp b/modes/MEMLNautModeSoundAnalysisMIDI.hpp index ea06092..58c846f 100644 --- a/modes/MEMLNautModeSoundAnalysisMIDI.hpp +++ b/modes/MEMLNautModeSoundAnalysisMIDI.hpp @@ -85,4 +85,6 @@ public: } + void loopCore0() {} + }; diff --git a/modes/MEMLNautModeVerbFX.hpp b/modes/MEMLNautModeVerbFX.hpp new file mode 100644 index 0000000..696687b --- /dev/null +++ b/modes/MEMLNautModeVerbFX.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "./AudioApps/VerbFXAudioApp.hpp" +#include "MEMLNautMode.hpp" +#include +#include +#include "../XiasriAnalysis.hpp" +#include "../src/memllib/utils/sharedMem.hpp" +#include "../src/memllib/audio/AudioDriver.hpp" +#include "../src/memllib/examples/InterfaceRL.hpp" +#include "../src/memllib/PicoDefs.hpp" + + + +class MEMLNautModeVerbFX { +public: + constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS; + InterfaceRL interface; + std::shared_ptr interfacePtr; + // XiasriAnalysis mlAnalysis{kSampleRate}; + // SharedBuffer machine_list_buffer; + + VerbFXAudioApp<> audioAppVerbFX; + std::shared_ptr midi_interf; + + void setupInterface() { + interface.setup(kN_InputParams, VerbFXAudioApp<>::kN_Params); + interface.bindInterface(MEMLNAUT_INPUT_MODE, JOYSTICK_IS_4D); + interfacePtr = make_non_owning(interface); + } + + String getHelpTitle() { + return "VerbFX Mode"; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppVerbFX.Process(x); + } + + void setupMIDI(std::shared_ptr new_midi_interf) { + midi_interf = new_midi_interf; + midi_interf->Setup(0); + midi_interf->SetMIDISendChannel(1); + interface.bindMIDI(midi_interf, true); + } + + void addViews() { + }; + + void setupAudio(float sample_rate) { + audioAppVerbFX.Setup(sample_rate, interfacePtr); + // Reinitialize XiasriAnalysis filters after maxiSettings is properly configured + // mlAnalysis.ReinitFilters(); + } + + __force_inline void loop() { + audioAppVerbFX.loop(); + } + + __force_inline void analyse(stereosample_t x) { + // union { + // XiasriAnalysis::parameters_t p; + // float v[XiasriAnalysis::kN_Params]; + // } param_u; + // param_u.p = mlAnalysis.Process(x.L + x.R); + // // Write params into shared_buffer + // machine_list_buffer.writeNonBlocking(param_u.v, XiasriAnalysis::kN_Params); + } + + __force_inline void processAnalysisParams() { + // // Read SharedBuffer + // std::vector mlist_params(XiasriAnalysis::kN_Params, 0); + // machine_list_buffer.readNonBlocking(mlist_params); + // // Send parameters to RL interface + // interface.readAnalysisParameters(mlist_params); + // PERIODIC_RUN( + // Serial.printf("%f %f %f\n", mlist_params[0], mlist_params[1], mlist_params[2]); + // , 100); + + } + + void loopCore0() {} + +}; diff --git a/modes/MEMLNautModeXIASRI.hpp b/modes/MEMLNautModeXIASRI.hpp index 403dda7..77a10f5 100644 --- a/modes/MEMLNautModeXIASRI.hpp +++ b/modes/MEMLNautModeXIASRI.hpp @@ -80,4 +80,6 @@ public: } + void loopCore0() {} + }; diff --git a/src/memllib b/src/memllib index 799130e..5d67d15 160000 --- a/src/memllib +++ b/src/memllib @@ -1 +1 @@ -Subproject commit 799130e740a392839587f24c3747b27e841957b1 +Subproject commit 5d67d157228572f458b2744296c3e9585289187d diff --git a/src/memlp b/src/memlp index 83f5a84..dd1e1f3 160000 --- a/src/memlp +++ b/src/memlp @@ -1 +1 @@ -Subproject commit 83f5a84089d4a61604c6c429738c3f68f67bd103 +Subproject commit dd1e1f36eac66f95f14daad48ad9def2e4c5f3ec