브라우저에서 재생 중인 컴퓨터 소리(유튜브, 웹 플레이어 등)를 실시간으로 캡처하고, 원하는 구간을 잘라 고음질 WAV 파일로 저장하는 웹 오디오 샘플링 도구입니다. 별도의 프로그램 설치 없이 Web Audio API와 화면 공유(탭 오디오 캡처) API만으로 동작하며, 추출한 샘플은 브라우저에서 직접 구동되는 로컬 AI 모델(Demucs) 로 보컬/드럼/베이스/반주 트랙으로 분리할 수 있습니다.
🔗 Live Demo: ear-cut.vercel.app
- 탭 오디오 캡처 —
getDisplayMedia로 브라우저 탭의 오디오 스트림을 직접 녹음 (외부 소프트웨어/드라이버 불필요) - 실시간 파형 모니터링 — Canvas 기반 오디오 비주얼라이저로 녹음 중 파형을 실시간 확인
- Always-on-Top 컨트롤러 — Document Picture-in-Picture API로 다른 창 위에 항상 떠 있는 독립 컨트롤러 제공
- PiP 미지원 브라우저 대응 — PiP를 지원하지 않는 브라우저에서는 페이지 내 드래그 가능한 모달로 자동 대체
- 고음질 WAV 내보내기 — 녹음된 오디오를 압축 없는 16-bit Stereo PCM WAV로 인코딩
- AI 보컬/악기 분리 — Demucs ONNX 모델을
onnxruntime-web(WebGPU/WASM) + Web Worker로 브라우저에서 직접 실행해, 60초 이하 샘플을 Vocal/Drum/Bass/Inst 4트랙으로 분리 (서버 업로드 없이 로컬 연산, 모델은 최초 1회 다운로드 후 Cache API로 캐싱) - 샘플 영구 저장 — 녹음/분리 결과를 IndexedDB에 저장해 새로고침 후에도 유지
- 샘플 목록 관리 — 원본 및 분리된 각 트랙의 재생/다운로드/삭제 지원
| STEP 1. 소스 연결 | STEP 2. 오디오 공유 |
|---|---|
![]() |
![]() |
| 컨트롤러 열기 → 소리 소스 연결로 오디오를 재생 중인 탭을 선택 | 공유 대화상자 하단의 "오디오 공유(Share audio)" 체크박스 활성화 |
| STEP 3. 샘플링 | STEP 4. 샘플 목록 |
|---|---|
![]() |
![]() |
| 샘플링 시작/중지로 원하는 구간을 녹음, WAV로 자동 인코딩 | 녹음된 샘플을 목록에서 바로 재생·다운로드 |
| STEP 5. AI 분리 실행 | STEP 6. 스템 결과 |
|---|---|
![]() |
![]() |
| 샘플 카드를 펼쳐 "보컬/악기 분리 실행" 클릭 (60초 이하 샘플만 지원) | Vocal/Drum/Bass/Inst 4트랙이 개별 재생·다운로드 가능하게 표시 |
앱 내 헤더의 "컨트롤러 사용법" 패널에서도 동일한 과정을 스크린샷과 함께 단계별로 확인할 수 있습니다.
개인 사이드 프로젝트로 기획부터 설계, 구현, 배포까지 단독으로 진행했습니다.
- 탭 오디오 캡처 파이프라인 설계·구현 —
getDisplayMedia+AudioContext기반으로 외부 드라이버 없이 브라우저 탭 오디오를 직접 캡처하는useAudioCapture훅 작성 - 무손실 WAV 인코더 직접 구현 — 서버나 외부 인코딩 라이브러리 없이
AudioBuffer를 16-bit Stereo PCM WAV로 변환하는 인코더를 클라이언트 단에서 완결 (utils/wav.js) - Always-on-Top 컨트롤러 UX 설계 — Document Picture-in-Picture API로 독립 팝업 컨트롤러를 구현하고, PiP 미지원 브라우저를 위한 드래그 가능한 인페이지 모달 폴백을 별도로 설계
- AI 음원 분리 파이프라인 통합 — ONNX Runtime Web + Web Worker로 Demucs 모델 추론을 메인 스레드와 분리해 UI 끊김 없이 로컬에서 실행되도록 구성 (
separation.worker.js) - 교차 출처 격리(COOP/COEP) 이슈 해결 — 스레드 WASM 백엔드 초기화 실패 원인을 규명하고 dev(
vite.config.js)/배포(vercel.json) 환경에 헤더를 일관되게 설정 - 제한된 네트워크 환경 대응 — 외부 CDN 의존으로 인한 WASM 로딩 실패 문제를 ONNX Runtime WASM/JSEP 바이너리 로컬 내장(
public/)으로 해결 - AI 모델 로딩 진행률 UX 개선 — 캐시 히트 시 진행률 조기 완료, 모델 컴파일 단계 멈춤 현상 등 여러 체감 버그를 재현하고 순차적으로 수정
- 영속화 계층 설계 — IndexedDB 기반 샘플/스템 저장소(
utils/db.js)를 설계해 새로고침 후에도 결과가 유지되도록 구현
| Area | Stack |
|---|---|
| Frontend | React 19, Vite 8 |
| Audio Capture/Encoding | Web Audio API, getDisplayMedia(MediaRecorder 계열), 자체 구현 WAV 인코더 |
| AI Inference | ONNX Runtime Web (WebGPU 우선 / WASM 폴백), demucs-web, Web Worker |
| Storage | IndexedDB, Cache API (AI 모델 캐싱) |
| UI/UX | Document Picture-in-Picture API, lucide-react |
| Deploy | Vercel |
| Tooling | ESLint (flat config) |
별도 백엔드 서버 없이 브라우저 안에서 캡처 → 인코딩 → 저장 → AI 추론까지 모두 처리하는 클라이언트 단일 구조입니다.
1. "컨트롤러 열기" 클릭
→ PiP 지원 브라우저: Always-on-Top 팝업 창 (documentPictureInPicture)
→ 미지원 브라우저: 페이지 내 드래그 가능한 모달로 폴백 (useDraggable)
2. "소리 소스 연결" 클릭
→ getDisplayMedia로 탭 오디오 스트림 획득 (useAudioCapture)
→ AudioContext에 연결, Canvas로 실시간 파형 시각화
3. "샘플링 시작/중지"
→ 캡처된 PCM을 useWavRecorder에서 16-bit Stereo WAV로 인코딩 (utils/wav.js)
→ 결과를 IndexedDB(EarCutStudioDB)에 영속화 → 새로고침 후에도 샘플 유지
4. "보컬/악기 분리 실행" (선택, 60초 이하 샘플)
→ 샘플 PCM을 Web Worker(separation.worker.js)로 전달 (메인 스레드 논블로킹)
→ onnxruntime-web이 ONNX 모델을 Cache API로 캐싱하며 로드 (WebGPU 우선, WASM 폴백)
→ demucs-web이 Vocal / Drums / Bass / Inst 4트랙으로 분리
→ 분리된 각 트랙을 WAV로 인코딩해 IndexedDB에 저장, UI에서 개별 재생/다운로드
src/
├── App.jsx # 랜딩 화면 및 컨트롤러 오픈/PiP 상태 관리
├── components/
│ ├── AudioSampler.jsx # 오디오 캡처, 녹음, WAV 인코딩, 시각화, 샘플/스템 목록 UI
│ └── ControllerGuide.jsx # 컨트롤러 열기~AI 분리 결과 확인까지 단계별 사용법 가이드 패널
├── hooks/
│ ├── useAudioCapture.js # getDisplayMedia 탭 오디오 캡처, AudioContext 관리
│ ├── useWavRecorder.js # 녹음, WAV 인코딩, IndexedDB 영속화(recordings/stems)
│ ├── useAudioSeparation.js # Web Worker 기반 AI 분리 오케스트레이션 및 진행률 추정
│ └── useDraggable.js # PiP 미지원 시 페이지 내 모달 드래그 처리
├── workers/
│ └── separation.worker.js # onnxruntime-web + demucs-web 실행, 모델 다운로드/캐싱(Cache API)
├── utils/
│ ├── db.js # IndexedDB(EarCutStudioDB) 래퍼
│ └── wav.js # AudioBuffer → WAV 인코딩
├── assets/guide/ # 사용법 가이드 스텝별 스크린샷
├── App.css / index.css # 전역 스타일, 디자인 토큰(CSS 변수)
└── main.jsx # 엔트리 포인트
public/
└── ort-wasm-simd-threaded.* # onnxruntime-web WASM/WebGPU(jsep) 런타임 바이너리 (정적 서빙, CDN 미사용)
- 스레드 WASM 실행을 위한 교차 출처 격리 문제 — onnxruntime-web의 WebGPU/threaded WASM 백엔드가 별다른 에러 메시지 없이 초기화에 실패하는 문제를 만나, 원인이
SharedArrayBuffer사용에 필요한 교차 출처 격리(COOP/COEP) 헤더 누락임을 확인.vite.config.js(dev 서버)와vercel.json(배포) 양쪽에 동일한 헤더를 설정해 로컬-배포 환경 간 동작 차이를 없앴습니다. - 제한된 네트워크 환경에서의 CDN 차단 대응 — onnxruntime-web이 기본적으로 외부 CDN에서 WASM 바이너리를 받아오는 구조라, 특정 네트워크 환경에서 로딩이 막혀
no available backend found오류가 발생하는 것을 확인. WASM/JSEP 런타임 파일 전체를public/에 직접 내장하고ort.env.wasm.wasmPaths를import.meta.env.BASE_URL로 지정해 정적 서빙으로 전환, 배포 경로(서브 디렉토리 포함)에도 대응하도록 만들었습니다. - AI 모델 로딩 진행률 UX 버그 다수 수정 — ① 모델이 이미 캐싱된 경우 진행률이 조기에 100%로 표시되는 문제, ② 다운로드 완료 후 실제로는 ONNX 모델 컴파일이 진행 중인데도 UI가 멈춘 것처럼 보이는 문제를 각각 재현·수정. Cache API 응답을 스트리밍으로 읽어 실제 바이트 기준 진행률을 계산하고, "다운로드 → 컴파일 → 처리" 단계를 UI 상태로 명시적으로 분리해 사용자가 진행 상황을 오해하지 않도록 개선했습니다.
- Node.js 18 이상
- Chrome/Edge 등 Chromium 기반 브라우저 권장 (탭 오디오 캡처 및 Document PiP는 Chromium 계열에서만 완전히 지원됩니다)
- AI 분리 기능은 스레드 WASM 실행을 위해
Cross-Origin-Opener-Policy: same-origin/Cross-Origin-Embedder-Policy: require-corp헤더(교차 출처 격리)가 필요합니다.vite.config.js(dev)와vercel.json(배포)에 이미 설정되어 있습니다.
npm install
npm run dev # 개발 서버 실행 (HMR)npm run build # 프로덕션 빌드 (dist/)
npm run preview # 빌드 결과 미리보기npm run lint- 탭 오디오 캡처(
getDisplayMediaaudio)와 Document Picture-in-Picture는 Chrome/Edge 등 Chromium 계열에서만 안정적으로 동작합니다. - Firefox/Safari 등에서는 오디오 공유 옵션이 없거나 PiP가 지원되지 않아 기능이 제한될 수 있습니다.
- AI 보컬/악기 분리는 WebGPU를 우선 시도하고 WASM으로 폴백합니다. 브라우저 메모리 한계로 60초를 초과하는 샘플과 모바일 기기에서는 비활성화됩니다.
public/ort-wasm-simd-threaded.*런타임 파일들은 반드시 저장소에 커밋되어 배포되어야 합니다. 빠지면 AI 분리 시no available backend found오류가 발생합니다.
- 커밋 컨벤션:
feat,fix,docs,refactor,style,design등 Conventional Commits 스타일 태그로 변경 단위를 구분해 커밋했습니다. - 코드 품질: ESLint(flat config)로 코드 스타일을 일관되게 유지했습니다.
- 개발 방식: 1인 개발로 별도 브랜치 전략 없이
main브랜치에 직접 커밋하는 방식으로 진행했습니다.
- Email: gks12090607@gmail.com







