
MixLab
A collaborative multitrack audio workspace
Rewrote MixLab for Transient Lab from Next.js to SvelteKit, from the audio engine threading to PG integration.
MixLab은 음악 작업물을 함께 듣고 피드백을 주고받는 협업 플랫폼입니다. 트랙을 올리고, 버전을 비교하고, 들은 자리에 바로 의견을 남길 수 있습니다. Transient Lab의 의뢰로, Next.js로 만들어져 있던 MixLab 전체를 SvelteKit으로 다시 만들었습니다.
피드백은 들은 자리에
음악 작업의 피드백은 대개 메신저로 오갑니다. “2분쯤에서 보컬이 좀 묻혀요” 같은 말을 받으면, 어느 버전의 어느 부분 이야기인지부터 다시 확인해야 합니다. 파일은 파일대로, 대화는 대화대로 흩어지고, 버전이 쌓일수록 무엇을 고쳤는지 따라가기 어려워집니다.
MixLab에서는 피드백을 소리 위에 바로 남깁니다. 새 버전을 올리면 브라우저에서 여러 트랙을 함께 재생하고 이전 버전과 비교해 들을 수 있고, 의견은 타임라인의 정확한 구간에 붙습니다. 무엇을, 어디서 고칠지가 한 화면에 남습니다. 프로젝트 자료와 대화도 같은 공간에 모입니다.
처음부터 다시
재작업은 화면을 옮겨 심는 일로 끝나지 않았습니다. 화면과 서버는 물론, 파일 업로드와 후처리, 실시간 대화, 오디오 재생 구조까지 MixLab을 이루는 거의 모든 부분을 SvelteKit 위에서 새로 짰습니다.
그중 음악 협업 서비스만의 어려움이 몰려 있는 곳은 두 군데였습니다. 하나는 크고 민감한 오디오 파일을 다루는 일, 다른 하나는 그 파일을 브라우저에서 끊김 없이 재생하는 일입니다.
올리는 순간부터 들을 수 있을 때까지
확인을 거친 파일만
트랙 파일 하나가 수십 MB를 훌쩍 넘는 일은 흔합니다. 그래서 파일은 서버를 거치지 않고 브라우저에서 저장소로 곧장 올라갑니다. 다만 곧장 올라가는 만큼, 무엇이 올라왔는지는 서버가 다시 확인해야 합니다.
업로드 주소는 임시 위치에만 쓸 수 있도록 발급됩니다. 업로드가 끝나면 서버는 실제로 올라온 파일의 크기와 형식, 파일 앞부분의 시그니처까지 처음 요청한 내용과 대조합니다. 확인을 통과한 파일만 정식 위치로 옮겨지고, 파일을 프로젝트에 등록하는 일과 임시 파일을 치우는 일은 하나의 트랜잭션으로 묶입니다. 그래서 업로드가 중간에 끊기거나 확인에 실패해도, 반쯤 등록된 버전이나 주인 없는 파일이 남지 않습니다.
한 번 읽어서 두 가지를
원본을 매번 통째로 받아 재생하면 느리고 무겁습니다. 그래서 MixLab은 원본과 별도로 가벼운 미리듣기 파일과 파형 데이터를 미리 만들어 둡니다.
이때 원본은 한 번만 디코딩합니다. 디코딩한 소리를 두 갈래로 나눠, 한쪽은 미리듣기용 WebM/Opus 파일로 인코딩하고 다른 쪽은 파형 계산에 씁니다. 템포와 박자 정보도 함께 뽑습니다. 또 “이 파일은 후처리가 필요하다”는 사실 자체를 파일 등록과 같은 트랜잭션에 기록해 두기 때문에, 작업 서버가 중간에 멈춰도 남은 작업은 다시 시작될 때 이어서 처리됩니다.
발매 전 음원을 지키는 저장소
MixLab에 올라오는 음원은 대부분 아직 세상에 나오지 않은 곡입니다. 그래서 원본과 미리듣기 파일은 공개 주소가 없는 비공개 저장소에 나누어 보관하고, 데이터베이스에도 파일 주소가 아닌 식별자만 저장합니다.
재생 버튼을 누르면 앱은 항상 같은 MixLab 주소로 요청을 보냅니다. 서버는 그때마다 이 사람에게 이 파일을 들을 권한이 있는지 확인한 뒤, 짧은 시간만 유효한 저장소 주소로 넘겨 줍니다. 주소가 밖으로 새어 나가더라도 금방 만료되고, 권한을 잃은 사람은 다음 요청부터 바로 막힙니다.
파일을 지울 때도 삭제 직전에 그 파일을 쓰는 곳이 남아 있는지 한 번 더 확인합니다. 업로드와 정리 작업이 엇갈려 살아 있는 파일을 지우는 일을 막기 위해서입니다.
브라우저에서 듣는 믹스
여러 트랙을 동시에 재생하면서 파형을 그리고 소리를 실시간으로 측정하는 일은 브라우저에게 가벼운 일이 아닙니다. 이 일들이 한 스레드에 몰리면 화면이 버벅이고, 오디오에서는 그 버벅임이 곧 끊김으로 들립니다.
무거운 일은 메인 스레드 밖으로
재생 엔진은 무거운 계산이 화면과 소리를 붙잡지 않도록 일을 나눕니다. 파형은 서버가 미리 만들어 둔 데이터를 받아 그리고, 직접 계산해야 할 때도 Web Worker에서 계산합니다. 신호 처리의 핵심은 C++로 작성해 WebAssembly로 돌립니다.
미터는 트랙마다 분석기를 따로 두지 않고, 하나의 분석 결과를 모든 미터가 나눠 씁니다. 같은 소리를 미터 수만큼 반복해서 분석하지 않기 위해서입니다. 미터를 별도 창으로 띄워도 그 창은 오디오를 다시 처리하지 않고, 메인 창이 계산한 결과만 받아 그립니다.
버전을 바꿀 때는 소리를 짧게 크로스페이드해 튀는 소리 없이 넘어갑니다. 빠르게 여러 번 바꾸더라도, 늦게 끝난 이전 로딩이 새로 고른 소리를 덮어쓰지 않습니다.
믿을 수 있는 미터
미터가 평소 쓰는 측정 도구와 다른 숫자를 보여 주면, 그 숫자를 두고 오가는 피드백도 믿을 수 없게 됩니다. 그래서 MixLab의 미터는 방송과 스트리밍에서 쓰는 라우드니스 측정 표준(ITU-R BS.1770)에 맞췄습니다. 곡 전체의 라우드니스는 조용한 구간을 걸러 내는 게이팅 규칙까지 표준대로 계산하고, 샘플과 샘플 사이에서 생기는 실제 피크(True Peak)도 따로 측정합니다.
결제
재작업 후반부에는 구독 결제를 붙였습니다. 결제는 작은 실수 하나가 곧 돈 문제로 이어지기 때문에, 어떤 일이 생겨도 결제 상태는 한 방향으로만 움직인다는 원칙을 세웠습니다.
- 결제를 요청하기 전에 먼저 “대기 중” 기록을 남겨, 응답을 받지 못해도 결제의 흔적을 잃지 않습니다.
- 결과가 애매하면 결제 대행사에 직접 다시 물어 상태를 맞춥니다.
- 결제 창을 여러 번 열었다가 예전 창의 결과가 뒤늦게 돌아와도, 이미 지난 시도로 보고 반영하지 않습니다.
- 결제 대행사의 알림은 먼저 그대로 저장한 뒤 처리합니다. 같은 알림이 여러 번 와도 한 번만 반영되고, 처리 도중 서버가 멈춰도 다시 처리할 수 있습니다.
MixLab: Live
MixLab은 지금 mixlab.co.kr에서 운영되고 있습니다. 재작업 이후에도 Transient Lab 팀과 함께 계속 다듬어지고 있습니다.