From 80fda28de606eedbd5c9b600a0b1e59db521773b Mon Sep 17 00:00:00 2001 From: Andrea Martelloni Date: Sat, 26 Apr 2025 13:50:55 +0100 Subject: [PATCH] Completed example --- fmsynthiml.ino | 461 ----------------------------------------------- memlnaut_xor.ino | 97 ++++++++++ 2 files changed, 97 insertions(+), 461 deletions(-) delete mode 100644 fmsynthiml.ino create mode 100644 memlnaut_xor.ino diff --git a/fmsynthiml.ino b/fmsynthiml.ino deleted file mode 100644 index 0de35e4..0000000 --- a/fmsynthiml.ino +++ /dev/null @@ -1,461 +0,0 @@ -#include "src/memllib/interface/InterfaceBase.hpp" -#include "src/memllib/audio/AudioAppBase.hpp" -#include "src/memllib/audio/AudioDriver.hpp" -#include "src/memllib/hardware/memlnaut/MEMLNaut.hpp" -#include - -// Includes for the IML interface -#include "src/memlp/Dataset.hpp" -#include "src/memlp/MLP.h" - -// Includes for FM Synth -#include "src/memllib/synth/FMSynth.hpp" - - -class IMLInterface : public InterfaceBase -{ -public: - IMLInterface() : InterfaceBase() {} - - void setup(size_t n_inputs, size_t n_outputs) override - { - InterfaceBase::setup(n_inputs, n_outputs); - // Additional setup code specific to IMLInterface - n_inputs_ = n_inputs; - n_outputs_ = n_outputs; - - MLSetup_(); - n_iterations_ = 1000; - input_state_.resize(n_inputs, 0.5f); - output_state_.resize(n_outputs, 0); - // Init/reset state machine - training_mode_ = INFERENCE_MODE; - perform_inference_ = true; - input_updated_ = false; - - Serial.println("IMLInterface setup done"); - Serial.print("Address of n_inputs_: "); - Serial.println(reinterpret_cast(&n_inputs_)); - Serial.print("Inputs: "); - Serial.print(n_inputs_); - Serial.print(", Outputs: "); - Serial.println(n_outputs_); - } - - enum training_mode_t { - INFERENCE_MODE, - TRAINING_MODE - }; - - void SetTrainingMode(training_mode_t training_mode) - { - Serial.print("Training mode: "); - Serial.println(training_mode == INFERENCE_MODE ? "Inference" : "Training"); - - if (training_mode == INFERENCE_MODE && training_mode_ == TRAINING_MODE) { - // Train the network! - MLTraining_(); - } - training_mode_ = training_mode; - } - - void ProcessInput() - { - // Check if input is updated - if (perform_inference_ && input_updated_) { - MLInference_(input_state_); - input_updated_ = false; - } - } - - void SetInput(size_t index, float value) - { - Serial.print("Input "); - Serial.print(index); - Serial.print(" set to: "); - Serial.println(value); - - if (index >= n_inputs_) { - Serial.print("Input index "); - Serial.print(index); - Serial.println(" out of bounds."); - return; - } - - if (value < 0) { - value = 0; - } else if (value > 1.0) { - value = 1.0; - } - - // Update state of input - input_state_[index] = value; - input_updated_ = true; - } - - enum saving_mode_t { - STORE_VALUE_MODE, - STORE_POSITION_MODE, - }; - - void SaveInput(saving_mode_t mode) - { - if (STORE_VALUE_MODE == mode) { - - Serial.println("Move input to position..."); - perform_inference_ = false; - - } else { // STORE_POSITION_MODE - - Serial.println("Creating example in this position."); - // Save pair in the dataset - dataset_->Add(input_state_, output_state_); - perform_inference_ = true; - MLInference_(input_state_); - - } - } - - void ClearData() - { - if (training_mode_ == TRAINING_MODE) { - Serial.println("Clearing dataset..."); - dataset_->Clear(); - } - } - - void Randomise() - { - if (training_mode_ == TRAINING_MODE) { - Serial.println("Randomising weights..."); - MLRandomise_(); - MLInference_(input_state_); - } - } - - void SetIterations(size_t iterations) - { - n_iterations_ = iterations; - Serial.print("Iterations set to: "); - Serial.println(n_iterations_); - } - -protected: - size_t n_inputs_; - size_t n_outputs_; - size_t n_iterations_; - - // State machine - training_mode_t training_mode_; - bool perform_inference_; - bool input_updated_; - - // Controls/sensors - std::vector input_state_; - std::vector output_state_; - - // MLP core - std::unique_ptr dataset_; - std::unique_ptr> mlp_; - MLP::mlp_weights mlp_stored_weights_; - bool randomised_state_; - - void MLSetup_() - { - // Constants for MLP init - const unsigned int kBias = 1; - const std::vector layers_activfuncs = { - RELU, RELU, RELU, SIGMOID - }; - const bool use_constant_weight_init = false; - const float constant_weight_init = 0; - // Layer size definitions - const std::vector layers_nodes = { - n_inputs_ + kBias, - 10, 10, 14, - n_outputs_ - }; - - // Create dataset - dataset_ = std::make_unique(); - // Create MLP - mlp_ = std::make_unique>( - layers_nodes, - layers_activfuncs, - loss::LOSS_MSE, - use_constant_weight_init, - constant_weight_init - ); - - // State machine - randomised_state_ = false; - } - - void MLInference_(std::vector input) - { - if (!dataset_ || !mlp_) { - Serial.println("ML not initialized!"); - return; - } - - if (input.size() != n_inputs_) { - Serial.print("Input size mismatch - "); - Serial.print("Expected: "); - Serial.print(n_inputs_); - Serial.print(", Got: "); - Serial.println(input.size()); - return; - } - - input.push_back(1.0f); // Add bias term - // Perform inference - std::vector output(n_outputs_); - mlp_->GetOutput(input, &output); - // Process inferenced data - output_state_ = output; - SendParamsToQueue(output); - } - - void MLRandomise_() - { - if (!mlp_) { - Serial.println("ML not initialized!"); - return; - } - - // Randomize weights - mlp_stored_weights_ = mlp_->GetWeights(); - mlp_->DrawWeights(); - randomised_state_ = true; - } - - void MLTraining_() - { - if (!mlp_) { - Serial.println("ML not initialized!"); - return; - } - // Restore old weights - if (randomised_state_) { - mlp_->SetWeights(mlp_stored_weights_); - } - randomised_state_ = false; - - // Prepare for training - // Extract dataset to training pair - MLP::training_pair_t dataset(dataset_->GetFeatures(), dataset_->GetLabels()); - // Check and report on dataset size - Serial.print("Feature size "); - Serial.print(dataset.first.size()); - Serial.print(", label size "); - Serial.println(dataset.second.size()); - if (!dataset.first.size() || !dataset.second.size()) { - Serial.println("Empty dataset!"); - return; - } - Serial.print("Feature dim "); - Serial.print(dataset.first[0].size()); - Serial.print(", label dim "); - Serial.println(dataset.second[0].size()); - if (!dataset.first[0].size() || !dataset.second[0].size()) { - Serial.println("Empty dataset dimensions!"); - return; - } - - // Training loop - Serial.print("Training for max "); - Serial.print(n_iterations_); - Serial.println(" iterations..."); - float loss = mlp_->Train(dataset, - 1., - n_iterations_, - 0.00001, - false); - Serial.print("Trained, loss = "); - Serial.println(loss, 10); - } -}; - -class FMSynthAudioApp : public AudioAppBase -{ -public: - static constexpr size_t kN_Params = kN_synthparams; - - FMSynthAudioApp() : AudioAppBase(), - synth_(AudioDriver::GetSampleRate()) {} - - stereosample_t Process(const stereosample_t x) override - { - float y = synth_.process(); - stereosample_t ret { y, y }; - return ret; - } - - void Setup(float sample_rate, std::shared_ptr interface) override - { - AudioAppBase::Setup(sample_rate, interface); - // Additional setup code specific to FMSynthAudioApp - } - - void ProcessParams(const std::vector& params) override - { - // Map parameters to the synth - synth_.mapParameters(params); - //Serial.print("Params processed."); - } - -protected: - - FMSynth synth_; -}; - - -// Global objects -std::shared_ptr interface; -std::shared_ptr audio_app; - -// Inter-core communication -volatile bool core_0_ready = false; -volatile bool core_1_ready = false; -volatile bool serial_ready = false; -volatile bool interface_ready = false; - - -// We're only bound to the joystick inputs (x, y, rotate) -const 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 bind_interface(std::shared_ptr interface) -{ - // Set up momentary switch callbacks - MEMLNaut::Instance()->setMomA1Callback([interface] () { - interface->Randomise(); - }); - MEMLNaut::Instance()->setMomA2Callback([interface] () { - interface->ClearData(); - }); - - // Set up toggle switch callbacks - MEMLNaut::Instance()->setTogA1Callback([interface] (bool state) { - interface->SetTrainingMode(state ? IMLInterface::TRAINING_MODE : IMLInterface::INFERENCE_MODE); - }); - MEMLNaut::Instance()->setJoySWCallback([interface] (bool state) { - interface->SaveInput(state ? IMLInterface::STORE_VALUE_MODE : IMLInterface::STORE_POSITION_MODE); - }); - - // Set up ADC callbacks - MEMLNaut::Instance()->setJoyXCallback([interface] (float value) { - interface->SetInput(0, value); - }); - MEMLNaut::Instance()->setJoyYCallback([interface] (float value) { - interface->SetInput(1, value); - }); - MEMLNaut::Instance()->setJoyZCallback([interface] (float value) { - interface->SetInput(2, value); - }); - MEMLNaut::Instance()->setRVZ1Callback([interface] (float value) { - // Scale value from 0-1 range to 1-3000 - value = 1.0f + (value * 2999.0f); - interface->SetIterations(static_cast(value)); - }); - - // Set up loop callback - MEMLNaut::Instance()->setLoopCallback([interface] () { - interface->ProcessInput(); - }); -} - - -void setup() -{ - Serial.begin(115200); - while (!Serial) {} - Serial.println("Serial initialised."); - WRITE_VOLATILE(serial_ready, true); - - // Setup board - MEMLNaut::Initialize(); - pinMode(33, OUTPUT); - - // Setup interface with memory barrier protection - { - auto temp_interface = std::make_shared(); - temp_interface->setup(kN_InputParams, FMSynthAudioApp::kN_Params); - MEMORY_BARRIER(); - interface = temp_interface; - MEMORY_BARRIER(); - } - WRITE_VOLATILE(interface_ready, true); - - // Bind interface after ensuring it's fully initialized - bind_interface(interface); - Serial.println("Bound interface to MEMLNaut."); - - WRITE_VOLATILE(core_0_ready, true); - while (!READ_VOLATILE(core_1_ready)) { - MEMORY_BARRIER(); - delay(1); - } - - Serial.println("Finished initialising core 0."); -} - -void loop() -{ - MEMLNaut::Instance()->loop(); - static int blip_counter = 0; - if (blip_counter++ > 100) { - blip_counter = 0; - Serial.println("."); - // Blink LED - digitalWrite(33, HIGH); - } else { - // Un-blink LED - digitalWrite(33, LOW); - } - delay(10); // Add a small delay to avoid flooding the serial output -} - -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 - { - auto temp_audio_app = std::make_shared(); - temp_audio_app->Setup(AudioDriver::GetSampleRate(), interface); - MEMORY_BARRIER(); - audio_app = temp_audio_app; - MEMORY_BARRIER(); - } - - // Start audio driver - AudioDriver::Setup(); - - 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(); -} diff --git a/memlnaut_xor.ino b/memlnaut_xor.ino new file mode 100644 index 0000000..2abc37e --- /dev/null +++ b/memlnaut_xor.ino @@ -0,0 +1,97 @@ +#include "src/memlp/MLP.h" +#include "src/memlp/Dataset.hpp" + +// Define minimal network parameters +const int INPUT_SIZE = 2; +const int HIDDEN_SIZE = 2; // Minimum required for XOR +const int OUTPUT_SIZE = 1; +const float LEARNING_RATE = 0.1f; + +// Network architecture +const std::vector LAYERS = {INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE}; +const std::vector ACTIVATIONS = { + ACTIVATION_FUNCTIONS::RELU, + ACTIVATION_FUNCTIONS::SIGMOID +}; + +// XOR training data +const std::vector> XOR_INPUTS = { + {0.0f, 0.0f}, + {0.0f, 1.0f}, + {1.0f, 0.0f}, + {1.0f, 1.0f} +}; +const std::vector> XOR_OUTPUTS = { + {0.0f}, + {1.0f}, + {1.0f}, + {0.0f} +}; + +// Global objects +MLP* mlp; +Dataset dataset; +bool trained = false; + +void setup() { + Serial.begin(115200); + while (!Serial) { + ; // Wait for serial port to connect. Needed for native USB port only + } + + // Initialize the MLP + mlp = new MLP(LAYERS, ACTIVATIONS); + + // Load XOR data into dataset + dataset.Clear(); + for(size_t i = 0; i < XOR_INPUTS.size(); i++) { + if(!dataset.Add(XOR_INPUTS[i], XOR_OUTPUTS[i])) { + Serial.println("Failed to add training example!"); + } + } + + Serial.println("Training XOR network..."); + + // Get training data with bias term + auto training_data = std::make_pair( + dataset.GetFeatures(true), // true to include bias + dataset.GetLabels() + ); + + // Train network + float final_loss = mlp->Train( + training_data, + LEARNING_RATE, + 5000, // max iterations + 0.001f, // min error threshold + true // log output + ); + + Serial.print("Training complete! Final loss: "); + Serial.println(final_loss); + trained = true; +} + +void loop() { + if(!trained) return; + + // Test all XOR combinations + for(const auto& input : XOR_INPUTS) { + // Add bias term + std::vector input_with_bias = input; + input_with_bias.push_back(1.0f); + + // Get network output + std::vector output; + mlp->GetOutput(input_with_bias, &output); + + // Print result + Serial.print(input[0], 1); + Serial.print(" XOR "); + Serial.print(input[1], 1); + Serial.print(" = "); + Serial.println(output[0], 3); + } + + delay(2000); // Wait 2 seconds before next test +}