From 9d41d1318b1c55ea726e959d59b626ba4a1d5454 Mon Sep 17 00:00:00 2001 From: chriskiefer Date: Wed, 15 Oct 2025 17:48:26 +0100 Subject: [PATCH] paf upgrades --- MEMLNaut-PAF-CARL.ino | 303 +++++++++++++++++++++--------------------- PAFSynthAudioApp.hpp | 182 +++++++++++++++++++++---- src/memllib | 2 +- 3 files changed, 314 insertions(+), 173 deletions(-) diff --git a/MEMLNaut-PAF-CARL.ino b/MEMLNaut-PAF-CARL.ino index 82b1203..04f0222 100644 --- a/MEMLNaut-PAF-CARL.ino +++ b/MEMLNaut-PAF-CARL.ino @@ -18,15 +18,15 @@ 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; + 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; } @@ -52,7 +52,12 @@ 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 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; }) @@ -62,176 +67,178 @@ constexpr size_t kN_InputParams = 3; // return true; // } -void setup() -{ - set_sys_clock_khz(AudioDriver::GetSysClockSpeed(), 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; + 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); + uint32_t seed = get_rosc_entropy_seed(32); + srand(seed); - Serial.begin(115200); - // while (!Serial) {} - Serial.println("Serial initialised."); - WRITE_VOLATILE(serial_ready, true); + Serial.begin(115200); + // while (!Serial) {} + Serial.println("Serial initialised."); + WRITE_VOLATILE(serial_ready, true); - // Setup board - MEMLNaut::Initialize(); - pinMode(33, OUTPUT); + // 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."); + { + 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(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); - } - Serial.printf("MIDI Note %d: %d\n", note_number, vel_value); - }); - Serial.println("MIDI note callback set."); + midi_interf = std::make_shared(); + 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\n", note_number, vel_value); + }); + Serial.println("MIDI note callback set."); - interface->bindMIDI(midi_interf); - } + interface->bindMIDI(midi_interf); + } - WRITE_VOLATILE(core_0_ready, true); - while (!READ_VOLATILE(core_1_ready)) { - MEMORY_BARRIER(); - delay(1); - } + WRITE_VOLATILE(core_0_ready, true); + while (!READ_VOLATILE(core_1_ready)) { + MEMORY_BARRIER(); + delay(1); + } - std::shared_ptr helpView = std::make_shared("Help"); - helpView->post("PAF synth CARL"); - helpView->post("TA: Down: Forget replay memory"); - helpView->post("MA: Up: Randomise actor"); - helpView->post("MA: Down: Randomise critic"); - helpView->post("MB: Up: Positive reward"); - helpView->post("MB: Down: Negative reward"); - helpView->post("Y: Optimisation rate"); - helpView->post("Z: OU noise"); - helpView->post("Joystick: Explore"); - MEMLNaut::Instance()->disp->AddView(helpView); + std::shared_ptr helpView = std::make_shared("Help"); + helpView->post("PAF synth CARL"); + helpView->post("TA: Down: Forget replay memory"); + helpView->post("MA: Up: Randomise actor"); + helpView->post("MA: Down: Randomise critic"); + helpView->post("MB: Up: Positive reward"); + helpView->post("MB: Down: Negative reward"); + helpView->post("Y: Optimisation rate"); + helpView->post("Z: OU noise"); + helpView->post("Joystick: Explore"); + MEMLNaut::Instance()->disp->AddView(helpView); - MEMLNaut::Instance()->addSystemInfoView(); - - Serial.println("Finished initialising core 0."); + MEMLNaut::Instance()->addSystemInfoView(); + Serial.println("Finished initialising core 0."); } PERF_DECLARE(MLSTATS); -void loop() -{ +void loop() { - PERIODIC_RUN_US( - PERF_BEGIN(MLSTATS); - MEMLNaut::Instance()->loop(); - PERF_END(MLSTATS); - , 5000) + PERIODIC_RUN_US( + PERF_BEGIN(MLSTATS); + MEMLNaut::Instance()->loop(); + PERF_END(MLSTATS); + , 5000) - static int AUDIO_MEM blip_counter = 0; - if (blip_counter++ > 100) { - 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); + 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); + // Un-blink LED + digitalWrite(33, LOW); } + , 100000) + + PERIODIC_RUN_US( midi_interf->Poll(); - // delay(10); // Add a small delay to avoid flooding the serial output + , 10000) } -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) { +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; + 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; + // 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); - - - 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 - audio_app->loop(); +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); + + + 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 + audio_app->loop(); + delay(1); +} diff --git a/PAFSynthAudioApp.hpp b/PAFSynthAudioApp.hpp index ec47dba..e6aa7d8 100644 --- a/PAFSynthAudioApp.hpp +++ b/PAFSynthAudioApp.hpp @@ -6,17 +6,98 @@ #include #include -// #include #include // Added for std::shared_ptr #include "src/memllib/synth/maxiPAF.hpp" #include "src/memllib/interface/InterfaceBase.hpp" // Added missing include -#define ARPEGGIATOR +// #define ARPEGGIATOR +class ADSRLite { + public: + + enum envStage{WAITTOTRIG, ATTACK, DECAY, SUSTAIN, RELEASE}; + void setup(float attackTimeMs, float decayTimeMs, float newSustainLevel, float releaseTimeMs) { + attackIncFull = 1.f/((attackTimeMs / 1000.f) * kSampleRate); + attackInc = attackIncFull; + decayInc = 1.f/((decayTimeMs / 1000.f) * kSampleRate); + sustainLevel = newSustainLevel; + decayInc *= (1.f - sustainLevel); + releaseMs = releaseTimeMs; + } -template + float play() { + + switch(stage) { + case envStage::WAITTOTRIG: + { + envelopeValue = 0.f; + break; + } + case envStage::ATTACK: + { + envelopeValue += attackInc; + if (envelopeValue >= 1.f) { + stage = envStage::DECAY; + Serial.printf("decay %f\n", envelopeValue); + } + break; + } + case envStage::DECAY: + { + envelopeValue -= decayInc; + if (envelopeValue<=sustainLevel) { + stage = envStage::SUSTAIN; + Serial.printf("sus %f\n", envelopeValue); + } + break; + } + case envStage::SUSTAIN: + { + // envelopeValue = sustainLevel; + break; + } + case envStage::RELEASE: + { + envelopeValue -= relInc; + if (envelopeValue <= 0.f) { + stage = envStage::WAITTOTRIG; + Serial.println("wait"); + envelopeValue=0.f; + } + break; + } + } + return envelopeValue * velocity; + } + + void reset() { + stage = envStage::WAITTOTRIG; + envelopeValue=0; + + } + + void trigger(float vel) { + stage = envStage::ATTACK; + attackInc = attackIncFull * (1.f - envelopeValue); + velocity = vel; + } + + void release() { + relInc = 1.f/((releaseMs / 1000.f) * kSampleRate); + relInc *= envelopeValue; + stage = envStage::RELEASE; + } + +private: + envStage stage = envStage::WAITTOTRIG; + float attackInc=0, attackIncFull=0, decayInc=0, sustainLevel=0,relInc=0, releaseMs; + float envelopeValue=0; + float velocity = 1.f; +}; + +template class PAFSynthAudioApp : public AudioAppBase { public: @@ -49,17 +130,38 @@ public: // const float trig = pulse.square(1); paf0.play(x1, 1, baseFreq, baseFreq + (paf0_cf * baseFreq), paf0_bw * baseFreq, paf0_vib, paf0_vfr, paf0_shift, 0); - float y = x1[0]; + float p0 = *x1; const float freq1 = baseFreq * detune; paf1.play(x1, 1, freq1, freq1 + (paf1_cf * freq1), paf1_bw * freq1, paf1_vib, paf1_vfr, paf1_shift, 1); - y += x1[0]; + const float p1 = *x1; const float freq2 = freq1 * detune; paf2.play(x1, 1, freq2, freq2 + (paf2_cf * freq2), paf2_bw * freq2, paf2_vib, paf2_vfr, paf2_shift, 1); - y += x1[0]; + const float p2 = *x1; + + const float freq3 = freq2 * detune; + + paf3.play(x1, 1, freq3, freq3 + (paf3_cf * freq3), paf3_bw * freq3, paf3_vib, paf3_vfr, paf3_shift, 1); + const float p3 = *x1; + + auto shapedSine = [](float phasor, float gain, float asym) -> float { + // This function shapes the sine wave to create a foldback effect + float x = sinf(phasor * TWOPI); + x = sinf(((x * TWOPI) * gain) + asym); + return x; + }; + + float y = p0; //+ p1 + p2 + p3; + + // float rm = p0 * p1 * p2 * p3; + + // y = y + (rm * rmGain); + + y = y + (shapedSine(y, sineShapeGain, sineShapeGain) * sineShapeMix); + #ifdef ARPEGGIATOR const float ph = phasorOsc.phasor(1); @@ -90,13 +192,15 @@ public: } } #endif - y = y * envamp* envamp; + float envval = env.play(); + y = y * envval; + // y = y * envamp* envamp; #ifndef ARPEGGIATOR y *= noteVel; #endif - float d1 = (dl1.play(y, 3500, 0.8f) * dl1mix); + float d1 = (dl1.play(y, 100, 0.8f) * dl1mix); // float d2 = (dl2.play(y, 15000, 0.8f) * dl2mix); y = y + d1;// + d2; stereosample_t ret { y, y }; @@ -132,27 +236,26 @@ public: paf2.init(); paf2.setsr(maxiSettings::getSampleRate(), 1); - // paf2.freq(190, 0); - // // paf2.amp(1,0); - // paf2.bw(500,0); - // paf2.cf(210,0); - // paf2.vfr(5,0); - // paf2.vib(0.1,0); - // paf2.shift(6,0); - env.setupAR(10,100); + paf3.init(); + paf3.setsr(maxiSettings::getSampleRate(), 1); + + // env.setupAR(10,100); arpFreq = frequencies[0]; // line.prepare(1.f,0.f,100.f,false); // line.triggerEnable(true); envamp=1.f; + env.setup(500,500,0.8,1000); + queue_init(&qMIDINoteOn, sizeof(uint8_t)*2, 1); + queue_init(&qMIDINoteOff, sizeof(uint8_t)*2, 1); } inline float mtof(uint8_t note) { // Convert MIDI note to frequency - return 440.0f * powf(2.0f, (note - 69) / 12.0f); + return 440.0f * exp2f((note - 69) / 12.0f); } void loop() override { @@ -163,6 +266,12 @@ public: noteVel = midimsg[1] / 127.0f; // Normalize velocity to [0, 1] noteVel = noteVel * noteVel; // Square the velocity for more pronounced effect newNote = true; + env.trigger(noteVel); + } + if (firstParamsReceived && queue_try_remove(&qMIDINoteOff, &midimsg)) { + // Serial.printf("PAFSynthAudioApp::ProcessParams - Received MIDI Note On: %d, Velocity: %d\n", midimsg[0], midimsg[1]); + env.release(); + Serial.println("release"); } AudioAppBase::loop(); } @@ -179,28 +288,33 @@ public: // paf0_cf = arpFreq + (params[2] * params[2] * arpFreq * 1.f); // paf1_cf = arpFreq + (params[3] * params[3] * arpFreq * 1.f); // paf2_cf = arpFreq + (params[4] * params[4] * arpFreq * 1.f); - paf0_cf = (params[2] * params[2] * 1.f); + paf0_cf = (params[2] * params[2] * 0.1f); paf1_cf = (params[3] * params[3] * 1.f); paf2_cf = (params[4] * params[4] * 1.f); + paf3_cf = (params[21] * params[21] * 1.f); // paf0_bw = 5.f + (params[5] * arpFreq * 0.5f); // paf1_bw = 5.f + (params[6] * arpFreq * 0.5f); // paf2_bw = 5.f + (params[7] * arpFreq * 0.5f); - paf0_bw = 0.1f + (params[5] * 2.f); + paf0_bw = 0.1f + (params[5] * 0.2f); paf1_bw = 0.1f + (params[6] * 2.f); paf2_bw = 0.1f + (params[7] * 2.f); + paf3_bw = 0.1f + (params[22] * 2.f); - paf0_vib = (params[8] * params[8] * 0.99f); + paf0_vib = (params[8] * params[8] * 0.1f); paf1_vib = (params[9] * params[9] * 0.99f); paf2_vib = (params[10] * params[10] * 0.99f); + paf3_vib = (params[23] * params[23] * 0.99f); - paf0_vfr = (params[11] * params[11]* 10.f); + paf0_vfr = (params[11] * params[11]* 0.1f); paf1_vfr = (params[12] * params[12] * 10.f); paf2_vfr = (params[13] * params[13] * 10.f); + paf3_vfr = (params[24] * params[24] * 10.f); - paf0_shift = (params[14] * 1000.f); + paf0_shift = (params[14] * 10.f); paf1_shift = (params[15] * 1000.f); paf2_shift = (params[16] * 1000.f); + paf3_shift = (params[25] * 1000.f); dl1mix = params[17] * params[17] * 0.4f; // dl2mix = params[18] * params[18] * 0.4f; @@ -208,9 +322,15 @@ public: euclidN = static_cast(2 + (params[19] * 5)); - envdec=((params[20] * 3.f) + 0.1f)/9000.f; // Decay rate for the envelope + // envdec=((params[20] * 1.f) + 0.01f)/9000.f; // Decay rate for the envelope + env.setup(params[30] * 200.f,params[20] * params[20] * 500.f, params[31] * 0.5f, params[32] * 500.f ); + sineShapeGain = params[26] * params[26]; + sineShapeASym = params[27] * params[27] * 0.1f; + sineShapeMix = params[28]; + rmGain = params[29] * params[29]; + // sineShapeMixInv = 1.f-sineShapeMix; // Serial.printf("%f %f %f %f %f\n", paf0_cf, paf0_bw, paf0_vib, paf0_vfr, paf0_shift); } @@ -221,43 +341,57 @@ protected: maxiPAFOperator paf0; maxiPAFOperator paf1; maxiPAFOperator paf2; + maxiPAFOperator paf3; maxiDelayline<5000> dl1; maxiDelayline<15100> dl2; maxiOsc pulse; - maxiEnvGen env; + // maxiEnvGen env; + ADSRLite env; float frame=0; float paf0_freq = 100; float paf1_freq = 100; float paf2_freq = 50; + float paf3_freq = 50; float paf0_cf = 200; float paf1_cf = 250; float paf2_cf = 250; + float paf3_cf = 250; float paf0_bw = 100; float paf1_bw = 5000; float paf2_bw = 5000; + float paf3_bw = 5000; float paf0_vib = 0; float paf1_vib = 1; float paf2_vib = 1; + float paf3_vib = 1; float paf0_vfr = 2; float paf1_vfr = 2; float paf2_vfr = 2; + float paf3_vfr = 2; float paf0_shift = 0; float paf1_shift = 0; float paf2_shift = 0; + float paf3_shift = 0; float dl1mix = 0.0f; float dl2mix = 0.0f; + float rmGain = 0.f; + + float sineShapeGain=0.1; + float sineShapeASym = 0.f; + float sineShapeMix = 0.f; + float sineShapeMixInv = 1.f; size_t counter=0; size_t freqIndex = 0; size_t freqOffset = 0; diff --git a/src/memllib b/src/memllib index d9fe679..66279fd 160000 --- a/src/memllib +++ b/src/memllib @@ -1 +1 @@ -Subproject commit d9fe679e77acffc156d3a91b0c51b16df38f6d0d +Subproject commit 66279fdfeaf3f454419d3f1f0ee022b065757bcd