This is a tiny metronome app, meant to exercise augmented-audio
libraries a bit.
You can read about it on: https://beijaflor.io/blog/01-2022/rust-audio-experiments-3/
And you can download it from the app store on Simple Metronome.
Simple Metronome requires codegen of rust, C headers and dart code to work. To build it, run:
make
flutter build
If on a M1 mac, you will need x86 homebrew and LLVM installed, since flutter tooling will only run via rosetta. See - https://stackoverflow.com/questions/67386941/using-x86-libraries-and-openmp-on-macos-arm64-architecture.
The dev-loop is as follows:
- If making changes to the dart/flutter side
- Open the
metronome
folder in VSCode or Idea with the flutter extension - Run
flutter run
or use the IDE to start the app in debug mode - Run dart codegen whenever ORM or MobX models change
- To run once
flutter pub run build_runner build --delete-conflicting-outputs
- To watch for changes
flutter pub run build_runner watch --delete-conflicting-outputs
- To run once
- Commit the generated files, they are suffixed with
*.g.dart
- Open the
- If making changes to the Rust side
- Run
make
whenever the rust code changes - Build/run flutter app as normal
- Run
- Run
flutter test
to run dart tests - Run
cargo test
to run rust tests, they likely exist in external crates instead of this one
Flutter is used for the UI, with MobX for state management and Floor for persistence.
Flutter is a high-level framework for building cross-platform apps using
Dart. Apps are built using a declarative UI pattern and implement a flexbox style layout system.
Internally, the framework will use a custom skia
renderer to draw the UI. To interface with
platform APIs a message passing approach is used. Flutter code will use a single message passing API to interface with
multiple platform-specific plug-ins.
The flutter
code runs on a single UI thread.
We don't require any significant plug-ins that weren't available for install from the pub.dev
registry.
MobX is used for state-management. It's based on reactive objects, observers and computed properties.
Rust is used for audio-thread code. An audio-thread is started when the app boots and a shared pointer to atomic state is used to communicate between the flutter UI and the audio-thread. When data to be queried / mutated is small we use atomics. When data is larger we use one of two other strategies: message-queues or immutable data atomic pointer swaps.
- UI
- Audio
lib
- Flutter applib/bridge_generated.dart
- Generateddart
code usingrust_flutter_bridge
to call Rust code, calls into the native C API using FFIsrc/bridge_generated.rs
- Generatedrust
code usingrust_flutter_bridge
, exposes a C API for dart to callsrc/api.rs
- High-level API exposing bindings fromrust
src
- Rust audio-processingsrc/api/*.rs
- Expose state management and audio wrapper for dart to call intoaudio-processor-traits
- Library for declaring audio processorsaudio-processor-metronome
- Library for metronome codeaudio-garbage-collector
- Reference counting GC strategy for real-time audioaudio-processor-standalone
- Wrapper for audio processors usingcpal
to support cross-platform audio, as well as supporting offline rendering and plug-ins
lib/ui
- Viewslib/modules/*
- Business logic- DB handling
- State management and synchronization with rust back-end
State management is done using dart mobx
. SQL is handled using sqflite
and the floor
ORM.
Both mobx
, floor
and the rust to dart require code-gen processes to be ran.
- Make sure Android SDK/Android Studio is set-up
- Set
ANDROID_HOME
environment variable to point to the Android SDK andNDK_HOME
to point to the NDK - Install
cargo-ndk
usingcargo install cargo-ndk
- Make sure the
ANDROID_NDK
gradle property is set on~/.gradle/gradle.properties
This subdirectory is licensed under AGPLv3 for now.