faust_min
A high-fidelity physical modeling music synthesis plugin and sequencing engine for Flutter, powered by Faust.
faust_min is a full-fledged, real-time computational acoustics engine based on Faust. Instead of relying on static, lifeless samples, faust_min mathematically simulates the physical properties of instruments (string tension, resonance, air pressure, tube length) to generate deeply expressive and highly realistic audio directly on the device. Result is very low memory and CPU footprint.
๐ Key Features
๐ธ 50+ Physically Modeled Instruments
The engine ships with an extensive, highly optimized library of over 50 real-time physical models, covering a vast array of global instruments:
- Western: Electric/Acoustic Guitars, Pianos, Saxophones, Bass, Flutes, Drum Kits.
- Indian Classical & Folk: Sitar, Sarod, Santoor, Bansuri, Tanpura, Tumbi, Tabla, Dhol, Dholak.
- Cinematic Chinese Nature: Guzheng, Erhu, Dagu, Wind, Thunder, Chougong.
- Electronic & Ambient: Tibetan Singing Bowls, Rainmakers, Synths, Vocal Formant Synthesizers.
๐น Advanced Polyphony & Multithreading
- Dynamic Voice Allocation: Built-in polyphonic support allows multiple notes to ring out simultaneously without cutting each other off. Availabe only for Piano and Guitars at the moment. Plan to add more western and eastern instruments in future.
- Persistent Thread Pool: Driven by a highly performant
FaustMixerthread pool that renders heavy DSP algorithms concurrently across multiple CPU cores, avoiding UI thread blocks completely. - Sample-Accurate Timing: Perfect for high-density, multi-instrument sequencing.
๐ผ The Universal Music Language (UML) Sequencer
faust_min includes a deeply integrated, zero-latency Sequence Orchestrator capable of parsing plain-text text files into complex musical arrangements. The sequencer intelligently adapts to different cultural notations:
- Western Notation: Standard
C4,D#5,Ebformatting. - Indian Solfรจge (Sargam): Fully parses
Sa,Re,Ga,Ma,Pa,Dha,Niusing complex Shruti frequency ratios. - Percussion Bols/Strokes: Unpitched parsing of specific strokes like
Dha,Ge,Dhinfor Tabla orKik,Snr,Hatfor drum kits.
๐ Getting Started
Installation
Add faust_min to your pubspec.yaml:
dependencies:
faust_min: ^0.6.0
Initialization
Before creating instruments or sequences, initialize the engine and the audio mixer:
import 'package:faust_min/faust_min.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Extract the bundled DSP assets to app storage and configure the
// native C++ engines. Unchanged files are skipped on subsequent launches.
await FaustEngine.init();
// Configure the mixer with a sample rate (48 kHz) and start the audio device.
FaustMixer.instance.init(48000);
FaustMixer.instance.start();
// ... create instruments / sequences below
}
All instruments and sequences must be created with the same sample rate as the mixer (48 kHz).
Basic Instrument Usage
Generate high-fidelity audio on the fly:
import 'package:faust_min/faust_min.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FaustEngine.init();
FaustMixer.instance.init(48000);
// Create a new Flute instance (Instrument ID 10, StaticCompiled, 48 kHz)
final flute = FaustInstrument.create(10, 0, 48000);
// Play a note (Frequency, Velocity)
flute.noteOn(freq: 440.0, velocity: 0.8);
// Apply real-time physical parameter modulations
flute.setParameter("breath_pressure", 0.9);
flute.setParameter("vibrato_depth", 0.05);
// Let it ring, then release
flute.noteOff();
}
Mixer Controls (Tracks, Mute/Unmute & FX)
Every registered instrument lives on a mixer track you can mute, unmute, automate and shape with per-track FX โ all without touching the audio thread:
import 'package:faust_min/faust_min.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FaustEngine.init();
final mixer = FaustMixer.instance;
mixer.init(48000);
mixer.start();
// Register instruments โ each returns a mixer track ID
// (sampleRate is optional and defaults to the mixer's rate).
final flute = FaustInstrument.create(10);
final sitar = FaustInstrument.create(9);
final tFlute = mixer.registerInstrument(flute, 1.0);
final tSitar = mixer.registerInstrument(sitar, 0.8);
// Mute / unmute tracks. Muted tracks skip DSP rendering entirely.
mixer.muteTrack(tFlute);
print(mixer.getTrackMute(tFlute)); // true
// Atomically un-gate multiple tracks for sample-tight sync.
mixer.unmuteTracks([tFlute, tSitar]);
// Per-track envelopes (times in seconds, linear/exponential/S-curve).
mixer.setTrackEnvelope(tFlute,
[0.0, 0.05, 1.0], // times
[0.0, 1.0, 1.0], // values
[2]); // interp: 0=linear, 1=exponential, 2=S-curve
// Per-track FX chain.
mixer.setTrackEQ(tFlute, 2.0, 1.5);
mixer.setTrackReverbSend(tFlute, 0.25);
mixer.setTrackEcho(tSitar, 0.3, feedback: 0.35, delaySec: 0.22);
// Global output level.
mixer.masterGain = 0.9;
}
Advanced Multi-Instrument Sequencing
Orchestrate a massive arrangement using the UML Sequencer:
import 'package:faust_min/faust_min.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FaustEngine.init();
FaustMixer.instance.init(48000);
FaustMixer.instance.start();
final orchestrator = SequenceOrchestrator();
// Define a Tumbi sequence using Indian notation and parameter overrides
String tumbiUML = '''
grid: 4
bpm: 120
instrument: tumbi
notation: Indian
basefreq: 659.25
vibrato_depth: 0.08
vibrato_rate: 7.0
5Sa . . 5Re . 5Ga . . 5Re . . 5Sa . . .
''';
// Define a Dhol sequence using Percussion strokes
String dholUML = '''
grid: 4
bpm: 120
instrument: dhol
Dha . . Na Na . . Tin Na . . Dha Dha . . Na
''';
// Load and play flawlessly synchronized
orchestrator.addSequence("Tumbi", tumbiUML);
orchestrator.addSequence("Dhol", dholUML);
orchestrator.play();
}
๐จ Build Process (Native Engine)
The native engine must be built with CMake โ there is no other supported build path.
Prerequisites
- CMake 3.10+
- A C++17 compiler (GCC/Clang)
- Faust compiler (
faust) onPATHโ the build auto-compiles everyassets/dsp/*.dspfile into a generatedFaust<Name>DSP.hppheader whenever the DSP changes - FFTW3 (
libfftw3) โ required only bytest_instruments
Configure
Run CMake from the linux/ directory (the CMake root that includes src/), into a build directory:
cmake -S linux -B build-release -DCMAKE_BUILD_TYPE=Release
Build โ always in one shot
Whenever the faust_min core is modified (any .dsp, any src/*.cpp/*.hpp, or any assets/dsp/*.csv), all artifacts must be built together in a single invocation โ never build only one artifact:
cmake --build build-release -j$(nproc)
# or equivalently
make -C build-release -j$(nproc)
This is mandatory because every test binary (test_sequence, test_mixer, test_instruments) links against the faust_min shared library, and faust_min post-build syncs all DSP CSV lookup tables. Building one test in isolation leaves the others linked against a stale core โ the whole set (engine + all test binaries) must be rebuilt together so they stay in lockstep. CMake tracks the generated DSP headers automatically, so this is safe and incremental.
Artifacts
build-release/shared/libfaust_min.soโ the engine (bundled by the Flutter Linux plugin).build-release/shared/test_instruments,test_mixer,test_sequenceโ test binaries (also reachable viabuild-release/).- DSP calibration CSVs are auto-synced to both
assets/dsp/andbuild-release/assets/dsp/on every build.
Running tests
Test binaries resolve libfaust_min.so via the build dir, so run them from the repo root with LD_LIBRARY_PATH set:
LD_LIBRARY_PATH=build-release/shared ./build-release/test_instruments <id> f=220 v=0.8 s=0 --render
๐ Architecture & Stability
Legacy C++ audio libraries often suffer from thread-safety issues, SIGSEGV crashes, and timing drift when used in a modern, multithreaded environment like Flutter's Dart Isolates.
faust_min solves this. The underlying DSP code is generated by Faust (Functional Audio Stream), which outputs completely stateless, re-entrant, memory-safe C++ arrays that are fully decoupled from the UI. This ensures industrial-grade stability and zero latency for professional audio applications.
๐ Roadmap
- Enhanced Playing Techniques: Support for enhanced playing techniques for all instruments.
- Enhanced Mixer: Support for chaining (parallel/serial) various DSP elements like echo, bandpass, etc.
- More wrappers: Currently only Dart but in future Java, and Python too.
๐ License
This project is licensed under the MIT License.