//hardware #include "src/memllib/utils/perf.hpp" #include "src/memllib/interface/MIDIInOut.hpp" #include "src/memllib/audio/AudioDriver.hpp" #include "src/memllib/hardware/memlnaut/MEMLNaut.hpp" #include "hardware/structs/bus_ctrl.h" #include //sound #include "src/memllib/audio/AudioAppBase.hpp" #include "PAFSynthAudioApp.hpp" #include "ChannelStripAudioApp.hpp" //interface #include "src/memllib/examples/InterfaceRL.hpp" #include "src/memllib/hardware/memlnaut/display/XYPadView.hpp" #include "src/memllib/hardware/memlnaut/display/MessageView.hpp" #include "src/memllib/hardware/memlnaut/display/VoiceSpaceSelectView.hpp" #define INTERFACE_TYPE InterfaceRL #include template concept MEMLNautMode = requires(T proc) { {proc.getHelpTitle()} -> std::same_as; {proc.getNParams()} -> std::same_as; {proc.setVoiceSpace(size_t{})} -> std::same_as; {proc.setupMIDI(std::shared_ptr{})} -> std::same_as; {proc.addViews()} -> std::same_as; {proc.Setup(float{}, std::shared_ptr{})} -> std::same_as; {proc.loop()} -> std::same_as; {proc.getVoiceSpaceList()} -> std::same_as>; {proc.process(stereosample_t{})} -> std::same_as; }; class MEMLNautModePAFSynth { public: inline static PAFSynthAudioApp<> audioAppPAFSynth; std::array::nVoiceSpaces> voiceSpaceList; String getHelpTitle() { return "PAF Synth Mode"; } size_t getNParams() { return PAFSynthAudioApp<>::kN_Params; } void setVoiceSpace(size_t i) { audioAppPAFSynth.setVoiceSpace(i); } std::span getVoiceSpaceList() { return voiceSpaceList; } __force_inline stereosample_t process(stereosample_t x) { return audioAppPAFSynth.Process(x); } void Setup(float sample_rate, std::shared_ptr interface) { audioAppPAFSynth.Setup(sample_rate, interface); voiceSpaceList = audioAppPAFSynth.getVoiceSpaceNames(); } __force_inline void loop() { audioAppPAFSynth.loop(); } std::shared_ptr midi_interf; void setupMIDI(std::shared_ptr new_midi_interf) { midi_interf = new_midi_interf; midi_interf->SetNoteCallback([this](bool noteon, uint8_t note_number, uint8_t vel_value) { if (noteon) { uint8_t midimsg[2] = { note_number, vel_value }; queue_try_add(&audioAppPAFSynth.qMIDINoteOn, &midimsg); }else{ uint8_t midimsg[2] = { note_number, vel_value }; queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg); } // Serial.printf("MIDI Note %d: %d %d\n", note_number, vel_value, noteon); }); // Serial.println("MIDI note callback set."); } void addViews() { std::shared_ptr noteTrigView = std::make_shared("Play", TFT_SILVER); // Cache MIDI notes being echoed static bool is_playing_note = false; static uint8_t last_note_number = 0; noteTrigView->SetOnTouchCallback([this](float x, float y) { // Serial.printf("Note trigger at: %.2f, %.2f\n", x, y); // If a note is already playing, stop it if (is_playing_note) { midi_interf->sendNoteOff(last_note_number, 0); is_playing_note = false; } uint8_t noteVel = static_cast(powf(y, 0.5f) * 127.f); uint8_t midimsg[2] = {static_cast(x * 127.f), noteVel}; queue_try_add(&audioAppPAFSynth.qMIDINoteOn, &midimsg); midi_interf->sendNoteOn(midimsg[0], midimsg[1]); last_note_number = midimsg[0]; is_playing_note = true; // Set flag to indicate a note is playing // Serial.printf("sending %d %d\n",midimsg[0], midimsg[1]); }); noteTrigView->SetOnTouchReleaseCallback([this](float x, float y) { // Serial.printf("Note release at: %.2f, %.2f\n", x, y); uint8_t midimsg[2] = {last_note_number,0}; queue_try_add(&audioAppPAFSynth.qMIDINoteOff, &midimsg); midi_interf->sendNoteOff(last_note_number, 0); is_playing_note = false; // Reset flag when note is released }); MEMLNaut::Instance()->disp->AddView(noteTrigView); }; }; class MEMLNautModeChannelStrip { public: ChannelStripAudioApp<> audioAppChannelStrip; std::array::nVoiceSpaces> voiceSpaceList; String getHelpTitle() { return "Channel Strip Mode"; } size_t getNParams() { return ChannelStripAudioApp<>::kN_Params; } void setVoiceSpace(size_t i) { audioAppChannelStrip.setVoiceSpace(i); } std::span getVoiceSpaceList() { return voiceSpaceList; } __force_inline stereosample_t process(stereosample_t x) { return audioAppChannelStrip.Process(x); } void setupMIDI(std::shared_ptr midi_interf) { } void addViews() { }; void Setup(float sample_rate, std::shared_ptr interface) { audioAppChannelStrip.Setup(sample_rate, interface); voiceSpaceList = audioAppChannelStrip.getVoiceSpaceNames(); } __force_inline void loop() { audioAppChannelStrip.loop(); } }; MEMLNautModeChannelStrip AUDIO_MEM channelStripMode; // MEMLNautModePAFSynth AUDIO_MEM pafSynthMode; MEMLNautMode auto* AUDIO_MEM currentMode = &channelStripMode; #define APP_SRAM __not_in_flash("app") bool core1_disable_systick = true; bool core1_separate_stack = true; uint32_t get_rosc_entropy_seed(int bits) { uint32_t seed = 0; for (int i = 0; i < bits; ++i) { // Wait for a bit of time to allow jitter to accumulate busy_wait_us_32(5); // Pull LSB from ROSC rand output seed <<= 1; seed |= (rosc_hw->randombit & 1); } return seed; } // Global objects std::shared_ptr APP_SRAM interface; std::shared_ptr APP_SRAM midi_interf; // Statically allocated, properly aligned storage in AUDIO_MEM for objects // alignas(PAFSynthAudioApp<>) char AUDIO_MEM audio_app_mem[sizeof(PAFSynthAudioApp<>)]; // std::shared_ptr > __scratch_y("audio") audio_app; // alignas(ChannelStripAudioApp<>) char AUDIO_MEM audio_app_mem_chstrip[sizeof(ChannelStripAudioApp<>)]; // std::shared_ptr > __scratch_y("audio") audio_app_chstrip; //preallocate ChannelStripAudioApp // static ChannelStripAudioApp<> AUDIO_MEM audioAppChannelStrip; // Inter-core communication volatile bool APP_SRAM core_0_ready = false; volatile bool APP_SRAM core_1_ready = false; volatile bool APP_SRAM serial_ready = false; volatile bool APP_SRAM interface_ready = false; // We're only bound to the joystick inputs (x, y, rotate) constexpr size_t kN_InputParams = 3; // Add these macros near other globals #define MEMORY_BARRIER() __sync_synchronize() #define WRITE_VOLATILE(var, val) \ do { \ MEMORY_BARRIER(); \ (var) = (val); \ MEMORY_BARRIER(); \ } while (0) #define READ_VOLATILE(var) ({ MEMORY_BARRIER(); typeof(var) __temp = (var); MEMORY_BARRIER(); __temp; }) void setup() { set_sys_clock_khz(AudioDriver::GetSysClockSpeed(), true); bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS | BUSCTRL_BUS_PRIORITY_PROC1_BITS; uint32_t seed = get_rosc_entropy_seed(32); srand(seed); Serial.begin(115200); // while (!Serial) {} Serial.println("Serial initialised."); WRITE_VOLATILE(serial_ready, true); // Setup board MEMLNaut::Initialize(); pinMode(33, OUTPUT); { auto temp_interface = std::make_shared(); // temp_interface->setup(kN_InputParams, PAFSynthAudioApp<>::kN_Params); // temp_interface->setup(kN_InputParams, ChannelStripAudioApp<>::kN_Params); temp_interface->setup(kN_InputParams, currentMode->getNParams()); MEMORY_BARRIER(); interface = temp_interface; MEMORY_BARRIER(); } // Setup interface with memory barrier protection WRITE_VOLATILE(interface_ready, true); // Bind interface after ensuring it's fully initialized interface->bindInterface(false); Serial.println("Bound interface to MEMLNaut."); midi_interf = std::make_shared(); // midi_interf->Setup(PAFSynthAudioApp<>::kN_Params); midi_interf->Setup(16); // midi_interf->Setup(0); midi_interf->SetMIDISendChannel(1); Serial.println("MIDI setup complete."); if (midi_interf) { currentMode->setupMIDI(midi_interf); interface->bindMIDI(midi_interf); } WRITE_VOLATILE(core_0_ready, true); while (!READ_VOLATILE(core_1_ready)) { MEMORY_BARRIER(); delay(1); } std::shared_ptr voiceSpaceSelectView; voiceSpaceSelectView = std::make_shared("Voice Spaces"); MEMLNaut::Instance()->disp->InsertViewAfter(interface->rlStatsView, voiceSpaceSelectView); // voiceSpaceSelectView->setOptions(voiceSpaceList); //set by core 1 on startup // voiceSpaceSelectView->setOptions(voiceSpaceList_chstrip); //set by core 1 on startup voiceSpaceSelectView->setOptions(currentMode->getVoiceSpaceList()); //set by core 1 on startup voiceSpaceSelectView->setNewVoiceCallback( [](size_t idx) { // Serial.println(idx); // audio_app_chstrip->setVoiceSpace(idx); // audioAppChannelStrip.setVoiceSpace(idx); // audio_app->setVoiceSpace(idx); currentMode->setVoiceSpace(idx); } ); currentMode->addViews(); std::shared_ptr helpView = std::make_shared("Help"); // helpView->post("PAF synth NISPS"); String title =currentMode->getHelpTitle(); helpView->post(title); helpView->post("TA: Down: Clear replay memory"); helpView->post("MA: Up: Randomise / Down: Jolt "); helpView->post("MB: Up: Positive reward"); helpView->post("MB: Down: Negative reward"); helpView->post("X: Learning rate"); helpView->post("Y: Reward Scale"); helpView->post("Z: Exploration noise"); helpView->post("Joystick: Explore / SW: Drag sound"); MEMLNaut::Instance()->disp->AddView(helpView); MEMLNaut::Instance()->addSystemInfoView(); Serial.println("Finished initialising core 0."); } PERF_DECLARE(MLSTATS); #define ML_INFERENCE_PERIOD_US 5000 void loop() { PERIODIC_RUN_US( PERF_BEGIN(MLSTATS); MEMLNaut::Instance()->loop(); PERF_END(MLSTATS); , ML_INFERENCE_PERIOD_US) PERIODIC_RUN_US( static size_t blip_counter=0; if (blip_counter++ > 10) { blip_counter = 0; Serial.println("."); // Blink LED digitalWrite(33, HIGH); constexpr float audioHeadroomMul = 1.0 / (1000000 * 48.0 / kSampleRate); Serial.printf("ml: %d, aud: %d, q: %f\n", PERF_GET_MEAN(MLSTATS), AUDIOLOOP_MEAN, AUDIOLOOP_MEAN * audioHeadroomMul); } else { // Un-blink LED digitalWrite(33, LOW); } , 100000) } void AUDIO_FUNC(audio_block_callback)(float in[][kBufferSize], float out[][kBufferSize], size_t n_channels, size_t n_frames) { // Serial.println(in[0][0]); for (size_t i = 0; i < n_frames; ++i) { stereosample_t x{ in[0][i], in[1][i] }, y; y = currentMode->process(x); out[0][i] = y.L; out[1][i] = y.R; } // Serial.println(in[0][0]); } void setup1() { while (!READ_VOLATILE(serial_ready)) { MEMORY_BARRIER(); delay(1); } while (!READ_VOLATILE(interface_ready)) { MEMORY_BARRIER(); delay(1); } // Create audio app with memory barrier protection { // PAFSynthAudioApp<>* audio_raw = new (audio_app_mem) PAFSynthAudioApp<>(); // audio_raw->Setup(AudioDriver::GetSampleRate(), interface); // // shared_ptr with custom deleter calling only the destructor (control block still allocates) // auto audio_deleter = [](PAFSynthAudioApp<>* p) { // if (p) p->~PAFSynthAudioApp<>(); // }; // std::shared_ptr> temp_audio_app(audio_raw, audio_deleter); // ChannelStripAudioApp<>* audio_raw = new (audio_app_mem_chstrip) ChannelStripAudioApp<>(); // audio_raw->Setup(AudioDriver::GetSampleRate(), interface); // // shared_ptr with custom deleter calling only the destructor (control block still allocates) // auto audio_deleter = [](ChannelStripAudioApp<>* p) { // if (p) p->~ChannelStripAudioApp<>(); // }; // std::shared_ptr> temp_audio_app(audio_raw, audio_deleter); // MEMORY_BARRIER(); // audio_app_chstrip = temp_audio_app; // MEMORY_BARRIER(); } // audioAppChannelStrip.Setup(AudioDriver::GetSampleRate(), interface); currentMode->Setup(AudioDriver::GetSampleRate(), interface); AudioDriver::SetBlockCallback(audio_block_callback); // AudioDriver::SetBlockCallback(currentMode->getAudioCallBack()); // Start audio driver AudioDriver::Setup(); // AudioDriver::SetBlockCallback(audio_block_callback); // voiceSpaceList = audio_app->getVoiceSpaceNames(); // voiceSpaceList_chstrip = audio_app_chstrip->getVoiceSpaceNames(); // voiceSpaceList_chstrip = audioAppChannelStrip.getVoiceSpaceNames(); WRITE_VOLATILE(core_1_ready, true); while (!READ_VOLATILE(core_0_ready)) { MEMORY_BARRIER(); delay(1); } Serial.println("Finished initialising core 1."); } void loop1() { // Audio app parameter processing loop PERIODIC_RUN_US( // audio_app_chstrip->loop(); // audioAppChannelStrip.loop(); currentMode->loop(); // audio_app->loop(); , ML_INFERENCE_PERIOD_US) PERIODIC_RUN_US( midi_interf->Poll(); , 10000) // #if 1 //test ARP // PERIODIC_RUN_US( // static size_t arpCount=0; // static size_t noteIndex=30; // , 100000) // #endif }