Nari

LevelUp Piano — MIDI 피아노 연습 앱 · 블로그

MIDI 피아노를 게임으로 — LevelUp Piano 개발기

블로그 목록
개발 일지2026-07-06
FlutteriOS 네이티브Kotlin게이미피케이션
LevelUp Piano 로그인 화면
로그인 화면 — Sign in with Apple, 아이패드 우선 다크 UI
4역할 (러너·학생·선생님·운영자)
88건반 · 화음 판정
3반주 속도
iPad우선 설계
App Store에 출시됨

LevelUp Piano는 iPad용으로 App Store에 출시되어 있습니다 — App Store에서 보기.

왜 만들었나

피아노를 혼자 연습하다 보면 늘 같은 벽에 부딪힙니다. "내가 제대로 치고 있는 건가?"

선생님이 없는 시간에는 틀린 음도, 어긋난 박자도 스스로 알아차리기 어렵습니다. 여러 방식을 고민하던 중 마이크로 소리를 듣고 채점하는 방법도 떠올렸지만, 마이크는 주변 소음과 화음에 약하고 "몇 번째 음이 얼마나 늦었는지"까지는 잡아내지 못합니다.

디지털 피아노에는 이미 정답이 있습니다. 바로 MIDI입니다. 어떤 건반을, 몇 밀리초에, 얼마나 세게 눌렀는지가 정확한 숫자로 나옵니다. 이 신호를 제대로 쓰면 마이크로는 불가능한 음표 단위 피드백을 줄 수 있습니다.

그래서 목표를 이렇게 잡았습니다.

  • 디지털 피아노를 아이패드에 연결해 연주를 음표 단위로 실시간 판정한다.
  • 판정 결과를 점수·경험치·진도로 바꿔 혼자 하는 연습을 게임처럼 만든다.
  • 피아노 앞에 매일 다시 앉게 만드는 습관 루프를 붙인다.
💡 Insight

마이크는 "틀렸다"까지만 안다. MIDI는 몇 번째 음이 몇 ms 늦었는지까지 안다 — 음표 단위 피드백은 바로 이 차이에서 나온다.

무엇을 만들었나

실시간 연주 판정

앱 둘러보기 — 실제 동작 흐름 (무음 자동재생·루프)

핵심은 판정 엔진입니다. 곡의 노트 차트(어떤 음이 언제 나와야 하는지)와 사용자의 MIDI 입력을 시간축에서 맞춰, 각 음이 정확/빠름/느림/놓침인지 판정합니다. 화면에는 노트가 흐르고, 건반을 누르면 즉시 반응이 돌아옵니다.

네 가지 역할

한 앱 안에 성격이 다른 사용자가 공존합니다.

  • 독립 학습자(Learner) — 선생님 없이 스스로 커리큘럼을 밟는 일반 사용자. 구독 기반.
  • 학생(Student) — 선생님에게 배정받은 곡을 연습하고, 선생님이 진도를 봅니다.
  • 선생님(Teacher) — MIDI 파일을 올려 곡을 만들고 학생에게 배정합니다.
  • 운영자(Operator) — 모두가 공유하는 공식 곡 카탈로그를 관리합니다.

매일 앉게 만드는 게이미피케이션

연습은 지루합니다. 그래서 게임의 언어를 빌려왔습니다.

  • 경험치·레벨: 연습할수록 쌓이는 진행감
  • 스트릭 + 스트릭 프리즈: 연속 학습을 지키되, 하루쯤 빠져도 무너지지 않게
  • 일일 목표: 오늘 할 약속을 스스로 정하기
  • 티어 리그·친구: 주간 순위와 사회적 동기
  • 미션·마스코트 "마에스트로": 짧은 목표와 캐릭터의 응원

어디가 약한지 짚어주는 학습 진단

단순히 "80점"이 아니라, 몇 번째 마디에서 어느 손이 자주 틀리는지를 마디·손 단위로 기록합니다. 이 데이터로 "3~7마디만 왼손으로 다시" 같은 타겟 재연습을 제안하고, 시간이 지나면 다시 물어보는 간격 반복으로 약점을 굳힙니다.

피아노가 없어도 둘러보기

MIDI 키보드가 없는 사람도 앱을 경험할 수 있도록, 온보딩에서 화면 터치 건반으로 샘플 곡을 쳐볼 수 있게 했습니다(점수 미반영). 하드웨어 의존 앱의 진입 장벽을 낮추는 장치입니다.

기술 스택과 아키텍처

🎹USB-MIDI PianoiPad · Flutterjudge · render · playBackend · Ktorauth · scoring · socialPostgreSQLgame state · progressCloudflare R2note charts · audio
핵심 데이터 흐름 — 피아노 → 아이패드 앱(판정·렌더·재생) → 백엔드 → 저장소(상태·오디오)
영역기술
FrontendFlutter (iPad 우선), BLoC 패턴
네이티브 오디오iOS AVAudioEngine + SoundFont (오프라인 렌더링)
BackendKotlin, Ktor, Exposed ORM
DatabasePostgreSQL (Flyway 마이그레이션)
파일 저장Cloudflare R2 (Presigned URL 업로드)
InfrastructureFly.io (도쿄 리전), Docker
ObservabilityGrafana Alloy + OpenTelemetry
Push / 인증Firebase Cloud Messaging, Sign in with Apple

곡의 노트 차트와 반주 오디오는 R2에 저장하고, 게임 상태·진도·소셜 데이터는 PostgreSQL에서 관리합니다. 앱은 아이패드를 우선으로 설계했습니다 — 넓은 화면이 노트가 흐르는 게임플레이와 건반 배치에 잘 맞기 때문입니다.

왜 이 스택인가. iPad 우선이라 크로스플랫폼 UI는 Flutter로 빠르게 짓되, 실시간 오디오 합성만은 Flutter로 부족해 iOS 네이티브(AVAudioEngine) 로 브리지했습니다. 백엔드는 판정·점수·소셜의 도메인 로직이 무거워 정적 타입의 Kotlin/Ktor를 골랐고, 노트 차트·오디오 같은 큰 파일은 DB가 아니라 R2에, 관계형 게임 상태는 PostgreSQL에 나눠 담았습니다.

핵심 설계 결정

MIDI 하드웨어라는 전제

이 앱의 핵심 경험은 물리 피아노가 있어야 완성됩니다. 그래서 연결 마법사로 기기 선택을 명시적으로 안내하고(같은 피아노가 여러 엔드포인트로 잡히기도 합니다), 키보드가 없으면 터치 체험으로 우회하도록 했습니다. 연결과 판정 자체는 실시간 MIDI 판정 엔진에서 ±50/150/300ms 윈도우까지 풀었습니다.

기기 안에서 MIDI를 오디오로 굽기

선생님이 올린 임의의 MIDI를 연습용 반주 오디오(1x·0.75x·0.5x 세 속도) 로 바꿔야 하는데, 서버에 신디사이저를 두는 대신 아이패드 안에서 렌더링하기로 했습니다. iOS AVAudioEngine의 오프라인 렌더링과 SoundFont를 쓰는 이 파이프라인은 별도 글에서 2패스·서브청크·64프레임 트림까지 다뤘습니다.

하나의 앱, 네 개의 역할

역할마다 홈 화면·권한·데이터 접근이 다르지만, 연습 화면처럼 공유하는 부분도 많습니다. 학생과 독립 학습자가 같은 게임플레이 엔진을 쓰되 곡의 출처(배정 곡 vs 카탈로그)만 다르게 흐르도록, 공용 코어와 역할별 셸을 분리했습니다 — 역할 설계 이야기에서 권한 매트릭스까지 풀었습니다.

한국어에서 세계로

처음엔 한국어 전용이었지만, 다국어를 도입하며 UI 문자열뿐 아니라 곡 제목 같은 콘텐츠 데이터까지 로케일별로 관리하도록 확장했습니다(현재 한국어·영어, 이후 중국어·일본어). 그 세 겹의 로컬라이제이션은 한국어에서 세계로에 정리했습니다.

연습곡을 무한 생산하는 AI 작곡

칠 곡이 부족하다는 가장 큰 리스크를, LLM으로 연습곡을 생성·검증하는 파이프라인으로 풀고 있습니다 — LLM에게 ms 대신 마디·박으로 작곡시키고, 검증 실패를 그대로 되먹여 재생성합니다. 설계는 LLM을 이용한 곡 만들기에 자세히 담았습니다.

마치며

LevelUp Piano는 "정확한 신호(MIDI) + 게임의 습관 설계"라는 단순한 아이디어에서 출발했지만, 실제로는 실시간 판정, 온디바이스 오디오 렌더링, 다중 역할, 게이미피케이션, 다국어까지 여러 결이 맞물린 프로젝트가 되었습니다. 혼자 연습하는 사람이 "오늘도 한 번 더" 앉게 만드는 것 — 그 작은 순간을 위해 만들었습니다.