이 글은 Today I Learn 시리즈의 44번째 기록입니다. (총 125개)

파일을 틀어 보는 것만 되던 앱에 실시간 연주를 붙이며 나온 것들.

차이가 어디까지인지 보기

입력만 다르면 아래는 그대로 쓴다

파일 재생과 실시간 연주는 다른 기능처럼 보인다. 그런데 뜯어 보니 “음이 언제 오는가”만 달랐다. 파일은 타임라인을 보고 때가 되면 오고, 연주는 사람이 건반을 누를 때 온다. 그 뒤로 벌어지는 일 — 파티클이 피어오르고 소리가 나고 건반이 눌리는 것 — 은 완전히 같다.

그래서 “음이 시작됐다”와 “음이 끝났다” 두 지점을 만들고, 양쪽이 거기로 모이게 했다. 화면을 그리는 부분은 한 줄도 안 고치고 연주 모드가 얹혔다.

새 기능을 검토할 때 기존 기능과 무엇이 다른지를 끝까지 좁혀 보는 게 견적을 가른다. “비슷한데 다르다”에서 멈추면 통째로 새로 만들게 되고, “입력만 다르다”까지 좁히면 붙이는 일이 된다.

하드웨어가 필요한 기능은 없이도 쓸 길을 만든다

연주 모드는 원래 건반 악기를 연결해야 쓸 수 있다. 그러면 장비가 없는 사람은 이 기능을 아예 못 만져 본다.

그래서 컴퓨터 자판을 건반에 매핑했다. 개러지밴드의 뮤지컬 타이핑처럼 음악 프로그램들이 흔히 하는 방식이다. 자판으로는 두 옥타브쯤 닿고, 옥타브를 옮기면 전체를 커버한다.

이게 세 군데서 값을 했다. 장비 없이 개발하고 확인할 수 있고, 남에게 보여 줄 때 장비를 안 들고 가도 되고, 장비 없는 사용자도 기능을 경험하고 나서 살지 말지 정할 수 있다.

하드웨어를 전제로 하는 기능을 만들 때 “없으면 어디까지 되게 할 것인가”를 같이 정해 두는 게 좋다. 아예 못 쓰게 두면 그 기능은 소수만 아는 기능으로 남는다.

자판으로 연주할 때 옥타브를 바꾸면 누르고 있던 음이 안 꺼지는 일이 있었다. 켤 때와 끌 때 다른 값을 쓰면 이렇게 된다. 켜고 끄는 짝은 켤 때의 값을 기억해 뒀다가 그걸로 꺼야 한다.

권한은 그 기능에 들어갈 때 묻는다

MIDI 악기를 읽으려면 Web MIDI 권한(브라우저가 연결된 MIDI 기기를 읽어도 되는지 사용자에게 묻는 것)이 필요하다. 시작하자마자 물으면 파일만 볼 사람에게도 팝업이 뜬다.

연주 모드에 처음 들어갈 때 묻게 미뤘다. 그 시점에는 사용자가 왜 묻는지 알기 때문에 허용할 이유가 분명하다.

권한 요청은 “필요한 순간에, 이유가 보이는 자리에서” 하는 게 허용률도 높고 인상도 좋다. 시작할 때 몰아서 묻는 건 만드는 쪽만 편한 방식이다.


한계를 숫자로 확인하기

실측해야 플랫폼을 바꿀 근거가 된다

실시간 연주는 누른 순간 소리가 나야 한다. 늦으면 연주 자체가 안 된다.

줄일 수 있는 건 다 해 봤다. 그러고 나서 실제 지연을 재서 화면에 띄웠다. 우리가 손댈 수 있는 내부 버퍼가 10밀리초, 그 아래 운영체제와 장치가 쓰는 출력 지연이 48밀리초, 합쳐서 58밀리초였다.

여기서 안 게 컸다. 브라우저는 소리를 낼 때 윈도우의 공용 통로(WASAPI 공유 모드)만 쓸 수 있는데, 그 통로는 여러 앱의 소리를 섞느라 버퍼(소리를 미리 채워 두는 대기열)가 크다. 음악 프로그램들이 5밀리초를 내는 건 장비에 거의 직결하는 ASIO라는 저지연 규격을 쓰기 때문인데, 브라우저는 거기 접근할 수 없다. 좋은 오디오 장비를 꽂아도 웹에서는 안 나온다.

즉 이건 우리 코드로 더 못 줄이는 벽이었다. 이 숫자가 나중에 설치형 앱으로 가는 결정의 근거가 됐다.

막연히 “웹은 느리다”고 아는 것과 “58밀리초이고 그중 48은 우리가 못 건드린다”고 아는 것은 다르다. 앞엣것으로는 아무 결정도 못 하고, 뒤엣것으로는 플랫폼을 바꾸는 큰 결정을 할 수 있다. 한계에 부딪혔을 때 그 한계를 숫자로 만들어 두면, 그게 다음 결정의 재료가 된다.


요약

  • 새 기능이 기존과 무엇이 다른지 끝까지 좁힌다 — “입력만 다르다”까지 좁히면 붙이는 일이 된다
  • 하드웨어가 필요한 기능은 없이도 쓸 길을 만든다. 개발·시연·구매 판단 세 군데서 값을 한다
  • 켜고 끄는 짝은 켤 때의 값으로 꺼야 한다. 중간에 설정이 바뀌면 어긋난다
  • 권한은 필요한 순간, 이유가 보이는 자리에서 묻는다
  • 한계는 숫자로 만들어 둔다 — 막연한 한계로는 결정을 못 하고, 숫자는 플랫폼을 바꿀 근거가 된다

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 insertOnConflictUpdate vs DoUpdate(target) 27 세션 필터 28 아코디언(Accordiaon) UI를 펼친상태로 만들기 29 input의 step 30 Word Cloud 31 Google Sheets를 데이터 버스로(with AppSheet) 32 Django 모델 텍스트 필드 자동 수집 패턴 33 localStorage로 섹션 토글 상태 유지 34 순차 ID 생성(`select_for_update()` + `max()` 조합) 35 역참조 검색과 distinct() 36 xlsx 다운로드와 로딩 오버레이 충돌 37 Android 파일 공유 MIME 타입 38 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 39 UTF-8 BOM과 PowerShell 파일 쓰기 40 소리꽃 KeyBloom TIL 1 41 소리꽃 KeyBloom TIL 2 42 소리꽃 KeyBloom TIL 3 43 메트로놈 Simple Metronome TIL 1 44 소리꽃 KeyBloom TIL 4 읽는 중 45 메트로놈 Simple Metronome TIL 2 46 소리꽃 KeyBloom TIL 5 47 메트로놈 Simple Metronome TIL 3 48 정적 블로그 SEO 정비와 Pagefind 검색 도입 49 메트로놈 Simple Metronome TIL 4 50 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 51 메트로놈 Simple Metronome TIL 5 52 모래게임 Sandrop TIL 1 53 메트로놈 Simple Metronome TIL 6 54 모래게임 Sandrop TIL 2 55 모래게임 Sandrop TIL 3 56 메트로놈 Simple Metronome TIL 7 57 모래게임 Sandrop TIL 4 58 모래게임 Sandrop TIL 5 59 모래게임 Sandrop TIL 6 60 소리꽃 KeyBloom TIL 6 61 모래게임 Sandrop TIL 7 62 모래게임 Sandrop TIL 8 63 소리꽃 KeyBloom TIL 7 64 모래게임 Sandrop TIL 9 65 소리꽃 KeyBloom TIL 8 66 모래게임 Sandrop TIL 10 67 소리꽃 KeyBloom TIL 9 68 소리꽃 KeyBloom TIL 10 69 소리꽃 KeyBloom TIL 11 70 소리꽃 KeyBloom TIL 12 71 소리꽃 KeyBloom TIL 13 72 온실 GreenHouse TIL 1 73 소리꽃 KeyBloom TIL 14 74 온실 GreenHouse TIL 2 75 소리꽃 KeyBloom TIL 15 76 소리꽃 KeyBloom TIL 16 77 소리꽃 KeyBloom TIL 17 78 소리꽃 KeyBloom TIL 18 79 소리꽃 KeyBloom TIL 19 80 소리꽃 KeyBloom TIL 20 81 로그스톤 상표 셀프 출원 TIL 1 82 소리꽃 KeyBloom TIL 21 83 소리꽃 KeyBloom TIL 22 84 소리꽃 KeyBloom TIL 23 85 소리꽃 KeyBloom TIL 24 86 MiniMacro TIL 1 87 Astro가 무엇인지, 왜 옮기는지 88 MiniMacro TIL 2 89 메트로놈 Simple Metronome TIL 8 90 메트로놈 Simple Metronome TIL 9 91 블로그 Astro 이관 TIL 92 오픈데이 Openday TIL 1 93 오픈데이 Openday TIL 2 94 Unreal Engine MCP TIL 1 95 콘티온 Conti On TIL 11 96 오픈데이 Openday TIL 3 97 오픈데이 Openday TIL 4 98 오픈데이 Openday TIL 5 99 ScorePlayer TIL 1 100 BuildingHub TIL 1 101 ScorePlayer TIL 2 102 BuildingHub TIL 2 103 ScorePlayer TIL 3 104 Unreal Engine MCP TIL 2 105 궁 미로 PalaceMaze TIL 1 106 메트로놈 Simple Metronome TIL 10 107 메트로놈 Simple Metronome TIL 11 108 오픈데이 Openday TIL 6 109 BuildingHub TIL 3 110 Obsidian vault에서 코드만 빼기 — directory junction 111 ScorePlayer TIL 4 112 오픈데이 Openday TIL 7 113 URL은 바뀔 수 있는 것에 묶지 않는다 114 도구는 기능이 아니라 내 일에 맞는지로 고른다 115 돌이키기 힘든 변경은 작게 먼저 확인한다 116 궁 미로 PalaceMaze TIL 3 117 궁 미로 PalaceMaze TIL 2 118 소리꽃 KeyBloom TIL 29 119 오픈데이 Openday TIL 8 120 BuildingHub TIL 4 121 ScorePlayer TIL 5 122 BuildingHub TIL 5 123 MiniMacro TIL 3 124 소리꽃 KeyBloom TIL 30 125 궁 미로 PalaceMaze TIL 4