From d095688cd9295c0031dc5b99fa9a1c21f1e56338 Mon Sep 17 00:00:00 2001 From: chriskiefer Date: Mon, 13 Apr 2026 16:55:39 +0100 Subject: [PATCH] drum voice --- MEMLNaut-NISPS.ino | 4 +- modes/AudioApps/BreakOrAudioApp.hpp | 169 +++------ modes/AudioApps/MEMLCeliumAudioApp.hpp | 493 +++++++++++++++++++++++++ modes/AudioApps/RatioSeqEngine.hpp | 143 +++++++ modes/MEMLNautModeBreakOr.hpp | 2 +- modes/MEMLNautModeChannelStrip.hpp | 2 +- modes/MEMLNautModeElysiamorfs.hpp | 2 +- modes/MEMLNautModeMEMLCelium.hpp | 121 ++++++ modes/MEMLNautModePAFSynth.hpp | 2 +- src/memllib | 2 +- 10 files changed, 808 insertions(+), 132 deletions(-) create mode 100644 modes/AudioApps/MEMLCeliumAudioApp.hpp create mode 100644 modes/AudioApps/RatioSeqEngine.hpp create mode 100644 modes/MEMLNautModeMEMLCelium.hpp diff --git a/MEMLNaut-NISPS.ino b/MEMLNaut-NISPS.ino index 4af8157..c7d68a1 100644 --- a/MEMLNaut-NISPS.ino +++ b/MEMLNaut-NISPS.ino @@ -31,6 +31,7 @@ #include "modes/MEMLNautModeBreakOr.hpp" #include "modes/MEMLNautModeVerbFX.hpp" #include "modes/MEMLNautModeElysiamorfs.hpp" +#include "modes/MEMLNautModeMEMLCelium.hpp" //hook up the memlnaut mode @@ -40,7 +41,8 @@ // #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr // #define MEMLNAUT_MODE_TYPE MEMLNautModeElysiamorfs // #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip -#define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth +// #define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth +#define MEMLNAUT_MODE_TYPE MEMLNautModeMEMLCelium MEMLNAUT_MODE_TYPE AUDIO_MEM MEMLNautModeHub; diff --git a/modes/AudioApps/BreakOrAudioApp.hpp b/modes/AudioApps/BreakOrAudioApp.hpp index 9d96a40..42bbd3e 100644 --- a/modes/AudioApps/BreakOrAudioApp.hpp +++ b/modes/AudioApps/BreakOrAudioApp.hpp @@ -1,16 +1,16 @@ #ifndef __BREAKOR_AUDIO_APP_HPP__ #define __BREAKOR_AUDIO_APP_HPP__ -#include "../../src/memllib/audio/AudioAppBase.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 -#include "../../src/memllib/interface/InterfaceBase.hpp" +#include "../../src/memllib/interface/InterfaceBase.hpp" #include @@ -18,6 +18,7 @@ #include "../../voicespaces/VoiceSpaces.hpp" #include "RatioSeq.hpp" +#include "RatioSeqEngine.hpp" template @@ -37,13 +38,14 @@ public: MIDI_CLOCK } sequencerClockMode = INTERNAL; - bool sequencerPlaying = false; std::shared_ptr midiIO; + RatioSeqEngine seqEngine; + std::array, nVoiceSpaces> voiceSpaces; - + VoiceSpaceFn currentVoiceSpace; std::array getVoiceSpaceNames() { @@ -61,7 +63,6 @@ public: } 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); @@ -70,7 +71,6 @@ public: 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; @@ -86,70 +86,55 @@ public: { if (sequencerPlaying) { if (sequencerClockMode == INTERNAL){ - midiClockPhasor += midiClockPhasorInc; - if (midiClockPhasor >= 1.f) { - midiClockPhasor -= 1.f; + seqEngine.midiClockPhasor += seqEngine.midiClockPhasorInc; + if (seqEngine.midiClockPhasor >= 1.f) { + seqEngine.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)) { + seqEngine.resetBar(); } - 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; + if (seqEngine.tick()) { + // Capture trigger state for I2C after tick + for (size_t i = 0; i < NSEQUENCES; i++) { + i2cValues[i] = seqEngine.states[i].lastTrig; } 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) + 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++]; + + 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]; } + + 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); + }; } void setupMIDI(std::shared_ptr new_midi_interf) { @@ -161,106 +146,38 @@ public: } 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); + float newBPM; + if (queue_try_remove(&bpmQueue, &newBPM)) { + seqEngine.updateBPM(newBPM); } int sequencerControl; if (queue_try_remove(&sequencerControlQueue, &sequencerControl)) { sequencerPlaying = sequencerControl == 1; + seqEngine.setPlaying(sequencerPlaying); 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{ + } 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; + seqEngine.updateParams(params, 0); } void setTimeSignature(float beats, float division) { - timeSigBeats = beats; - timeSigDivision = division; - updateBPM(bpm); // Recalculate phasor increment with new time signature + seqEngine.setTimeSignature(beats, division); } - - 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/MEMLCeliumAudioApp.hpp b/modes/AudioApps/MEMLCeliumAudioApp.hpp new file mode 100644 index 0000000..ba164c1 --- /dev/null +++ b/modes/AudioApps/MEMLCeliumAudioApp.hpp @@ -0,0 +1,493 @@ +#ifndef __MEMLCELIUM_AUDIO_APP_HPP__ +#define __MEMLCELIUM_AUDIO_APP_HPP__ + +#include "../../src/memllib/audio/AudioAppBase.hpp" +#include "../../src/memllib/synth/maximilian.h" + +#include +#include +#include + +#include "../../src/memllib/synth/maxiPAF.hpp" +#include "../../src/memllib/synth/ADSRLite.hpp" +#include "../../src/memllib/interface/InterfaceBase.hpp" + +#include + +#include "../../voicespaces/VoiceSpaces.hpp" + +#include "RatioSeqEngine.hpp" + +static constexpr size_t kMEMLCeliumNSequences = 2; + +// MIDI notes assigned to each sequencer index +// static constexpr uint8_t kMEMLCeliumSeqNotes[kMEMLCeliumNSequences] = {60}; + +template +class MEMLCeliumAudioApp : public AudioAppBase +{ +public: + static constexpr size_t kN_Params = NPARAMS; + static constexpr size_t nFREQs = 17; + static constexpr float frequencies[nFREQs] = {100, 200, 400,800, 400, 800, 100,1600,100,400,100,50,1600,200,100,800,400}; + static constexpr size_t nVoiceSpaces=7; + + std::array, nVoiceSpaces> voiceSpaces; + + VoiceSpaceFn currentVoiceSpace; + + RatioSeqEngine seqEngine; + + queue_t sequencerControlQueue; + + 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; + } + } + + MEMLCeliumAudioApp() : AudioAppBase() { + // auto voiceSpace1 = [this](const std::array& params) { + // VOICE_SPACE_1_BODY + // }; + + // auto voiceSpace2 = [this](const std::array& params) { + // VOICE_SPACE_2_BODY + // }; + + // auto voiceSpacePerc = [this](const std::array& params) { + // VOICE_SPACE_PERC_BODY + // }; + + // auto voiceSpaceSingle1 = [this](const std::array& params) { + // VOICE_SPACE_SINGLE_1_BODY + // }; + + // auto voiceSpaceQuadDetune = [this](const std::array& params) { + // VOICE_SPACE_QUAD_DETUNE_BODY + // }; + + // auto voiceSpaceQuadOct = [this](const std::array& params) { + // VOICE_SPACE_QUAD_OCT_BODY + // }; + + // auto voiceSpaceQuadDist = [this](const std::array& params) { + // VOICE_SPACE_QUAD_DIST_BODY + // }; + + + // voiceSpaces[0] = {"Ellipticacacia", voiceSpaceQuadDetune}; + // voiceSpaces[1] = {"Rowantares", voiceSpace1}; + // voiceSpaces[2] = {"Neemeda", voiceSpace2}; + // voiceSpaces[3] = {"Aquillow", voiceSpacePerc}; + // voiceSpaces[4] = {"Magnetarch", voiceSpaceSingle1}; + // voiceSpaces[5] = {"Elderstar", voiceSpaceQuadOct}; + // voiceSpaces[6] = {"Ipeleiades", voiceSpaceQuadDist}; + + // currentVoiceSpace = voiceSpaces[0].mappingFunction; + + queue_init(&sequencerControlQueue, sizeof(int), 1); + queue_init(&qMIDINoteOn, sizeof(uint8_t)*2, 1); + queue_init(&qMIDINoteOff, sizeof(uint8_t)*2, 1); + }; + + 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(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 + { + seqEngine.tick(); + + float x1[1]; + + float envval = v0AmpEnv.play(); + float v0PitchEnvVal = v0PitchEnv.play() * v0PitchEmph; + + float fbsmooth = (fbzm1 * fbSmoothAlpha) + (feedback * (1.f-fbSmoothAlpha)); + fbzm1 = fbsmooth; + + float freq0 = baseFreq * (1.f + fbsmooth) + (v0PitchEnvVal * baseFreq); + paf0.play(x1, 1, freq0, freq0 + (paf0_cf * freq0), paf0_bw, paf0_vib, paf0_vfr, paf0_shift, 0); + float p0 = *x1 * p0Gain; + + const float freq1 = freq0 * detune1; + + paf1.play(x1, 1, freq1, freq1 + (paf1_cf * freq1), paf1_bw, paf1_vib, paf1_vfr, paf1_shift, 1); + const float p1 = *x1 * p1Gain; + + const float freq2 = freq1 * detune2; + + paf2.play(x1, 1, freq2, freq2 + (paf2_cf * freq2), paf2_bw, paf2_vib, paf2_vfr, paf2_shift, 1); + const float p2 = *x1 * p2Gain; + + float v0 = p0 + p1 + p2;// + p3; + + const float rm = p0 * p1 * p2;// * p3; + + v0 = v0 + (rm * rmGain); + + + float shape = sinf(v0 * TWOPI); + shape = sinf(((shape * TWOPI) * sineShapeGain) + sineShapeASym); + v0 = v0 + (shape * sineShapeMix); + + v0 = v0 * envval; + feedback = v0 * feedbackGain; + + //v1 + float v1Envval = v1AmpEnv.play(); + float v1PitchEnvVal = v1PitchEnv.play() * v1PitchEmph; + + float v1freq0 = v1BaseFreq + (v1PitchEnvVal * v1BaseFreq); + v1paf0.play(x1, 1, v1freq0, v1freq0 + (v1paf0_cf * v1freq0), v1paf0_bw, 0, 0, v1paf0_shift, 0); + float v1p0 = *x1 * v1p0Gain; + + const float v1freq1 = v1freq0 * v1Detune1; + + v1paf1.play(x1, 1, v1freq1, v1freq1 + (v1paf1_cf * v1freq1), v1paf1_bw, 0, 0, v1paf1_shift, 1); + const float v1p1 = *x1 * v1p1Gain; + + const float v1freq2 = v1freq1 * v1Detune2; + + v1paf2.play(x1, 1, v1freq2, v1freq2 + (v1paf2_cf * freq2), v1paf2_bw, 0, 0, v1paf2_shift, 1); + const float v1p2 = *x1 * v1p2Gain; + + float v1 = v1p0 + v1p1 + v1p2; + + v1 = v1 * v1Envval; + ///// + + float mix = v0 + v1; + + mix = tanhf(mix); + + stereosample_t ret { mix, mix }; + 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); + + paf0.init(); + paf0.setsr(maxiSettings::getSampleRate(), 1); + + paf1.init(); + paf1.setsr(maxiSettings::getSampleRate(), 1); + + paf2.init(); + paf2.setsr(maxiSettings::getSampleRate(), 1); + + paf3.init(); + paf3.setsr(maxiSettings::getSampleRate(), 1); + + v1paf0.init(); + v1paf0.setsr(maxiSettings::getSampleRate(), 1); + + v1paf1.init(); + v1paf1.setsr(maxiSettings::getSampleRate(), 1); + + v1paf2.init(); + v1paf2.setsr(maxiSettings::getSampleRate(), 1); + + arpFreq = frequencies[0]; + envamp=1.f; + + v0AmpEnv.setup(500,500,0.8,1000,sampleRatef); + v0PitchEnv.setup(10,500,0.f,100,sampleRatef); + v1AmpEnv.setup(500,500,0.8,1000,sampleRatef); + v1PitchEnv.setup(10,500,0.f,100,sampleRatef); + + seqEngine.setup(sample_rate); + seqEngine.updateBPM(120.f); + seqEngine.setPlaying(true); + + seqEngine.onNoteOn = [this](size_t seqIdx, int velocity) { + // uint8_t note = kMEMLCeliumSeqNotes[seqIdx]; + // uint8_t midimsg[2] = { note, static_cast(velocity) }; + // queue_try_add(&qMIDINoteOn, &midimsg); + // baseFreq = 80.f; + noteVel = velocity / 127.0f; + noteVel = noteVel * noteVel; + switch(seqIdx) { + case 0: + v0AmpEnv.trigger(noteVel); + break; + case 1: + v1AmpEnv.trigger(noteVel); + break; + } + }; + seqEngine.onNoteOff = [this](size_t seqIdx) { + // uint8_t note = kMEMLCeliumSeqNotes[seqIdx]; + // uint8_t midimsg[2] = { note, 0 }; + // queue_try_add(&qMIDINoteOff, &midimsg); + switch(seqIdx) { + case 0: + v0AmpEnv.release(); + break; + case 1: + v1AmpEnv.release(); + break; + } + }; + } + + inline float mtof(uint8_t note) { + return 440.0f * exp2f((note - 69) / 12.0f); + } + + size_t currNote=0; + void loop() override { + // uint8_t midimsg[2]; + // if (firstParamsReceived && queue_try_remove(&qMIDINoteOn, &midimsg)) { + // baseFreq = 80.f; + // noteVel = midimsg[1] / 127.0f; + // noteVel = noteVel * noteVel; + // newNote = true; + // env.trigger(noteVel); + // currNote = midimsg[0]; + // } + // if (firstParamsReceived && queue_try_remove(&qMIDINoteOff, &midimsg)) { + // if (currNote == midimsg[0]) { + // env.release(); + // } + // } + AudioAppBase::loop(); + } + + void ProcessParams(const std::array& params) + { + firstParamsReceived = true; + int seqControl; + if (queue_try_remove(&sequencerControlQueue, &seqControl)) { + seqEngine.setPlaying(seqControl == 1); + } + seqEngine.updateParams(params, 0); + p0Gain=1.f; + p1Gain=1.f; + p2Gain=1.f; + p3Gain=1.f; + + detune1 = 1.0f; + detune2 = 1.1f; + // detune3 = 1.2f; + + size_t paramIdx = 14; // 0-13 reserved for seqEngine (2 sequencers × 7 params) + + baseFreq = 20.f + (params[paramIdx++] * 80.f); + + paf0_cf = (params[paramIdx++] * 2.f); + paf1_cf = (params[paramIdx++] * 2.f); + paf2_cf = (params[paramIdx++] * 2.f); + // paf3_cf = (params[paramIdx++] * 2.f); + + paf0_bw = 10.f + (params[paramIdx++] * 400.f); + paf1_bw = 10.f + (params[paramIdx++] * 50.f); + paf2_bw = 10.f + (params[paramIdx++] * 50.f); + // paf3_bw = 10.f + (params[paramIdx++] * 100.f); + + paf0_vib = params[paramIdx++] * params[paramIdx++] * 0.01f; + paf1_vib = paf0_vib; + paf2_vib = paf0_vib; + // paf3_vib = 0.f; + + paf0_vfr = (params[paramIdx++] * params[paramIdx++]* 15.0f); + paf1_vfr = paf0_vfr; + paf2_vfr = paf0_vfr; + // paf3_vfr = 0.f; + + paf0_shift = -500.f + (params[paramIdx++] * 500.f); + paf1_shift = -300.f + (params[paramIdx++] * 600.f); + paf2_shift = -100.f + (params[paramIdx++] * 200.f); + // paf3_shift = -300.f + (params[paramIdx++] * 300.f); + + v0AmpEnv.setup(0.01f + (params[paramIdx++] * 1.f), + 0.5f + params[paramIdx++] * params[paramIdx++] * 200.f, + 0.01 + (params[paramIdx++] * 0.5f),1.f+ params[paramIdx++] * params[paramIdx++] * 800.f, sampleRatef ); + + v0PitchEnv.setup(0.01f + (params[paramIdx++] * 3.f), + 0.5f + params[paramIdx++] * params[paramIdx++] * 100.f, + 0.f, 0.1f, sampleRatef ); + + v0PitchEmph = params[paramIdx++]* 50.f; + + sineShapeGain = params[paramIdx++]; + sineShapeASym = params[paramIdx++]* 0.5f; + sineShapeMix = params[paramIdx++]; + + rmGain = params[paramIdx++]; + feedbackGain = 0.1f; + + fbSmoothAlpha=0.5f; + + //v1 + v1p0Gain=1.f; + v1p1Gain=1.f; + v1p2Gain=1.f; + + v1BaseFreq = 100.f + (params[paramIdx++] * 400.f); + v1Detune1 = 0.5f + (params[paramIdx++] * 3.5f); + v1Detune2 = 0.5f + (params[paramIdx++] * 3.5f); + + v1paf0_cf = (params[paramIdx++] * 3.f); + v1paf1_cf = (params[paramIdx++] * 3.f); + v1paf2_cf = (params[paramIdx++] * 3.f); + + v1paf0_bw = 10.f + (params[paramIdx++] * 400.f); + v1paf1_bw = 10.f + (params[paramIdx++] * 600.f); + v1paf2_bw = 10.f + (params[paramIdx++] * 500.f); + + v1paf0_shift = -800.f + (params[paramIdx++] * 800.f); + v1paf1_shift = -300.f + (params[paramIdx++] * 600.f); + v1paf2_shift = -100.f + (params[paramIdx++] * 200.f); + + v1AmpEnv.setup(0.01f + (params[paramIdx++] * 1.f), + 0.5f + params[paramIdx++] * params[paramIdx++] * 100.f, + 0.01 + (params[paramIdx++] * 0.3f), 1.f + params[paramIdx++] * params[paramIdx++] * 200.f, sampleRatef); + + v1PitchEnv.setup(0.01f + (params[paramIdx++] * 3.f), + 0.5f + params[paramIdx++] * params[paramIdx++] * 100.f, + 0.f, 0.1f, sampleRatef); + + v1PitchEmph = params[paramIdx++] * 10.f; + + // currentVoiceSpace(params); + } + + queue_t qMIDINoteOn, qMIDINoteOff; + + + +protected: + + maxiPAFOperator paf0; + maxiPAFOperator paf1; + maxiPAFOperator paf2; + maxiPAFOperator paf3; + + maxiPAFOperator v1paf0; + maxiPAFOperator v1paf1; + maxiPAFOperator v1paf2; + + + maxiOsc pulse; + + ADSRLite v0AmpEnv, v0PitchEnv, v1AmpEnv, v1PitchEnv; + + float frame=0; + + float feedback=0.f, feedbackGain=0.f; + + float p0Gain=1.f, p1Gain = 1.f, p2Gain=1.f, p3Gain=1.f; + float v1p0Gain=1.f, v1p1Gain=1.f, v1p2Gain=1.f; + + float v0PitchEmph = 1.f; + float v1PitchEmph = 1.f; + + float paf0_freq = 100; + float paf1_freq = 100; + float paf2_freq = 50; + float paf3_freq = 50; + + float paf0_cf = 200; + float paf1_cf = 250; + float paf2_cf = 250; + float paf3_cf = 250; + + float v1paf0_cf = 200; + float v1paf1_cf = 250; + float v1paf2_cf = 250; + + float paf0_bw = 100; + float paf1_bw = 5000; + float paf2_bw = 5000; + float paf3_bw = 5000; + + float v1paf0_bw = 100; + float v1paf1_bw = 5000; + float v1paf2_bw = 5000; + + float paf0_vib = 0; + float paf1_vib = 1; + float paf2_vib = 1; + float paf3_vib = 1; + + float paf0_vfr = 2; + float paf1_vfr = 2; + float paf2_vfr = 2; + float paf3_vfr = 2; + + float paf0_shift = 0; + float paf1_shift = 0; + float paf2_shift = 0; + float paf3_shift = 0; + + float v1paf0_shift = 0; + float v1paf1_shift = 0; + float v1paf2_shift = 0; + + + float rmGain = 0.f; + + float sineShapeGain=0.1; + float sineShapeASym = 0.f; + float sineShapeMix = 0.f; + float sineShapeMixInv = 1.f; + size_t counter=0; + size_t freqIndex = 0; + size_t freqOffset = 0; + float arpFreq=50; + + maxiLine line; + float envamp=0.f; + + float detune1 = 1.0; + float detune2 = 1.0; + float detune3 = 1.0; + + float v1Detune1 = 1.5; + float v1Detune2 = 2.1; + + maxiOsc phasorOsc; + maxiTrigger zxdetect; + + size_t euclidN=4; + + float baseFreq = 50.0f; + float v1BaseFreq = 200.0f; + bool newNote=false; + float noteVel = 0.f; + bool firstParamsReceived = false; + + // float envdec=0.2f/9000.f; + + float sampleRatef = maxiSettings::getSampleRate(); + + float fbzm1=0.f; + size_t delayMax=10; + float fbSmoothAlpha=0.95f; + +}; + +#endif // __MEMLCELIUM_AUDIO_APP_HPP__ diff --git a/modes/AudioApps/RatioSeqEngine.hpp b/modes/AudioApps/RatioSeqEngine.hpp new file mode 100644 index 0000000..185ecaf --- /dev/null +++ b/modes/AudioApps/RatioSeqEngine.hpp @@ -0,0 +1,143 @@ +#ifndef __RATIO_SEQ_ENGINE_HPP__ +#define __RATIO_SEQ_ENGINE_HPP__ + +#include "RatioSeq.hpp" +#include +#include +#include +#include + +template +class RatioSeqEngine { +public: + std::array states; + + std::function onNoteOn; + std::function onNoteOff; + std::function onBarReset; + + void setup(float sample_rate) { + sampleRatef = sample_rate; + updateBPM(bpm); + } + + __force_inline void updateBPM(float newBPM) { + bpm = newBPM; + float beatLengthInSeconds = 60.f / bpm; + float barLengthInSeconds = beatLengthInSeconds * timeSigBeats; + float barLengthInSamples = barLengthInSeconds * (sampleRatef / sequencingSampleDiv); + barPhasorInc = 1.f / barLengthInSamples; + float midiClockLengthInSeconds = beatLengthInSeconds / 24.f; + float midiClockLengthInSamples = midiClockLengthInSeconds * sampleRatef; + midiClockPhasorInc = 1.f / midiClockLengthInSamples; + } + + void setTimeSignature(float beats, float division) { + timeSigBeats = beats; + timeSigBeatsInv = 1.f / beats; + timeSigDivision = division; + updateBPM(bpm); + } + + void setPlaying(bool play) { + playing = play; + if (!playing) { + if (onNoteOff) { + for (size_t i = 0; i < NSEQUENCES; i++) { + onNoteOff(i); + } + } + barPhasor = 0.f; + midiClockPhasor = 0.f; + sequencingSampleCounter = 0; + for (auto &s : states) { + s.phasor = 0.f; + s.lastTrig = false; + } + } + } + + __force_inline void resetBar() { + barPhasor = 0.f; + if (onBarReset) onBarReset(); + } + + // Returns true on a sequencing tick. Call once per audio sample from Process(). + __force_inline bool tick() { + if (!playing) return false; + + bool ticked = false; + if (sequencingSampleCounter == 0) { + barPhasor += barPhasorInc; + if (barPhasor >= 1.f) { + barPhasor -= 1.f; + } + + for (size_t i = 0; i < NSEQUENCES; i++) { + auto &seq = states[i]; + float seqPhasor = barPhasor * seq.phasorMul; + seqPhasor = fmodf(seqPhasor + seq.phaseOffset, 1.f); + + 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 (onNoteOn) onNoteOn(i, highAmp ? 127 : 64); + } else if (!trig && seq.lastTrig) { + if (onNoteOff) onNoteOff(i); + } + seq.lastTrig = trig; + } + ticked = true; + } + + sequencingSampleCounter++; + if (sequencingSampleCounter >= sequencingSampleDiv) { + sequencingSampleCounter = 0; + } + + return ticked; + } + + // Call from loop() (not audio path). startIdx is the first param index to consume. + template + void updateParams(const std::array& params, size_t startIdx) { + size_t paramIdx = startIdx; + for (auto &v : states) { + 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[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; + } + } + + float midiClockPhasorInc = 0.f; + float midiClockPhasor = 0.f; + bool playing = false; + +private: + float sampleRatef = 48000.f; + float bpm = 120.f; + float timeSigBeats = 4.f; + float timeSigBeatsInv = 0.25f; + float timeSigDivision = 4.f; + float barPhasor = 0.f; + float barPhasorInc = 0.f; + size_t sequencingSampleDiv = 400; + size_t sequencingSampleCounter = 0; +}; + +#endif // __RATIO_SEQ_ENGINE_HPP__ diff --git a/modes/MEMLNautModeBreakOr.hpp b/modes/MEMLNautModeBreakOr.hpp index 4b7697d..91c2e2d 100644 --- a/modes/MEMLNautModeBreakOr.hpp +++ b/modes/MEMLNautModeBreakOr.hpp @@ -16,7 +16,7 @@ class MEMLNautModeBreakOr { public: - constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate + constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS; USeqI2C i2cOut; inline static BreakOrAudioApp<> audioAppBreakOr; diff --git a/modes/MEMLNautModeChannelStrip.hpp b/modes/MEMLNautModeChannelStrip.hpp index f1a4dc0..5192df1 100644 --- a/modes/MEMLNautModeChannelStrip.hpp +++ b/modes/MEMLNautModeChannelStrip.hpp @@ -10,7 +10,7 @@ class MEMLNautModeChannelStrip { public: - constexpr static size_t kN_InputParams = 4; // joystick x, y, left and right joysticks + constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS; ChannelStripAudioApp<> audioAppChannelStrip; std::array::nVoiceSpaces> voiceSpaceList; InterfaceRL interface; diff --git a/modes/MEMLNautModeElysiamorfs.hpp b/modes/MEMLNautModeElysiamorfs.hpp index 6a35cd7..6fdb7f9 100644 --- a/modes/MEMLNautModeElysiamorfs.hpp +++ b/modes/MEMLNautModeElysiamorfs.hpp @@ -16,7 +16,7 @@ class MEMLNautModeElysiamorfs { public: - constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate + constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS; USeqI2C i2cOut; inline static ElysiamorfAudioApp<> audioAppElysiamorfs; diff --git a/modes/MEMLNautModeMEMLCelium.hpp b/modes/MEMLNautModeMEMLCelium.hpp new file mode 100644 index 0000000..af40f5c --- /dev/null +++ b/modes/MEMLNautModeMEMLCelium.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../src/memllib/hardware/memlnaut/display/XYPadView.hpp" +#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp" +#include "AudioApps/MEMLCeliumAudioApp.hpp" +#include "../src/memllib/examples/InterfaceRL.hpp" +#include "../src/memllib/PicoDefs.hpp" +#include "MEMLNautMode.hpp" +#include +#include + +class MEMLNautModeMEMLCelium { +public: + constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS; + + inline static MEMLCeliumAudioApp<> audioAppMEMLCelium; + std::array::nVoiceSpaces> voiceSpaceList; + + InterfaceRL interface; + std::shared_ptr interfacePtr; + + bool sequencerPlaying = false; + + + void setupInterface() { + interface.setup(kN_InputParams, MEMLCeliumAudioApp<>::kN_Params); + interface.bindInterface(InterfaceRL::INPUT_MODES::JOYSTICK, true); + interface.setModeInfo("memlcelium", "MEMLCelium"); + 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(&audioAppMEMLCelium.sequencerControlQueue, &sequencerPlaying); + } + + }); + } + + String getHelpTitle() { + return "MEMLCelium Mode"; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppMEMLCelium.Process(x); + } + + void setupAudio(float sample_rate) { + audioAppMEMLCelium.Setup(sample_rate, interfacePtr); + voiceSpaceList = audioAppMEMLCelium.getVoiceSpaceNames(); + } + + __force_inline void loop() { + audioAppMEMLCelium.loop(); + } + + std::shared_ptr midi_interf; + + void setupMIDI(std::shared_ptr new_midi_interf) { + midi_interf = new_midi_interf; + midi_interf->Setup(16); + midi_interf->SetMIDISendChannel(1); + interface.bindMIDI(midi_interf); + + midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) { + if (noteon) { + uint8_t midimsg[2] = { note_number, vel_value }; + queue_try_add(&audioAppMEMLCelium.qMIDINoteOn, &midimsg); + }else{ + uint8_t midimsg[2] = { note_number, vel_value }; + queue_try_add(&audioAppMEMLCelium.qMIDINoteOff, &midimsg); + } + }); + } + + void addViews() { + std::shared_ptr voiceSpaceSelectView; + voiceSpaceSelectView = std::make_shared("Voice Spaces"); + + MEMLNaut::Instance()->disp->InsertViewAfter(interface.rlStatsView, voiceSpaceSelectView); + voiceSpaceSelectView->setOptions(voiceSpaceList); + voiceSpaceSelectView->setNewVoiceCallback( + [this](size_t idx) { + audioAppMEMLCelium.setVoiceSpace(idx); + }); + + std::shared_ptr noteTrigView = std::make_shared("Play", TFT_SILVER); + + static bool is_playing_note = false; + static uint8_t last_note_number = 0; + + noteTrigView->SetOnTouchCallback([this](float x, float y) { + if (is_playing_note) { + midi_interf->sendNoteOff(last_note_number, 0); + is_playing_note = false; + } + uint8_t noteVel = static_cast(powf(y, 0.5f) * 127.f); + uint8_t midimsg[2] = {static_cast(x * 127.f), noteVel}; + queue_try_add(&audioAppMEMLCelium.qMIDINoteOn, &midimsg); + midi_interf->sendNoteOn(midimsg[0], midimsg[1]); + last_note_number = midimsg[0]; + is_playing_note = true; + }); + noteTrigView->SetOnTouchReleaseCallback([this](float x, float y) { + uint8_t midimsg[2] = {last_note_number,0}; + queue_try_add(&audioAppMEMLCelium.qMIDINoteOff, &midimsg); + midi_interf->sendNoteOff(last_note_number, 0); + is_playing_note = false; + }); + MEMLNaut::Instance()->disp->AddView(noteTrigView); + }; + + inline void processAnalysisParams() {} + + void analyse(stereosample_t) {} + + void loopCore0() {} + +}; diff --git a/modes/MEMLNautModePAFSynth.hpp b/modes/MEMLNautModePAFSynth.hpp index d78ad1c..f97dd85 100644 --- a/modes/MEMLNautModePAFSynth.hpp +++ b/modes/MEMLNautModePAFSynth.hpp @@ -12,7 +12,7 @@ class MEMLNautModePAFSynth { public: - constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate + constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS; inline static PAFSynthAudioApp<> audioAppPAFSynth; std::array::nVoiceSpaces> voiceSpaceList; diff --git a/src/memllib b/src/memllib index 3310e51..6bf7d2c 160000 --- a/src/memllib +++ b/src/memllib @@ -1 +1 @@ -Subproject commit 3310e517ac6f412fd6391fa122890e4af695ddcb +Subproject commit 6bf7d2c362541047f02fdbf2f6dc359873bcd51f