synthkit 0.0.2 copy "synthkit: ^0.0.2" to clipboard
synthkit: ^0.0.2 copied to clipboard

Cross-platform Flutter synth plugin for note playback, envelopes, filters, and beat scheduling.

synthkit #

synthkit is a synth-first Flutter plugin for musical note playback and simple beat-based scheduling across Flutter platforms.

Note: Windows and Linux backends are implemented, but they have not been runtime-tested in this workspace yet.

It gives you one Dart API for:

  • creating synths
  • configuring waveform, ADSR envelope, and low-pass filtering
  • triggering notes by note name, MIDI note, or raw frequency
  • scheduling short patterns in beat time

The API is intentionally small. It is designed for metronomes, ear-training apps, music toys, UI sound prototypes, and lightweight sequencer-style interfaces.

Platform support #

Platform Supported Backend
Web Yes Tone.js loaded at runtime
iOS Yes Native AVAudioEngine synth
macOS Yes Native AVAudioEngine synth
Android Yes Native AudioTrack synth
Windows Yes Native waveOut synth
Linux Yes Native ALSA synth

Features #

  • Unified SynthKitEngine, SynthKitSynth, and SynthKitTransport API.
  • sine, square, triangle, and sawtooth waveforms.
  • ADSR envelope per synth.
  • Optional low-pass filter per synth.
  • Per-note velocity and delayed triggering.
  • Beat-based transport for scheduling note sequences from Dart.
  • Automatic Tone.js bootstrap on web.
  • Native backends for mobile and desktop.

Installation #

Add the package to your app:

dependencies:
  synthkit: ^0.0.1

Then install dependencies:

flutter pub get

No extra platform setup is required for the current supported platforms beyond normal Flutter plugin integration.

For Linux builds, install ALSA development headers first. On Debian or Ubuntu:

sudo apt install libasound2-dev

Quick start #

import 'package:synthkit/synthkit.dart';

final engine = SynthKitEngine();

await engine.initialize(
  bpm: 112,
  masterVolume: 0.7,
);

final synth = await engine.createSynth(
  const SynthKitSynthOptions(
    waveform: SynthKitWaveform.sawtooth,
    envelope: SynthKitEnvelope(
      attack: Duration(milliseconds: 8),
      decay: Duration(milliseconds: 140),
      sustain: 0.65,
      release: Duration(milliseconds: 260),
    ),
    filter: SynthKitFilter.lowPass(cutoffHz: 1600),
    volume: 0.75,
  ),
);

await synth.triggerAttackRelease(
  SynthKitNote.parse('C4'),
  const Duration(milliseconds: 380),
);

In most apps the flow looks like this:

  1. Create one SynthKitEngine.
  2. Call initialize() once before creating synths.
  3. Create one or more synths with createSynth().
  4. Trigger notes directly or schedule them with transport.
  5. Dispose the engine when the owning widget or service is destroyed.

Example:

class _ExampleState extends State<Example> {
  final SynthKitEngine _engine = SynthKitEngine();
  SynthKitSynth? _synth;

  Future<void> initializeAudio() async {
    await _engine.initialize(bpm: 120, masterVolume: 0.8);
    _synth ??= await _engine.createSynth();
  }

  Future<void> playA4() async {
    await initializeAudio();
    await _synth!.triggerAttackRelease(
      SynthKitNote.parse('A4'),
      const Duration(milliseconds: 250),
    );
  }

  @override
  void dispose() {
    _engine.dispose();
    super.dispose();
  }
}

Core API #

SynthKitEngine #

Main entry point for audio.

final engine = SynthKitEngine();

Important members:

  • initialize({double bpm = 120, double masterVolume = 0.8, String? webToneJsUrl}) Initializes the platform backend and attaches the transport.
  • createSynth([SynthKitSynthOptions options]) Creates a synth on the active backend.
  • setMasterVolume(double volume) Sets the global output volume from 0.0 to 1.0.
  • backendName Returns a backend identifier such as tonejs-web, native-ios, native-macos, native-android, native-linux, or native-windows.
  • transport Beat-based scheduler for short sequences.
  • dispose() Stops transport playback, disposes synths, and tears down the backend.

Notes:

  • initialize() must be awaited before createSynth() or setMasterVolume().
  • Calling initialize() more than once is safe; subsequent calls are ignored.
  • dispose() is idempotent.

SynthKitSynth #

Represents a synth instance created by the engine.

final synth = await engine.createSynth();

Important members:

  • update(SynthKitSynthOptions nextOptions) Reconfigures the synth waveform, envelope, filter, and volume.
  • triggerAttackRelease(SynthKitNote note, Duration duration, {double velocity = 1, Duration delay = Duration.zero}) Plays a note immediately or after a delay.
  • cancelScheduledNotes() Cancels queued delayed notes for that synth.
  • dispose() Disposes the synth and releases its native or web resources.

SynthKitTransport #

Schedules notes in beat time from Dart.

await engine.transport.schedule(
  synth: synth,
  note: SynthKitNote.parse('C4'),
  beat: 0,
  durationBeats: 0.5,
);
await engine.transport.start();

Important members:

  • schedule(...) Adds a note event to the transport sequence.
  • start() Starts playback of the scheduled sequence.
  • stop({bool clearSequence = false}) Stops playback, clears active notes, and optionally removes the sequence.
  • setBpm(double bpm) Changes transport tempo. If transport is already running, remaining notes are rescheduled at the new BPM.
  • clear() Removes all scheduled note events.
  • unscheduleSynth(SynthKitSynth synth) Removes all events for a given synth.
  • bpm Current BPM value.
  • isRunning Whether the transport is currently active.

Notes:

  • The transport is a one-shot scheduler. It does not loop automatically.
  • Scheduling is managed from Dart, not from a native sequencer.
  • SynthKitEngine.initialize() attaches the transport for you.

SynthKitNote #

You can construct notes in three ways:

final byName = SynthKitNote.parse('C#4');
final byMidi = SynthKitNote.midi(69); // A4
final byFrequency = SynthKitNote.frequency(440.0);

Supported note-name format:

  • note letter A through G
  • optional # or b
  • octave number, including negative octaves

Examples:

  • C4
  • Bb3
  • F#5

Synth configuration #

Use SynthKitSynthOptions to configure a synth:

const SynthKitSynthOptions(
  waveform: SynthKitWaveform.square,
  envelope: SynthKitEnvelope(
    attack: Duration(milliseconds: 20),
    decay: Duration(milliseconds: 100),
    sustain: 0.5,
    release: Duration(milliseconds: 200),
  ),
  filter: SynthKitFilter.lowPass(cutoffHz: 1200),
  volume: 0.7,
)

Defaults #

Setting Default
waveform SynthKitWaveform.sine
envelope.attack 10ms
envelope.decay 120ms
envelope.sustain 0.75
envelope.release 240ms
filter SynthKitFilter.disabled()
volume 0.8

Waveforms #

Supported waveforms:

  • SynthKitWaveform.sine
  • SynthKitWaveform.square
  • SynthKitWaveform.triangle
  • SynthKitWaveform.sawtooth

Envelope #

SynthKitEnvelope models a standard ADSR envelope:

  • attack
  • decay
  • sustain
  • release

Filter #

SynthKitFilter currently supports:

  • SynthKitFilter.disabled()
  • SynthKitFilter.lowPass(cutoffHz: ...)

Scheduling a pattern #

This schedules a short four-beat phrase:

await engine.transport.setBpm(112);

await engine.transport.schedule(
  synth: synth,
  note: SynthKitNote.parse('A3'),
  beat: 0,
  durationBeats: 0.5,
);
await engine.transport.schedule(
  synth: synth,
  note: SynthKitNote.parse('C4'),
  beat: 1,
  durationBeats: 0.5,
);
await engine.transport.schedule(
  synth: synth,
  note: SynthKitNote.parse('E4'),
  beat: 2,
  durationBeats: 0.5,
);
await engine.transport.schedule(
  synth: synth,
  note: SynthKitNote.parse('G4'),
  beat: 3,
  durationBeats: 1,
);

await engine.transport.start();

To stop and clear the scheduled pattern:

await engine.transport.stop(clearSequence: true);

Web usage #

On web, synthkit loads Tone.js automatically the first time you initialize the engine.

Default Tone.js URL:

https://cdn.jsdelivr.net/npm/tone@15.1.0/build/Tone.js

If you want to self-host Tone.js or use a different CDN, pass a custom URL:

await engine.initialize(
  bpm: 120,
  masterVolume: 0.8,
  webToneJsUrl: 'https://your-cdn.example.com/Tone.js',
);

Important web note:

  • Browser audio usually must be unlocked from a user gesture. In practice, call initialize() from a button tap or another direct user interaction.

Platform notes #

iOS and macOS #

  • Uses a native AVAudioEngine-based synth backend.
  • No additional manual setup is required in a standard Flutter app.

Android #

  • Uses a native AudioTrack-based synth backend.
  • Good fit for lightweight synthesis and UI sound playback.

Windows #

  • Uses a native waveOut backend.
  • Best suited to simple synthesis and note triggering.

Linux #

  • Uses a native ALSA PCM playback backend.
  • Requires ALSA development headers when building the Linux app or example.

Backend differences #

The public Dart API is shared, but exact sound character can vary by backend. This is expected because each platform uses its own underlying audio engine.

Error handling and best practices #

  • Always await initialize() before using the engine.
  • Dispose the engine when you no longer need audio resources.
  • Keep masterVolume, synth volume, and note velocity in the 0.0 to 1.0 range.
  • On web, initialize from a user gesture.
  • For long-running or advanced sequencing needs, treat the current transport as a lightweight musical scheduler rather than a DAW-style timeline.

Current limitations #

synthkit is intentionally narrow today. It does not currently provide:

  • audio file playback or sampling
  • MIDI input or output
  • recording or offline rendering
  • effect chains beyond a simple low-pass filter
  • looped transport playback

Example app #

The package includes a runnable Flutter example in example/ that demonstrates:

  • initializing the engine
  • creating a synth
  • playing a one-shot note
  • scheduling a short beat-based pattern
  • stopping playback

Run it with:

cd example
flutter run

Verification #

This package has been verified in this workspace with:

  • flutter analyze
  • flutter build macos --debug
  • flutter build ios --simulator --debug --no-codesign
  • flutter drive --driver=test_driver/integration_test.dart --target=integration_test/initialize_test.dart -d macos
  • flutter drive --driver=test_driver/integration_test.dart --target=integration_test/initialize_test.dart -d <ios-simulator-id>
1
likes
0
points
25
downloads

Publisher

unverified uploader

Weekly Downloads

Cross-platform Flutter synth plugin for note playback, envelopes, filters, and beat scheduling.

Repository (GitHub)
View/report issues

Topics

#audio #synth #music #tonejs #flutter-plugin

License

unknown (license)

Dependencies

flutter, flutter_web_plugins, plugin_platform_interface, web

More

Packages that depend on synthkit

Packages that implement synthkit