From 3ddbcdfed96f89038257be2bee9c034698a14bcd Mon Sep 17 00:00:00 2001 From: chriskiefer Date: Tue, 3 Mar 2026 11:53:50 +0000 Subject: [PATCH] break|| midi clock and seq control --- MEMLNaut-NISPS.ino | 2 + modes/AudioApps/BreakOrAudioApp.hpp | 123 ++++++++++++++------ modes/AudioApps/VerbFXAudioApp.hpp | 169 ++++++++++++++++++++++++++++ modes/MEMLNautModeBreakOr.hpp | 31 +++++ modes/MEMLNautModeVerbFX.hpp | 83 ++++++++++++++ 5 files changed, 376 insertions(+), 32 deletions(-) create mode 100644 modes/AudioApps/VerbFXAudioApp.hpp create mode 100644 modes/MEMLNautModeVerbFX.hpp diff --git a/MEMLNaut-NISPS.ino b/MEMLNaut-NISPS.ino index 93105d7..c6a955f 100644 --- a/MEMLNaut-NISPS.ino +++ b/MEMLNaut-NISPS.ino @@ -24,11 +24,13 @@ #include "modes/MEMLNautModeSoundAnalysisMIDI.hpp" #include "modes/MEMLNautModeXIASRI.hpp" #include "modes/MEMLNautModeBreakOr.hpp" +#include "modes/MEMLNautModeVerbFX.hpp" //hook up the memlnaut mode // #define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI // #define MEMLNAUT_MODE_TYPE MEMLNautModeXIASRI +// #define MEMLNAUT_MODE_TYPE MEMLNautModeVerbFX #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr // #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip // #define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth diff --git a/modes/AudioApps/BreakOrAudioApp.hpp b/modes/AudioApps/BreakOrAudioApp.hpp index 6f8f6da..f6c6804 100644 --- a/modes/AudioApps/BreakOrAudioApp.hpp +++ b/modes/AudioApps/BreakOrAudioApp.hpp @@ -26,19 +26,23 @@ struct ratioSeqState { 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)(ratioSeqState &seq) { +inline bool __not_in_flash_func(ratioSeq)(float phasor, float phaseOffset, float ratioSum, const std::array &ratios, float pulseWidth) { bool trig = 0; - float offsetPhase = seq.phaseOffset + seq.phasor; + float offsetPhase = phaseOffset + phasor; if (offsetPhase >= 1.f) { offsetPhase -= 1.f; } - float phaseAdj = seq.ratioSum * offsetPhase; + float phaseAdj = ratioSum * offsetPhase; float accumulatedSum = 0; float lastAccumulatedSum = 0; - for (size_t v : seq.ratios) + for (size_t v : ratios) { accumulatedSum += v; if (phaseAdj <= accumulatedSum) @@ -46,7 +50,7 @@ inline bool __not_in_flash_func(ratioSeq)(ratioSeqState &seq) { // check pulse width float beatPhase = (phaseAdj - lastAccumulatedSum) / (accumulatedSum - lastAccumulatedSum); - trig = beatPhase <= seq.pulseWidth; + trig = beatPhase <= pulseWidth; break; } lastAccumulatedSum = accumulatedSum; @@ -55,13 +59,18 @@ inline bool __not_in_flash_func(ratioSeq)(ratioSeqState &seq) { } -template +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; @@ -83,7 +92,9 @@ public: } BreakOrAudioApp() : AudioAppBase() { - // currentVoiceSpace = voiceSpaces[0].mappingFunction; + // 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) @@ -102,31 +113,41 @@ public: stereosample_t __force_inline Process(const stereosample_t x) override { - 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); - if (trig && !seq.lastTrig) { - if (trig) { - midiIO->queueNoteOn(seq.midiNote, 127); - }else{ - midiIO->queueNoteOff(seq.midiNote, 0); - } - } - seq.lastTrig = trig; + 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(); } - midiIO->flushQueue(); - } - sequencingSampleCounter ++; - if (sequencingSampleCounter >= sequencingSampleDiv) { - sequencingSampleCounter = 0; - } + sequencingSampleCounter ++; + if (sequencingSampleCounter >= sequencingSampleDiv) { + sequencingSampleCounter = 0; + } + + } stereosample_t ret { 0.f,0.f }; return ret; } @@ -155,18 +176,50 @@ public: 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++] * 7.f) + 1.f; + 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[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); @@ -181,6 +234,9 @@ public: 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) { @@ -205,6 +261,9 @@ protected: size_t sequencingSampleCounter = 0; std::array ratioSeqStates; + + float midiClockPhasor=0; + float midiClockPhasorInc=0; }; diff --git a/modes/AudioApps/VerbFXAudioApp.hpp b/modes/AudioApps/VerbFXAudioApp.hpp new file mode 100644 index 0000000..735f0ac --- /dev/null +++ b/modes/AudioApps/VerbFXAudioApp.hpp @@ -0,0 +1,169 @@ +#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; + mix *= 2.0f; + + smoother.Process(neuralNetOutputs.data(), smoothParams.data()); + + float dl1mix = smoothParams[0] * 0.4f; + float dl2mix = smoothParams[1] * 0.4f; + float dl3mix = smoothParams[2] * 0.8f; + float dl4mix = smoothParams[22] * smoothParams[22] * 0.41f; + + float allp1fb = smoothParams[4] * 0.99f; + float allp2fb = smoothParams[5] * 0.99f; + float comb1fb = (smoothParams[6] * 0.95f); + float comb2fb = (smoothParams[7] * 0.95f); + + float dl1fb = (smoothParams[8] * 0.95f); + float dl2fb = (smoothParams[9] * 0.95f); + float dl3fb = (smoothParams[10] * 0.95f); + float dl4fb = (smoothParams[23] * 0.95f); + // Wet-dry mix between 0.2 and 1 + wetdry_mix_ = (smoothParams[11] * 0.7f) + 0.3f; + // Pitch shift transposition between -12 and +12 semitones + // float pitchshift_transpose = (smoothParams[12] * 24.f) - 12.f; // Scale to -12 to +12 semitones + pitchshifter_.SetTransposition(12.f + smoothParams[12]); + //pitchshifter_.SetTransposition(-5.f); + // Set pitch shifter mix + pitchshifter_mix_ = smoothParams[13] * 0.99f; + + float allp3fb = smoothParams[14] * 0.99f; + float allp4fb = smoothParams[15] * 0.99f; + float allp5fb = smoothParams[16] * 0.99f; + float allp6fb = smoothParams[17] * 0.99f; + float allp3fbmix = smoothParams[18]; + float allp4fbmix = smoothParams[19]; + float allp5fbmix = smoothParams[20]; + float allp6fbmix = smoothParams[21]; + + // // PROCESS + float pitchshifted = pitchshifter_.Process(mix); + // // Mix the original signal with the pitch-shifted signal + pitchshifted = (mix * (1.f - pitchshifter_mix_)) + (pitchshifted * pitchshifter_mix_); + + float y = dcb.play(pitchshifted, 0.99f) * 2.f; + // float y = dcb.play(pitchshifted, 0.99f) * 3.f; + float y1 = allp1.allpass(y, 30, allp1fb); + y1 = comb1.combfb(y1, 127, comb1fb); + + float y2 = allp2.allpass(y, 482, allp2fb); + y2 = comb2.combfb(y2, 808, comb2fb); + + float y3 = allp3.allpass(y, 19, allp3fb) * allp3fbmix; + float y4 = allp4.allpass(y, 69, allp4fb) * allp4fbmix; + float y5 = allp5.allpass(y, 131, allp5fb) * allp5fbmix; + float y6 = allp6.allpass(y, 287, allp6fb) * allp6fbmix; + y = y1 + y2 + y3 + y4 +y5 +y6; + float d1 = (dl1.play(y, 3500, dl1fb) * dl1mix); + float d2 = (dl2.play(y, 7886, dl2fb) * dl2mix); + float d3 = (dl3.play(y, 299, dl3fb) * dl3mix); + float d4 = (dl4.play(y, 15873, dl4fb) * dl4mix); + + + y = y + d1 + d2 + d3; + + // Mix dry + y = (y * wetdry_mix_) + (mix * (1.f - wetdry_mix_)); + + y = tanhf(y*1.2f); + + 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; + pitchshifter_.Init(sample_rate); + } + + __attribute__((always_inline)) void ProcessParams(const std::array& params) + { + // currentVoiceSpace(params); + neuralNetOutputs = params; + } + + +protected: + + std::array neuralNetOutputs{0}, smoothParams{0}; + + + maxiDelayline<5000> dl1; + maxiDelayline<8000> dl2; + maxiDelayline<1201> dl3; + maxiDelayline<16000> dl4; + + maxiReverbFilters<300> allp1; + maxiReverbFilters<500> allp2; + maxiReverbFilters<500> allp3; + maxiReverbFilters<500> allp4; + maxiReverbFilters<500> allp5; + maxiReverbFilters<500> allp6; + maxiReverbFilters<200> comb1; + maxiReverbFilters<900> comb2; + + maxiDCBlocker dcb; + + daisysp::PitchShifter pitchshifter_; + float pitchshifter_mix_{0.5f}; + float wetdry_mix_{0.5f}; + + OnePoleSmoother smoother{150.f, kSampleRate}; + +}; + +#endif diff --git a/modes/MEMLNautModeBreakOr.hpp b/modes/MEMLNautModeBreakOr.hpp index 856814f..27818e5 100644 --- a/modes/MEMLNautModeBreakOr.hpp +++ b/modes/MEMLNautModeBreakOr.hpp @@ -22,10 +22,36 @@ public: InterfaceRL interface; std::shared_ptr interfacePtr; + 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); + } + + // //tiggle up + // if (state) { + // //store output + // savedAction = action; + // actionBeingDragged=true; + // msgView->post("Where do you want it?"); + // }else{ + // //button up + // if (actionBeingDragged) { + // this->storeExperience(1.f, controlInput, savedAction); + // actionBeingDragged=false; + // msgView->post("Here!"); + // } + // } + }); + } String getHelpTitle() { @@ -40,6 +66,11 @@ public: audioAppBreakOr.Setup(sample_rate, interfacePtr); voiceSpaceList = audioAppBreakOr.getVoiceSpaceNames(); audioAppBreakOr.setupMIDI(midi_interf); + MEMLNaut::Instance()->setRVGain1Callback([this](float value) { + // Serial.printf("Volume control: %f\n", value); + queue_try_add(&audioAppBreakOr.bpmQueue, &value); + }, 0); // Set threshold to 0 to trigger on any change + } __force_inline void loop() { diff --git a/modes/MEMLNautModeVerbFX.hpp b/modes/MEMLNautModeVerbFX.hpp new file mode 100644 index 0000000..a264b6b --- /dev/null +++ b/modes/MEMLNautModeVerbFX.hpp @@ -0,0 +1,83 @@ +#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 = XiasriAnalysis::kN_Params; //ML + 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(InterfaceRL::INPUT_MODES::MACHINE_LISTENING); + 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); + + } + +};