From d623adfe42e110cee86d1bc251883a83fce7bc31 Mon Sep 17 00:00:00 2001 From: chriskiefer Date: Sun, 23 Nov 2025 15:48:14 +0000 Subject: [PATCH] modes --- MEMLNaut-NISPS.ino | 215 +++--------------------- ThruAudioApp.hpp | 76 +++++++++ XiasriAnalysis.cpp | 149 ++++++++++++++++ XiasriAnalysis.hpp | 59 +++++++ modes/MEMLNautMode.hpp | 26 +++ modes/MEMLNautModeChannelStrip.hpp | 56 ++++++ modes/MEMLNautModePAFSynth.hpp | 101 +++++++++++ modes/MEMLNautModeSoundAnalysisMIDI.hpp | 80 +++++++++ modes/MEMLNautModeXIASRI.hpp | 59 +++++++ 9 files changed, 634 insertions(+), 187 deletions(-) create mode 100644 ThruAudioApp.hpp create mode 100644 XiasriAnalysis.cpp create mode 100644 XiasriAnalysis.hpp create mode 100644 modes/MEMLNautMode.hpp create mode 100644 modes/MEMLNautModeChannelStrip.hpp create mode 100644 modes/MEMLNautModePAFSynth.hpp create mode 100644 modes/MEMLNautModeSoundAnalysisMIDI.hpp create mode 100644 modes/MEMLNautModeXIASRI.hpp diff --git a/MEMLNaut-NISPS.ino b/MEMLNaut-NISPS.ino index 56d3920..64b3a6a 100644 --- a/MEMLNaut-NISPS.ino +++ b/MEMLNaut-NISPS.ino @@ -14,158 +14,25 @@ //interface #include "src/memllib/examples/InterfaceRL.hpp" -#include "src/memllib/hardware/memlnaut/display/XYPadView.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" +//modes +#include "modes/MEMLNautMode.hpp" +#include "modes/MEMLNautModePAFSynth.hpp" +#include "modes/MEMLNautModeChannelStrip.hpp" +#include "modes/MEMLNautModeSoundAnalysisMIDI.hpp" #define INTERFACE_TYPE InterfaceRL -#include +#define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI -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; +MEMLNAUT_MODE_TYPE AUDIO_MEM soundAnalysisMIDIMode; +// MEMLNautModeChannelStrip AUDIO_MEM channelStripMode; // MEMLNautModePAFSynth AUDIO_MEM pafSynthMode; -MEMLNautMode auto* AUDIO_MEM currentMode = &channelStripMode; - +MEMLNautMode auto* AUDIO_MEM currentMode = &soundAnalysisMIDIMode; #define APP_SRAM __not_in_flash("app") @@ -192,16 +59,6 @@ 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; @@ -212,7 +69,7 @@ volatile bool APP_SRAM interface_ready = false; // We're only bound to the joystick inputs (x, y, rotate) -constexpr size_t kN_InputParams = 3; +// constexpr size_t kN_InputParams = 3; // Add these macros near other globals #define MEMORY_BARRIER() __sync_synchronize() @@ -247,8 +104,8 @@ void setup() { // temp_interface->setup(kN_InputParams, PAFSynthAudioApp<>::kN_Params); // temp_interface->setup(kN_InputParams, ChannelStripAudioApp<>::kN_Params); - temp_interface->setup(kN_InputParams, currentMode->getNParams()); - + temp_interface->setup(currentMode->kN_InputParams, currentMode->getNParams()); + MEMORY_BARRIER(); interface = temp_interface; @@ -257,14 +114,12 @@ void setup() { // Setup interface with memory barrier protection WRITE_VOLATILE(interface_ready, true); // Bind interface after ensuring it's fully initialized - interface->bindInterface(false); + interface->bindInterface(true); 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->Setup(currentMode->getNMIDICtrlOutputs()); midi_interf->SetMIDISendChannel(1); Serial.println("MIDI setup complete."); if (midi_interf) { @@ -282,11 +137,11 @@ void setup() { 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->setOptions(currentMode->getVoiceSpaceList()); //set by core 1 on startup voiceSpaceSelectView->setNewVoiceCallback( [](size_t idx) { // Serial.println(idx); @@ -294,14 +149,13 @@ void setup() { // 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(); + String title = currentMode->getHelpTitle(); helpView->post(title); helpView->post("TA: Down: Clear replay memory"); helpView->post("MA: Up: Randomise / Down: Jolt "); @@ -322,16 +176,19 @@ PERF_DECLARE(MLSTATS); #define ML_INFERENCE_PERIOD_US 5000 + void loop() { PERIODIC_RUN_US( PERF_BEGIN(MLSTATS); + currentMode->processAnalysisParams(interface); MEMLNaut::Instance()->loop(); PERF_END(MLSTATS); , ML_INFERENCE_PERIOD_US) + //show profiling stats PERIODIC_RUN_US( - static size_t blip_counter=0; + static size_t blip_counter = 0; if (blip_counter++ > 10) { blip_counter = 0; Serial.println("."); @@ -342,16 +199,12 @@ void loop() { } else { // Un-blink LED digitalWrite(33, LOW); - } - , 100000) - - - + }, + 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) { @@ -365,9 +218,9 @@ void AUDIO_FUNC(audio_block_callback)(float in[][kBufferSize], float out[][kBuff out[0][i] = y.L; out[1][i] = y.R; - } - // Serial.println(in[0][0]); + currentMode->analyse(x); + } } @@ -431,22 +284,10 @@ void setup1() { 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/ThruAudioApp.hpp b/ThruAudioApp.hpp new file mode 100644 index 0000000..72e5755 --- /dev/null +++ b/ThruAudioApp.hpp @@ -0,0 +1,76 @@ +#ifndef __THRU_AUDIO_APP_HPP__ +#define __THRU_AUDIO_APP_HPP__ + +#include "src/memllib/audio/AudioAppBase.hpp" // Added missing include + +#include +#include +#include + +//#include "src/memllib/interface/InterfaceBase.hpp" // Added missing include + +#include +#include "voicespaces/VoiceSpaces.hpp" + + + + +template //params just go out as MIDI for this one +class ThruAudioApp : public AudioAppBase +{ +public: + static constexpr size_t kN_Params = NPARAMS; + static constexpr size_t nVoiceSpaces=1; + + + 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; + } + } + + ThruAudioApp() : AudioAppBase() { + auto voiceSpaceThru = [this](const std::array& params) { + }; + + voiceSpaces[0] = {"thru", voiceSpaceThru}; + + currentVoiceSpace = voiceSpaces[0].mappingFunction; + + }; + + + __attribute__((hot)) stereosample_t __force_inline Process(const stereosample_t x) override + { + return x; + } + + void Setup(float sample_rate, std::shared_ptr interface) override + { + AudioAppBase::Setup(sample_rate, interface); + maxiSettings::sampleRate = sample_rate; + } + + __attribute__((always_inline)) void ProcessParams(const std::array& params) + { + currentVoiceSpace(params); + } + + +protected: + +}; + +#endif diff --git a/XiasriAnalysis.cpp b/XiasriAnalysis.cpp new file mode 100644 index 0000000..c85bf5f --- /dev/null +++ b/XiasriAnalysis.cpp @@ -0,0 +1,149 @@ +#include "XiasriAnalysis.hpp" + +#include + +#include "src/memllib/hardware/memlnaut/Pins.hpp" +#include "src/memllib/utils/Maths.hpp" +#include "src/memllib/PicoDefs.hpp" + + +XiasriAnalysis::XiasriAnalysis(const float sample_rate) : + sample_rate_(sample_rate), + one_over_sample_rate_(1.0f / sample_rate), + zc_median_filter_(kZC_MedianFilterSize) { + + // Initialize filters and detectors + common_hpf_.set(maxiBiquad::filterTypes::HIGHPASS, 30.f, 0.707f, 0); + // Zero crossing + zc_lpf_.set(maxiBiquad::filterTypes::LOWPASS, 4000.0f, 0.707f, 0); + elapsed_samples_ = 0; + // Envelope follower + ef_follower_.setAttack(10.0f); + ef_follower_.setRelease(100.0f); + ef_deriv_y_ = 0; + // Brightness + br_lpf1_.set(maxiBiquad::filterTypes::LOWPASS, 1000.0f, 0.707f, 0); + br_hpf2_.set(maxiBiquad::filterTypes::HIGHPASS, 1000.0f, 0.707f, 0); + br_lpf2_.set(maxiBiquad::filterTypes::LOWPASS, 4000.0f, 0.707f, 0); + for (size_t i = 0; i < kBR_NBands; ++i) { + br_follower_[i].setAttack(10.f); + br_follower_[i].setRelease(100.f); + } +} + + +inline float logEnvelopeFast(float linearEnv) { + // -60 dBFS corresponds to a linear amplitude ratio of 10^(-60/20) = 10^(-3) = 0.001 + static constexpr float MIN_ENV = 1e-3f; // 10^(-60dB/20dB) = 0.001 linear + + // Mathematical derivation: + // We want to map linear amplitude [0.001, 1.0] to normalized range [0, 1] + // where 0.001 corresponds to -60 dBFS and 1.0 corresponds to 0 dBFS + // + // Using logarithmic mapping: output = (log2(input) - log2(min)) / (log2(max) - log2(min)) + // log2(0.001) = log2(10^-3) = -3 * log2(10) ≈ -9.966 + // log2(1.0) = 0 + // Range = 0 - (-9.966) = 9.966 + + static constexpr float LOG2_MIN_ENV = -3.0f * 3.321928095f; // -3 * log2(10) ≈ -9.966 + static constexpr float LOG2_MAX_ENV = 0.0f; // log2(1.0) = 0 + static constexpr float LOG_RANGE = LOG2_MAX_ENV - LOG2_MIN_ENV; // 9.966 + static constexpr float INV_LOG_RANGE = 1.0f / LOG_RANGE; // 1 / 9.966 ≈ 0.1003 + + // Clamp input to minimum envelope value + linearEnv = (linearEnv > MIN_ENV) ? linearEnv : MIN_ENV; + + // Precise logarithmic conversion + float log2_val = std::log2f(linearEnv); + + // Map to [0,1]: (log2_val - log2_min) / (log2_max - log2_min) + float y = (log2_val - LOG2_MIN_ENV) * INV_LOG_RANGE; + + // Clamp to [0,1] range (should be unnecessary given our math, but safety first) + y = (y > 1.0f) ? 1.0f : y; + y = (y < 0.0f) ? 0.0f : y; + + return y; +} + +XiasriAnalysis::parameters_t AUDIO_FUNC(XiasriAnalysis::Process)(const float x) { + parameters_t params = {}; + + // Pre-filter + float pre_filtered = common_hpf_.play(x); + + // Zero crossing detection + float zc_y = zc_lpf_.play(pre_filtered); + bool positive_zero_crossing = zc_detector_.zx(zc_y); + if (positive_zero_crossing) { + size_t median_elapsed_samples = zc_median_filter_.process(elapsed_samples_); + zc_buffer_.push(median_elapsed_samples); + elapsed_samples_ = 0; + } + // Convert zero crossing value to pitch + size_t zc_value = zc_buffer_[zc_buffer_.size() - 1]; + float pitch = 1.0f / (zc_value * one_over_sample_rate_); + // Map [100..800] hz to [0..1] range + float normalized_pitch; + if (pitch <= kPitchMin) { + normalized_pitch = 0.0f; + } else if (pitch >= kPitchMax) { + normalized_pitch = 1.0f; + } else { + normalized_pitch = (pitch - kPitchMin) * kPitchScale; + } + elapsed_samples_++; + // Aperiodicity calculation + float zc_copy[kZC_ZCBufferSize]; + // Copy contents of circular buffer to float array + for (size_t i = 0; i < kZC_ZCBufferSize; ++i) { + zc_copy[i] = static_cast(zc_buffer_[i]); + } + float mad = meanAbsoluteDeviation(zc_copy, kZC_ZCBufferSize); + // Scale MAD relative to median period + float medianPeriod = static_cast(zc_value); + float relativeMad = mad / (medianPeriod + 1.0f); // +1 to avoid div/0 + + // Typical relative MAD ranges from 0 to 0.3 for musical sounds + static constexpr float ONE_OVER_RELATIVE_MAD_MAX = 1/0.3f; + float normalizedAperiodicity = std::min(1.0f, relativeMad * ONE_OVER_RELATIVE_MAD_MAX); + + // Envelope follower + float ef_y = ef_follower_.play(pre_filtered); + // Convert to log + ef_y = logEnvelopeFast(ef_y); + float ef_d_dy = ef_y - ef_deriv_y_; + ef_deriv_y_ = ef_y; + // Half-wave rectify the derivative + if (ef_d_dy < 0) { + ef_d_dy = 0; + } + // Scale to [0..1] range + ef_d_dy = std::min(ef_d_dy * 10.0f, 1.0f); + + + // Brightness calculation + float br_low = br_lpf1_.play(pre_filtered); + float br_high = br_hpf2_.play(pre_filtered); + br_high = br_lpf2_.play(br_high); + + br_low = br_follower_[0].play(br_low); + br_high = br_follower_[1].play(br_high); + // brightness = high_band_energy / (low_band_energy + high_band_energy) + float br_energy = br_low + br_high; + if (br_energy > 0.0f) { + br_high /= br_energy; + } else { + br_high = 0.0f; // Avoid division by zero + } + + // Fill parameters + params.pitch = normalized_pitch; + params.aperiodicity = normalizedAperiodicity; + params.energy = ef_y; + params.attack = ef_d_dy; + params.brightness = br_high; + params.energy_crude = std::abs(x); + + return params; +} \ No newline at end of file diff --git a/XiasriAnalysis.hpp b/XiasriAnalysis.hpp new file mode 100644 index 0000000..4a41c3a --- /dev/null +++ b/XiasriAnalysis.hpp @@ -0,0 +1,59 @@ +#ifndef __SAX_ANALYSIS_HPP__ +#define __SAX_ANALYSIS_HPP__ + +#include "src/memllib/audio/AudioDriver.hpp" +#include "src/memllib/utils/MedianFilter.h" +#include "src/memllib/utils/CircularBuffer.hpp" +#include "src/memllib/synth/maximilian.h" + +#include + + +class XiasriAnalysis { + +public: + struct parameters_t { + float pitch; + float aperiodicity; + float energy; + float attack; + float brightness; + float energy_crude; + }; + static constexpr size_t kN_Params = sizeof(parameters_t) / sizeof(float); + + XiasriAnalysis(const float sample_rate); + + parameters_t Process(const float x); + +protected: + const float sample_rate_; + const float one_over_sample_rate_; + // Pre-filter + maxiBiquad common_hpf_; + // Zero crossing + static constexpr size_t kZC_MedianFilterSize = 16; + static constexpr size_t kZC_ZCBufferSize = 32; + static constexpr float kPitchMin = 20.0f; + static constexpr float kPitchMax = 800.0f; + static constexpr float kPitchRange = kPitchMax - kPitchMin; + static constexpr float kPitchScale = 1.0f / kPitchRange; + maxiBiquad zc_lpf_; + maxiZeroCrossingDetector zc_detector_; + size_t elapsed_samples_; + MedianFilter zc_median_filter_; + CircularBuffer zc_buffer_; + // Envelope follower + maxiEnvelopeFollowerF ef_follower_; + float ef_deriv_y_; + // Brightness + static constexpr size_t kBR_NBands = 2; + maxiBiquad br_lpf1_; + maxiBiquad br_hpf2_; + maxiBiquad br_lpf2_; + maxiEnvelopeFollowerF br_follower_[kBR_NBands]; + +}; + + +#endif // __SAX_ANALYSIS_HPP__ diff --git a/modes/MEMLNautMode.hpp b/modes/MEMLNautMode.hpp new file mode 100644 index 0000000..a8346d9 --- /dev/null +++ b/modes/MEMLNautMode.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include "WString.h" +#include "../src/memllib/interface/InterfaceBase.hpp" +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../src/memllib/audio/AudioDriver.hpp" + +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; + {proc.analyse(stereosample_t{})} -> std::same_as; + {proc.processAnalysisParams(std::shared_ptr{})} -> std::same_as; + {proc.getNMIDICtrlOutputs()} -> std::same_as; + requires std::same_as; +}; diff --git a/modes/MEMLNautModeChannelStrip.hpp b/modes/MEMLNautModeChannelStrip.hpp new file mode 100644 index 0000000..1678289 --- /dev/null +++ b/modes/MEMLNautModeChannelStrip.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../ChannelStripAudioApp.hpp" +#include "MEMLNautMode.hpp" +#include +#include + +class MEMLNautModeChannelStrip { +public: + constexpr static size_t kN_InputParams = 3; // joystick x, y, rotate + 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(); + } + + size_t getNMIDICtrlOutputs() { + return 0; + } + + inline void processAnalysisParams(std::shared_ptr interface) {} + +}; diff --git a/modes/MEMLNautModePAFSynth.hpp b/modes/MEMLNautModePAFSynth.hpp new file mode 100644 index 0000000..ad8ed2d --- /dev/null +++ b/modes/MEMLNautModePAFSynth.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../src/memllib/hardware/memlnaut/display/XYPadView.hpp" +#include "../src/memllib/hardware/memlnaut/MEMLNaut.hpp" +#include "../PAFSynthAudioApp.hpp" +#include "MEMLNautMode.hpp" +#include +#include + +class MEMLNautModePAFSynth { +public: + constexpr static size_t kN_InputParams = 3; // joystick x, y, rotate + + 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); + }; + + size_t getNMIDICtrlOutputs() { + return 16; + } + + inline void processAnalysisParams(std::shared_ptr interface) {} + +}; diff --git a/modes/MEMLNautModeSoundAnalysisMIDI.hpp b/modes/MEMLNautModeSoundAnalysisMIDI.hpp new file mode 100644 index 0000000..9c7f0cb --- /dev/null +++ b/modes/MEMLNautModeSoundAnalysisMIDI.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../ThruAudioApp.hpp" +#include "MEMLNautMode.hpp" +#include +#include +#include "../XiasriAnalysis.hpp" +#include "../src/memllib/utils/sharedMem.hpp" +#include "../src/memllib/audio/AudioDriver.hpp" + + +class MEMLNautModeSoundAnalysisMIDI { +public: + constexpr static size_t kN_InputParams = XiasriAnalysis::kN_Params; // joystick x, y, rotate + XiasriAnalysis mlAnalysis{kSampleRate}; + SharedBuffer machine_list_buffer; + + ThruAudioApp<> audioAppSoundAnalysisMIDI; + std::array::nVoiceSpaces> voiceSpaceList; + std::shared_ptr midi_interf; + + String getHelpTitle() { + return "Sound Analysis MIDI Mode"; + } + size_t getNParams() { + return ThruAudioApp<>::kN_Params; + } + + void setVoiceSpace(size_t i) { + audioAppSoundAnalysisMIDI.setVoiceSpace(i); + } + + std::span getVoiceSpaceList() { + return voiceSpaceList; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppSoundAnalysisMIDI.Process(x); + } + + void setupMIDI(std::shared_ptr new_midi_interf) { + midi_interf = new_midi_interf; + } + + void addViews() { + }; + + void Setup(float sample_rate, std::shared_ptr interface) { + audioAppSoundAnalysisMIDI.Setup(sample_rate, interface); + voiceSpaceList = audioAppSoundAnalysisMIDI.getVoiceSpaceNames(); + } + + __force_inline void loop() { + audioAppSoundAnalysisMIDI.loop(); + } + + inline void analyse(stereosample_t x) { + union { + XiasriAnalysis::parameters_t p; + float v[XiasriAnalysis::kN_Params]; + } param_u; + param_u.p = mlAnalysis.Process(x.L + x.R); + // Write params into shared_buffer + machine_list_buffer.writeNonBlocking(param_u.v, XiasriAnalysis::kN_Params); + } + + size_t getNMIDICtrlOutputs() { + return 8; + } + + inline void processAnalysisParams(std::shared_ptr interface) { + // Read SharedBuffer + std::vector mlist_params(XiasriAnalysis::kN_Params, 0); + machine_list_buffer.readNonBlocking(mlist_params); + // Send parameters to RL interface + interface->readAnalysisParameters(mlist_params); + } + +}; diff --git a/modes/MEMLNautModeXIASRI.hpp b/modes/MEMLNautModeXIASRI.hpp new file mode 100644 index 0000000..4701fd2 --- /dev/null +++ b/modes/MEMLNautModeXIASRI.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "../src/memllib/interface/MIDIInOut.hpp" +#include "../ChannelStripAudioApp.hpp" +#include "MEMLNautMode.hpp" +#include +#include + +class MEMLNautModeXIASRI { +public: + constexpr static size_t kN_InputParams = XiasriAnalysis::kN_Params; // joystick x, y, rotate + + ChannelStripAudioApp<> audioAppXIASRI; + std::array::nVoiceSpaces> voiceSpaceList; + + String getHelpTitle() { + return "XIASRI Mode"; + } + size_t getNParams() { + return ChannelStripAudioApp<>::kN_Params; + } + + void setVoiceSpace(size_t i) { + audioAppXIASRI.setVoiceSpace(i); + } + + std::span getVoiceSpaceList() { + return voiceSpaceList; + } + + __force_inline stereosample_t process(stereosample_t x) { + return audioAppXIASRI.Process(x); + } + + void setupMIDI(std::shared_ptr midi_interf) { + } + + void addViews() { + + }; + + void Setup(float sample_rate, std::shared_ptr interface) { + audioAppXIASRI.Setup(sample_rate, interface); + voiceSpaceList = audioAppXIASRI.getVoiceSpaceNames(); + } + + __force_inline void loop() { + audioAppXIASRI.loop(); + } + + size_t getNMIDICtrlOutputs() { + return 0; + } + + inline void processAnalysisParams(std::shared_ptr interface) {} + + + +};