stk_min

A minimalist, cross-platform Flutter wrapper for the Synthesis ToolKit (STK) library. This plugin provides direct FFI access to STK's physical modeling synthesis algorithms, enabling high-quality musical instrument synthesis on all Flutter platforms.

Features

  • 🎵 Cross-Platform: Works on Android, iOS, Linux, macOS, and Windows
  • High Performance: Direct FFI bindings to native C++ STK library
  • 🎹 Physical Modeling: Realistic instrument synthesis using physical models
  • 🎛️ Expressive Control: Full access to instrument parameters (vibrato, breath noise, tone color, etc.)
  • 🔧 Minimalist Design: Simple, focused API for instrument synthesis
  • 🎼 Extensible: Foundation for supporting multiple STK instruments

Currently Supported Instruments

  • Flute: Physical model of a flute with breath control, vibrato, and tonal adjustments
  • Saxophone: Physical model of a saxophone with reed stiffness control, vibrato, and breath dynamics
  • Shakers: Physically informed stochastic event modeling (PhISEM) of various percussion instruments (Maracas, Tambourine, Sleighbells, etc.)
  • Drummer: General MIDI-compatible drum kit using audio samples
  • ModalBar: Resonant bar instruments with presets for Marimba, Vibraphone, Agogo (African percussion), and more.

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  stk_min: ^0.2.0

Then run:

flutter pub get

Usage

Usage Example

import 'package:stk_min/stk_min.dart';

// Create a flute instance
final flute = Flute();

// Initialize with a frequency (A4 = 440 Hz)
flute.init(440.0);

// Trigger a note with frequency and amplitude
flute.noteOn(440.0, 0.8);

// Generate audio samples (44100 samples = 1 second at 44.1kHz)
final samples = flute.render(44100);

// Use the samples with your audio playback library
// (e.g., flutter_soloud, just_audio, audioplayers, etc.)

Saxophone Example

import 'package:stk_min/saxophone.dart';

// Create a saxophone instance
final saxophone = Saxophone();

// Initialize with a frequency (D4 = 293.66 Hz)
saxophone.init(293.66);

// Play a note
saxophone.noteOn(293.66, 0.9);

// Generate audio samples
final samples = saxophone.render(44100);

// Add expressive controls
saxophone.controlChange(1, 40.0);   // Vibrato depth
saxophone.controlChange(11, 50.0);  // Vibrato speed
saxophone.controlChange(2, 80.0);   // Reed stiffness
saxophone.controlChange(4, 15.0);   // Breath noise

Advanced Control

final flute = Flute();

// Initialize
flute.init(523.25); // C5

// Add expressive controls
flute.controlChange(1, 30.0);   // Vibrato depth
flute.controlChange(11, 60.0);  // Vibrato speed
flute.controlChange(4, 20.0);   // Breath noise
flute.controlChange(2, 65.0);   // Tone color (jet delay)

// Play the note
flute.noteOn(523.25, 0.75);

// Render audio
final samples = flute.render(44100);

Shakers Example

import 'package:stk_min/shakers.dart';

// Create a Shakers instance (default to Maraca)
final shakers = Shakers(Shakers.maraca);

// Shake it! (instrument type, amplitude)
shakers.noteOn(Shakers.maraca.toDouble(), 0.8);

// Trigger a different sound (e.g., Tambourine)
shakers.noteOn(Shakers.tambourine.toDouble(), 0.9);

// Add expressive controls
shakers.controlChange(2, 80.0);   // Shake energy
shakers.controlChange(4, 50.0);   // System decay
shakers.controlChange(11, 20.0);  // Number of objects

// Render audio
final samples = shakers.render(44100);

Drummer Example

import 'package:stk_min/stk_min.dart';
import 'package:stk_min/drummer.dart';

// Set path to STK rawwave files (required for Drummer)
setRawwavePath('/path/to/stk/rawwaves/');

final drummer = Drummer();

// Play a Bass Drum sound (MIDI 36)
drummer.noteOn(36.0, 0.9);

// Play a Snare Drum sound (MIDI 38)
drummer.noteOn(38.0, 0.82);

// Render audio
final samples = drummer.render(22050);

ModalBar Example (African Percussion)

import 'package:stk_min/modal_bar.dart';

final modalBar = ModalBar();

// Initialize with Agogo preset (African drum sound)
modalBar.init(ModalBar.agogo);

// Play a high "slap" sound (880 Hz)
modalBar.noteOn(880.0, 0.8);

// Gain control (Stick hardness)
modalBar.controlChange(2, 80.0);

final samples = modalBar.render(44100);

Control Change Parameters

The controlChange(int number, double value) method accepts the following parameters (values 0-128):

Flute Control Changes

Parameter Control # Description
Vibrato Gain 1 Depth of pitch vibrato (0 = none, 128 = maximum)
Jet Delay 2 Tone color/brightness (lower = brighter, higher = darker)
Noise Gain 4 Breath noise amount (adds realism)
Vibrato Frequency 11 Speed of vibrato oscillation
Breath Pressure 128 Overall breath pressure envelope

Saxophone Control Changes

Parameter Control # Description
Vibrato Gain 1 Depth of pitch vibrato (0 = none, 128 = maximum)
Reed Stiffness 2 Reed flexibility (lower = softer, higher = stiffer)
Noise Gain 4 Breath noise amount (adds realism)
Vibrato Frequency 11 Speed of vibrato oscillation
Breath Pressure 128 Overall breath pressure envelope

Shakers Control Changes

Parameter Control # Description
Shake Energy 2 Intensity of the shake
System Decay 4 How fast the sound fades out
Number Of Objects 11 Number of shaking objects (e.g., beads in maraca)
Resonance Frequency 1 Main resonance of the instrument
Shake Energy 128 Overall volume/energy

ModalBar Control Changes

Parameter Control # Description
Stick Hardness 2 Hardness of the strike (0 = soft, 128 = hard)
Strike Position 4 Where the bar is struck (0 = edge, 128 = center)
Vibrato Gain 1 Depth of pitch vibrato
Vibrato Frequency 11 Speed of vibrato oscillation
Preset 16 Switch between instruments (Marimba, Vibraphone, Agogo, etc.)

Complete Example with Audio Playback

See the example directory for a complete Flutter app demonstrating:

  • Real-time parameter adjustment with sliders
  • Audio playback using SoLoud
  • Playing single notes and melodies
  • Converting PCM samples to WAV format

Audio Playback

Important: This plugin generates audio samples but does not include audio playback functionality. You need to use a separate audio library to play the generated samples.

Converting Samples to WAV

To play the raw PCM samples, you'll need to convert them to a proper audio format. Here's a helper function to create WAV files:

Uint8List createWavFile(List<double> samples, int sampleRate) {
  final numSamples = samples.length;
  final dataSize = numSamples * 2; // 16-bit samples
  final buffer = ByteData(44 + dataSize);
  
  // RIFF header
  buffer.setUint32(0, 0x52494646, Endian.big); // "RIFF"
  buffer.setUint32(4, 36 + dataSize, Endian.little);
  buffer.setUint32(8, 0x57415645, Endian.big); // "WAVE"
  
  // fmt chunk
  buffer.setUint32(12, 0x666D7420, Endian.big); // "fmt "
  buffer.setUint32(16, 16, Endian.little); // chunk size
  buffer.setUint16(20, 1, Endian.little); // PCM format
  buffer.setUint16(22, 1, Endian.little); // mono
  buffer.setUint32(24, sampleRate, Endian.little);
  buffer.setUint32(28, sampleRate * 2, Endian.little); // byte rate
  buffer.setUint16(32, 2, Endian.little); // block align
  buffer.setUint16(34, 16, Endian.little); // bits per sample
  
  // data chunk
  buffer.setUint32(36, 0x64617461, Endian.big); // "data"
  buffer.setUint32(40, dataSize, Endian.little);
  
  // PCM data
  var offset = 44;
  for (var sample in samples) {
    final intSample = (sample * 32767).clamp(-32768, 32767).toInt();
    buffer.setInt16(offset, intSample, Endian.little);
    offset += 2;
  }
  
  return buffer.buffer.asUint8List();
}

Platform Support

Platform Status Notes
Android API 21+
iOS iOS 12+
Linux ALSA backend
macOS macOS 10.14+
Windows DirectSound backend

How It Works

This plugin uses Flutter's FFI (Foreign Function Interface) to directly call C++ functions from the STK library. The architecture is:

  1. Native Layer: STK C++ library compiled for each platform
  2. FFI Bridge: Minimal C wrapper exposing STK functions
  3. Dart Layer: Type-safe Dart API using dart:ffi

This approach provides:

  • Zero platform channels overhead: Direct native function calls
  • Platform independence: Same Dart API on all platforms
  • High performance: Native C++ execution speed

Future Instruments

The STK library includes many more instruments that could be added:

  • Clarinet, Saxophone, Brass instruments
  • Bowed strings (Violin, Cello)
  • Plucked strings (Guitar, Mandolin)
  • Percussion (Drums, Marimba)
  • Modal synthesis (Tubular bells, Bamboo chimes)

Contributions are welcome!

Credits

  • STK Library: Perry R. Cook and Gary P. Scavone - The Synthesis ToolKit
  • Plugin Development: Built with Flutter FFI

License

This plugin is licensed under the MIT License. See LICENSE for details.

The STK library is licensed under its own terms. Please see the STK License for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. Areas for contribution:

Issues

Please file issues on the GitHub repository.