From 81a7cb80b9ce01b1e0686bd86ba08ef759a11b30 Mon Sep 17 00:00:00 2001 From: chriskiefer Date: Wed, 19 Nov 2025 15:17:37 +0000 Subject: [PATCH] memlnautmodes, channelstrip --- .vscode/settings.json | 54 +++ ChannelStripAudioApp.hpp | 557 +++++++++++++++++++++++++++++ MEMLNaut-NISPS.ino | 452 +++++++++++++++++++++++ MEMLNaut-PAF-NISPS.ino | 314 ---------------- PAFSynthAudioApp.hpp | 12 +- voicespaces/ChannelStrip/basic.hpp | 80 +++++ voicespaces/VoiceSpaces.hpp | 17 + 7 files changed, 1164 insertions(+), 322 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 ChannelStripAudioApp.hpp create mode 100644 MEMLNaut-NISPS.ino delete mode 100644 MEMLNaut-PAF-NISPS.ino create mode 100644 voicespaces/ChannelStrip/basic.hpp create mode 100644 voicespaces/VoiceSpaces.hpp diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..154cd4f --- /dev/null +++ b/.vscode/settings.json @@ -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" + } +} \ No newline at end of file diff --git a/ChannelStripAudioApp.hpp b/ChannelStripAudioApp.hpp new file mode 100644 index 0000000..9e25eb1 --- /dev/null +++ b/ChannelStripAudioApp.hpp @@ -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 +#include +#include // 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 +#include "voicespaces/VoiceSpaces.hpp" + +#include "voicespaces/ChannelStrip/basic.hpp" + +template +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; + /** + * 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 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 +// 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(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 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 inputPeak; + std::function inputRMS; + std::function 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 +class ChannelStripAudioApp : public AudioAppBase +{ +public: + static constexpr size_t kN_Params = NPARAMS; + static constexpr size_t nVoiceSpaces=3; + + + std::array, nVoiceSpaces> voiceSpaces; + + VoiceSpaceFn currentVoiceSpace; + + 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; + } + } + + ChannelStripAudioApp() : AudioAppBase() { + auto voiceSpaceBasic = [this](const std::array& params) { + VOICE_SPACE_CHSTRIP_BASIC_BODY + }; + auto voiceSpaceMaleVox = [this](const std::array& params) { + VOICE_SPACE_CHSTRIP_MALE_VOX_BODY + }; + auto voiceSpaceFemaleVox = [this](const std::array& 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 interface) override + { + AudioAppBase::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& 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 diff --git a/MEMLNaut-NISPS.ino b/MEMLNaut-NISPS.ino new file mode 100644 index 0000000..56d3920 --- /dev/null +++ b/MEMLNaut-NISPS.ino @@ -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 + +//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 + +template +concept MEMLNautMode = requires(T proc) { + {proc.getHelpTitle()} -> std::same_as; + {proc.getNParams()} -> std::same_as; + {proc.setVoiceSpace(size_t{})} -> std::same_as; + {proc.setupMIDI(std::shared_ptr{})} -> std::same_as; + {proc.addViews()} -> std::same_as; + {proc.Setup(float{}, std::shared_ptr{})} -> std::same_as; + {proc.loop()} -> std::same_as; + {proc.getVoiceSpaceList()} -> std::same_as>; + {proc.process(stereosample_t{})} -> std::same_as; +}; + +class MEMLNautModePAFSynth { +public: + inline static PAFSynthAudioApp<> audioAppPAFSynth; + std::array::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 getVoiceSpaceList() { + return voiceSpaceList; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppPAFSynth.Process(x); + } + + void Setup(float sample_rate, std::shared_ptr interface) { + audioAppPAFSynth.Setup(sample_rate, interface); + voiceSpaceList = audioAppPAFSynth.getVoiceSpaceNames(); + } + + __force_inline void loop() { + audioAppPAFSynth.loop(); + } + + std::shared_ptr midi_interf; + + void setupMIDI(std::shared_ptr 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 noteTrigView = std::make_shared("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(powf(y, 0.5f) * 127.f); + uint8_t midimsg[2] = {static_cast(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::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 getVoiceSpaceList() { + return voiceSpaceList; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppChannelStrip.Process(x); + } + + void setupMIDI(std::shared_ptr midi_interf) { + } + + void addViews() { + + }; + + void Setup(float sample_rate, std::shared_ptr 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 APP_SRAM interface; + +std::shared_ptr 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 > __scratch_y("audio") audio_app; + +// alignas(ChannelStripAudioApp<>) char AUDIO_MEM audio_app_mem_chstrip[sizeof(ChannelStripAudioApp<>)]; +// std::shared_ptr > __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(); + + // 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(); + // 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 = std::make_shared("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 helpView = std::make_shared("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> 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> 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 + +} diff --git a/MEMLNaut-PAF-NISPS.ino b/MEMLNaut-PAF-NISPS.ino deleted file mode 100644 index 19f33b6..0000000 --- a/MEMLNaut-PAF-NISPS.ino +++ /dev/null @@ -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 -#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 APP_SRAM interface; - -std::shared_ptr 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 > __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::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(); - 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(); - // 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 noteTrigView = std::make_shared("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(powf(y, 0.5f) * 127.f); - uint8_t midimsg[2] = {static_cast(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 = std::make_shared("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 helpView = std::make_shared("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> 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 - -} diff --git a/PAFSynthAudioApp.hpp b/PAFSynthAudioApp.hpp index 789764c..d16e58e 100644 --- a/PAFSynthAudioApp.hpp +++ b/PAFSynthAudioApp.hpp @@ -14,6 +14,8 @@ #include +#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&)>; - struct VoiceSpace { - char name[16]="default"; - VoiceSpaceFn mappingFunction = nullptr; - }; - - std::array voiceSpaces; + std::array, nVoiceSpaces> voiceSpaces; - VoiceSpaceFn currentVoiceSpace; + VoiceSpaceFn currentVoiceSpace; std::array getVoiceSpaceNames() { std::array names; diff --git a/voicespaces/ChannelStrip/basic.hpp b/voicespaces/ChannelStrip/basic.hpp new file mode 100644 index 0000000..1421d3a --- /dev/null +++ b/voicespaces/ChannelStrip/basic.hpp @@ -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 \ No newline at end of file diff --git a/voicespaces/VoiceSpaces.hpp b/voicespaces/VoiceSpaces.hpp new file mode 100644 index 0000000..8709c41 --- /dev/null +++ b/voicespaces/VoiceSpaces.hpp @@ -0,0 +1,17 @@ +#ifndef MEML_MEMLNAUT_NISPS_VOICESPACES_VOICESPACES_HPP +#define MEML_MEMLNAUT_NISPS_VOICESPACES_VOICESPACES_HPP + +#pragma once +#include +#include + +template +using VoiceSpaceFn = std::function&)>; + +template +struct VoiceSpace { + char name[16]="default"; + VoiceSpaceFn mappingFunction = nullptr; +}; + +#endif // MEML_MEMLNAUT_NISPS_VOICESPACES_VOICESPACES_HPP \ No newline at end of file