211 lines
6.2 KiB
Markdown
211 lines
6.2 KiB
Markdown
|
|
# NISPS Core
|
||
|
|
|
||
|
|
**N**eural **I**nteractive **S**haping of **P**arameter **S**paces - Core Library
|
||
|
|
|
||
|
|
A platform-agnostic C++20 header-only library for interactive machine learning. Train neural networks to map input parameters to output parameters through interactive demonstration.
|
||
|
|
|
||
|
|
## What Is This?
|
||
|
|
|
||
|
|
NISPS core is a **parameter mapping engine**, not a synthesizer. It takes N input parameters (joystick position, sensor data, audio features) and maps them to M output parameters through an interactively-trained neural network.
|
||
|
|
|
||
|
|
**Use it to control**: synthesizers, effects, lights, robots, game parameters, or anything that responds to control data.
|
||
|
|
|
||
|
|
## Features
|
||
|
|
|
||
|
|
- **Header-only**: No compilation needed, just include and use
|
||
|
|
- **Platform-agnostic**: Pure C++20, works anywhere
|
||
|
|
- **No dependencies**: Only standard library
|
||
|
|
- **Interactive learning**: Train by demonstration, not by code
|
||
|
|
- **Lightweight**: ~3,500 lines of optimized neural network code
|
||
|
|
- **Flexible**: Map 1-100 inputs to 1-100 outputs
|
||
|
|
|
||
|
|
## Quick Start
|
||
|
|
|
||
|
|
### Installation
|
||
|
|
|
||
|
|
Copy the `include/nisps/` directory to your project, or add it to your include path:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Option 1: Copy headers
|
||
|
|
cp -r nisps-core/include/nisps /path/to/your/project/include/
|
||
|
|
|
||
|
|
# Option 2: Add to CMakeLists.txt
|
||
|
|
target_include_directories(your_target PRIVATE /path/to/nisps-core/include)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Basic Usage
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
#include <nisps/nisps.hpp>
|
||
|
|
|
||
|
|
// Create IML with 2 inputs, 4 outputs
|
||
|
|
nisps::IML<float> iml(2, 4);
|
||
|
|
|
||
|
|
// Runtime: set inputs and get outputs
|
||
|
|
void update(float x, float y) {
|
||
|
|
iml.set_input(0, x);
|
||
|
|
iml.set_input(1, y);
|
||
|
|
iml.process();
|
||
|
|
|
||
|
|
const float* outputs = iml.get_outputs();
|
||
|
|
my_synth.set_filter_cutoff(outputs[0] * 10000.f);
|
||
|
|
my_synth.set_resonance(outputs[1]);
|
||
|
|
my_synth.set_envelope_attack(outputs[2] * 5.0f);
|
||
|
|
my_synth.set_envelope_release(outputs[3] * 10.0f);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Training Workflow
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
// 1. Enter training mode
|
||
|
|
iml.set_mode(nisps::IML<float>::Mode::Training);
|
||
|
|
|
||
|
|
// 2. Set input position
|
||
|
|
iml.set_input(0, 0.3f);
|
||
|
|
iml.set_input(1, 0.7f);
|
||
|
|
|
||
|
|
// 3. Save example (call twice per example)
|
||
|
|
iml.save_example(); // First call: stops inference, user positions output
|
||
|
|
// ... user adjusts outputs manually to desired values ...
|
||
|
|
iml.save_example(); // Second call: stores the input->output mapping
|
||
|
|
|
||
|
|
// 4. Repeat for multiple input positions
|
||
|
|
// ... add more examples ...
|
||
|
|
|
||
|
|
// 5. Exit training mode (automatically trains the network)
|
||
|
|
iml.set_mode(nisps::IML<float>::Mode::Inference);
|
||
|
|
```
|
||
|
|
|
||
|
|
## API Reference
|
||
|
|
|
||
|
|
### IML Constructor
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
nisps::IML<Float>(
|
||
|
|
size_t n_inputs, // Number of input parameters
|
||
|
|
size_t n_outputs, // Number of output parameters
|
||
|
|
std::vector<size_t> hidden_layers = {10, 10, 14}, // Hidden layer sizes
|
||
|
|
size_t max_iterations = 1000, // Training iterations
|
||
|
|
Float learning_rate = 1.0f, // Learning rate
|
||
|
|
Float convergence_threshold = 0.00001f // Stop training threshold
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Input/Output
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void set_input(size_t index, Float value); // Set single input (0-1 range)
|
||
|
|
void set_inputs(const Float* values, size_t count); // Set multiple inputs
|
||
|
|
const Float* get_outputs() const; // Get output array
|
||
|
|
void process(); // Run inference
|
||
|
|
```
|
||
|
|
|
||
|
|
### Training
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
enum class Mode { Inference, Training };
|
||
|
|
void set_mode(Mode mode); // Switch modes
|
||
|
|
void save_example(); // Store input->output pair
|
||
|
|
void clear_dataset(); // Clear training data
|
||
|
|
void randomise_weights(); // Randomize for exploration
|
||
|
|
```
|
||
|
|
|
||
|
|
### Logging
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
void set_logger(LogFn fn); // Set callback for messages
|
||
|
|
// LogFn = void(*)(const char*)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Building the Tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd nisps-core
|
||
|
|
mkdir build && cd build
|
||
|
|
cmake ..
|
||
|
|
make
|
||
|
|
ctest --output-on-failure
|
||
|
|
```
|
||
|
|
|
||
|
|
## Requirements
|
||
|
|
|
||
|
|
- **C++20** compiler (GCC 10+, Clang 10+, MSVC 2019+)
|
||
|
|
- **CMake 3.14+** (for building tests only)
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
### Core Components
|
||
|
|
|
||
|
|
- **IML**: High-level interactive ML interface
|
||
|
|
- **MLP**: Multi-layer perceptron (feedforward neural network)
|
||
|
|
- **Dataset**: Training data management
|
||
|
|
- **Layer/Node**: Neural network building blocks
|
||
|
|
- **Loss**: MSE and categorical cross-entropy functions
|
||
|
|
- **Utils**: Activation functions (sigmoid, ReLU, tanh, etc.)
|
||
|
|
|
||
|
|
### Design Decisions
|
||
|
|
|
||
|
|
1. **Header-only**: Simplifies integration, allows template specialization
|
||
|
|
2. **C++20**: Enables `std::span` for efficient array views
|
||
|
|
3. **No SIMD**: Portable code, relies on compiler auto-vectorization
|
||
|
|
4. **RMSProp optimizer**: Fast convergence for interactive training
|
||
|
|
5. **Gradient clipping**: Prevents numerical instability
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
Typical performance on modern hardware:
|
||
|
|
- **Inference**: 1-10 µs for small networks (2-10-10-4)
|
||
|
|
- **Training**: 10-100 ms for 100 examples, 1000 iterations
|
||
|
|
- **Memory**: ~1 KB per hidden neuron
|
||
|
|
|
||
|
|
## Examples
|
||
|
|
|
||
|
|
See `test/main.cpp` for a complete example. More examples coming soon:
|
||
|
|
- Audio synthesis control
|
||
|
|
- Game parameter mapping
|
||
|
|
- Sensor fusion for robotics
|
||
|
|
- MIDI controller mapping
|
||
|
|
|
||
|
|
## Origin
|
||
|
|
|
||
|
|
Extracted from [MEMLNaut-NISPS](https://github.com/musicallyembodiedml/memlnaut) - an embedded ML platform for audio synthesis on Raspberry Pi Pico.
|
||
|
|
|
||
|
|
**Key changes from MEMLNaut-NISPS**:
|
||
|
|
- Removed Arduino/RP2040 dependencies
|
||
|
|
- Removed audio synthesis code (use this to *control* your synth)
|
||
|
|
- Added proper namespacing
|
||
|
|
- Converted to header-only library
|
||
|
|
- Updated to modern C++20
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
Mozilla Public License Version 2.0
|
||
|
|
|
||
|
|
Original MLP code derived from [David Alberto Nogueira's MLP project](https://github.com/davidalbertonogueira/MLP).
|
||
|
|
|
||
|
|
## Contributing
|
||
|
|
|
||
|
|
This library is extracted from an active research project. Contributions welcome:
|
||
|
|
- Bug fixes
|
||
|
|
- Performance optimizations
|
||
|
|
- Example code
|
||
|
|
- Documentation improvements
|
||
|
|
|
||
|
|
Please keep the library dependency-free and platform-agnostic.
|
||
|
|
|
||
|
|
## Citation
|
||
|
|
|
||
|
|
If you use this in research, please cite:
|
||
|
|
|
||
|
|
```
|
||
|
|
MEMLNaut-NISPS: Neural Interactive Shaping of Parameter Spaces
|
||
|
|
https://musicallyembodiedml.github.io/memlnaut/approaches/nisps
|
||
|
|
```
|
||
|
|
|
||
|
|
## Support
|
||
|
|
|
||
|
|
- **Issues**: File at parent project (MEMLNaut-NISPS repo)
|
||
|
|
- **Docs**: https://musicallyembodiedml.github.io/memlnaut/
|
||
|
|
- **Examples**: See `examples/` directory (coming soon)
|