memlnautmodes, channelstrip

This commit is contained in:
chriskiefer 2025-11-19 15:17:37 +00:00
parent adc6e3f279
commit 81a7cb80b9
7 changed files with 1164 additions and 322 deletions

54
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,54 @@
{
"files.associations": {
"cmath": "cpp",
"cstdint": "cpp",
"compare": "cpp",
"memory": "cpp",
"type_traits": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"concepts": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
}
}

557
ChannelStripAudioApp.hpp Normal file
View file

@ -0,0 +1,557 @@
#ifndef __CHANNEL_STRIP_AUDIO_APP_HPP__
#define __CHANNEL_STRIP_AUDIO_APP_HPP__
#include "src/memllib/audio/AudioAppBase.hpp" // Added missing include
#include "src/memllib/synth/maximilian.h" // Added missing include for maxiSettings, maxiOsc, maxiTrigger, maxiDelayline, maxiEnvGen, maxiLine
#include <cstddef>
#include <cstdint>
#include <memory> // Added for std::shared_ptr
#include "src/memllib/synth/maxiPAF.hpp"
#include "src/memllib/synth/ADSRLite.hpp"
#include "src/memllib/interface/InterfaceBase.hpp" // Added missing include
#include <span>
#include "voicespaces/VoiceSpaces.hpp"
#include "voicespaces/ChannelStrip/basic.hpp"
template<size_t BUFSIZE>
class maxiRingBufLite {
public:
maxiRingBufLite();
/*!Add the latest value to the buffer \param x A value*/
__force_inline void push(float x) {
buf[idx] = x;
idx++;
if (idx==BUFSIZE) {
idx=0;
}
}
/*! \returns The size of the buffer*/
size_t size() {return BUFSIZE;}
/*! \returns the value at the front of the buffer*/
__force_inline float head() {return idx == 0 ? buf[BUFSIZE-1] : buf[idx-1];}
/*! \returns the oldest value in the buffer, for a particular window size \param N The size of the window, N < the size of the buffer*/
__force_inline float tail(const size_t N) {
float val=0;
if (idx >= N) {
val = buf[idx-N];
}else{
size_t tailIdx = BUFSIZE - (N-idx);
val = buf[tailIdx];
}
return val;
}
using reduceFunction = std::function<float(float, float)>;
/**
* Apply a function of the previous N values in the buffer
* \param N The number of values in the window
* \param func A function in the form float func(float previousResult, float nextValue)
* \param initval The initial value to pass into the function (usually 0)
* \returns The last result of the function, after passing in all values from the window
* Example: this function will sum the values in the window:
* auto sumfunc = [](float val, float n) {return val + n;};
*/
float reduce(size_t N, reduceFunction func, float initval) {
float val=0;
if (idx >= N) {
for(size_t i=idx-N; i < idx; i++) {
val = func(val, buf[i]);
}
}else{
//first chunk
for(size_t i=F64_ARRAY_SIZE(buf)-(N-idx); i < buf.size(); i++) {
val = func(val, buf[i]);
}
//second chunk
for(int i=0; i < idx; i++) {
val = func(val, buf[i]);
}
}
return val;
}
private:
std::array<float, BUFSIZE> buf{};
size_t idx=0;
};
// /**
// * Calculate the Root Mean Square of a signal over a window of time
// * This is a good measurement of the amount of power in a signal
// */
// template<size_t BUFSIZE>
// class maxiRMSLite {
// public:
// maxiRMS();
// /*!Configure the analyser \param maxLength The maximum length of time to analyse (ms) \param windowSize The size of the window of time to analyse initially (ms, <= maxLength) */
// void setup(float maxLength, float windowSize) {
// buf.setup(maxiConvert::msToSamps(maxLength));
// setWindowSize(windowSize);
// }
// /*!Set the size of the analysis window \param newWindowSize the size of the analysis window (in ms). Large values will smooth out the measurement, and make it less responsive to transients*/
// void setWindowSize(float newWindowSize) {
// size_t windowSizeInSamples = maxiConvert::msToSamps(newWindowSize);
// if (windowSizeInSamples <= buf.size() && windowSizeInSamples > 0) {
// windowSize = windowSizeInSamples;
// windowSizeInv = 1.f / static_cast<float>(windowSize);
// }
// runningRMS = 0;
// }
// /*!Find out the size of the analysis window (in ms)*/
// float getWindowSize() {
// return maxiConvert::sampsToMs(windowSize);
// }
// /*Analyse the signal \param signal a signal \returns RMS*/
// float play(float signal) {
// float sigPow2 = (signal * signal);
// runningRMS -= buf.tail(windowSize);
// buf.push(sigPow2);
// runningRMS += sigPow2;
// return sqrtf(runningRMS * windowSizeInv);
// }
// private:
// maxiRingBuf<BUFSIZE> buf;
// size_t windowSize=BUFSIZE; // in samples
// float windowSizeInv=1.f/BUFSIZE;
// float runningRMS=0;
// };
class maxiDynamicsLite {
public:
enum ANALYSERS {PEAK, RMS};
static constexpr float maxRMSSizeMS = 300.f;
maxiDynamicsLite() {
//define detector functions
inputPeak = [](float sig) {
return abs(sig);
};
rms.setup(maxRMSSizeMS,300);
inputRMS = [&](float sig) {
return rms.play(sig);
};
//default RMS
inputAnalyser = inputRMS;
//setup envelopes
arEnvHigh.setup(10,0,1.f,10.f, maxiSettings::sampleRate);
arEnvLow.setup(10,0,1.f,10.f, maxiSettings::sampleRate);
lookAheadDelay.setup(maxiSettings::sampleRate * 0.1); //max 0.1s
}
/**
* This functions compands the signal, providing download compression or upward expansion above an upper thresold, and
* upward compression or downward expansion below a lower threshold.
* \param sig The input signal to be companded
* \param control This signal is used to trigger the compander. Use it for sidechaining, or if no sidechain is needed, use the same signal for this and the input signal
* \param thresholdHigh The high threshold, in Dbs
* \param ratioHigh The ratio for companding above the high threshold
* \param kneeHigh The size of the knee for companding above the high threshold (in Dbs)
* \param thresholdLow The low threshold, in Dbs
* \param ratioLow The ratio for companding below the low threshold
* \param kneeLow The size of the knee for companding below the low threshold (in Dbs)
* \returns a companded signal
*/
__attribute__((always_inline)) __attribute__((hot)) float play(float sig, float control,
float thresholdHigh, float ratioHigh, float kneeHigh,
float thresholdLow, float ratioLow, float kneeLow
) {
const float inputEnv = inputAnalyser(control) + 0.00001f; //avoid log of zero
const float controlDB = maxiConvert::ampToDbs(inputEnv);
float outDB = controlDB;
const float halfKneeHigh = kneeHigh * 0.5f;
//companding above the high threshold
if (ratioHigh > 0) {
if (kneeHigh > 0) {
float lowerKnee = thresholdHigh - (kneeHigh*0.5f);
float higherKnee = thresholdHigh +(kneeHigh*0.5f);
//attack/release
float envRatio = 1.f;
if (controlDB >= lowerKnee) {
arEnvHigh.triggerIfReady(1.f);
float envVal = arEnvHigh.play();
envRatio = envToRatio(envVal, ratioHigh);
}else {
arEnvHigh.release();
}
if ((controlDB >= lowerKnee) && (controlDB < higherKnee)) {
float kneeHighOut = ((higherKnee - thresholdHigh) / envRatio) + thresholdHigh;
float kneeRange = (kneeHighOut - lowerKnee);
float t = (controlDB - lowerKnee) / kneeHigh;
//bezier on x only
float curve = ratioHigh > 1.f ? 0.8f : 0.2f;
float kneex = (2.f * (1.f-t) * t * curve) + (t*t);
outDB = lowerKnee + (kneex * kneeRange);
}
else if (controlDB >= higherKnee) {
outDB = ((controlDB - thresholdHigh) / envRatio) + thresholdHigh;
}else{
outDB = controlDB;
}
}
else {
//no knee
if (controlDB > thresholdHigh) {
arEnvHigh.trigger(1.f);
}else {
arEnvHigh.release();
}
float envVal = arEnvHigh.play();
// const float envVal = arEnvHigh.play(controlDB > thresholdHigh ? 1.f : 0.f);
const float envRatio = envToRatio(envVal, ratioHigh);
outDB = ((controlDB - thresholdHigh) / envRatio) + thresholdHigh;
}
}
// //companding below the low threshold
// if (ratioLow > 0) {
// if (kneeLow > 0) {
// float lowerKnee = thresholdLow - (kneeLow*0.5f);
// float higherKnee = thresholdLow +(kneeLow*0.5f);
// //attack/release
// float envRatio = 1;
// if (controlDB < lowerKnee) {
// float envVal = arEnvLow.play(1.f);
// envRatio = envToRatio(envVal, ratioLow);
// }else {
// float envVal = arEnvLow.play(-1.f);
// }
// if ((controlDB >= lowerKnee) && (controlDB < higherKnee)) {
// float kneeLowOut = thresholdLow - ((thresholdLow-lowerKnee) / ratioLow);
// float kneeRange = (higherKnee - kneeLowOut);
// float t = (controlDB - lowerKnee) / kneeLow;
// //bezier on x only
// float curve = ratioLow > 1.f ? 0.2f : 0.8f;
// float kneex = (2.f * (1.f-t) * t * curve) + (t*t);
// outDB = kneeLowOut + (kneex * kneeRange);
// }
// else if (controlDB < lowerKnee) {
// outDB = thresholdLow - ((thresholdLow-controlDB) / ratioLow);
// }
// }
// else {
// //no knee
// if (controlDB < thresholdLow) {
// float envVal = arEnvLow.play(1.f);
// // float envRatio = envToRatio(envVal, ratioLow);
// outDB = thresholdLow - ((thresholdLow-controlDB) / ratioLow);
// }else {
// float envVal = arEnvLow.play(-1.f);
// outDB = maxiConvert::ampToDbs(fabsf(sig));
// }
// }
// }
//scale the signal according to the amount of compansion on the control signal
float outAmp = maxiConvert::dbsToAmp(outDB);
// float ctrlAmp = maxiConvert::dbsToAmp(controlDB);
float sigOut = sig;
if (outAmp > 0.f) {
if (lookAheadSize > 0.f) {
lookAheadDelay.push(sig);
sigOut = lookAheadDelay.tail(lookAheadSize);
}
// sigOut = sigOut * fabsf(control / outAmp);
float gainReduction = outAmp / inputEnv;
sigOut = sig * gainReduction;
// PERIODIC_DEBUG(1000,
// Serial.printf("%f %f %f %f %f\n",controlDB, outDB, control, outAmp, gainReduction);
// )
}else{
// printf("Warning: maxiDynamicsLite output amplitude is zero or negative!\n");
}
return sigOut;
}
/**
* Compress a signal (using downward compression)
* \param sig The input signal to be compressed
* \param threshold The threshold, in Dbs
* \param ratio The compression ratio (>1 provides compression, <1 provides expansion)
* \param knee The size of the knee (in Dbs)
* \returns a compressed signal
*/
__attribute__((always_inline)) __attribute__((hot)) float compress(float sig, float threshold, float ratio, float knee) {
return play(sig, sig, threshold, ratio, knee, 0.f, 0.f, 0.f);
}
/**
* Compress a signal with sidechaining (using downward compression)
* \param sig The input signal to be compressed
* \param control The sidechain signal
* \param threshold The threshold, in Dbs
* \param ratio The compression ratio (>1 provides compression, <1 provides expansion)
* \param knee The size of the knee (in Dbs)
* \returns a compressed signal
*/
float sidechainCompress(float sig, float control, float threshold, float ratio, float knee) {
return play(sig, control, threshold, ratio, knee, 0, 0, 0);
}
/**
* Compand a signal, using detection above a threshold (provides downward compression or upward expansion)
* \param sig The input signal to be compressed
* \param control The sidechain signal
* \param threshold The threshold, in Dbs
* \param ratio The compression ratio (>1 provides compression, <1 provides expansion)
* \param knee The size of the knee (in Dbs)
* \returns a companded signal
*/
float compandAbove(float sig, float control, float threshold, float ratio, float knee) {
return play(sig, control, threshold, ratio, knee, 0, 0, 0);
}
/**
* Compand a signal, using detection below a threshold (provides upward compression or downward expansion)
* \param sig The input signal to be compressed
* \param control The sidechain signal
* \param threshold The threshold, in Dbs
* \param ratio The compression ratio (>1 provides compression, <1 provides expansion)
* \param knee The size of the knee (in Dbs)
* \returns a companded signal
*/
float compandBelow(float sig, float control, float threshold, float ratio, float knee) {
return play(sig, control, 0, 0, 0, threshold, ratio, knee);
}
/**
* Set the attack time for the high threshold. This is the amount of time over which the ratio moves from 1 to its full value, following the input analyser going over the threshold.
* \param attack The attack time (in milliseconds)
*/
__attribute__((always_inline)) void setAttackHigh(float attack) {
arEnvHigh.setAttackTime(attack, maxiSettings::sampleRate);
}
/**
* Set the release time for the high threshold. This is the amount of time over which the ratio moves from its full value to 1, following the input analyser going under the threshold.
* \param release The release time (in milliseconds)
*/
__attribute__((always_inline)) void setReleaseHigh(float release) {
arEnvHigh.setReleaseTime(release, maxiSettings::sampleRate);
}
/**
* Set the attack time for the low threshold. This is the amount of time over which the ratio moves from 1 to its full value, following the input analyser going under the threshold.
* \param attack The attack time (in milliseconds)
*/
__attribute__((always_inline)) void setAttackLow(float attack) {
arEnvLow.setAttackTime(attack, maxiSettings::sampleRate);
}
/**
* Set the release time for the low threshold. This is the amount of time over which the ratio moves from its full value to 1, following the input analyser going over the threshold.
* \param release The release time (in milliseconds)
*/
__attribute__((always_inline)) void setReleaseLow(float release) {
arEnvLow.setReleaseTime(release, maxiSettings::sampleRate);
}
/**
* The look ahead creates a delay on the input signal, meaning that that the signal is compressed according to event that have already happened in the control signal. This can be useful for limiting and catching fast transients.
* \param length The amount of time the compressor looks ahead (in milliseconds)
*/
void setLookAhead(float length) {
lookAheadSize = maxiConvert::msToSamps(length);
lookAheadSize = std::min(lookAheadSize, lookAheadDelay.size());
}
/**
* \returns the look ahead time (in milliseconds)
*/
float getLookAhead() {
return maxiConvert::sampsToMs(lookAheadSize);
}
/**
* Set the size of the RMS window. Longer times give a slower response
* \param winSize The size of the window (in milliseconds)
*/
void setRMSWindowSize(float winSize) {
rms.setWindowSize(std::min(winSize, maxRMSSizeMS));
}
/**
* Set the method by which the compressor analyses the control input
* \mode maxiDynamics::PEAK for peak analysis, maxiDynamics::RMS for rms analysis
*/
void setInputAnalyser(ANALYSERS mode) {
if (mode == PEAK) {
inputAnalyser = inputPeak;
}else{
inputAnalyser = inputRMS;
}
}
private:
ADSRLite arEnvHigh, arEnvLow;
maxiRingBuf lookAheadDelay;
size_t lookAheadSize = 0;
maxiRMS rms;
std::function<float(float)> inputPeak;
std::function<float(float)> inputRMS;
std::function<float(float)> inputAnalyser;
// maxiPoll poll;
//mapping from attack/release envelope to ratio
inline float envToRatio(float envVal, float ratio) {
float envRatio = 1.f;
if (ratio > 1.f) {
envRatio = 1.f + ((ratio-1.f) * envVal);
}else {
envRatio = 1.f - ((1.f-ratio) * envVal);
}
return envRatio;
}
};
template<size_t NPARAMS=24>
class ChannelStripAudioApp : public AudioAppBase<NPARAMS>
{
public:
static constexpr size_t kN_Params = NPARAMS;
static constexpr size_t nVoiceSpaces=3;
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;
}
}
ChannelStripAudioApp() : AudioAppBase<NPARAMS>() {
auto voiceSpaceBasic = [this](const std::array<float, NPARAMS>& params) {
VOICE_SPACE_CHSTRIP_BASIC_BODY
};
auto voiceSpaceMaleVox = [this](const std::array<float, NPARAMS>& params) {
VOICE_SPACE_CHSTRIP_MALE_VOX_BODY
};
auto voiceSpaceFemaleVox = [this](const std::array<float, NPARAMS>& params) {
VOICE_SPACE_CHSTRIP_FEMALE_VOX_BODY
};
voiceSpaces[0] = {"WannabeNeve66", voiceSpaceBasic};
voiceSpaces[1] = {"MaleVox", voiceSpaceBasic};
voiceSpaces[2] = {"FemaleVox", voiceSpaceBasic};
currentVoiceSpace = voiceSpaces[0].mappingFunction;
};
__attribute__((hot)) stereosample_t __force_inline Process(const stereosample_t x) override
{
float y = x[0];
y = tanhf(y * preGain);
y = inLowPass.loresChamberlain(y, inLowPassCutoff, 1.f);
y = inHighPass.hiresChamberlain(y, inHighPassCutoff, 1.f);
y = lowshelf.play(y);
y = peak0.play(y);
y = peak1.play(y);
y = highshelf.play(y);
y = dyn.compress(y, compThreshold, compRatio, 0.f);
y = tanhf(y * postGain);
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;
dyn.setLookAhead(0);
dyn.setAttackHigh(50);
dyn.setReleaseHigh(200);
}
__attribute__((always_inline)) void ProcessParams(const std::array<float, NPARAMS>& params)
{
currentVoiceSpace(params);
dyn.setAttackHigh(compAttack);
dyn.setReleaseHigh(compRelease);
peak0.set(maxiBiquad::PEAK, peak0Freq, peak0Q, peak0Gain);
peak1.set(maxiBiquad::PEAK, peak1Freq, peak1Q, peak1Gain);
lowshelf.set(maxiBiquad::LOWSHELF, 100.f, 2.f, 3.f);
highshelf.set(maxiBiquad::HIGHSHELF, 1000.f, 2.f, 3.f);
}
protected:
float sampleRatef = maxiSettings::getSampleRate();
float preGain=1.f;
float postGain=1.f;
maxiFilter inHighPass, inLowPass;
float inLowPassCutoff=200.f;
float inHighPassCutoff=2000.f;
float compThreshold=0.f;
float compRatio = 1.f;
float compAttack=10.f;
float compRelease=50.f;
float peak0Freq=100.f;
float peak0Q=1.f;
float peak0Gain=1.f;
float peak1Freq=1000.f;
float peak1Q=1.f;
float peak1Gain=1.f;
float lowShelfFreq=1000.f;
float lowShelfQ=1.f;
float lowShelfGain=1.f;
float highShelfFreq=1000.f;
float highShelfQ=1.f;
float highShelfGain=1.f;
maxiDynamicsLite dyn;
maxiBiquad lowshelf;
maxiBiquad peak0;
maxiBiquad peak1;
maxiBiquad highshelf;
};
#endif

452
MEMLNaut-NISPS.ino Normal file
View file

@ -0,0 +1,452 @@
//hardware
#include "src/memllib/utils/perf.hpp"
#include "src/memllib/interface/MIDIInOut.hpp"
#include "src/memllib/audio/AudioDriver.hpp"
#include "src/memllib/hardware/memlnaut/MEMLNaut.hpp"
#include "hardware/structs/bus_ctrl.h"
#include <memory>
//sound
#include "src/memllib/audio/AudioAppBase.hpp"
#include "PAFSynthAudioApp.hpp"
#include "ChannelStripAudioApp.hpp"
//interface
#include "src/memllib/examples/InterfaceRL.hpp"
#include "src/memllib/hardware/memlnaut/display/XYPadView.hpp"
#include "src/memllib/hardware/memlnaut/display/MessageView.hpp"
#include "src/memllib/hardware/memlnaut/display/VoiceSpaceSelectView.hpp"
#define INTERFACE_TYPE InterfaceRL
#include <concepts>
template<typename T>
concept MEMLNautMode = requires(T proc) {
{proc.getHelpTitle()} -> std::same_as<String>;
{proc.getNParams()} -> std::same_as<size_t>;
{proc.setVoiceSpace(size_t{})} -> std::same_as<void>;
{proc.setupMIDI(std::shared_ptr<MIDIInOut>{})} -> std::same_as<void>;
{proc.addViews()} -> std::same_as<void>;
{proc.Setup(float{}, std::shared_ptr<InterfaceBase>{})} -> std::same_as<void>;
{proc.loop()} -> std::same_as<void>;
{proc.getVoiceSpaceList()} -> std::same_as<std::span<String>>;
{proc.process(stereosample_t{})} -> std::same_as<stereosample_t>;
};
class MEMLNautModePAFSynth {
public:
inline static PAFSynthAudioApp<> audioAppPAFSynth;
std::array<String, PAFSynthAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "PAF Synth Mode";
}
size_t getNParams() {
return PAFSynthAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppPAFSynth.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppPAFSynth.Process(x);
}
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface) {
audioAppPAFSynth.Setup(sample_rate, interface);
voiceSpaceList = audioAppPAFSynth.getVoiceSpaceNames();
}
__force_inline void loop() {
audioAppPAFSynth.loop();
}
std::shared_ptr<MIDIInOut> midi_interf;
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
midi_interf = new_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(&audioAppPAFSynth.qMIDINoteOn, &midimsg);
}else{
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg);
}
// Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon);
});
// Serial.println("MIDI note callback set.");
}
void addViews() {
std::shared_ptr<XYPadView> noteTrigView = std::make_shared<XYPadView>("Play", TFT_SILVER);
// Cache MIDI notes being echoed
static bool is_playing_note = false;
static uint8_t last_note_number = 0;
noteTrigView->SetOnTouchCallback([this](float x, float y) {
// Serial.printf("Note trigger at: %.2f, %.2f\n", x, y);
// If a note is already playing, stop it
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(&audioAppPAFSynth.qMIDINoteOn, &midimsg);
midi_interf->sendNoteOn(midimsg[0], midimsg[1]);
last_note_number = midimsg[0];
is_playing_note = true; // Set flag to indicate a note is playing
// Serial.printf("sending %d %d\n",midimsg[0], midimsg[1]);
});
noteTrigView->SetOnTouchReleaseCallback([this](float x, float y) {
// Serial.printf("Note release at: %.2f, %.2f\n", x, y);
uint8_t midimsg[2] = {last_note_number,0};
queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg);
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false; // Reset flag when note is released
});
MEMLNaut::Instance()->disp->AddView(noteTrigView);
};
};
class MEMLNautModeChannelStrip {
public:
ChannelStripAudioApp<> audioAppChannelStrip;
std::array<String, ChannelStripAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "Channel Strip Mode";
}
size_t getNParams() {
return ChannelStripAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppChannelStrip.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppChannelStrip.Process(x);
}
void setupMIDI(std::shared_ptr<MIDIInOut> midi_interf) {
}
void addViews() {
};
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface) {
audioAppChannelStrip.Setup(sample_rate, interface);
voiceSpaceList = audioAppChannelStrip.getVoiceSpaceNames();
}
__force_inline void loop() {
audioAppChannelStrip.loop();
}
};
MEMLNautModeChannelStrip AUDIO_MEM channelStripMode;
// MEMLNautModePAFSynth AUDIO_MEM pafSynthMode;
MEMLNautMode auto* AUDIO_MEM currentMode = &channelStripMode;
#define APP_SRAM __not_in_flash("app")
bool core1_disable_systick = true;
bool core1_separate_stack = true;
uint32_t get_rosc_entropy_seed(int bits) {
uint32_t seed = 0;
for (int i = 0; i < bits; ++i) {
// Wait for a bit of time to allow jitter to accumulate
busy_wait_us_32(5);
// Pull LSB from ROSC rand output
seed <<= 1;
seed |= (rosc_hw->randombit & 1);
}
return seed;
}
// Global objects
std::shared_ptr<INTERFACE_TYPE> APP_SRAM interface;
std::shared_ptr<MIDIInOut> APP_SRAM midi_interf;
// Statically allocated, properly aligned storage in AUDIO_MEM for objects
// alignas(PAFSynthAudioApp<>) char AUDIO_MEM audio_app_mem[sizeof(PAFSynthAudioApp<>)];
// std::shared_ptr<PAFSynthAudioApp<> > __scratch_y("audio") audio_app;
// alignas(ChannelStripAudioApp<>) char AUDIO_MEM audio_app_mem_chstrip[sizeof(ChannelStripAudioApp<>)];
// std::shared_ptr<ChannelStripAudioApp<> > __scratch_y("audio") audio_app_chstrip;
//preallocate ChannelStripAudioApp
// static ChannelStripAudioApp<> AUDIO_MEM audioAppChannelStrip;
// Inter-core communication
volatile bool APP_SRAM core_0_ready = false;
volatile bool APP_SRAM core_1_ready = false;
volatile bool APP_SRAM serial_ready = false;
volatile bool APP_SRAM interface_ready = false;
// We're only bound to the joystick inputs (x, y, rotate)
constexpr size_t kN_InputParams = 3;
// Add these macros near other globals
#define MEMORY_BARRIER() __sync_synchronize()
#define WRITE_VOLATILE(var, val) \
do { \
MEMORY_BARRIER(); \
(var) = (val); \
MEMORY_BARRIER(); \
} while (0)
#define READ_VOLATILE(var) ({ MEMORY_BARRIER(); typeof(var) __temp = (var); MEMORY_BARRIER(); __temp; })
void setup() {
set_sys_clock_khz(AudioDriver::GetSysClockSpeed(), true);
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS | BUSCTRL_BUS_PRIORITY_PROC1_BITS;
uint32_t seed = get_rosc_entropy_seed(32);
srand(seed);
Serial.begin(115200);
// while (!Serial) {}
Serial.println("Serial initialised.");
WRITE_VOLATILE(serial_ready, true);
// Setup board
MEMLNaut::Initialize();
pinMode(33, OUTPUT);
{
auto temp_interface = std::make_shared<INTERFACE_TYPE>();
// temp_interface->setup(kN_InputParams, PAFSynthAudioApp<>::kN_Params);
// temp_interface->setup(kN_InputParams, ChannelStripAudioApp<>::kN_Params);
temp_interface->setup(kN_InputParams, currentMode->getNParams());
MEMORY_BARRIER();
interface = temp_interface;
MEMORY_BARRIER();
}
// Setup interface with memory barrier protection
WRITE_VOLATILE(interface_ready, true);
// Bind interface after ensuring it's fully initialized
interface->bindInterface(false);
Serial.println("Bound interface to MEMLNaut.");
midi_interf = std::make_shared<MIDIInOut>();
// midi_interf->Setup(PAFSynthAudioApp<>::kN_Params);
midi_interf->Setup(16);
// midi_interf->Setup(0);
midi_interf->SetMIDISendChannel(1);
Serial.println("MIDI setup complete.");
if (midi_interf) {
currentMode->setupMIDI(midi_interf);
interface->bindMIDI(midi_interf);
}
WRITE_VOLATILE(core_0_ready, true);
while (!READ_VOLATILE(core_1_ready)) {
MEMORY_BARRIER();
delay(1);
}
std::shared_ptr<VoiceSpaceSelectView> voiceSpaceSelectView;
voiceSpaceSelectView = std::make_shared<VoiceSpaceSelectView>("Voice Spaces");
MEMLNaut::Instance()->disp->InsertViewAfter(interface->rlStatsView, voiceSpaceSelectView);
// voiceSpaceSelectView->setOptions(voiceSpaceList); //set by core 1 on startup
// voiceSpaceSelectView->setOptions(voiceSpaceList_chstrip); //set by core 1 on startup
voiceSpaceSelectView->setOptions(currentMode->getVoiceSpaceList()); //set by core 1 on startup
voiceSpaceSelectView->setNewVoiceCallback(
[](size_t idx) {
// Serial.println(idx);
// audio_app_chstrip->setVoiceSpace(idx);
// audioAppChannelStrip.setVoiceSpace(idx);
// audio_app->setVoiceSpace(idx);
currentMode->setVoiceSpace(idx);
}
);
currentMode->addViews();
std::shared_ptr<MessageView> helpView = std::make_shared<MessageView>("Help");
// helpView->post("PAF synth NISPS");
String title =currentMode->getHelpTitle();
helpView->post(title);
helpView->post("TA: Down: Clear replay memory");
helpView->post("MA: Up: Randomise / Down: Jolt ");
helpView->post("MB: Up: Positive reward");
helpView->post("MB: Down: Negative reward");
helpView->post("X: Learning rate");
helpView->post("Y: Reward Scale");
helpView->post("Z: Exploration noise");
helpView->post("Joystick: Explore / SW: Drag sound");
MEMLNaut::Instance()->disp->AddView(helpView);
MEMLNaut::Instance()->addSystemInfoView();
Serial.println("Finished initialising core 0.");
}
PERF_DECLARE(MLSTATS);
#define ML_INFERENCE_PERIOD_US 5000
void loop() {
PERIODIC_RUN_US(
PERF_BEGIN(MLSTATS);
MEMLNaut::Instance()->loop();
PERF_END(MLSTATS);
, ML_INFERENCE_PERIOD_US)
PERIODIC_RUN_US(
static size_t blip_counter=0;
if (blip_counter++ > 10) {
blip_counter = 0;
Serial.println(".");
// Blink LED
digitalWrite(33, HIGH);
constexpr float audioHeadroomMul = 1.0 / (1000000 * 48.0 / kSampleRate);
Serial.printf("ml: %d, aud: %d, q: %f\n", PERF_GET_MEAN(MLSTATS), AUDIOLOOP_MEAN, AUDIOLOOP_MEAN * audioHeadroomMul);
} else {
// Un-blink LED
digitalWrite(33, LOW);
}
, 100000)
}
void AUDIO_FUNC(audio_block_callback)(float in[][kBufferSize], float out[][kBufferSize], size_t n_channels, size_t n_frames) {
// Serial.println(in[0][0]);
for (size_t i = 0; i < n_frames; ++i) {
stereosample_t x{
in[0][i],
in[1][i]
},
y;
y = currentMode->process(x);
out[0][i] = y.L;
out[1][i] = y.R;
}
// Serial.println(in[0][0]);
}
void setup1() {
while (!READ_VOLATILE(serial_ready)) {
MEMORY_BARRIER();
delay(1);
}
while (!READ_VOLATILE(interface_ready)) {
MEMORY_BARRIER();
delay(1);
}
// Create audio app with memory barrier protection
{
// PAFSynthAudioApp<>* audio_raw = new (audio_app_mem) PAFSynthAudioApp<>();
// audio_raw->Setup(AudioDriver::GetSampleRate(), interface);
// // shared_ptr with custom deleter calling only the destructor (control block still allocates)
// auto audio_deleter = [](PAFSynthAudioApp<>* p) {
// if (p) p->~PAFSynthAudioApp<>();
// };
// std::shared_ptr<PAFSynthAudioApp<>> temp_audio_app(audio_raw, audio_deleter);
// ChannelStripAudioApp<>* audio_raw = new (audio_app_mem_chstrip) ChannelStripAudioApp<>();
// audio_raw->Setup(AudioDriver::GetSampleRate(), interface);
// // shared_ptr with custom deleter calling only the destructor (control block still allocates)
// auto audio_deleter = [](ChannelStripAudioApp<>* p) {
// if (p) p->~ChannelStripAudioApp<>();
// };
// std::shared_ptr<ChannelStripAudioApp<>> temp_audio_app(audio_raw, audio_deleter);
// MEMORY_BARRIER();
// audio_app_chstrip = temp_audio_app;
// MEMORY_BARRIER();
}
// audioAppChannelStrip.Setup(AudioDriver::GetSampleRate(), interface);
currentMode->Setup(AudioDriver::GetSampleRate(), interface);
AudioDriver::SetBlockCallback(audio_block_callback);
// AudioDriver::SetBlockCallback(currentMode->getAudioCallBack());
// Start audio driver
AudioDriver::Setup();
// AudioDriver::SetBlockCallback(audio_block_callback);
// voiceSpaceList = audio_app->getVoiceSpaceNames();
// voiceSpaceList_chstrip = audio_app_chstrip->getVoiceSpaceNames();
// voiceSpaceList_chstrip = audioAppChannelStrip.getVoiceSpaceNames();
WRITE_VOLATILE(core_1_ready, true);
while (!READ_VOLATILE(core_0_ready)) {
MEMORY_BARRIER();
delay(1);
}
Serial.println("Finished initialising core 1.");
}
void loop1() {
// Audio app parameter processing loop
PERIODIC_RUN_US(
// audio_app_chstrip->loop();
// audioAppChannelStrip.loop();
currentMode->loop();
// audio_app->loop();
, ML_INFERENCE_PERIOD_US)
PERIODIC_RUN_US(
midi_interf->Poll();
, 10000)
// #if 1 //test ARP
// PERIODIC_RUN_US(
// static size_t arpCount=0;
// static size_t noteIndex=30;
// , 100000)
// #endif
}

View file

@ -1,314 +0,0 @@
#include "src/memllib/utils/perf.hpp"
#include "src/memllib/interface/MIDIInOut.hpp"
#include "src/memllib/audio/AudioAppBase.hpp"
#include "src/memllib/audio/AudioDriver.hpp"
#include "src/memllib/hardware/memlnaut/MEMLNaut.hpp"
#include <memory>
#include "hardware/structs/bus_ctrl.h"
#include "PAFSynthAudioApp.hpp"
#include "src/memllib/examples/InterfaceRL.hpp"
#include "src/memllib/hardware/memlnaut/display/XYPadView.hpp"
#include "src/memllib/hardware/memlnaut/display/MessageView.hpp"
#include "src/memllib/hardware/memlnaut/display/VoiceSpaceSelectView.hpp"
#define INTERFACE_TYPE InterfaceRL
#define APP_SRAM __not_in_flash("app")
bool core1_disable_systick = true;
bool core1_separate_stack = true;
uint32_t get_rosc_entropy_seed(int bits) {
uint32_t seed = 0;
for (int i = 0; i < bits; ++i) {
// Wait for a bit of time to allow jitter to accumulate
busy_wait_us_32(5);
// Pull LSB from ROSC rand output
seed <<= 1;
seed |= (rosc_hw->randombit & 1);
}
return seed;
}
// Global objects
std::shared_ptr<INTERFACE_TYPE> APP_SRAM interface;
std::shared_ptr<MIDIInOut> APP_SRAM midi_interf;
// Statically allocated, properly aligned storage in AUDIO_MEM for objects
alignas(PAFSynthAudioApp<>) char AUDIO_MEM audio_app_mem[sizeof(PAFSynthAudioApp<>)];
std::shared_ptr<PAFSynthAudioApp<> > __scratch_y("audio") audio_app;
// Inter-core communication
volatile bool APP_SRAM core_0_ready = false;
volatile bool APP_SRAM core_1_ready = false;
volatile bool APP_SRAM serial_ready = false;
volatile bool APP_SRAM interface_ready = false;
std::array<String, PAFSynthAudioApp<>::nVoiceSpaces> voiceSpaceList;
// We're only bound to the joystick inputs (x, y, rotate)
constexpr size_t kN_InputParams = 3;
// Add these macros near other globals
#define MEMORY_BARRIER() __sync_synchronize()
#define WRITE_VOLATILE(var, val) \
do { \
MEMORY_BARRIER(); \
(var) = (val); \
MEMORY_BARRIER(); \
} while (0)
#define READ_VOLATILE(var) ({ MEMORY_BARRIER(); typeof(var) __temp = (var); MEMORY_BARRIER(); __temp; })
// struct repeating_timer APP_SRAM timerDisplay;
// inline bool __not_in_flash_func(displayUpdate)(__unused struct repeating_timer *t) {
// scr.update();
// return true;
// }
void setup() {
set_sys_clock_khz(AudioDriver::GetSysClockSpeed(), true);
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS | BUSCTRL_BUS_PRIORITY_PROC1_BITS;
uint32_t seed = get_rosc_entropy_seed(32);
srand(seed);
Serial.begin(115200);
// while (!Serial) {}
Serial.println("Serial initialised.");
WRITE_VOLATILE(serial_ready, true);
// Setup board
MEMLNaut::Initialize();
pinMode(33, OUTPUT);
{
auto temp_interface = std::make_shared<INTERFACE_TYPE>();
temp_interface->setup(kN_InputParams, PAFSynthAudioApp<>::kN_Params);
MEMORY_BARRIER();
interface = temp_interface;
MEMORY_BARRIER();
}
// Setup interface with memory barrier protection
WRITE_VOLATILE(interface_ready, true);
// Bind interface after ensuring it's fully initialized
interface->bindInterface(false);
Serial.println("Bound interface to MEMLNaut.");
midi_interf = std::make_shared<MIDIInOut>();
// midi_interf->Setup(PAFSynthAudioApp<>::kN_Params);
midi_interf->Setup(16);
// midi_interf->Setup(0);
midi_interf->SetMIDISendChannel(1);
Serial.println("MIDI setup complete.");
if (midi_interf) {
midi_interf->SetNoteCallback([interface](bool noteon, uint8_t note_number, uint8_t vel_value) {
if (noteon) {
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audio_app->qMIDINoteOn, &midimsg);
}else{
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audio_app->qMIDINoteOff, &midimsg);
}
// Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon);
});
// Serial.println("MIDI note callback set.");
interface->bindMIDI(midi_interf);
}
WRITE_VOLATILE(core_0_ready, true);
while (!READ_VOLATILE(core_1_ready)) {
MEMORY_BARRIER();
delay(1);
}
std::shared_ptr<XYPadView> noteTrigView = std::make_shared<XYPadView>("Play", TFT_SILVER);
// Cache MIDI notes being echoed
static bool is_playing_note = false;
static uint8_t last_note_number = 0;
noteTrigView->SetOnTouchCallback([](float x, float y) {
// Serial.printf("Note trigger at: %.2f, %.2f\n", x, y);
if (audio_app) {
// If a note is already playing, stop it
if (is_playing_note) {
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false;
}
int 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(&audio_app->qMIDINoteOn, &midimsg);
midi_interf->sendNoteOn(midimsg[0], midimsg[1]);
last_note_number = midimsg[0];
is_playing_note = true; // Set flag to indicate a note is playing
// Serial.printf("sending %d %d\n",midimsg[0], midimsg[1]);
}
});
noteTrigView->SetOnTouchReleaseCallback([](float x, float y) {
// Serial.printf("Note release at: %.2f, %.2f\n", x, y);
if (audio_app) {
uint8_t midimsg[2] = {last_note_number,0};
queue_try_add(&audio_app->qMIDINoteOff, &midimsg);
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false; // Reset flag when note is released
}
});
std::shared_ptr<VoiceSpaceSelectView> voiceSpaceSelectView;
voiceSpaceSelectView = std::make_shared<VoiceSpaceSelectView>("Voice Spaces");
MEMLNaut::Instance()->disp->InsertViewAfter(interface->rlStatsView, voiceSpaceSelectView);
voiceSpaceSelectView->setOptions(voiceSpaceList); //set by core 1 on startup
voiceSpaceSelectView->setNewVoiceCallback(
[](size_t idx) {
// Serial.println(idx);
audio_app->setVoiceSpace(idx);
}
);
MEMLNaut::Instance()->disp->AddView(noteTrigView);
std::shared_ptr<MessageView> helpView = std::make_shared<MessageView>("Help");
helpView->post("PAF synth NISPS");
helpView->post("TA: Down: Clear replay memory");
helpView->post("MA: Up: Randomise / Down: Jolt ");
helpView->post("MB: Up: Positive reward");
helpView->post("MB: Down: Negative reward");
helpView->post("X: Learning rate");
helpView->post("Y: Reward Scale");
helpView->post("Z: Exploration noise");
helpView->post("Joystick: Explore / SW: Drag sound");
MEMLNaut::Instance()->disp->AddView(helpView);
MEMLNaut::Instance()->addSystemInfoView();
Serial.println("Finished initialising core 0.");
}
PERF_DECLARE(MLSTATS);
#define ML_INFERENCE_PERIOD_US 5000
void loop() {
PERIODIC_RUN_US(
PERF_BEGIN(MLSTATS);
MEMLNaut::Instance()->loop();
PERF_END(MLSTATS);
, ML_INFERENCE_PERIOD_US)
PERIODIC_RUN_US(
static size_t blip_counter=0;
if (blip_counter++ > 10) {
blip_counter = 0;
Serial.println(".");
// Blink LED
digitalWrite(33, HIGH);
constexpr float audioHeadroomMul = 1.0 / (1000000 * 48.0 / kSampleRate);
Serial.printf("ml: %d, aud: %d, q: %f\n", PERF_GET_MEAN(MLSTATS), AUDIOLOOP_MEAN, AUDIOLOOP_MEAN * audioHeadroomMul);
} else {
// Un-blink LED
digitalWrite(33, LOW);
}
, 100000)
}
void AUDIO_FUNC(audio_block_callback)(float in[][kBufferSize], float out[][kBufferSize], size_t n_channels, size_t n_frames) {
for (size_t i = 0; i < n_frames; ++i) {
stereosample_t x{
in[0][i],
in[1][i]
},
y;
// Audio processing
if (audio_app) {
y = audio_app->Process(x);
}
out[0][i] = y.L;
out[1][i] = y.R;
}
}
void setup1() {
while (!READ_VOLATILE(serial_ready)) {
MEMORY_BARRIER();
delay(1);
}
while (!READ_VOLATILE(interface_ready)) {
MEMORY_BARRIER();
delay(1);
}
// Create audio app with memory barrier protection
{
PAFSynthAudioApp<>* audio_raw = new (audio_app_mem) PAFSynthAudioApp<>();
audio_raw->Setup(AudioDriver::GetSampleRate(), interface);
// shared_ptr with custom deleter calling only the destructor (control block still allocates)
auto audio_deleter = [](PAFSynthAudioApp<>* p) {
if (p) p->~PAFSynthAudioApp<>();
};
std::shared_ptr<PAFSynthAudioApp<>> temp_audio_app(audio_raw, audio_deleter);
MEMORY_BARRIER();
audio_app = temp_audio_app;
MEMORY_BARRIER();
}
AudioDriver::SetBlockCallback(audio_block_callback);
// Start audio driver
AudioDriver::Setup();
// AudioDriver::SetBlockCallback(audio_block_callback);
voiceSpaceList = audio_app->getVoiceSpaceNames();
WRITE_VOLATILE(core_1_ready, true);
while (!READ_VOLATILE(core_0_ready)) {
MEMORY_BARRIER();
delay(1);
}
Serial.println("Finished initialising core 1.");
}
void loop1() {
// Audio app parameter processing loop
PERIODIC_RUN_US(
audio_app->loop();
, ML_INFERENCE_PERIOD_US)
PERIODIC_RUN_US(
midi_interf->Poll();
, 10000)
// #if 1 //test ARP
// PERIODIC_RUN_US(
// static size_t arpCount=0;
// static size_t noteIndex=30;
// , 100000)
// #endif
}

View file

@ -14,6 +14,8 @@
#include <span>
#include "voicespaces/VoiceSpaces.hpp"
#include "voicespaces/VoiceSpace1.hpp"
#include "voicespaces/VoiceSpace2.hpp"
#include "voicespaces/VoiceSpacePerc.hpp"
@ -36,15 +38,9 @@ public:
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;
using VoiceSpaceFn = std::function<void(const std::array<float, NPARAMS>&)>;
struct VoiceSpace {
char name[16]="default";
VoiceSpaceFn mappingFunction = nullptr;
};
std::array<VoiceSpace, nVoiceSpaces> voiceSpaces;
std::array<VoiceSpace<NPARAMS>, nVoiceSpaces> voiceSpaces;
VoiceSpaceFn currentVoiceSpace;
VoiceSpaceFn<NPARAMS> currentVoiceSpace;
std::array<String, nVoiceSpaces> getVoiceSpaceNames() {
std::array<String, nVoiceSpaces> names;

View file

@ -0,0 +1,80 @@
#ifndef __VOICE_SPACE_CHSTRIP_BASIC_HPP__
#define __VOICE_SPACE_CHSTRIP_BASIC_HPP__
#define VOICE_SPACE_CHSTRIP_BASIC_BODY \
preGain=0.5f + (params[0] * 4.f); \
inLowPassCutoff = 1000.f + (params[7] * 19000.f); \
inHighPassCutoff = 10.f + (params[8] * params[8] * params[8] * 1990.f); \
compThreshold = params[10] * -30.f; \
compRatio = 1.0f + (params[11] * 11.f); \
compAttack = 0.08f + (params[12] * 50.f); \
compRelease = 50.0f + (params[13] * 1050.f); \
postGain=0.5f + (params[23] * 4.f); \
peak0Freq = 60.f + (params[1] * params[1] * 2940.f); \
peak0Q = 0.6f + (params[5]*4.4f); \
peak0Gain = -18.f + (params[6]*36.f);\
peak1Freq = 300.f + (params[4] * params[4] * 7700.f); \
peak1Q = 0.6f + (params[5]*4.4f); \
peak1Gain = -18.f + (params[6]*36.f);\
lowShelfFreq = 20.f + (params[14] * params[14] * 980.f); \
lowShelfQ = 0.6f + (params[15]*4.4f); \
lowShelfGain = -18.f + (params[16]*36.f);\
highShelfFreq = 1000.f + (params[17] * params[17] * 9000.f); \
highShelfQ = 0.6f + (params[18]*4.4f); \
highShelfGain = -18.f + (params[19]*36.f);\
#define VOICE_SPACE_CHSTRIP_MALE_VOX_BODY \
preGain=0.5f + (params[0] * 4.f); \
inLowPassCutoff = 1000.f + (params[7] * 19000.f); \
inHighPassCutoff = 10.f + (params[8] * params[8] * params[8] * 1990.f); \
compThreshold = params[10] * -30.f; \
compRatio = 2.0f + (params[11] * 6.f); \
compAttack = 0.08f + (params[12] * 50.f); \
compRelease = 50.0f + (params[13] * 500.f); \
postGain=0.5f + (params[23] * 4.f); \
\
lowShelfFreq = 60.f + (params[14] * params[14] * 240.f); \
lowShelfQ = 0.6f + (params[15]*4.4f); \
lowShelfGain = -18.f + (params[16]*36.f);\
\
peak0Freq = 60.f + (params[1] * params[1] * 440.f); \
peak0Q = 0.6f + (params[5]*4.4f); \
peak0Gain = -18.f + (params[6]*36.f);\
\
peak1Freq = 300.f + (params[4] * params[4] * 7700.f); \
peak1Q = 0.6f + (params[5]*4.4f); \
peak1Gain = -18.f + (params[6]*36.f);\
\
highShelfFreq = 1000.f + (params[17] * params[17] * 7000.f); \
highShelfQ = 0.6f + (params[18]*4.4f); \
highShelfGain = -18.f + (params[19]*36.f);\
#define VOICE_SPACE_CHSTRIP_FEMALE_VOX_BODY \
preGain=0.5f + (params[0] * 4.f); \
inLowPassCutoff = 1000.f + (params[7] * 19000.f); \
inHighPassCutoff = 10.f + (params[8] * params[8] * params[8] * 1990.f); \
compThreshold = params[10] * -30.f; \
compRatio = 2.0f + (params[11] * 6.f); \
compAttack = 0.08f + (params[12] * 50.f); \
compRelease = 50.0f + (params[13] * 500.f); \
postGain=0.5f + (params[23] * 4.f); \
\
lowShelfFreq = 120.f + (params[14] * params[14] * 180.f); \
lowShelfQ = 0.6f + (params[15]*4.4f); \
lowShelfGain = -18.f + (params[16]*36.f);\
\
peak0Freq = 120.f + (params[1] * params[1] * 380.f); \
peak0Q = 0.6f + (params[5]*4.4f); \
peak0Gain = -18.f + (params[6]*36.f);\
\
peak1Freq = 300.f + (params[4] * params[4] * 9700.f); \
peak1Q = 0.6f + (params[5]*4.4f); \
peak1Gain = -18.f + (params[6]*36.f);\
\
highShelfFreq = 1000.f + (params[17] * params[17] * 9000.f); \
highShelfQ = 0.6f + (params[18]*4.4f); \
highShelfGain = -18.f + (params[19]*36.f);\
#endif

View file

@ -0,0 +1,17 @@
#ifndef MEML_MEMLNAUT_NISPS_VOICESPACES_VOICESPACES_HPP
#define MEML_MEMLNAUT_NISPS_VOICESPACES_VOICESPACES_HPP
#pragma once
#include <functional>
#include <array>
template<size_t NPARAMS>
using VoiceSpaceFn = std::function<void(const std::array<float, NPARAMS>&)>;
template<size_t NPARAMS>
struct VoiceSpace {
char name[16]="default";
VoiceSpaceFn<NPARAMS> mappingFunction = nullptr;
};
#endif // MEML_MEMLNAUT_NISPS_VOICESPACES_VOICESPACES_HPP