This commit is contained in:
chriskiefer 2025-11-23 15:48:14 +00:00
parent 81a7cb80b9
commit d623adfe42
9 changed files with 634 additions and 187 deletions

View file

@ -18,154 +18,21 @@
#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 <concepts>
#define MEMLNAUT_MODE_TYPE MEMLNautModeSoundAnalysisMIDI
template<typename T>
concept MEMLNautMode = requires(T proc) {
{proc.getHelpTitle()} -> std::same_as<String>;
{proc.getNParams()} -> std::same_as<size_t>;
{proc.setVoiceSpace(size_t{})} -> std::same_as<void>;
{proc.setupMIDI(std::shared_ptr<MIDIInOut>{})} -> std::same_as<void>;
{proc.addViews()} -> std::same_as<void>;
{proc.Setup(float{}, std::shared_ptr<InterfaceBase>{})} -> std::same_as<void>;
{proc.loop()} -> std::same_as<void>;
{proc.getVoiceSpaceList()} -> std::same_as<std::span<String>>;
{proc.process(stereosample_t{})} -> std::same_as<stereosample_t>;
};
class MEMLNautModePAFSynth {
public:
inline static PAFSynthAudioApp<> audioAppPAFSynth;
std::array<String, PAFSynthAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "PAF Synth Mode";
}
size_t getNParams() {
return PAFSynthAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppPAFSynth.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppPAFSynth.Process(x);
}
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface) {
audioAppPAFSynth.Setup(sample_rate, interface);
voiceSpaceList = audioAppPAFSynth.getVoiceSpaceNames();
}
__force_inline void loop() {
audioAppPAFSynth.loop();
}
std::shared_ptr<MIDIInOut> midi_interf;
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
midi_interf = new_midi_interf;
midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) {
if (noteon) {
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audioAppPAFSynth.qMIDINoteOn, &midimsg);
}else{
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg);
}
// Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon);
});
// Serial.println("MIDI note callback set.");
}
void addViews() {
std::shared_ptr<XYPadView> noteTrigView = std::make_shared<XYPadView>("Play", TFT_SILVER);
// Cache MIDI notes being echoed
static bool is_playing_note = false;
static uint8_t last_note_number = 0;
noteTrigView->SetOnTouchCallback([this](float x, float y) {
// Serial.printf("Note trigger at: %.2f, %.2f\n", x, y);
// If a note is already playing, stop it
if (is_playing_note) {
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false;
}
uint8_t noteVel = static_cast<uint8_t>(powf(y, 0.5f) * 127.f);
uint8_t midimsg[2] = {static_cast<uint8_t>(x * 127.f), noteVel};
queue_try_add(&audioAppPAFSynth.qMIDINoteOn, &midimsg);
midi_interf->sendNoteOn(midimsg[0], midimsg[1]);
last_note_number = midimsg[0];
is_playing_note = true; // Set flag to indicate a note is playing
// Serial.printf("sending %d %d\n",midimsg[0], midimsg[1]);
});
noteTrigView->SetOnTouchReleaseCallback([this](float x, float y) {
// Serial.printf("Note release at: %.2f, %.2f\n", x, y);
uint8_t midimsg[2] = {last_note_number,0};
queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg);
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false; // Reset flag when note is released
});
MEMLNaut::Instance()->disp->AddView(noteTrigView);
};
};
class MEMLNautModeChannelStrip {
public:
ChannelStripAudioApp<> audioAppChannelStrip;
std::array<String, ChannelStripAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "Channel Strip Mode";
}
size_t getNParams() {
return ChannelStripAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppChannelStrip.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppChannelStrip.Process(x);
}
void setupMIDI(std::shared_ptr<MIDIInOut> midi_interf) {
}
void addViews() {
};
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface) {
audioAppChannelStrip.Setup(sample_rate, interface);
voiceSpaceList = audioAppChannelStrip.getVoiceSpaceNames();
}
__force_inline void loop() {
audioAppChannelStrip.loop();
}
};
MEMLNautModeChannelStrip AUDIO_MEM channelStripMode;
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<INTERFACE_TYPE> APP_SRAM interface;
std::shared_ptr<MIDIInOut> APP_SRAM midi_interf;
// Statically allocated, properly aligned storage in AUDIO_MEM for objects
// alignas(PAFSynthAudioApp<>) char AUDIO_MEM audio_app_mem[sizeof(PAFSynthAudioApp<>)];
// std::shared_ptr<PAFSynthAudioApp<> > __scratch_y("audio") audio_app;
// alignas(ChannelStripAudioApp<>) char AUDIO_MEM audio_app_mem_chstrip[sizeof(ChannelStripAudioApp<>)];
// std::shared_ptr<ChannelStripAudioApp<> > __scratch_y("audio") audio_app_chstrip;
//preallocate ChannelStripAudioApp
// static ChannelStripAudioApp<> AUDIO_MEM audioAppChannelStrip;
// Inter-core communication
volatile bool APP_SRAM core_0_ready = false;
volatile bool APP_SRAM core_1_ready = false;
@ -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,7 +104,7 @@ 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();
@ -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<MIDIInOut>();
// 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) {
@ -286,7 +141,7 @@ void setup() {
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<MessageView> helpView = std::make_shared<MessageView>("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
}

76
ThruAudioApp.hpp Normal file
View file

@ -0,0 +1,76 @@
#ifndef __THRU_AUDIO_APP_HPP__
#define __THRU_AUDIO_APP_HPP__
#include "src/memllib/audio/AudioAppBase.hpp" // Added missing include
#include <cstddef>
#include <cstdint>
#include <memory>
//#include "src/memllib/interface/InterfaceBase.hpp" // Added missing include
#include <span>
#include "voicespaces/VoiceSpaces.hpp"
template<size_t NPARAMS=8> //params just go out as MIDI for this one
class ThruAudioApp : public AudioAppBase<NPARAMS>
{
public:
static constexpr size_t kN_Params = NPARAMS;
static constexpr size_t nVoiceSpaces=1;
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;
}
}
ThruAudioApp() : AudioAppBase<NPARAMS>() {
auto voiceSpaceThru = [this](const std::array<float, NPARAMS>& 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<InterfaceBase> interface) override
{
AudioAppBase<NPARAMS>::Setup(sample_rate, interface);
maxiSettings::sampleRate = sample_rate;
}
__attribute__((always_inline)) void ProcessParams(const std::array<float, NPARAMS>& params)
{
currentVoiceSpace(params);
}
protected:
};
#endif

149
XiasriAnalysis.cpp Normal file
View file

@ -0,0 +1,149 @@
#include "XiasriAnalysis.hpp"
#include <cmath>
#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<float>(zc_buffer_[i]);
}
float mad = meanAbsoluteDeviation(zc_copy, kZC_ZCBufferSize);
// Scale MAD relative to median period
float medianPeriod = static_cast<float>(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;
}

59
XiasriAnalysis.hpp Normal file
View file

@ -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 <cmath>
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<size_t> zc_median_filter_;
CircularBuffer<size_t, kZC_ZCBufferSize> 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__

26
modes/MEMLNautMode.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <concepts>
#include <memory>
#include <span>
#include "WString.h"
#include "../src/memllib/interface/InterfaceBase.hpp"
#include "../src/memllib/interface/MIDIInOut.hpp"
#include "../src/memllib/audio/AudioDriver.hpp"
template<typename T>
concept MEMLNautMode = requires(T proc) {
{proc.getHelpTitle()} -> std::same_as<String>;
{proc.getNParams()} -> std::same_as<size_t>;
{proc.setVoiceSpace(size_t{})} -> std::same_as<void>;
{proc.setupMIDI(std::shared_ptr<MIDIInOut>{})} -> std::same_as<void>;
{proc.addViews()} -> std::same_as<void>;
{proc.Setup(float{}, std::shared_ptr<InterfaceBase>{})} -> std::same_as<void>;
{proc.loop()} -> std::same_as<void>;
{proc.getVoiceSpaceList()} -> std::same_as<std::span<String>>;
{proc.process(stereosample_t{})} -> std::same_as<stereosample_t>;
{proc.analyse(stereosample_t{})} -> std::same_as<void>;
{proc.processAnalysisParams(std::shared_ptr<InterfaceBase>{})} -> std::same_as<void>;
{proc.getNMIDICtrlOutputs()} -> std::same_as<size_t>;
requires std::same_as<decltype(T::kN_InputParams), const size_t>;
};

View file

@ -0,0 +1,56 @@
#pragma once
#include "../src/memllib/interface/MIDIInOut.hpp"
#include "../ChannelStripAudioApp.hpp"
#include "MEMLNautMode.hpp"
#include <memory>
#include <array>
class MEMLNautModeChannelStrip {
public:
constexpr static size_t kN_InputParams = 3; // joystick x, y, rotate
ChannelStripAudioApp<> audioAppChannelStrip;
std::array<String, ChannelStripAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "Channel Strip Mode";
}
size_t getNParams() {
return ChannelStripAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppChannelStrip.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppChannelStrip.Process(x);
}
void setupMIDI(std::shared_ptr<MIDIInOut> midi_interf) {
}
void addViews() {
};
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface) {
audioAppChannelStrip.Setup(sample_rate, interface);
voiceSpaceList = audioAppChannelStrip.getVoiceSpaceNames();
}
__force_inline void loop() {
audioAppChannelStrip.loop();
}
size_t getNMIDICtrlOutputs() {
return 0;
}
inline void processAnalysisParams(std::shared_ptr<InterfaceBase> interface) {}
};

View file

@ -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 <memory>
#include <array>
class MEMLNautModePAFSynth {
public:
constexpr static size_t kN_InputParams = 3; // joystick x, y, rotate
inline static PAFSynthAudioApp<> audioAppPAFSynth;
std::array<String, PAFSynthAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "PAF Synth Mode";
}
size_t getNParams() {
return PAFSynthAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppPAFSynth.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppPAFSynth.Process(x);
}
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> interface) {
audioAppPAFSynth.Setup(sample_rate, interface);
voiceSpaceList = audioAppPAFSynth.getVoiceSpaceNames();
}
__force_inline void loop() {
audioAppPAFSynth.loop();
}
std::shared_ptr<MIDIInOut> midi_interf;
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
midi_interf = new_midi_interf;
midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) {
if (noteon) {
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audioAppPAFSynth.qMIDINoteOn, &midimsg);
}else{
uint8_t midimsg[2] = { note_number, vel_value };
queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg);
}
// Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon);
});
// Serial.println("MIDI note callback set.");
}
void addViews() {
std::shared_ptr<XYPadView> noteTrigView = std::make_shared<XYPadView>("Play", TFT_SILVER);
// Cache MIDI notes being echoed
static bool is_playing_note = false;
static uint8_t last_note_number = 0;
noteTrigView->SetOnTouchCallback([this](float x, float y) {
// Serial.printf("Note trigger at: %.2f, %.2f\n", x, y);
// If a note is already playing, stop it
if (is_playing_note) {
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false;
}
uint8_t noteVel = static_cast<uint8_t>(powf(y, 0.5f) * 127.f);
uint8_t midimsg[2] = {static_cast<uint8_t>(x * 127.f), noteVel};
queue_try_add(&audioAppPAFSynth.qMIDINoteOn, &midimsg);
midi_interf->sendNoteOn(midimsg[0], midimsg[1]);
last_note_number = midimsg[0];
is_playing_note = true; // Set flag to indicate a note is playing
// Serial.printf("sending %d %d\n",midimsg[0], midimsg[1]);
});
noteTrigView->SetOnTouchReleaseCallback([this](float x, float y) {
// Serial.printf("Note release at: %.2f, %.2f\n", x, y);
uint8_t midimsg[2] = {last_note_number,0};
queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg);
midi_interf->sendNoteOff(last_note_number, 0);
is_playing_note = false; // Reset flag when note is released
});
MEMLNaut::Instance()->disp->AddView(noteTrigView);
};
size_t getNMIDICtrlOutputs() {
return 16;
}
inline void processAnalysisParams(std::shared_ptr<InterfaceBase> interface) {}
};

View file

@ -0,0 +1,80 @@
#pragma once
#include "../src/memllib/interface/MIDIInOut.hpp"
#include "../ThruAudioApp.hpp"
#include "MEMLNautMode.hpp"
#include <memory>
#include <array>
#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<float, XiasriAnalysis::kN_Params> machine_list_buffer;
ThruAudioApp<> audioAppSoundAnalysisMIDI;
std::array<String, ThruAudioApp<>::nVoiceSpaces> voiceSpaceList;
std::shared_ptr<MIDIInOut> 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<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppSoundAnalysisMIDI.Process(x);
}
void setupMIDI(std::shared_ptr<MIDIInOut> new_midi_interf) {
midi_interf = new_midi_interf;
}
void addViews() {
};
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> 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<InterfaceBase> interface) {
// Read SharedBuffer
std::vector<float> mlist_params(XiasriAnalysis::kN_Params, 0);
machine_list_buffer.readNonBlocking(mlist_params);
// Send parameters to RL interface
interface->readAnalysisParameters(mlist_params);
}
};

View file

@ -0,0 +1,59 @@
#pragma once
#include "../src/memllib/interface/MIDIInOut.hpp"
#include "../ChannelStripAudioApp.hpp"
#include "MEMLNautMode.hpp"
#include <memory>
#include <array>
class MEMLNautModeXIASRI {
public:
constexpr static size_t kN_InputParams = XiasriAnalysis::kN_Params; // joystick x, y, rotate
ChannelStripAudioApp<> audioAppXIASRI;
std::array<String, ChannelStripAudioApp<>::nVoiceSpaces> voiceSpaceList;
String getHelpTitle() {
return "XIASRI Mode";
}
size_t getNParams() {
return ChannelStripAudioApp<>::kN_Params;
}
void setVoiceSpace(size_t i) {
audioAppXIASRI.setVoiceSpace(i);
}
std::span<String> getVoiceSpaceList() {
return voiceSpaceList;
}
__force_inline stereosample_t process(stereosample_t x) {
return audioAppXIASRI.Process(x);
}
void setupMIDI(std::shared_ptr<MIDIInOut> midi_interf) {
}
void addViews() {
};
void Setup(float sample_rate, std::shared_ptr<InterfaceBase> 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<InterfaceBase> interface) {}
};