break|| midi clock and seq control

This commit is contained in:
chriskiefer 2026-03-03 11:53:50 +00:00
parent 1656e7a4a4
commit 3ddbcdfed9
5 changed files with 376 additions and 32 deletions

View file

@ -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

View file

@ -26,19 +26,23 @@ struct ratioSeqState {
float ratioSum=1.f;
int midiNote = 36;
float pulseWidth = 0.5f;
std::array<float, 2> ampRatios{1.f};
float ampRatioSum=1.f;
};
template<size_t seqLength>
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<float, seqLength> &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<size_t NPARAMS=50, size_t NSEQUENCES=10>
template<size_t NPARAMS=70, size_t NSEQUENCES=10>
class BreakOrAudioApp : public AudioAppBase<NPARAMS>
{
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<MIDIInOut> midiIO;
std::array<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces;
@ -84,6 +93,8 @@ public:
BreakOrAudioApp() : AudioAppBase<NPARAMS>() {
// 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<float, NPARAMS>& 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) {
@ -206,6 +262,9 @@ protected:
std::array<ratioSeqState, NSEQUENCES> ratioSeqStates;
float midiClockPhasor=0;
float midiClockPhasorInc=0;
};
#endif // __BREAKOR_AUDIO_APP_HPP__

View file

@ -0,0 +1,169 @@
#ifndef __VERBFX_AUDIO_APP_HPP__
#define __VERBFX_AUDIO_APP_HPP__
#include "../../src/memllib/audio/AudioAppBase.hpp" // Added missing include
#include <cstddef>
#include <cstdint>
#include <memory>
//#include "src/memllib/interface/InterfaceBase.hpp" // Added missing include
#include <span>
#include "../../voicespaces/VoiceSpaces.hpp"
#include "../../src/memllib/synth/OnePoleSmoother.hpp"
#include "../../src/memllib/synth/maximilian.h"
#include "../../src/daisysp/Effects/pitchshifter.h"
template<size_t NPARAMS=24>
class VerbFXAudioApp : public AudioAppBase<NPARAMS>
{
public:
static constexpr size_t kN_Params = NPARAMS;
static constexpr size_t nVoiceSpaces=1;
std::array<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces;
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;
}
}
VerbFXAudioApp() : AudioAppBase<NPARAMS>() {
};
__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<InterfaceBase> interface) override
{
AudioAppBase<NPARAMS>::Setup(sample_rate, interface);
maxiSettings::sampleRate = sample_rate;
pitchshifter_.Init(sample_rate);
}
__attribute__((always_inline)) void ProcessParams(const std::array<float, NPARAMS>& params)
{
// currentVoiceSpace(params);
neuralNetOutputs = params;
}
protected:
std::array<float,NPARAMS> 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<kN_Params> smoother{150.f, kSampleRate};
};
#endif

View file

@ -22,10 +22,36 @@ public:
InterfaceRL interface;
std::shared_ptr<InterfaceRL> 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() {

View file

@ -0,0 +1,83 @@
#pragma once
#include "../src/memllib/interface/MIDIInOut.hpp"
#include "./AudioApps/VerbFXAudioApp.hpp"
#include "MEMLNautMode.hpp"
#include <memory>
#include <array>
#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<InterfaceRL> interfacePtr;
XiasriAnalysis mlAnalysis{kSampleRate};
SharedBuffer<float, XiasriAnalysis::kN_Params> machine_list_buffer;
VerbFXAudioApp<> audioAppVerbFX;
std::shared_ptr<MIDIInOut> 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<MIDIInOut> 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<float> 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);
}
};