memlnaut-nisps/modes/AudioApps/MEMLCeliumAudioApp.hpp

520 lines
16 KiB
C++
Raw Normal View History

2026-04-13 17:55:39 +02:00
#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};
2026-04-14 09:20:57 +02:00
template<size_t NPARAMS=56>
2026-04-13 17:55:39 +02:00
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;
2026-04-13 18:07:31 +02:00
// Focus group bitmasks
static constexpr uint32_t kFocusSeq = (1u << 0);
static constexpr uint32_t kFocusSyn = (1u << 1);
2026-04-14 09:20:57 +02:00
// Per-param group membership: params 0-13 = sequencer, 14-55 = synthesis
2026-04-13 18:07:31 +02:00
static constexpr std::array<uint32_t, NPARAMS> kParamGroupMask = {
// 0-13: sequencer (14 entries)
kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq,
kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq, kFocusSeq,
2026-04-14 09:20:57 +02:00
// 14-55: synthesis (42 entries)
2026-04-13 18:07:31 +02:00
kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn,
kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn,
kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn,
kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn,
kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn,
kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn, kFocusSyn,
};
2026-04-13 17:55:39 +02:00
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;
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;
2026-04-14 09:20:57 +02:00
const float rm = v1p0 * v1p1 * v1p2;// * p3;
v1 = ((1.0 - rmGain) * v1) + (rm * rmGain);
2026-04-13 17:55:39 +02:00
v1 = v1 * v1Envval;
/////
float mix = v0 + v1;
2026-04-14 09:20:57 +02:00
float shape = sinf(mix * TWOPI);
shape = sinf(((shape * TWOPI) * sineShapeGain) + sineShapeASym);
mix = mix + (shape * sineShapeMix);
2026-04-13 17:55:39 +02:00
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);
2026-04-14 09:20:57 +02:00
v0PitchEnv.trigger(1.0);
2026-04-13 17:55:39 +02:00
break;
case 1:
v1AmpEnv.trigger(noteVel);
2026-04-14 09:20:57 +02:00
v1PitchEnv.trigger(1.0);
2026-04-13 17:55:39 +02:00
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();
2026-04-14 09:20:57 +02:00
v0PitchEnv.release();
2026-04-13 17:55:39 +02:00
break;
case 1:
v1AmpEnv.release();
2026-04-14 09:20:57 +02:00
v1PitchEnv.release();
2026-04-13 17:55:39 +02:00
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;
2026-04-14 09:20:57 +02:00
detune1 = 1.01f;
detune2 = 1.02f;
2026-04-13 17:55:39 +02:00
// detune3 = 1.2f;
size_t paramIdx = 14; // 0-13 reserved for seqEngine (2 sequencers × 7 params)
2026-04-14 09:20:57 +02:00
auto sqParam = [&]() { const float p = params[paramIdx++]; return p * p; };
2026-04-13 17:55:39 +02:00
2026-04-14 09:20:57 +02:00
baseFreq = 60.f + (params[paramIdx++] * 10.f);
2026-04-13 17:55:39 +02:00
paf0_cf = (params[paramIdx++] * 2.f);
paf1_cf = (params[paramIdx++] * 2.f);
paf2_cf = (params[paramIdx++] * 2.f);
// paf3_cf = (params[paramIdx++] * 2.f);
2026-04-14 09:20:57 +02:00
paf0_bw = 10.f + (params[paramIdx++] * 100.f);
paf1_bw = 10.f + (params[paramIdx++] * 100.f);
paf2_bw = 10.f + (params[paramIdx++] * 100.f);
2026-04-13 17:55:39 +02:00
// paf3_bw = 10.f + (params[paramIdx++] * 100.f);
2026-04-14 09:20:57 +02:00
paf0_vib = sqParam() * 0.01f;
2026-04-13 17:55:39 +02:00
paf1_vib = paf0_vib;
paf2_vib = paf0_vib;
// paf3_vib = 0.f;
2026-04-14 09:20:57 +02:00
paf0_vfr = sqParam() * 15.0f;
2026-04-13 17:55:39 +02:00
paf1_vfr = paf0_vfr;
paf2_vfr = paf0_vfr;
// paf3_vfr = 0.f;
2026-04-14 09:20:57 +02:00
paf0_shift = -100.f + (params[paramIdx++] * 200.f);
paf1_shift = -100.f + (params[paramIdx++] * 200.f);
2026-04-13 17:55:39 +02:00
paf2_shift = -100.f + (params[paramIdx++] * 200.f);
// paf3_shift = -300.f + (params[paramIdx++] * 300.f);
v0AmpEnv.setup(0.01f + (params[paramIdx++] * 1.f),
2026-04-14 09:20:57 +02:00
0.5f + sqParam() * 200.f,
0.01 + (params[paramIdx++] * 0.5f), 1.f + sqParam() * 800.f, sampleRatef);
2026-04-13 17:55:39 +02:00
2026-04-14 09:20:57 +02:00
v0PitchEnv.setup(0.01f + (params[paramIdx++] * 3.f),
0.5f + sqParam() * 100.f,
0.f, 0.1f, sampleRatef);
2026-04-13 17:55:39 +02:00
v0PitchEmph = params[paramIdx++]* 50.f;
sineShapeGain = params[paramIdx++];
sineShapeASym = params[paramIdx++]* 0.5f;
sineShapeMix = params[paramIdx++];
rmGain = params[paramIdx++];
2026-04-14 09:20:57 +02:00
feedbackGain = 0; //0.1f;
2026-04-13 17:55:39 +02:00
fbSmoothAlpha=0.5f;
//v1
v1p0Gain=1.f;
v1p1Gain=1.f;
v1p2Gain=1.f;
2026-04-14 09:20:57 +02:00
v1BaseFreq = 300.f + (params[paramIdx++] * 10.f);
v1Detune1 = 1.0f + (params[paramIdx++] * 1.0f);
v1Detune2 = 1.0f + (params[paramIdx++] * 1.0f);
2026-04-13 17:55:39 +02:00
2026-04-14 09:20:57 +02:00
v1paf0_cf = (params[paramIdx++] * 2.f);
v1paf1_cf = (params[paramIdx++] * 2.f);
v1paf2_cf = (params[paramIdx++] * 2.f);
2026-04-13 17:55:39 +02:00
v1paf0_bw = 10.f + (params[paramIdx++] * 400.f);
v1paf1_bw = 10.f + (params[paramIdx++] * 600.f);
v1paf2_bw = 10.f + (params[paramIdx++] * 500.f);
2026-04-14 09:20:57 +02:00
v1paf0_shift = -500.f + (params[paramIdx++] * 1000.f);
2026-04-13 17:55:39 +02:00
v1paf1_shift = -300.f + (params[paramIdx++] * 600.f);
v1paf2_shift = -100.f + (params[paramIdx++] * 200.f);
v1AmpEnv.setup(0.01f + (params[paramIdx++] * 1.f),
2026-04-14 09:20:57 +02:00
0.5f + sqParam() * 100.f,
0.01 + (params[paramIdx++] * 0.3f), 1.f + sqParam() * 200.f, sampleRatef);
2026-04-13 17:55:39 +02:00
v1PitchEnv.setup(0.01f + (params[paramIdx++] * 3.f),
2026-04-14 09:20:57 +02:00
0.5f + sqParam() * 100.f,
2026-04-13 17:55:39 +02:00
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__