diff --git a/firmware/lib/cpputil b/firmware/lib/cpputil index 3e510c2b1..6822ef580 160000 --- a/firmware/lib/cpputil +++ b/firmware/lib/cpputil @@ -1 +1 @@ -Subproject commit 3e510c2b15483501351730a1203b96e230f6a498 +Subproject commit 6822ef580f8db031445269d7af56a178fe9e5acb diff --git a/firmware/src/core_a7/aux_core_main.cc b/firmware/src/core_a7/aux_core_main.cc index 9b8b5de4c..3d535a801 100644 --- a/firmware/src/core_a7/aux_core_main.cc +++ b/firmware/src/core_a7/aux_core_main.cc @@ -1,17 +1,14 @@ +#include "aux_core_player.hh" #include "conf/hsem_conf.hh" #include "core_a7/a7_shared_memory.hh" #include "core_a7/async_thread_control.hh" -#include "core_a7/smp_api.hh" #include "debug.hh" #include "drivers/hsem.hh" -#include "drivers/smp.hh" -#include "drivers/timekeeper.hh" #include "dynload/plugin_manager.hh" #include "fs/filesystem.hh" #include "fs/norflash_layout.hh" #include "gui/ui.hh" #include "internal_plugin_manager.hh" -#include "patch_play/patch_player.hh" using FrameBufferT = std::array; @@ -31,29 +28,22 @@ extern "C" void aux_core_main() { pr_info("A7 Core 2 starting\n"); - auto patch_player = A7SharedMemoryS::ptrs.patch_player; - auto patch_playloader = A7SharedMemoryS::ptrs.patch_playloader; - auto file_storage_proxy = A7SharedMemoryS::ptrs.patch_storage; - auto open_patch_manager = A7SharedMemoryS::ptrs.open_patch_manager; - auto sync_params = A7SharedMemoryS::ptrs.sync_params; - auto patch_mod_queue = A7SharedMemoryS::ptrs.patch_mod_queue; - auto ramdisk_storage = A7SharedMemoryS::ptrs.ramdrive; #ifdef CONSOLE_USE_USB UartLog::use_usb(A7SharedMemoryS::ptrs.console_buffer); #endif LVGLDriver gui{MMDisplay::flush_to_screen, MMDisplay::read_input, MMDisplay::wait_cb, framebuf1, framebuf2}; - RamDiskOps ramdisk_ops{*ramdisk_storage}; + RamDiskOps ramdisk_ops{*A7SharedMemoryS::ptrs.ramdrive}; FatFileIO ramdisk{&ramdisk_ops, Volume::RamDisk}; AssetFS asset_fs{AssetVolFlashOffset}; Filesystem::Init(ramdisk); - PluginManager plugin_manager{*file_storage_proxy, ramdisk}; - Ui ui{*patch_playloader, - *file_storage_proxy, - *open_patch_manager, - *sync_params, - *patch_mod_queue, + PluginManager plugin_manager{*A7SharedMemoryS::ptrs.patch_storage, ramdisk}; + Ui ui{*A7SharedMemoryS::ptrs.patch_playloader, + *A7SharedMemoryS::ptrs.patch_storage, + *A7SharedMemoryS::ptrs.open_patch_manager, + *A7SharedMemoryS::ptrs.sync_params, + *A7SharedMemoryS::ptrs.patch_mod_queue, plugin_manager, ramdisk}; ui.update_screen(); @@ -61,62 +51,7 @@ extern "C" void aux_core_main() { InternalPluginManager internal_plugin_manager{ramdisk, asset_fs}; - struct AuxCoreModulesToRun { - uint32_t starting_idx = 1; - uint32_t num_modules = 0; - uint32_t idx_increment = 2; - } modules_to_run; - - constexpr auto PlayModuleListIRQn = SMPControl::IRQn(SMPCommand::PlayModuleList); - InterruptManager::register_and_start_isr(PlayModuleListIRQn, 1, 0, [&modules_to_run, &patch_player]() { - // Debug::Pin1::high(); - for (unsigned i = modules_to_run.starting_idx; i < modules_to_run.num_modules; - i += modules_to_run.idx_increment) - { - patch_player->modules[i]->update(); - } - // Debug::Pin1::low(); - SMPThread::signal_done(); - }); - - constexpr auto NewModuleListIRQn = SMPControl::IRQn(SMPCommand::NewModuleList); - InterruptManager::register_and_start_isr(NewModuleListIRQn, 0, 0, [&modules_to_run]() { - modules_to_run.starting_idx = SMPControl::read(); - modules_to_run.num_modules = SMPControl::read(); - modules_to_run.idx_increment = SMPControl::read(); - SMPThread::signal_done(); - }); - - constexpr auto ReadPatchLightsIRQn = SMPControl::IRQn(SMPCommand::ReadPatchLights); - InterruptManager::register_and_start_isr(ReadPatchLightsIRQn, 2, 0, [patch_player, &ui]() { - if (ui.new_patch_data == false) { - - for (auto &w : ui.lights().watch_lights) { - if (w.is_active()) { - auto val = patch_player->get_module_light(w.module_id, w.light_id); - w.value = val; - } - } - - for (auto &d : ui.displays().watch_displays) { - if (d.is_active()) { - auto text = std::span(d.text._data, d.text.capacity); - auto sz = patch_player->get_display_text(d.module_id, d.light_id, text); - d.text._data[sz] = '\0'; - } - } - - for (auto &p : ui.watched_params().active_watched_params()) { - if (p.is_active()) { - p.value = patch_player->get_param(p.module_id, p.param_id); - } - } - - ui.new_patch_data = true; - } - - SMPThread::signal_done(); - }); + AuxPlayer aux_player{*A7SharedMemoryS::ptrs.patch_player, ui}; // Wait for M4 to be ready (so USB and SD are available) while (mdrivlib::HWSemaphore::is_locked()) diff --git a/firmware/src/core_a7/aux_core_player.hh b/firmware/src/core_a7/aux_core_player.hh new file mode 100644 index 000000000..c53336eed --- /dev/null +++ b/firmware/src/core_a7/aux_core_player.hh @@ -0,0 +1,96 @@ +#pragma once +#include "core_a7/smp_api.hh" +#include "drivers/interrupt.hh" +#include "drivers/smp.hh" +#include "gui/ui.hh" +#include "patch_play/patch_player.hh" +#include "util/fixed_vector.hh" + +namespace MetaModule +{ + +struct AuxPlayer { + PatchPlayer &patch_player; + Ui &ui; + + FixedVector module_ids; + + AuxPlayer(PatchPlayer &patch_player, Ui &ui) + : patch_player{patch_player} + , ui{ui} { + using namespace mdrivlib; + + constexpr auto NewModuleListIRQn = SMPControl::IRQn(SMPCommand::NewModuleList); + InterruptManager::register_and_start_isr(NewModuleListIRQn, 0, 0, [this]() { assign_module_list(); }); + + constexpr auto PlayModuleListIRQn = SMPControl::IRQn(SMPCommand::PlayModuleList); + InterruptManager::register_and_start_isr(PlayModuleListIRQn, 1, 0, [this]() { play_modules(); }); + + constexpr auto ReadPatchLightsIRQn = SMPControl::IRQn(SMPCommand::ReadPatchLights); + InterruptManager::register_and_start_isr(ReadPatchLightsIRQn, 2, 0, [this]() { read_patch_gui_elements(); }); + } + + void play_modules() { + + for (auto module_i : module_ids) { + patch_player.process_module_outputs(module_i); + } + + for (auto module_i : module_ids) { + patch_player.step_module(module_i); + } + + mdrivlib::SMPThread::signal_done(); + } + + void assign_module_list() { + using namespace mdrivlib; + + module_ids.clear(); + + auto num_modules = SMPControl::read(); + + if (num_modules < module_ids.max_size()) { + for (auto i = 0u; i < num_modules; i++) { + auto id = SMPControl::read(i + 2); + module_ids.push_back(id); + } + + } else + pr_err("Error: %u modules requested to run on core 2, max is %z\n", num_modules, module_ids.size()); + + SMPThread::signal_done(); + } + + void read_patch_gui_elements() { + if (ui.new_patch_data == false) { + + for (auto &w : ui.lights().watch_lights) { + if (w.is_active()) { + auto val = patch_player.get_module_light(w.module_id, w.light_id); + w.value = val; + } + } + + for (auto &d : ui.displays().watch_displays) { + if (d.is_active()) { + auto text = std::span(d.text._data, d.text.capacity); + auto sz = patch_player.get_display_text(d.module_id, d.light_id, text); + d.text._data[sz] = '\0'; + } + } + + for (auto &p : ui.watched_params().active_watched_params()) { + if (p.is_active()) { + p.value = patch_player.get_param(p.module_id, p.param_id); + } + } + + ui.new_patch_data = true; + } + + SMPThread::signal_done(); + } +}; + +} // namespace MetaModule diff --git a/firmware/src/core_a7/smp_api.hh b/firmware/src/core_a7/smp_api.hh index 15c2a68ed..e04c3553d 100644 --- a/firmware/src/core_a7/smp_api.hh +++ b/firmware/src/core_a7/smp_api.hh @@ -13,12 +13,6 @@ namespace SMPRegister { enum : uint32_t { DoneZero, - ModuleID, - ParamID, - ParamVal, - FunctionAddress, NumModulesInPatch, - UpdateModuleOffset, - Unused, }; } // namespace SMPRegister diff --git a/firmware/src/patch_play/balance_modules.hh b/firmware/src/patch_play/balance_modules.hh new file mode 100644 index 000000000..6e7a4ae36 --- /dev/null +++ b/firmware/src/patch_play/balance_modules.hh @@ -0,0 +1,73 @@ +#pragma once +#include "CoreModules/CoreProcessor.hh" +#include "drivers/cycle_counter.hh" +#include "patch/module_type_slug.hh" +#include "util/partition.hh" +#include +#include + +#include "console/pr_dbg.hh" + +namespace MetaModule +{ + +template +struct Balancer { + Partition cores; + + std::vector + measure_modules(std::span> modules, unsigned num_modules, auto run) { + + mdrivlib::CycleCounter counter; + + constexpr size_t NumIterations = 512; + constexpr size_t DropFirst = 32; + + std::vector times(num_modules - 1, 0); + + for (auto iter_i = 0u; iter_i < NumIterations + DropFirst; iter_i++) { + + for (size_t module_i = 1; module_i < num_modules; module_i++) { + + counter.start_measurement(); + run(module_i); + counter.end_measurement(); + + if (iter_i >= DropFirst) + times[module_i - 1] += counter.get_last_measurement_raw(); + } + } + + return times; + } + + void balance_loads(std::span times) { + if (NumCores == 2) { + // Core 2 needs extra time to respond to its interrupt + // units is 1/24MHz + auto bias = std::array{0, 1000}; + cores.calc_partitions(times, bias); + } else + cores.calc_partitions(times); + + // Adjust indices since we skip module 0 + for (auto &part : cores.parts) { + for (auto &idx : part) + idx++; + } + } + + void print_times(std::span times, std::span slugs) { + // Debug output: + for (auto core = 0u; core < NumCores; core++) { + unsigned sum = 0; + for (auto idx : cores.parts[core]) { + pr_dbg("Core %d: Module %u (%s): %u\n", core, idx, slugs[idx].c_str(), times[idx - 1]); + sum += times[idx - 1]; + } + pr_dbg("Core %d Total: %u\n", core, sum); + } + } +}; + +} // namespace MetaModule diff --git a/firmware/src/patch_play/cable_cache.hh b/firmware/src/patch_play/cable_cache.hh new file mode 100644 index 000000000..12b506136 --- /dev/null +++ b/firmware/src/patch_play/cable_cache.hh @@ -0,0 +1,64 @@ +#pragma once +#include "conf/patch_conf.hh" +#include "patch/patch_data.hh" +#include + +namespace MetaModule +{ + +struct CableCache { + CableCache() = default; + + struct CableOut { + float val; + uint16_t jack_id; + }; + + struct CableIn { + uint16_t jack_id; + uint16_t out_module_id; + uint16_t out_cache_idx; + //todo: profile using this instead: + // CableOut *out; + }; + + void clear() { + for (auto &out : outs) + out.clear(); + for (auto &in : ins) + in.clear(); + } + + void build(std::span cables) { + clear(); + + for (auto &cable : cables) { + if (cable.out.module_id >= outs.size()) + continue; + + auto &out = outs[cable.out.module_id]; + auto out_idx = out.size(); + out.emplace_back(0.f, cable.out.jack_id); + + for (auto &in : cable.ins) { + if (in.module_id < ins.size()) { + ins[in.module_id].emplace_back(in.jack_id, cable.out.module_id, out_idx); + } + } + } + } + + void add(Jack injack, Jack outjack) { + if (injack.module_id < ins.size() && outjack.module_id < outs.size()) { + auto &out = outs[outjack.module_id]; + auto out_idx = out.size(); + out.emplace_back(0.f, outjack.jack_id); + ins[injack.module_id].emplace_back(injack.jack_id, outjack.module_id, out_idx); + } + } + + // outs[N] and ins[N] are the cables connected to module id N + std::array, MAX_MODULES_IN_PATCH> outs; + std::array, MAX_MODULES_IN_PATCH> ins; +}; +} // namespace MetaModule diff --git a/firmware/src/patch_play/multicore_play.hh b/firmware/src/patch_play/multicore_play.hh index defe4ee25..1084ec776 100644 --- a/firmware/src/patch_play/multicore_play.hh +++ b/firmware/src/patch_play/multicore_play.hh @@ -1,52 +1,44 @@ #pragma once #include "core_a7/smp_api.hh" #include "drivers/smp.hh" +#include namespace MetaModule { class MulticorePlayer { public: - static constexpr unsigned ModuleStride = mdrivlib::SMPControl::NumCores; - - void load_patch(unsigned num_modules) { - - num_modules_ = num_modules; - //Module 0 is the hub - //Module 1 is processed by first core - //Module 2 is processed by second core - //Module 3 is processed by first core - //Module 4 is processed by second core - //... etc - if constexpr (mdrivlib::SMPControl::NumCores > 1) { + static constexpr unsigned NumCores = mdrivlib::SMPControl::NumCores; + + void assign_modules(std::span module_ids) { + if constexpr (NumCores > 1) { mdrivlib::SMPThread::init(); - mdrivlib::SMPControl::write(2); //first module to process - mdrivlib::SMPControl::write(num_modules); - mdrivlib::SMPControl::write(ModuleStride); + mdrivlib::SMPControl::write(SMPRegister::NumModulesInPatch, module_ids.size()); + + for (auto i = 2u; auto module_id : module_ids) { // regs 2 and up are the module ids + mdrivlib::SMPControl::write(i++, module_id); + } mdrivlib::SMPControl::notify(); } } void update_modules() { - if constexpr (mdrivlib::SMPControl::NumCores > 1) { + if constexpr (NumCores > 1) { mdrivlib::SMPThread::split_with_command(); } } void read_patch_state() { - if constexpr (mdrivlib::SMPControl::NumCores > 1) { + if constexpr (NumCores > 1) { mdrivlib::SMPThread::split_with_command(); } } void join() { - if constexpr (mdrivlib::SMPControl::NumCores > 1) { + if constexpr (NumCores > 1) { mdrivlib::SMPThread::join(); } } - -private: - unsigned num_modules_ = 0; }; } // namespace MetaModule diff --git a/firmware/src/patch_play/patch_player.hh b/firmware/src/patch_play/patch_player.hh index 530676a8b..3bc7a0e89 100644 --- a/firmware/src/patch_play/patch_player.hh +++ b/firmware/src/patch_play/patch_player.hh @@ -4,8 +4,6 @@ #include "CoreModules/moduleFactory.hh" #include "conf/panel_conf.hh" #include "conf/patch_conf.hh" -#include "core_a7/smp_api.hh" -#include "drivers/smp.hh" #include "midi/midi_message.hh" #include "midi/midi_router.hh" #include "null_module.hh" @@ -14,12 +12,13 @@ #include "patch/midi_def.hh" #include "patch/patch.hh" #include "patch/patch_data.hh" +#include "patch_play/balance_modules.hh" +#include "patch_play/cable_cache.hh" #include "patch_play/multicore_play.hh" #include "patch_play/patch_player_query_patch.hh" #include "pr_dbg.hh" #include "result_t.hh" #include "util/countzip.hh" -#include "util/math.hh" #include "util/oscs.hh" #include #include @@ -36,6 +35,8 @@ namespace MetaModule class PatchPlayer { public: std::array, MAX_MODULES_IN_PATCH> modules; + CableCache cables; + unsigned num_modules = 0; std::atomic is_loaded = false; @@ -78,6 +79,7 @@ private: std::array in_patched{}; MulticorePlayer smp; + Balancer core_balancer; float samplerate = 48000.f; @@ -120,9 +122,6 @@ public: return {false, "Too many modules in the patch! Max is 32"}; } - // Tell the other core about the patch - smp.load_patch(num_modules); - // First module is the hub modules[0] = ModuleFactory::create(PanelDef::typeID); if (modules[0] != nullptr) @@ -178,13 +177,12 @@ public: calc_multiple_module_indicies(); + cables.build(pd.int_cables); + active_knob_set = 0; catchup_manager.reset(modules, knob_maps[active_knob_set]); - // Test-run the modules once - for (size_t i = 1; i < num_modules; i++) { - modules[i]->update(); - } + rebalance_modules(); is_loaded = true; if (num_not_found == 1) @@ -198,46 +196,52 @@ public: return {true}; } + void rebalance_modules() { + auto cpu_times = + core_balancer.measure_modules(modules, num_modules, [this](unsigned module_i) { step_module(module_i); }); + core_balancer.balance_loads(cpu_times); + + core_balancer.print_times(cpu_times, pd.module_slugs); + + smp.assign_modules(core_balancer.cores.parts[MulticorePlayer::NumCores - 1]); + } + // Runs the patch void update_patch() { - if (num_modules <= 1) - return; - else if (num_modules == 2) + if (num_modules == 2) modules[1]->update(); - else { + + else if (num_modules > 2) { smp.update_modules(); - // Debug::Pin2::high(); - for (size_t module_i = 1; module_i < num_modules; module_i += smp.ModuleStride) { - modules[module_i]->update(); + for (auto module_i : core_balancer.cores.parts[0]) { + process_module_outputs(module_i); } - // Debug::Pin2::low(); - smp.join(); - } - - for (auto &cable : pd.int_cables) { - float out_val = modules[cable.out.module_id]->get_output(cable.out.jack_id); - for (auto &input_jack : cable.ins) { - modules[input_jack.module_id]->set_input(input_jack.jack_id, out_val); + for (auto module_i : core_balancer.cores.parts[0]) { + step_module(module_i); } - } + smp.join(); + } else + return; update_midi_pulses(); } + void process_module_outputs(unsigned module_i) { + for (auto &out : cables.outs[module_i]) + out.val = modules[module_i]->get_output(out.jack_id); + } + + void step_module(unsigned module_i) { + for (auto const &in : cables.ins[module_i]) + modules[module_i]->set_input(in.jack_id, cables.outs[in.out_module_id][in.out_cache_idx].val); + + modules[module_i]->update(); + } + void update_patch_singlecore() { - // Debug::Pin2::high(); for (size_t module_i = 1; module_i < num_modules; module_i++) { - modules[module_i]->update(); - } - // Debug::Pin2::low(); - - for (auto &cable : pd.int_cables) { - float out_val = modules[cable.out.module_id]->get_output(cable.out.jack_id); - for (auto &input_jack : cable.ins) { - modules[input_jack.module_id]->set_input(input_jack.jack_id, out_val); - } + step_module(module_i); } - update_midi_pulses(); } @@ -252,6 +256,7 @@ public: for (size_t i = 0; i < num_modules; i++) { modules[i].reset(nullptr); } + cables.clear(); pd.int_cables.clear(); pd.mapped_ins.clear(); pd.knob_sets.clear(); @@ -266,7 +271,7 @@ public: clear_cache(); } - // K-rate setters/getters: + // Interface with audio stream: void set_panel_param(unsigned panel_knob_id, float val) { catchup_manager.set_panel_param(modules, knob_maps[active_knob_set], panel_knob_id, val); @@ -357,6 +362,17 @@ public: } } + void set_samplerate(float hz) { + samplerate = hz; + + for (auto &mp : midi_pulses) + mp.pulse.set_update_rate_hz(samplerate); + + for (size_t i = 1; i < num_modules; i++) { + modules[i]->set_samplerate(samplerate); + } + } + private: void set_all_connected_jacks(std::vector const &jacks, float val) { for (auto const &jack : jacks) @@ -413,6 +429,8 @@ public: return pd.midi_poly_num; } + // Patch Mods: + void apply_static_param(const StaticParam &sparam) { if (sparam.module_id < num_modules && modules[sparam.module_id]) modules[sparam.module_id]->set_param(sparam.param_id, sparam.value); @@ -471,6 +489,7 @@ public: void add_internal_cable(Jack in, Jack out) { pd.add_internal_cable(in, out); + cables.add(in, out); modules[out.module_id]->mark_output_patched(out.jack_id); modules[in.module_id]->mark_input_patched(in.jack_id); } @@ -544,6 +563,8 @@ public: } pd.disconnect_injack(jack); + + cables.build(pd.int_cables); } void disconnect_outjack(Jack jack) { @@ -562,6 +583,8 @@ public: } pd.disconnect_outjack(jack); + + cables.build(pd.int_cables); } void reset_module(uint16_t module_id, std::string_view data = "") { @@ -587,7 +610,7 @@ public: reset_module(module_idx); - smp.load_patch(num_modules); + rebalance_modules(); } void remove_module(uint16_t module_idx) { @@ -634,7 +657,6 @@ public: unsigned ins_to_disconnect = 0; for (auto in : cable.ins) { - if (cable.out.module_id == module_idx) { modules[in.module_id]->mark_input_unpatched(in.jack_id); } @@ -681,26 +703,14 @@ public: modules[i]->id = i; } - //TODO: move async tasks to right core - - smp.load_patch(num_modules); - } - - void set_samplerate(float hz) { - samplerate = hz; - - for (auto &mp : midi_pulses) - mp.pulse.set_update_rate_hz(samplerate); - - for (size_t i = 1; i < num_modules; i++) { - modules[i]->set_samplerate(samplerate); - } + rebalance_modules(); } // General info getters: // Jack patched/unpatched status + // Follow every internal cable and tell the modules that their jacks are patched void mark_patched_jacks() { for (auto const &cable : pd.int_cables) { modules[cable.out.module_id]->mark_output_patched(cable.out.jack_id); diff --git a/simulator/stubs/drivers/cycle_counter.hh b/simulator/stubs/drivers/cycle_counter.hh new file mode 100644 index 000000000..5201debc5 --- /dev/null +++ b/simulator/stubs/drivers/cycle_counter.hh @@ -0,0 +1,57 @@ +#pragma once +#include +#include + +namespace mdrivlib +{ +class CycleCounter { +public: + CycleCounter() { + init(); + } + + void init() { + } + + void start_measurement() { + _start_tm = read_cycle_count(); + _period = _start_tm - _last_start_tm; + _last_start_tm = _start_tm; + } + + void end_measurement() { + _measured_tm = read_cycle_count() - _start_tm; + } + + uint32_t get_last_measurement_raw() { + return _measured_tm; + } + + uint32_t get_last_period_raw() { + return _period; + } + + float get_last_measurement_load_float() { + if (_period == 0) + return 0; + return (float)_measured_tm / (float)_period; + } + + uint32_t get_last_measurement_load_percent() { + if (_period == 0) + return 0; + return (_measured_tm * 100) / _period; + } + +private: + uint32_t _last_start_tm = 0; + uint32_t _start_tm = 0; + uint32_t _measured_tm = 0; + uint32_t _period = 0; + + uint32_t read_cycle_count() { + auto now = std::chrono::system_clock::now().time_since_epoch(); + return std::chrono::duration_cast(now).count(); + } +}; +} // namespace mdrivlib