fm ops and clock estimation fix
This commit is contained in:
parent
1af45be168
commit
c4659ce1d7
5 changed files with 499 additions and 44 deletions
|
|
@ -25,13 +25,15 @@
|
||||||
#include "modes/MEMLNautModeXIASRI.hpp"
|
#include "modes/MEMLNautModeXIASRI.hpp"
|
||||||
#include "modes/MEMLNautModeBreakOr.hpp"
|
#include "modes/MEMLNautModeBreakOr.hpp"
|
||||||
#include "modes/MEMLNautModeVerbFX.hpp"
|
#include "modes/MEMLNautModeVerbFX.hpp"
|
||||||
|
#include "modes/MEMLNautModeElysiamorfs.hpp"
|
||||||
|
|
||||||
//hook up the memlnaut mode
|
//hook up the memlnaut mode
|
||||||
|
|
||||||
// #define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI
|
||||||
// #define MEMLNAUT_MODE_TYPE MEMLNautModeXIASRI
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeXIASRI
|
||||||
// #define MEMLNAUT_MODE_TYPE MEMLNautModeVerbFX
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeVerbFX
|
||||||
#define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeBreakOr
|
||||||
|
#define MEMLNAUT_MODE_TYPE MEMLNautModeElysiamorfs
|
||||||
// #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModeChannelStrip
|
||||||
// #define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth
|
// #define MEMLNAUT_MODE_TYPE MEMLNautModePAFSynth
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,47 +17,7 @@
|
||||||
|
|
||||||
#include "../../voicespaces/VoiceSpaces.hpp"
|
#include "../../voicespaces/VoiceSpaces.hpp"
|
||||||
|
|
||||||
struct ratioSeqState {
|
#include "RatioSeq.hpp"
|
||||||
std::array<float, 3> ratios{1.f};
|
|
||||||
float phasor=0.f;
|
|
||||||
float phasorInc=0.f;
|
|
||||||
float phaseOffset = 0.f;
|
|
||||||
bool lastTrig = false;
|
|
||||||
float phasorMul = 1.f;
|
|
||||||
float ratioSum=1.f;
|
|
||||||
int midiNote = 36;
|
|
||||||
float pulseWidth = 0.5f;
|
|
||||||
|
|
||||||
std::array<float, 2> ampRatios{1.f};
|
|
||||||
float ampRatioSum=1.f;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
template<size_t seqLength>
|
|
||||||
inline bool __not_in_flash_func(ratioSeq)(float phasor, float phaseOffset, float ratioSum, const std::array<float, seqLength> &ratios, float pulseWidth) {
|
|
||||||
bool trig = 0;
|
|
||||||
float offsetPhase = phaseOffset + phasor;
|
|
||||||
if (offsetPhase >= 1.f) {
|
|
||||||
offsetPhase -= 1.f;
|
|
||||||
}
|
|
||||||
float phaseAdj = ratioSum * offsetPhase;
|
|
||||||
float accumulatedSum = 0;
|
|
||||||
float lastAccumulatedSum = 0;
|
|
||||||
for (size_t v : ratios)
|
|
||||||
{
|
|
||||||
accumulatedSum += v;
|
|
||||||
if (phaseAdj <= accumulatedSum)
|
|
||||||
{
|
|
||||||
// check pulse width
|
|
||||||
float beatPhase = (phaseAdj - lastAccumulatedSum) /
|
|
||||||
(accumulatedSum - lastAccumulatedSum);
|
|
||||||
trig = beatPhase <= pulseWidth;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lastAccumulatedSum = accumulatedSum;
|
|
||||||
}
|
|
||||||
return trig;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<size_t NPARAMS=56, size_t NSEQUENCES=8>
|
template<size_t NPARAMS=56, size_t NSEQUENCES=8>
|
||||||
|
|
@ -143,7 +103,6 @@ public:
|
||||||
int phasorResetValue=0;
|
int phasorResetValue=0;
|
||||||
if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) {
|
if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) {
|
||||||
barPhasor = 0.f;
|
barPhasor = 0.f;
|
||||||
Serial.println("Bar phase reset triggered by queue");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int i2cIdx=0;
|
int i2cIdx=0;
|
||||||
|
|
|
||||||
324
modes/AudioApps/ElysiamorfAudioApp.hpp
Normal file
324
modes/AudioApps/ElysiamorfAudioApp.hpp
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
#ifndef __ELYSIAMORF_AUDIO_APP_HPP__
|
||||||
|
#define __ELYSIAMORF_AUDIO_APP_HPP__
|
||||||
|
|
||||||
|
#include "../../src/memllib/audio/AudioAppBase.hpp"
|
||||||
|
#include "../../src/memllib/synth/maximilian.h"
|
||||||
|
#include "../../src/memllib/interface/MIDIInOut.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory> // Added for std::shared_ptr
|
||||||
|
|
||||||
|
#include "../../src/memllib/interface/InterfaceBase.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "../../voicespaces/VoiceSpaces.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
struct FMOp {
|
||||||
|
float prevOutput = 0.f;
|
||||||
|
|
||||||
|
// phase: external phasor 0-1 (e.g. barPhasor * phasorMul)
|
||||||
|
// modIn: signal from another operator [-1, 1], 0 = none
|
||||||
|
// freqMul: cycles per phase period (1 = once per bar, 2 = twice, 0.5 = half, etc.)
|
||||||
|
// modIndex: depth of modulation — scales how much modIn shifts the phase
|
||||||
|
// fbLevel: feedback 0-1 — feeds prevOutput back into own phase
|
||||||
|
float __force_inline process(float phase, float modIn,
|
||||||
|
float freqMul, float modIndex,
|
||||||
|
float fbLevel) {
|
||||||
|
float p = fmodf(phase * freqMul + modIndex * modIn + fbLevel * prevOutput, 1.f);
|
||||||
|
if (p < 0.f) p += 1.f;
|
||||||
|
float out = sinf(TWO_PI * p);
|
||||||
|
prevOutput = out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience: 2-op FM pair matching the (phase, carrierFreq, modFreq, modIndex, fbLevel) signature.
|
||||||
|
// fbLevel sits on the modulator (DX7 style). carrier and modulator are caller-owned FMOp instances.
|
||||||
|
float __force_inline fmPair(float phase,
|
||||||
|
float carrierFreq, float modFreq,
|
||||||
|
float modIndex, float fbLevel,
|
||||||
|
FMOp& carrier, FMOp& modulator) {
|
||||||
|
float modOut = modulator.process(phase, 0.f, modFreq, 0.f, fbLevel);
|
||||||
|
return carrier.process(phase, modOut, carrierFreq, modIndex, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fmSeqState {
|
||||||
|
float phaseOffset = 0.f;
|
||||||
|
float phasorMul = 1.f;
|
||||||
|
float ratio=1.f;
|
||||||
|
float carrierFreq=1.f;
|
||||||
|
float modFreq = 2.f;
|
||||||
|
float modIndex = 2.f;
|
||||||
|
float fbLevel = 0.f;
|
||||||
|
FMOp carrier;
|
||||||
|
FMOp modulator;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#include "RatioSeq.hpp"
|
||||||
|
|
||||||
|
template<size_t NPARAMS=56, size_t NSEQUENCES=8>
|
||||||
|
class ElysiamorfAudioApp : public AudioAppBase<NPARAMS>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr size_t kN_Params = NPARAMS;
|
||||||
|
static constexpr size_t nVoiceSpaces=0;
|
||||||
|
|
||||||
|
queue_t bpmQueue;
|
||||||
|
queue_t sequencerControlQueue;
|
||||||
|
queue_t i2cOutQueue;
|
||||||
|
queue_t barPhaseResetQueue;
|
||||||
|
|
||||||
|
enum SequencerClockModes {
|
||||||
|
INTERNAL,
|
||||||
|
MIDI_CLOCK
|
||||||
|
} sequencerClockMode = INTERNAL;
|
||||||
|
|
||||||
|
|
||||||
|
bool sequencerPlaying = false;
|
||||||
|
|
||||||
|
std::shared_ptr<MIDIInOut> midiIO;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElysiamorfAudioApp() : AudioAppBase<NPARAMS>() {
|
||||||
|
// currentVoiceSpace = voiceSpaces[0].mappingFunction;
|
||||||
|
queue_init(&bpmQueue, sizeof(float), 1);
|
||||||
|
queue_init(&sequencerControlQueue, sizeof(int), 1);
|
||||||
|
queue_init(&i2cOutQueue, sizeof(float) * 8, 1);
|
||||||
|
queue_init(&barPhaseResetQueue, sizeof(int), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool __force_inline euclidean(float phase, const size_t n, const size_t k, const size_t offset, const float pulseWidth)
|
||||||
|
{
|
||||||
|
// Euclidean function
|
||||||
|
const float fi = phase * n;
|
||||||
|
int i = static_cast<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
|
||||||
|
{
|
||||||
|
if (sequencerPlaying) {
|
||||||
|
if (sequencerClockMode == INTERNAL){
|
||||||
|
midiClockPhasor += midiClockPhasorInc;
|
||||||
|
if (midiClockPhasor >= 1.f) {
|
||||||
|
midiClockPhasor -= 1.f;
|
||||||
|
midiIO->queueClock();
|
||||||
|
midiIO->flushQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sequencingSampleCounter==0) {
|
||||||
|
|
||||||
|
barPhasor += barPhasorInc;
|
||||||
|
if (barPhasor >= 1.f) {
|
||||||
|
barPhasor -= 1.f;
|
||||||
|
}
|
||||||
|
if (sequencerClockMode == MIDI_CLOCK) {
|
||||||
|
int phasorResetValue=0;
|
||||||
|
if (queue_try_remove(&barPhaseResetQueue, &phasorResetValue)) {
|
||||||
|
barPhasor = 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// int i2cIdx=0;
|
||||||
|
int ccIndex=0;
|
||||||
|
static int ccNumbers[8] = {1,2,3,4,5,9,11,12};
|
||||||
|
// for(auto &seq: ratioSeqStates) {
|
||||||
|
// //update phasor
|
||||||
|
// float seqPhasor = barPhasor * seq.phasorMul;
|
||||||
|
// seqPhasor = fmodf(seqPhasor + seq.phaseOffset, 1.f); // Wrap phasor to [0,1]
|
||||||
|
|
||||||
|
// bool trig = ratioSeq<3>(seqPhasor, seq.phaseOffset, seq.ratioSum, seq.ratios, seq.pulseWidth);
|
||||||
|
// bool highAmp = ratioSeq<2>(seqPhasor, seq.phaseOffset, seq.ampRatioSum, seq.ampRatios, 0.5f);
|
||||||
|
// if (trig != seq.lastTrig) {
|
||||||
|
// midiIO->queueCC(ccNumbers[ccIndex++], trig ? highAmp ? 127 : 64 : 0);
|
||||||
|
// // Serial.printf("Seq %d - Phasor: %f, Trig: %d, HighAmp: %d\n", &seq - &ratioSeqStates[0], seqPhasor, trig, highAmp);
|
||||||
|
// }
|
||||||
|
// seq.lastTrig = trig;
|
||||||
|
// // i2cValues[i2cIdx++] = trig;
|
||||||
|
// }
|
||||||
|
for(auto &seq: fmSeqStates) {
|
||||||
|
//update phasor
|
||||||
|
float seqPhasor = barPhasor * seq.phasorMul;
|
||||||
|
seqPhasor = fmodf(seqPhasor + seq.phaseOffset, 1.f); // Wrap phasor to [0,1]
|
||||||
|
float fmVal = fmPair(seqPhasor, seq.carrierFreq, seq.modFreq, seq.modIndex, seq.fbLevel, seq.carrier, seq.modulator);
|
||||||
|
fmVal = (fmVal + 1.f) * 0.5f; // Normalize to [0,1]
|
||||||
|
fmVal *= 127.f; // Scale to MIDI range
|
||||||
|
// if (ccIndex==0) {
|
||||||
|
// Serial.printf("Seq %d - Phasor: %f, FM Val: %f\n", &seq - &fmSeqStates[0], seqPhasor, fmVal);
|
||||||
|
// }
|
||||||
|
midiIO->queueCC(ccNumbers[ccIndex++], static_cast<uint8_t>(fmVal));
|
||||||
|
}
|
||||||
|
midiIO->flushQueue();
|
||||||
|
// queue_try_add(&i2cOutQueue, &i2cValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
sequencingSampleCounter ++;
|
||||||
|
if (sequencingSampleCounter >= sequencingSampleDiv) {
|
||||||
|
sequencingSampleCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
stereosample_t ret { 0.f,0.f };
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface, SequencerClockModes clockMode)
|
||||||
|
{
|
||||||
|
AudioAppBase<NPARAMS>::Setup(sample_rate, interface);
|
||||||
|
maxiSettings::sampleRate = sample_rate;
|
||||||
|
sampleRatef = static_cast<float>(sample_rate);
|
||||||
|
sequencerClockMode = clockMode;
|
||||||
|
updateBPM(90.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
|
||||||
|
midiIO = new_midi_interf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
AudioAppBase<NPARAMS>::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessParams(const std::array<float, NPARAMS>& params)
|
||||||
|
{
|
||||||
|
firstParamsReceived = true;
|
||||||
|
if (queue_try_remove(&bpmQueue, &bpm)) {
|
||||||
|
updateBPM(bpm);
|
||||||
|
Serial.printf("BPM updated: %f\n", bpm);
|
||||||
|
}
|
||||||
|
int sequencerControl;
|
||||||
|
if (queue_try_remove(&sequencerControlQueue, &sequencerControl)) {
|
||||||
|
sequencerPlaying = sequencerControl == 1;
|
||||||
|
if (!sequencerPlaying) {
|
||||||
|
midiIO->queueClockStop();
|
||||||
|
// Send note offs for all sequences when stopping
|
||||||
|
for(auto &seq: ratioSeqStates) {
|
||||||
|
midiIO->queueNoteOff(seq.midiNote, 0);
|
||||||
|
seq.phasor = 0.f; // Reset phasor to start when stopping
|
||||||
|
}
|
||||||
|
midiIO->flushQueue();
|
||||||
|
midiClockPhasor = 0.f; // Reset MIDI clock phasor when stopping
|
||||||
|
sequencingSampleCounter = 0; // Reset sequencing sample counter
|
||||||
|
|
||||||
|
}else{
|
||||||
|
midiIO->queueClockStart();
|
||||||
|
midiIO->flushQueue();
|
||||||
|
}
|
||||||
|
Serial.printf("Sequencer %s\n", sequencerPlaying ? "Playing" : "Stopped");
|
||||||
|
}
|
||||||
|
// currentVoiceSpace(params);
|
||||||
|
// size_t paramIdx = 0;
|
||||||
|
// for(auto &v: ratioSeqStates) {
|
||||||
|
// float sum=0.f;
|
||||||
|
// for(size_t i=0; i < v.ratios.size(); i++) {
|
||||||
|
// v.ratios[i] = (float)(int)(params[paramIdx++] * 3.f) + 1.f;
|
||||||
|
// sum += v.ratios[i];
|
||||||
|
// }
|
||||||
|
// v.ratioSum = sum;
|
||||||
|
// // static float muls[7] = {0.25f, 0.33f, 0.5f, 1.f, 1.5f, 2.f, 3.f};
|
||||||
|
// // v.phasorMul = muls[(int)(params[paramIdx++] * 6.999999f)];
|
||||||
|
// static float muls[4] = {1.f, 2.f, 4.f, 8.f};
|
||||||
|
// v.phasorMul = muls[(int)(params[paramIdx++] * 3.999999f)];
|
||||||
|
// v.phaseOffset = ((int)(params[paramIdx++] * timeSigBeats)) * timeSigBeatsInv;
|
||||||
|
|
||||||
|
// sum=0.f;
|
||||||
|
// for(size_t i=0; i < v.ampRatios.size(); i++) {
|
||||||
|
// v.ampRatios[i] = (float)(int)(params[paramIdx++] * 3.f) + 1.f;
|
||||||
|
// sum += v.ampRatios[i];
|
||||||
|
// }
|
||||||
|
// v.ampRatioSum = sum;
|
||||||
|
// }
|
||||||
|
size_t paramIdx = 0;
|
||||||
|
for(auto &v: fmSeqStates) {
|
||||||
|
v.carrierFreq = (0.25f + params[paramIdx++] * 0.75f) * 0.125f; // 1 to 10
|
||||||
|
v.modFreq = (0.25f + params[paramIdx++] * 0.75f) * 0.25f; // 1 to 10
|
||||||
|
v.modIndex = params[paramIdx++] * 4.f; // 0 to 10
|
||||||
|
v.fbLevel = 0.f;//params[paramIdx++] ; // 0 to 1
|
||||||
|
static float muls[4] = {1.f};
|
||||||
|
v.phasorMul = 1.f;//muls[(int)(params[paramIdx++])];
|
||||||
|
v.phaseOffset = ((int)(params[paramIdx++] * timeSigBeats)) * timeSigBeatsInv;
|
||||||
|
}
|
||||||
|
// Serial.printf("pm: %f", ratioSeqStates[0].phasorMul);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBPM(float newBPM) {
|
||||||
|
bpm = newBPM;
|
||||||
|
float beatLengthInSeconds = 60.f / bpm;
|
||||||
|
float barLengthInSeconds = beatLengthInSeconds * timeSigBeats;
|
||||||
|
float barLengthInSamples = barLengthInSeconds * (sampleRatef/ sequencingSampleDiv);
|
||||||
|
barPhasorInc = 1.f/ barLengthInSamples;
|
||||||
|
// for(auto &v: ratioSeqStates) {
|
||||||
|
// v.phasorInc = barPhasorInc;
|
||||||
|
// }
|
||||||
|
float midiClockLengthInSeconds = beatLengthInSeconds / 24.f;
|
||||||
|
float midiClockLengthInSamples = midiClockLengthInSeconds * sampleRatef;
|
||||||
|
midiClockPhasorInc = 1.f / midiClockLengthInSamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTimeSignature(float beats, float division) {
|
||||||
|
timeSigBeats = beats;
|
||||||
|
timeSigDivision = division;
|
||||||
|
updateBPM(bpm); // Recalculate phasor increment with new time signature
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
float i2cValues[8] = {0,0,0,0,0,0,0,0};
|
||||||
|
|
||||||
|
float sampleRatef;
|
||||||
|
bool firstParamsReceived = false;
|
||||||
|
|
||||||
|
float bpm=90.f;
|
||||||
|
float timeSigBeats=4.f;
|
||||||
|
float timeSigBeatsInv=1.f/timeSigBeats;
|
||||||
|
float timeSigDivision=4.f;
|
||||||
|
|
||||||
|
float barPhasorInc=0.f;
|
||||||
|
float barPhasor;
|
||||||
|
|
||||||
|
size_t sequencingSampleDiv = 500;
|
||||||
|
size_t sequencingSampleCounter = 0;
|
||||||
|
|
||||||
|
std::array<ratioSeqState, NSEQUENCES> ratioSeqStates;
|
||||||
|
std::array<fmSeqState, NSEQUENCES> fmSeqStates;
|
||||||
|
|
||||||
|
|
||||||
|
float midiClockPhasor=0;
|
||||||
|
float midiClockPhasorInc=0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __ELYSIAMORF_AUDIO_APP_HPP__
|
||||||
|
|
||||||
49
modes/AudioApps/RatioSeq.hpp
Normal file
49
modes/AudioApps/RatioSeq.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#ifndef __RATIO_SEQ_HPP__
|
||||||
|
#define __RATIO_SEQ_HPP__
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
struct ratioSeqState {
|
||||||
|
std::array<float, 3> ratios{1.f};
|
||||||
|
float phasor=0.f;
|
||||||
|
float phasorInc=0.f;
|
||||||
|
float phaseOffset = 0.f;
|
||||||
|
bool lastTrig = false;
|
||||||
|
float phasorMul = 1.f;
|
||||||
|
float ratioSum=1.f;
|
||||||
|
int midiNote = 36;
|
||||||
|
float pulseWidth = 0.5f;
|
||||||
|
|
||||||
|
std::array<float, 2> ampRatios{1.f};
|
||||||
|
float ampRatioSum=1.f;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t seqLength>
|
||||||
|
inline bool __not_in_flash_func(ratioSeq)(float phasor, float phaseOffset, float ratioSum, const std::array<float, seqLength> &ratios, float pulseWidth) {
|
||||||
|
bool trig = 0;
|
||||||
|
float offsetPhase = phaseOffset + phasor;
|
||||||
|
if (offsetPhase >= 1.f) {
|
||||||
|
offsetPhase -= 1.f;
|
||||||
|
}
|
||||||
|
float phaseAdj = ratioSum * offsetPhase;
|
||||||
|
float accumulatedSum = 0;
|
||||||
|
float lastAccumulatedSum = 0;
|
||||||
|
for (size_t v : ratios)
|
||||||
|
{
|
||||||
|
accumulatedSum += v;
|
||||||
|
if (phaseAdj <= accumulatedSum)
|
||||||
|
{
|
||||||
|
// check pulse width
|
||||||
|
float beatPhase = (phaseAdj - lastAccumulatedSum) /
|
||||||
|
(accumulatedSum - lastAccumulatedSum);
|
||||||
|
trig = beatPhase <= pulseWidth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastAccumulatedSum = accumulatedSum;
|
||||||
|
}
|
||||||
|
return trig;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __RATIO_SEQ_HPP__
|
||||||
121
modes/MEMLNautModeElysiamorfs.hpp
Normal file
121
modes/MEMLNautModeElysiamorfs.hpp
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
#ifndef MEMLNAUT_MODE_ELYSIAMORFS_HPP
|
||||||
|
#define MEMLNAUT_MODE_ELYSIAMORFS_HPP
|
||||||
|
|
||||||
|
//Elysiamorfs
|
||||||
|
|
||||||
|
#include "../src/memllib/interface/MIDIInOut.hpp"
|
||||||
|
#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp"
|
||||||
|
#include "./AudioApps/ElysiamorfAudioApp.hpp"
|
||||||
|
#include "../src/memllib/examples/InterfaceRL.hpp"
|
||||||
|
#include "../src/memllib/PicoDefs.hpp"
|
||||||
|
#include "MEMLNautMode.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
#include "../src/memllib/interface/USeqI2C.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
class MEMLNautModeElysiamorfs {
|
||||||
|
public:
|
||||||
|
constexpr static size_t kN_InputParams = 4; // joystick x, y, rotate
|
||||||
|
|
||||||
|
USeqI2C i2cOut;
|
||||||
|
inline static ElysiamorfAudioApp<> audioAppElysiamorfs;
|
||||||
|
std::array<String, ElysiamorfAudioApp<>::nVoiceSpaces> voiceSpaceList;
|
||||||
|
|
||||||
|
InterfaceRL interface;
|
||||||
|
std::shared_ptr<InterfaceRL> interfacePtr;
|
||||||
|
|
||||||
|
ElysiamorfAudioApp<>::SequencerClockModes clockMode = ElysiamorfAudioApp<>::MIDI_CLOCK;
|
||||||
|
|
||||||
|
bool sequencerPlaying = false;
|
||||||
|
|
||||||
|
void setupInterface() {
|
||||||
|
interface.setup(kN_InputParams, ElysiamorfAudioApp<>::kN_Params);
|
||||||
|
interface.bindInterface(InterfaceRL::INPUT_MODES::JOYSTICK, true);
|
||||||
|
interfacePtr = make_non_owning(interface);
|
||||||
|
|
||||||
|
MEMLNaut::Instance()->setTogA2Callback([this](bool state) { // scr_ref no longer captured directly
|
||||||
|
Serial.println(state ? "TogA2 ON" : "TogA2 OFF");
|
||||||
|
if (state) {
|
||||||
|
sequencerPlaying = !sequencerPlaying;
|
||||||
|
queue_try_add(&audioAppElysiamorfs.sequencerControlQueue, &sequencerPlaying);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
i2cOut.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getHelpTitle() {
|
||||||
|
return "Elysiamorfs";
|
||||||
|
}
|
||||||
|
|
||||||
|
__force_inline stereosample_t process(stereosample_t x) {
|
||||||
|
return audioAppElysiamorfs.Process(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupAudio(float sample_rate) {
|
||||||
|
audioAppElysiamorfs.Setup(sample_rate, interfacePtr, clockMode);
|
||||||
|
voiceSpaceList = audioAppElysiamorfs.getVoiceSpaceNames();
|
||||||
|
audioAppElysiamorfs.setupMIDI(midi_interf);
|
||||||
|
MEMLNaut::Instance()->setRVGain1Callback([this](float value) {
|
||||||
|
if (clockMode == ElysiamorfAudioApp<>::INTERNAL) {
|
||||||
|
float bpm = 30.f + (value * 200.f); // Scale BPM from [0,1] to [30,230]
|
||||||
|
queue_try_add(&audioAppElysiamorfs.bpmQueue, &bpm);
|
||||||
|
}
|
||||||
|
}, 0); // Set threshold to 0 to trigger on any change
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
__force_inline void loop() {
|
||||||
|
audioAppElysiamorfs.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MIDIInOut> midi_interf;
|
||||||
|
|
||||||
|
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
|
||||||
|
midi_interf = new_midi_interf;
|
||||||
|
midi_interf->Setup(0);
|
||||||
|
// midi_interf->SetMIDISendChannel(1);
|
||||||
|
// midi_interf->SetMIDINoteChannel(10);
|
||||||
|
midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) {
|
||||||
|
// Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon);
|
||||||
|
if (note_number==0) {
|
||||||
|
int resetTrigger=1;
|
||||||
|
queue_try_add(&audioAppElysiamorfs.barPhaseResetQueue, &resetTrigger); // value doesn't matter, just a trigger
|
||||||
|
Serial.println("Bar phase reset triggered by MIDI note 0");
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
midi_interf->SetBPMCallback([this](float bpm) {
|
||||||
|
if (clockMode == ElysiamorfAudioApp<>::MIDI_CLOCK) {
|
||||||
|
queue_try_add(&audioAppElysiamorfs.bpmQueue, &bpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
interface.bindMIDI(midi_interf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addViews() {
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void processAnalysisParams() {}
|
||||||
|
|
||||||
|
void analyse(stereosample_t) {}
|
||||||
|
|
||||||
|
float i2cValues[8];
|
||||||
|
void loopCore0() {
|
||||||
|
// PERIODIC_RUN_US(
|
||||||
|
// if (queue_try_remove(&audioAppElysiamorfs.i2cOutQueue, &i2cValues)) {
|
||||||
|
// // Serial.printf("i2c received: %f %f %f %f %f %f %f %f\n", i2cValues[0], i2cValues[1], i2cValues[2], i2cValues[3], i2cValues[4], i2cValues[5], i2cValues[6], i2cValues[7]);
|
||||||
|
// bool i2cSuccess = i2cOut.sendValues(i2cValues, 8);
|
||||||
|
// }
|
||||||
|
// , 5000)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // MEMLNAUT_MODE_ELYSIAMORFS_HPP
|
||||||
Loading…
Reference in a new issue