Table of Contents
내부 비상 패턴 재생 사후 분석
- 작성일: 2026-01-16 (Asia/Seoul)
요약: 성공적으로 구현 완료
본 문서는 펌웨어 내부에 상시 탑재된 비상용(내장) 드럼 패턴을 안정적으로 재생하는 기능을 성공적으로 구현한 과정과 그 기술적 사후 분석을 정리한 것이다.
해당 비상 패턴은 SD 카드가 없거나 인식 실패 시에도 재생 가능하며, 다음과 같은 목표를 달성하였다.
- INTERNAL(내부) 패턴은 SD 카드 패턴과 동일한 Pattern Play UI 흐름을 사용한다.
- SD 카드 유무와 무관하게 패턴 선택 → READY → PLAY의 사용자 경험이 일관된다.
- 인코더 동작은 기존 규칙을 그대로 유지한다.
- 회전: BPM 조절
- 롱 프레스: 기존 파라미터 편집 진입 기능 유지 (신규 기능 추가 없음)
왜 이렇게 어려웠는가
이번 작업에서 문제는 크게 두 갈래로 나뉘었다.
- 컴파일 단계의 불안정성
.ino파일이 많아지면서 발생한 예측 불가능한 빌드 오류
- 런타임 재생 품질 문제 (INTERNAL 패턴 한정)
- 재생 시작 시 MIDI 이벤트가 한꺼번에 쏟아지는 현상(burst, ‘와르르’)
- 재생 시작 시 LED 박자와 실제 소리가 어긋남
- 동일 엔진을 사용하는 SD 카드 패턴에서는 문제가 발생하지 않음
이 차이는 중요한 단서였다.
문제는 재생 엔진 자체가 아니라, INTERNAL 패턴의 “재생 시작 경로”에 있었다.
시스템 배경: INTERNAL 패턴이 특별한 이유
SD 카드 패턴의 경우
- 패턴 데이터는 SD 카드 파일에서 로드된다.
- 재생은 오래 검증된 경로를 따른다.
- 기존 재생 정지
- 이벤트 인덱스 초기화
- 박자 기준점 재설정
- 비트 엔진 시작
- 그 결과, 시작 시 타이밍과 LED가 정확히 일치한다.
INTERNAL(비상) 패턴의 경우
- 패턴 데이터는 펌웨어 내부 플래시(PROGMEM) 에 상주한다.
- INTERNAL 모드 진입 시 다음과 같은 추가 단계가 존재한다.
- INTERNAL 모드 선택
- 내장 패턴 인덱스 선택
- 페이로드 디코딩 또는 오프셋 계산
- UI 전환
이 과정 중 재생 시작 시점이 엔진 상태 초기화보다 앞서거나, 일부 상태가 리셋되지 않으면 문제가 발생한다.
핵심 문제 1: 컴파일 오류 — “not declared in this scope”
대표적인 오류 메시지
error: 'startBeatEngine' was not declared in this scope
발생 원인
Arduino 빌드 시스템은 다음과 같이 동작한다.
- 여러
.ino파일을 하나의 파일로 결합 - 자동으로 함수 프로토타입을 생성하려 시도
하지만 다중 .ino 파일 구조에서는 이 자동 프로토타입 생성이 신뢰할 수 없다.
특히 다음 상황에서 문제가 발생한다.
- 파일 상단에 새 헬퍼 함수가 추가되었고
- 그 함수가 다른
.ino파일에 정의된 함수를 호출하며 - 해당 함수의 정의가 결합 순서상 뒤에 오는 경우
이번 작업에서 새로 도입한 공통 시작 함수
beginLoadedPatternPlayback() 이 내부에서 startBeatEngine() 를 호출했지만,
컴파일러는 그 시점에 해당 함수의 존재를 알지 못했다.
해결 방법
명시적인 전방 선언을 추가하였다.
void startBeatEngine();
이 한 줄로 컴파일 순서 문제는 완전히 해결되었다.
핵심 문제 2: INTERNAL 패턴 재생 시작 시 burst + LED 불일치
관찰된 현상
- 모든 INTERNAL 패턴에서 재생 시작 시 burst 발생
- 첫 박자에서 LED와 소리가 맞지 않음
- SD 카드 패턴에서는 동일 현상이 전혀 발생하지 않음
이는 다음을 의미한다.
- 재생 엔진 로직 자체는 정상
- INTERNAL 패턴 데이터도 재생 가능
- 차이는 재생 시작 시 초기화 순서 뿐이다
기술적 원인 분석
패턴 재생 엔진은 개념적으로 다음과 같이 동작한다.
- 현재 시각(
now)과 다음 이벤트 시각(next)을 비교 now >= next인 동안 이벤트를 계속 출력
만약 재생 시작 시점에:
- 이벤트 인덱스가 초기화되지 않았거나
- 기준 시각(origin)이 현재 시각보다 과거에 있거나
- next 값이 너무 이른 시점으로 설정되어 있다면
엔진은 “지연된 이벤트를 따라잡기(catch-up)” 위해
여러 이벤트를 매우 짧은 시간에 한꺼번에 출력한다.
→ 이것이 바로 와르르(burst) 현상이다.
해결책: SD와 INTERNAL 재생 시작 경로의 완전한 통합
설계 원칙
패턴 데이터의 출처(SD / INTERNAL)와 무관하게
재생 시작 시퀀스는 반드시 동일해야 한다.
이를 위해 공통 시작 함수 하나를 정의하였다.
beginLoadedPatternPlayback()
그리고 모든 재생 경로는 이 함수를 반드시 통과하도록 통일하였다.
- SD 카드 패턴
startSinglePatternPlayback()→ 패턴 로드 →beginLoadedPatternPlayback() - INTERNAL 패턴
내부 패턴 준비 →beginLoadedPatternPlayback()
beginLoadedPatternPlayback()의 역할
이 함수는 다음을 반드시 이 순서로 보장한다.
- 기존 재생 완전 정지
- 패턴 재생 상태 초기화
- 이벤트 인덱스 = 0
- first tick 플래그 리셋
- BPM 기반 타이밍 재계산
- 기준 시각을 “지금(now)”으로 재정렬
- 비트 엔진 시작 (
startBeatEngine()) - UI 상태를 READY / PLAYING으로 일관되게 전환
이 과정을 INTERNAL 패턴에도 동일하게 적용함으로써
burst와 LED 불일치 문제는 실질적으로 해소되었다.
UI 관련 원칙 (의도적으로 지킨 규칙)
- 인코더 롱 프레스에 절대 새로운 기능을 추가하지 않는다
- INTERNAL 패턴과 SD 패턴은 동일한 UI 흐름을 사용한다
그 결과:
- MENU > INTERNAL → 즉시 패턴 리스트 UI 진입
- 패턴 선택 → READY 상태
- A3 버튼으로 PLAY / PAUSE
- 인코더 회전은 BPM 조절
- 인코더 롱 프레스는 기존과 동일하게 파라미터 편집 진입
이는 UX 일관성뿐 아니라
패턴 로딩과 재생 시작 시점을 분리하여 타이밍 안정성에도 기여한다.
향후 회귀 테스트 체크리스트
- INTERNAL / SD 모두 동일한 재생 시작 함수 사용 여부
- 재생 시작 시 burst 발생 여부
- 첫 박자에서 LED와 소리 정렬 여부
- 인코더 롱 프레스 동작 변화 여부
- 다중
.ino파일에서 새 함수 추가 시 전방 선언 여부
결론
- 빌드 문제의 핵심 원인
→ Arduino 다중.ino환경에서의 불완전한 자동 프로토타입 생성
→ 해결: 명시적 전방 선언 - 런타임 문제의 핵심 원인
→ INTERNAL 패턴과 SD 패턴의 재생 시작 시퀀스 불일치
→ 해결: 공통 시작 함수로 완전 통합
이로써 비상용 내부 패턴은 안정적으로 재생 가능해졌으며,
UI와 사용자 조작 모델 역시 SD 카드 기반 재생과 완전히 동일하게 유지되었다.
