Generate haptic feedback files from audio, in pure Dart.
Dev preview. haptify is published as a prerelease: the CLI and library APIs work end-to-end but may still change before 1.0. Bug reports and feedback are very welcome on the issue tracker.
Point haptify at the sound effects in your Flutter project's assets and it produces haptic patterns that follow the audio — transient taps at every percussive hit, continuous rumbles tracing the loudness envelope. The generated files are consumed by the playback plugins you already use; haptify itself has no platform channels.
$ haptify convert assets/audio/*.wav
assets/audio/explosion.wav -> explosion.ahap, explosion.haptic.json, explosion_haptic.dart
assets/audio/tap.wav -> tap.ahap, tap.haptic.json, tap_haptic.dart
| File | Format | Play it with |
|---|---|---|
<name>.ahap |
Apple Core Haptics (AHAP JSON) | gaimon: Gaimon.pattern(ahapString), or core_haptics |
<name>.haptic.json |
{"timings": [...], "amplitudes": [...], "repeat": -1} |
vibration: Vibration.vibrate(pattern: timings, intensities: amplitudes) |
<name>_haptic.dart |
Dart constants (AHAP string + waveform arrays) | Compile the pattern into your app — no asset loading at runtime |
dart pub global activate haptify
haptify convert assets/audio/*.wavIf your shell cannot find haptify afterwards, add pub's bin directory to
your PATH:
- macOS/Linux:
export PATH="$PATH:$HOME/.pub-cache/bin"(add it to your~/.zshrcor~/.bashrc) - Windows: add
%LOCALAPPDATA%\Pub\Cache\bin
dart pub add dev:haptify # or: flutter pub add dev:haptify
dart run haptify:haptify convert assets/audio/*.wavThis pins the version in your pubspec so everyone on the team generates identical haptic files.
WAV and MP3 decode natively in pure Dart — no external tools needed. Other
formats (M4A, OGG, FLAC, ...) are converted through ffmpeg — or
afconvert, preinstalled on macOS — when available on the PATH:
- macOS:
brew install ffmpeg(or rely on the built-inafconvert) - Debian/Ubuntu:
sudo apt install ffmpeg - Windows:
winget install ffmpeg
haptify convert <audio files...> [options]
-o, --out Directory for generated files
-f, --formats ahap, waveform, dart (default: all three)
--resolution Analysis frame / waveform step in ms (default 10)
--onset-sensitivity Transient detection threshold; lower finds more
taps (default 1.5)
--min-gap Minimum ms between transients (default 50)
--curve-points Max intensity-curve points per segment (default 32)
--gamma Envelope exponent; <1.0 boosts quiet passages
(default 1.0)
--silence-threshold Level under which audio counts as silence
(default 0.02)
-v, --verbose Print analysis details and conversion warnings
- Decode the audio to mono samples (MP3 decoding is built in via a vendored Dart port of the minimp3 reference decoder, with ID3 handling and LAME gapless trimming so timing matches the original audio).
- Analyze: an RMS loudness envelope is computed per frame; energy-flux onset detection finds percussive hits; the zero-crossing rate estimates how sharp each moment feels.
- Model: hits become transient haptic events, sustained passages become continuous events shaped by an intensity curve (simplified with Ramer-Douglas-Peucker to stay compact).
- Encode: the same pattern is written as lossless AHAP for iOS and sampled into a 0-255 amplitude waveform for Android. Anything Android cannot express (sharpness, non-intensity curves) is reported as a warning, never an error.
The pattern model, DSL, analyzer, and encoders are a plain Dart API, so you can also author patterns by hand or build your own tooling:
import 'package:haptify/haptify.dart';
final tap = HapticPattern.events([
HapticEvent.transient(at: Duration.zero, intensity: 1.0, sharpness: 0.6),
]);
final rumble = HapticPattern.events([
HapticEvent.continuous(
at: Duration.zero,
duration: 400.ms,
intensity: 0.8,
envelope: HapticEnvelope(attack: 50.ms, release: 100.ms),
),
]);
final combo = tap.then(rumble, gap: 80.ms).repeat(3, gap: 200.ms);
final ahap = combo.toAhap(); // iOS
final wf = combo.toWaveform(); // Android: wf.timings, wf.amplitudesOr run the audio pipeline programmatically:
final audio = await const AudioDecoder().decodeFile('assets/audio/hit.wav');
final pattern = const AudioAnalyzer().analyze(audio);- Android primitive compositions (
VibrationEffect.Composition) as a fourth output format for richer haptics on API 30+ devices - AHAP parsing (
HapticPattern.fromAhap) — convert existing.ahapfiles to Android waveforms without re-analyzing audio - Preset patterns and an easing/curve library for hand-authoring
- Optional Flutter companion package with playback glue for gaimon / vibration
Audio-to-haptic generation is built in; playback itself (platform channels) stays out of scope: haptify authors patterns, your playback plugin plays them.
MIT