drum voice

This commit is contained in:
chriskiefer 2026-04-13 16:55:39 +01:00
parent c633b9c2ab
commit d095688cd9
10 changed files with 808 additions and 132 deletions

View file

@ -31,6 +31,7 @@
#include "modes/MEMLNautModeBreakOr.hpp" #include "modes/MEMLNautModeBreakOr.hpp"
#include "modes/MEMLNautModeVerbFX.hpp" #include "modes/MEMLNautModeVerbFX.hpp"
#include "modes/MEMLNautModeElysiamorfs.hpp" #include "modes/MEMLNautModeElysiamorfs.hpp"
#include "modes/MEMLNautModeMEMLCelium.hpp"
//hook up the memlnaut mode //hook up the memlnaut mode
@ -40,7 +41,8 @@
// #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr // #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr
// #define MEMLNAUT_MODE_TYPE MEMLNautModeElysiamorfs // #define MEMLNAUT_MODE_TYPE MEMLNautModeElysiamorfs
// #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip // #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; MEMLNAUT_MODE_TYPE AUDIO_MEM MEMLNautModeHub;

View file

@ -8,7 +8,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> // Added for std::shared_ptr #include <memory>
#include "../../src/memllib/interface/InterfaceBase.hpp" #include "../../src/memllib/interface/InterfaceBase.hpp"
@ -18,6 +18,7 @@
#include "../../voicespaces/VoiceSpaces.hpp" #include "../../voicespaces/VoiceSpaces.hpp"
#include "RatioSeq.hpp" #include "RatioSeq.hpp"
#include "RatioSeqEngine.hpp"
template<size_t NPARAMS=56, size_t NSEQUENCES=8> template<size_t NPARAMS=56, size_t NSEQUENCES=8>
@ -37,11 +38,12 @@ public:
MIDI_CLOCK MIDI_CLOCK
} sequencerClockMode = INTERNAL; } sequencerClockMode = INTERNAL;
bool sequencerPlaying = false; bool sequencerPlaying = false;
std::shared_ptr<MIDIInOut> midiIO; std::shared_ptr<MIDIInOut> midiIO;
RatioSeqEngine<NSEQUENCES> seqEngine;
std::array<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces; std::array<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces;
VoiceSpaceFn<NPARAMS> currentVoiceSpace; VoiceSpaceFn<NPARAMS> currentVoiceSpace;
@ -61,7 +63,6 @@ public:
} }
BreakOrAudioApp() : AudioAppBase<NPARAMS>() { BreakOrAudioApp() : AudioAppBase<NPARAMS>() {
// currentVoiceSpace = voiceSpaces[0].mappingFunction;
queue_init(&bpmQueue, sizeof(float), 1); queue_init(&bpmQueue, sizeof(float), 1);
queue_init(&sequencerControlQueue, sizeof(int), 1); queue_init(&sequencerControlQueue, sizeof(int), 1);
queue_init(&i2cOutQueue, sizeof(float) * 8, 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) 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; const float fi = phase * n;
int i = static_cast<int>(fi); int i = static_cast<int>(fi);
const float rem = fi - i; const float rem = fi - i;
@ -86,54 +86,30 @@ public:
{ {
if (sequencerPlaying) { if (sequencerPlaying) {
if (sequencerClockMode == INTERNAL){ if (sequencerClockMode == INTERNAL){
midiClockPhasor += midiClockPhasorInc; seqEngine.midiClockPhasor += seqEngine.midiClockPhasorInc;
if (midiClockPhasor >= 1.f) { if (seqEngine.midiClockPhasor >= 1.f) {
midiClockPhasor -= 1.f; seqEngine.midiClockPhasor -= 1.f;
midiIO->queueClock(); midiIO->queueClock();
midiIO->flushQueue(); midiIO->flushQueue();
} }
} }
if (sequencingSampleCounter==0) {
barPhasor += barPhasorInc;
if (barPhasor >= 1.f) {
barPhasor -= 1.f;
}
if (sequencerClockMode == MIDI_CLOCK) { if (sequencerClockMode == MIDI_CLOCK) {
int phasorResetValue=0; int phasorResetValue=0;
if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) { if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) {
barPhasor = 0.f; seqEngine.resetBar();
} }
} }
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); if (seqEngine.tick()) {
bool highAmp = ratioSeq<2>(seqPhasor, seq.phaseOffset, seq.ampRatioSum, seq.ampRatios, 0.5f); // Capture trigger state for I2C after tick
if (trig && !seq.lastTrig) { for (size_t i = 0; i < NSEQUENCES; i++) {
if (trig) { i2cValues[i] = seqEngine.states[i].lastTrig;
midiIO->queueNoteOn(seq.midiNote, highAmp ? 127 : 64);
}else{
midiIO->queueNoteOff(seq.midiNote, 0);
}
}
seq.lastTrig = trig;
i2cValues[i2cIdx++] = trig;
} }
midiIO->flushQueue(); midiIO->flushQueue();
queue_try_add(&i2cOutQueue, &i2cValues); queue_try_add(&i2cOutQueue, &i2cValues);
} }
sequencingSampleCounter ++;
if (sequencingSampleCounter >= sequencingSampleDiv) {
sequencingSampleCounter = 0;
} }
}
stereosample_t ret { 0.f,0.f }; stereosample_t ret { 0.f,0.f };
return ret; return ret;
} }
@ -144,12 +120,21 @@ public:
maxiSettings::sampleRate = sample_rate; maxiSettings::sampleRate = sample_rate;
sampleRatef = static_cast<float>(sample_rate); sampleRatef = static_cast<float>(sample_rate);
sequencerClockMode = clockMode; sequencerClockMode = clockMode;
updateBPM(90.f);
const size_t midiNotes[NSEQUENCES] = {36,37,38,39,40, 42,43,45/*,47,48*/}; const size_t midiNotes[NSEQUENCES] = {36,37,38,39,40, 42,43,45};
size_t midiNote = 0; for (size_t i = 0; i < NSEQUENCES; i++) {
for(auto &seq: ratioSeqStates) { seqEngine.states[i].midiNote = midiNotes[i];
seq.midiNote = midiNotes[midiNote++];
} }
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<MIDIInOut> new_midi_interf) { void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
@ -162,105 +147,37 @@ public:
void ProcessParams(const std::array<float, NPARAMS>& params) void ProcessParams(const std::array<float, NPARAMS>& params)
{ {
// if (sequencerPlaying) {
// }
firstParamsReceived = true; firstParamsReceived = true;
if (queue_try_remove(&bpmQueue, &bpm)) { float newBPM;
// bpm = 30.f + (bpm * 200.f); // Scale BPM from [0,1] to [30,230] if (queue_try_remove(&bpmQueue, &newBPM)) {
updateBPM(bpm); seqEngine.updateBPM(newBPM);
} }
int sequencerControl; int sequencerControl;
if (queue_try_remove(&sequencerControlQueue, &sequencerControl)) { if (queue_try_remove(&sequencerControlQueue, &sequencerControl)) {
sequencerPlaying = sequencerControl == 1; sequencerPlaying = sequencerControl == 1;
seqEngine.setPlaying(sequencerPlaying);
if (!sequencerPlaying) { if (!sequencerPlaying) {
midiIO->queueClockStop(); 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(); midiIO->flushQueue();
midiClockPhasor = 0.f; // Reset MIDI clock phasor when stopping
sequencingSampleCounter = 0; // Reset sequencing sample counter
} else { } else {
midiIO->queueClockStart(); midiIO->queueClockStart();
midiIO->flushQueue(); 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) { seqEngine.updateParams(params, 0);
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) { void setTimeSignature(float beats, float division) {
timeSigBeats = beats; seqEngine.setTimeSignature(beats, division);
timeSigDivision = division;
updateBPM(bpm); // Recalculate phasor increment with new time signature
} }
protected: protected:
float i2cValues[8] = {0,0,0,0,0,0,0,0}; float i2cValues[8] = {0,0,0,0,0,0,0,0};
float sampleRatef; float sampleRatef;
bool firstParamsReceived = false; 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<ratioSeqState, NSEQUENCES> ratioSeqStates;
float midiClockPhasor=0;
float midiClockPhasorInc=0;
}; };
#endif // __BREAKOR_AUDIO_APP_HPP__ #endif // __BREAKOR_AUDIO_APP_HPP__

View file

@ -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 <cstddef>
#include <cstdint>
#include <memory>
#include "../../src/memllib/synth/maxiPAF.hpp"
#include "../../src/memllib/synth/ADSRLite.hpp"
#include "../../src/memllib/interface/InterfaceBase.hpp"
#include <span>
#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<size_t NPARAMS=64>
class MEMLCeliumAudioApp : public AudioAppBase<NPARAMS>
{
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<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces;
VoiceSpaceFn<NPARAMS> currentVoiceSpace;
RatioSeqEngine<kMEMLCeliumNSequences> seqEngine;
queue_t sequencerControlQueue;
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;
}
}
MEMLCeliumAudioApp() : AudioAppBase<NPARAMS>() {
// auto voiceSpace1 = [this](const std::array<float, NPARAMS>& params) {
// VOICE_SPACE_1_BODY
// };
// auto voiceSpace2 = [this](const std::array<float, NPARAMS>& params) {
// VOICE_SPACE_2_BODY
// };
// auto voiceSpacePerc = [this](const std::array<float, NPARAMS>& params) {
// VOICE_SPACE_PERC_BODY
// };
// auto voiceSpaceSingle1 = [this](const std::array<float, NPARAMS>& params) {
// VOICE_SPACE_SINGLE_1_BODY
// };
// auto voiceSpaceQuadDetune = [this](const std::array<float, NPARAMS>& params) {
// VOICE_SPACE_QUAD_DETUNE_BODY
// };
// auto voiceSpaceQuadOct = [this](const std::array<float, NPARAMS>& params) {
// VOICE_SPACE_QUAD_OCT_BODY
// };
// auto voiceSpaceQuadDist = [this](const std::array<float, NPARAMS>& 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<int>(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<InterfaceBase> interface) override
{
AudioAppBase<NPARAMS>::Setup(sample_rate, interface);
maxiSettings::sampleRate = sample_rate;
sampleRatef = static_cast<float>(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<uint8_t>(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<NPARAMS>::loop();
}
void ProcessParams(const std::array<float, NPARAMS>& 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__

View file

@ -0,0 +1,143 @@
#ifndef __RATIO_SEQ_ENGINE_HPP__
#define __RATIO_SEQ_ENGINE_HPP__
#include "RatioSeq.hpp"
#include <array>
#include <functional>
#include <cstddef>
#include <cmath>
template<size_t NSEQUENCES>
class RatioSeqEngine {
public:
std::array<ratioSeqState, NSEQUENCES> states;
std::function<void(size_t seqIdx, int velocity)> onNoteOn;
std::function<void(size_t seqIdx)> onNoteOff;
std::function<void()> 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<size_t NPARAMS>
void updateParams(const std::array<float, NPARAMS>& 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__

View file

@ -16,7 +16,7 @@
class MEMLNautModeBreakOr { class MEMLNautModeBreakOr {
public: public:
constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS;
USeqI2C i2cOut; USeqI2C i2cOut;
inline static BreakOrAudioApp<> audioAppBreakOr; inline static BreakOrAudioApp<> audioAppBreakOr;

View file

@ -10,7 +10,7 @@
class MEMLNautModeChannelStrip { class MEMLNautModeChannelStrip {
public: 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; ChannelStripAudioApp<> audioAppChannelStrip;
std::array<String, ChannelStripAudioApp<>::nVoiceSpaces> voiceSpaceList; std::array<String, ChannelStripAudioApp<>::nVoiceSpaces> voiceSpaceList;
InterfaceRL interface; InterfaceRL interface;

View file

@ -16,7 +16,7 @@
class MEMLNautModeElysiamorfs { class MEMLNautModeElysiamorfs {
public: public:
constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS;
USeqI2C i2cOut; USeqI2C i2cOut;
inline static ElysiamorfAudioApp<> audioAppElysiamorfs; inline static ElysiamorfAudioApp<> audioAppElysiamorfs;

View file

@ -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 <memory>
#include <array>
class MEMLNautModeMEMLCelium {
public:
constexpr static size_t kN_InputParams = MEMLNAUT_ANALOG_INPUTS;
inline static MEMLCeliumAudioApp<> audioAppMEMLCelium;
std::array<String, MEMLCeliumAudioApp<>::nVoiceSpaces> voiceSpaceList;
InterfaceRL interface;
std::shared_ptr<InterfaceRL> 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<MIDIInOut> midi_interf;
void setupMIDI(std::shared_ptr<MIDIInOut> 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;
voiceSpaceSelectView = std::make_shared<VoiceSpaceSelectView>("Voice Spaces");
MEMLNaut::Instance()->disp->InsertViewAfter(interface.rlStatsView, voiceSpaceSelectView);
voiceSpaceSelectView->setOptions(voiceSpaceList);
voiceSpaceSelectView->setNewVoiceCallback(
[this](size_t idx) {
audioAppMEMLCelium.setVoiceSpace(idx);
});
std::shared_ptr<XYPadView> noteTrigView = std::make_shared<XYPadView>("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<uint8_t>(powf(y, 0.5f) * 127.f);
uint8_t midimsg[2] = {static_cast<uint8_t>(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() {}
};

View file

@ -12,7 +12,7 @@
class MEMLNautModePAFSynth { class MEMLNautModePAFSynth {
public: 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; inline static PAFSynthAudioApp<> audioAppPAFSynth;
std::array<String, PAFSynthAudioApp<>::nVoiceSpaces> voiceSpaceList; std::array<String, PAFSynthAudioApp<>::nVoiceSpaces> voiceSpaceList;

@ -1 +1 @@
Subproject commit 3310e517ac6f412fd6391fa122890e4af695ddcb Subproject commit 6bf7d2c362541047f02fdbf2f6dc359873bcd51f