소리를 내는 시계와 화면을 그리는 시계

이 글은 2026년 11월 4일 나래 로컬 프로젝트의 Android MetronomeEngine.cpp와 iOS MetronomeEngine.swift를 확인한 기록입니다. 두 플랫폼의 소스가 있다는 사실은 각 스토어의 출시 상태나 특정 기기에서의 지연 성능을 입증하지 않습니다.

화면 타이머가 깨어날 때마다 클릭음을 시작하면 UI 작업이 많을 때 호출 시점이 늦어질 수 있습니다. 확인한 엔진은 오디오 콜백 안에서 샘플 수를 세고 클릭 파형을 출력 버퍼에 씁니다. 화면의 박자 표시는 이 소리 생성 경로에서 나오는 별도 알림을 받아 갱신합니다.

박자 간격을 샘플 수로 바꾸기

한 분에 BPM번 박자가 온다면 한 박자의 길이는 60 / BPM초입니다. 여기에 실제 스트림의 샘플 레이트를 곱하면 박자당 프레임 수가 됩니다. 아래는 산술을 확인하는 예제이며 기기 지연 측정값이 아닙니다. Node.js나 브라우저 콘솔에서 실행할 수 있습니다.

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

137 BPM에서는 간격이 정수가 아닙니다. 매번 정수로 반올림해 누적하면 작은 차이가 쌓입니다. 확인한 엔진은 누산기가 박자 간격을 넘을 때 그 간격을 빼고 남은 소수 부분을 다음 박자에 유지합니다. 같은 샘플 레이트를 가정하더라도 박자 간격과 버튼을 누른 뒤 첫 소리가 나오는 시간은 다른 값입니다.

Android에서 확인한 경로

Android 소스는 Oboe 출력 스트림을 열면서 LowLatency 성능 모드와 Shared 공유 모드를 요청합니다. 스트림이 열린 뒤 실제 샘플 레이트를 읽어 박자 간격을 다시 계산합니다. 소스에 적힌 희망 설정과 기기에서 실제로 제공된 설정을 구분해야 합니다.

오디오 콜백은 전달받은 프레임 수만큼 출력 버퍼를 채웁니다. UI에서 BPM을 바꾸면 엔진의 박자 간격도 갱신됩니다. Android의 저지연 오디오 안내도 성능 모드·스트림 설정과 실제 기기 검증을 함께 다룹니다. 저지연 모드를 요청했다는 이유만으로 모든 기기에서 같은 지연을 보장할 수는 없습니다.

iOS에서 확인한 경로

iOS 소스는 AVAudioEngine과 AVAudioSourceNode를 사용합니다. 출력 노드의 포맷에서 샘플 레이트를 얻고, 렌더 블록에서 박자에 맞는 파형을 생성합니다. 박자 이벤트를 화면 쪽으로 전달할 때는 메인 큐에 작업을 보냅니다.

화면이 조금 늦게 바뀌었다고 오디오 버퍼의 박자까지 같은 만큼 늦었다고 볼 수는 없습니다. 반대로 화면이 정확해 보여도 블루투스 출력 경로에서 지연이 추가될 수 있습니다. API의 역할은 AVAudioSourceNode 문서를 참고하고, 실제 동기화는 화면·소리·출력 장치를 나눠 관찰해야 합니다.

박자가 이상하게 느껴질 때 비교할 조건

  1. 같은 BPM과 박자 설정에서 스피커, 유선, 블루투스 출력을 각각 비교합니다.
  2. 처음 재생할 때만 늦는지, 재생 중 간격도 불규칙한지 구분합니다.
  3. 화면 애니메이션만 늦는지 녹음한 클릭 간격도 달라지는지 확인합니다.
  4. 기기·OS·앱 버전과 출력 경로를 기록한 뒤 같은 조건으로 반복합니다.

이 목록은 사용자가 문제를 좁히기 위한 방법입니다. 이 글의 소스 검토에서는 실제 단말의 출력 지연, 장시간 드리프트, 백그라운드 동작을 측정하지 않았습니다. 앱 사용을 시작하는 방법은 나래 제품 안내, 마이크에서 음정을 찾는 과정은 튜너 글에서 확인할 수 있습니다.

나래 · 사용 안내 →