expo-precision-metronome
v1.0.0
Published
High-precision metronome engine for Expo and React Native with native audio scheduling support.
Maintainers
Readme
expo-precision-metronome
High-precision metronome engine for Expo and React Native. Beats are scheduled at the native audio layer — timing stays rock-solid regardless of JS thread load.
Features
- Sample-accurate beat scheduling via AVAudioEngine (iOS) and Oboe (Android)
onBeatevent with beat index and high-resolution timestamponStopevent distinguishing explicit stop from audio interruption (phone call, alarm, etc.)- Live BPM change without restarting the engine
- JSI bridge — no JSON serialization overhead
- Full TypeScript types included
Requirements
| | Minimum | | ----------- | ------------------------------------------------ | | Expo SDK | 55 | | iOS | 15.1 | | Android API | 24 (26+ recommended for AAudio low-latency path) | | Node | 18 |
Installation
npx expo install expo-precision-metronome[!NOTE] This package requires native code. It does not work with Expo Go — use a development build.
Usage
import { useEffect } from "react";
import { start, stop, setBpm } from "expo-precision-metronome";
import ExpoPrecisionMetronomeModule from "expo-precision-metronome";
export default function Metronome() {
useEffect(() => {
const beatSub = ExpoPrecisionMetronomeModule.addListener(
"onBeat",
({ beat, timestamp }) => {
console.log(`Beat ${beat} at ${timestamp}s`);
},
);
const stopSub = ExpoPrecisionMetronomeModule.addListener("onStop", ({ reason }) => {
console.log(`Stopped: ${reason}`);
});
start(120);
return () => {
stop();
beatSub.remove();
stopSub.remove();
};
}, []);
}API
Functions
start(bpm: number): Promise<void>
Starts the metronome at the given BPM. Resolves when the audio engine has started. Throws RangeError if bpm is outside BPM_MIN–BPM_MAX.
stop(): Promise<void>
Stops the metronome. Emits onStop with reason: "explicit".
setBpm(bpm: number): Promise<void>
Changes the tempo on the fly without stopping the engine. Throws RangeError if bpm is outside BPM_MIN–BPM_MAX.
Events
Subscribe via ExpoPrecisionMetronomeModule.addListener(eventName, handler). Always call .remove() on the returned subscription to avoid leaks.
onBeat
Emitted on every beat.
| Property | Type | Description |
| ----------- | -------- | ----------------------------------------------- |
| beat | number | Beat index, starting at 1 |
| timestamp | number | High-resolution audio clock timestamp (seconds) |
onStop
Emitted when the metronome stops for any reason.
| Property | Type | Description |
| -------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| reason | "explicit" \| "interruption" | "explicit" — stopped by stop(). "interruption" — stopped by the OS (incoming call, audio session interruption, etc.) |
Constants
| Constant | Value | Description |
| --------- | ----- | ----------------- |
| BPM_MIN | 20 | Minimum valid BPM |
| BPM_MAX | 300 | Maximum valid BPM |
Types
type BeatEventPayload = {
beat: number;
timestamp: number;
};
type StopEventPayload = {
reason: "explicit" | "interruption";
};Running the example app
cd example
# iOS
npx expo run:ios
# Android
npx expo run:androidContributing
See CONTRIBUTING.md.
License
MIT © Andrey Kotlyar
