The sound clock and the screen clock

This records a November 4, 2026 review of Android MetronomeEngine.cpp and iOS MetronomeEngine.swift in Narae’s local project. Source for both platforms does not establish store availability or measured latency on a device.

Starting a click whenever a UI timer wakes can make the start time depend on other UI work. The inspected engines count samples inside audio callbacks and write click waveforms into output buffers. Screen beat indicators receive separate notifications from that audio path.

Convert a beat interval into sample frames

At a given BPM, one beat lasts 60 / BPM seconds. Multiplying by the actual stream sample rate gives frames per beat. This standalone arithmetic example runs in Node.js or a browser console; its output is not a device-latency measurement.

const sampleRate = 48000;
for (const bpm of [120, 137]) {
  const framesPerBeat = sampleRate * 60 / bpm;
  console.log(bpm, framesPerBeat);
}
// 120 24000
// 137 21021.89781021898

At 137 BPM the interval is not an integer. Rounding every interval before accumulation can compound a small difference. The inspected engines subtract the interval after the accumulator crosses it and retain the fractional remainder for the next beat. Beat spacing and the time from pressing Play to hearing the first sound remain different quantities.

The Android path in the source

The Android engine requests an Oboe output stream with LowLatency performance mode and Shared sharing mode. After opening it, the code reads the actual sample rate and recalculates timing. A requested setting and the configuration a device actually provides must be distinguished.

The callback fills the requested frames, while BPM changes update the engine interval. Android’s low-latency audio guide covers stream configuration together with device verification. Requesting a low-latency mode does not guarantee the same delay on every device.

The iOS path in the source

The iOS code uses AVAudioEngine and AVAudioSourceNode. It obtains the output format’s sample rate and creates beat waveforms in the render block. Beat notifications for the screen are dispatched to the main queue.

A late screen update does not establish an equal delay in the audio buffer. A prompt screen update also cannot rule out added latency in a Bluetooth output path. See the AVAudioSourceNode documentation for the API role, and observe screen, sound, and output route separately.

Conditions to compare when timing feels wrong

  1. Compare speaker, wired, and Bluetooth output at the same BPM and beat settings.
  2. Separate a delayed first sound from irregular intervals during playback.
  3. Check whether only animation is late or recorded click intervals also vary.
  4. Record device, OS, app version, and output route, then repeat under the same conditions.

These steps help narrow a problem. This source review did not measure device output latency, long-term drift, or background operation. See the Narae product guide to get started and the tuner note for microphone pitch detection.

Narae · How to use →