{"_id":"demucs-web","_rev":"3-f338200f568837ffa25494256e356f67","name":"demucs-web","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"demucs-web","version":"1.0.0","keywords":["demucs","music","audio","source-separation","stem-separation","onnx","webgpu","wasm","browser"],"author":{"name":"timcsy"},"license":"MIT","_id":"demucs-web@1.0.0","maintainers":[{"name":"timcsy","email":"timocsy@yahoo.com.tw"}],"homepage":"https://github.com/timcsy/demucs-web","bugs":{"url":"https://github.com/timcsy/demucs-web/issues"},"dist":{"shasum":"ee99d330ee80e07a28e39d406f8d11fb183db399","tarball":"https://registry.npmjs.org/demucs-web/-/demucs-web-1.0.0.tgz","fileCount":7,"integrity":"sha512-eU3MDOipHY4s3ILgnI0s7udBRq9U+PAbLh9knuHlAfbqvbGG6vIGtW5YVxQh+DAclEiCXAzlFnza57M9Ns6Hzw==","signatures":[{"sig":"MEUCIQD7WDGrNgdQl7RUFp0/TE0Z5IOr+VHyo1Uu7PZTEJ9OcQIgbIgTI9w8TPOxI6/jk3X9dFUViyFMalanXturCPnBMCQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24965},"main":"src/index.js","type":"module","module":"src/index.js","engines":{"node":">=16.0.0"},"exports":{".":"./src/index.js","./fft":"./src/fft.js","./constants":"./src/constants.js","./processor":"./src/processor.js"},"gitHead":"b6a6713991427e238fdf01f8ff6d22e264128e6d","scripts":{"demo":"python3 -m http.server 8080"},"_npmUser":{"name":"timcsy","email":"timocsy@yahoo.com.tw"},"repository":{"url":"git+https://github.com/timcsy/demucs-web.git","type":"git"},"_npmVersion":"10.9.2","description":"Music source separation (Demucs) running in browser with ONNX Runtime Web","directories":{},"_nodeVersion":"22.16.0","_hasShrinkwrap":false,"peerDependencies":{"onnxruntime-web":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/demucs-web_1.0.0_1764519329089_0.377638102161058","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"demucs-web","version":"1.0.1","keywords":["demucs","music","audio","source-separation","stem-separation","onnx","webgpu","wasm","browser"],"author":{"name":"timcsy"},"license":"MIT","_id":"demucs-web@1.0.1","maintainers":[{"name":"timcsy","email":"timocsy@yahoo.com.tw"}],"homepage":"https://github.com/timcsy/demucs-web","bugs":{"url":"https://github.com/timcsy/demucs-web/issues"},"dist":{"shasum":"45c4e4452f3673ce2b10af1b7132a216b9aff5ea","tarball":"https://registry.npmjs.org/demucs-web/-/demucs-web-1.0.1.tgz","fileCount":7,"integrity":"sha512-tOXVWhDf7DVz0ifE85FM70B9EfJwpodm4TsNxZXPhxND4xPAhE3k+jQkq/5S0XvaOAS+PUI+1qJXJRdaJOyR3A==","signatures":[{"sig":"MEUCIQCL6fo0wA2ZliGvCabsEOBTQTQ/Bd2iSekiwpx6rNZ7bAIgTN2bBcUO4+9BWof9NDA8IkGyMWt8amBF0kt2ooLQy/w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29310},"main":"src/index.js","type":"module","module":"src/index.js","engines":{"node":">=16.0.0"},"exports":{".":"./src/index.js","./fft":"./src/fft.js","./constants":"./src/constants.js","./processor":"./src/processor.js"},"gitHead":"a3d98df56efd97ddeddc4c3ddbd246634d99c858","scripts":{"demo":"python3 -m http.server 8080"},"_npmUser":{"name":"timcsy","email":"timocsy@yahoo.com.tw"},"repository":{"url":"git+https://github.com/timcsy/demucs-web.git","type":"git"},"_npmVersion":"10.9.2","description":"Music source separation (Demucs) running in browser with ONNX Runtime Web","directories":{},"_nodeVersion":"22.16.0","_hasShrinkwrap":false,"peerDependencies":{"onnxruntime-web":">=1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/demucs-web_1.0.1_1764564923793_0.5363792597005728","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"demucs-web","version":"1.0.2","description":"Music source separation (Demucs) running in browser with ONNX Runtime Web","type":"module","main":"src/index.js","module":"src/index.js","exports":{".":"./src/index.js","./processor":"./src/processor.js","./fft":"./src/fft.js","./constants":"./src/constants.js"},"scripts":{"demo":"python3 -m http.server 8080"},"keywords":["demucs","music","audio","source-separation","stem-separation","onnx","webgpu","wasm","browser"],"author":{"name":"timcsy"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/timcsy/demucs-web.git"},"homepage":"https://github.com/timcsy/demucs-web","bugs":{"url":"https://github.com/timcsy/demucs-web/issues"},"peerDependencies":{"onnxruntime-web":">=1.17.0"},"engines":{"node":">=16.0.0"},"_id":"demucs-web@1.0.2","gitHead":"617385fbf5883b04a2e6f474be63ed4d93f4e44c","_nodeVersion":"22.16.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-G/HWbbBDvfGzaxDZQhqTp4+6i8nlCIRGEd6E1+EZJJONx5Es3bBc+jHqhfyblTlsKo/7cAdylA8HfetBpbQ98w==","shasum":"fb5c4143b17f15c20bb2cece818088d02374d8b9","tarball":"https://registry.npmjs.org/demucs-web/-/demucs-web-1.0.2.tgz","fileCount":7,"unpackedSize":31003,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD00yVMt3PvxmnSUilsFIFVDkPjoM3Ndc9s8qjln1682AIgFVx2n//aq8oQ2m+JTdwMle6JPYXnWQjyd7X5T+RSL1s="}]},"_npmUser":{"name":"timcsy","email":"timocsy@yahoo.com.tw"},"directories":{},"maintainers":[{"name":"timcsy","email":"timocsy@yahoo.com.tw"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/demucs-web_1.0.2_1764584719077_0.030679322542876974"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-30T16:15:29.089Z","modified":"2025-12-01T10:25:19.480Z","1.0.0":"2025-11-30T16:15:29.294Z","1.0.1":"2025-12-01T04:55:23.978Z","1.0.2":"2025-12-01T10:25:19.322Z"},"bugs":{"url":"https://github.com/timcsy/demucs-web/issues"},"author":{"name":"timcsy"},"license":"MIT","homepage":"https://github.com/timcsy/demucs-web","keywords":["demucs","music","audio","source-separation","stem-separation","onnx","webgpu","wasm","browser"],"repository":{"type":"git","url":"git+https://github.com/timcsy/demucs-web.git"},"description":"Music source separation (Demucs) running in browser with ONNX Runtime Web","maintainers":[{"name":"timcsy","email":"timocsy@yahoo.com.tw"}],"readme":"# Demucs Web\n\n在瀏覽器中運行的音樂源分離 AI，使用 ONNX Runtime Web 實現 WebGPU/WASM 加速推論。\n\n## 功能\n\n- 純前端運行，無需後端伺服器\n- 支援 WebGPU 加速（macOS Safari、Chrome 等）\n- 支援 4 軌道分離：drums、bass、other、vocals\n- 基於 Meta 的 HTDemucs 模型\n\n## 安裝\n\n```bash\nnpm install demucs-web\n```\n\n## 使用方式\n\n```javascript\nimport * as ort from 'onnxruntime-web';\nimport { DemucsProcessor } from 'demucs-web';\n\n// 初始化處理器\nconst processor = new DemucsProcessor({\n  ort,\n  onProgress: (progress) => console.log(`Progress: ${(progress * 100).toFixed(1)}%`),\n  onLog: (phase, msg) => console.log(`[${phase}] ${msg}`)\n});\n\n// 載入模型（約 172MB）\nawait processor.loadModel('./htdemucs_embedded.onnx');\n\n// 分離音軌（輸入為 44100Hz 立體聲）\nconst result = await processor.separate(leftChannel, rightChannel);\n\n// 結果包含四個軌道\nconsole.log(result.drums);   // { left: Float32Array, right: Float32Array }\nconsole.log(result.bass);\nconsole.log(result.other);\nconsole.log(result.vocals);\n```\n\n## Demo\n\n```bash\n# 啟動開發伺服器\ncd demo\npython3 -m http.server 8080\n\n# 需要 COOP/COEP headers 支援 SharedArrayBuffer\n# 可使用以下 server.py：\npython3 server.py\n```\n\n## 重要注意事項\n\n### Cross-Origin Isolation\n\nONNX Runtime Web 需要 SharedArrayBuffer，必須設定以下 HTTP headers：\n\n```\nCross-Origin-Opener-Policy: same-origin\nCross-Origin-Embedder-Policy: require-corp\n```\n\n### 模型檔案\n\nONNX 模型（約 172MB）託管於 Hugging Face Hub：\n\n**下載連結**：https://huggingface.co/timcsy/demucs-web-onnx/resolve/main/htdemucs_embedded.onnx\n\n```javascript\n// 方式 1：使用預設 URL（從 Hugging Face 下載）\nimport { CONSTANTS } from 'demucs-web';\nawait processor.loadModel(CONSTANTS.DEFAULT_MODEL_URL);\n\n// 方式 2：使用本地檔案\nawait processor.loadModel('./htdemucs_embedded.onnx');\n\n// 方式 3：傳入 ArrayBuffer\nconst response = await fetch(modelUrl);\nconst buffer = await response.arrayBuffer();\nawait processor.loadModel(buffer);\n```\n\n**自行託管模型**：\n```bash\n# 安裝 huggingface_hub\nuv tool install huggingface_hub\nhf auth login\n\n# 上傳到你的 HF 帳號\n./scripts/upload-model.sh <your-username>\n```\n\n## API\n\n### DemucsProcessor\n\n```typescript\ninterface DemucsProcessorOptions {\n  ort: typeof import('onnxruntime-web');\n  modelPath?: string;\n  sessionOptions?: ort.InferenceSession.SessionOptions;\n  onProgress?: (info: ProgressInfo) => void;\n  onLog?: (phase: string, message: string) => void;\n  onDownloadProgress?: (loaded: number, total: number) => void;\n}\n\ninterface ProgressInfo {\n  progress: number;        // 0-1 之間的進度值\n  currentSegment: number;  // 目前處理的區段編號\n  totalSegments: number;   // 總區段數量\n}\n\nclass DemucsProcessor {\n  constructor(options: DemucsProcessorOptions);\n  loadModel(pathOrBuffer?: string | ArrayBuffer): Promise<void>;\n  separate(left: Float32Array, right: Float32Array): Promise<SeparationResult>;\n}\n\ninterface SeparationResult {\n  drums: { left: Float32Array; right: Float32Array };\n  bass: { left: Float32Array; right: Float32Array };\n  other: { left: Float32Array; right: Float32Array };\n  vocals: { left: Float32Array; right: Float32Array };\n}\n```\n\n### 回呼函數詳細說明\n\n#### onProgress - 處理進度回呼\n\n在音訊分離過程中，每完成一個區段會觸發此回呼：\n\n```javascript\nconst processor = new DemucsProcessor({\n  ort,\n  onProgress: ({ progress, currentSegment, totalSegments }) => {\n    // progress: 0-1 之間的數值\n    const percent = (progress * 100).toFixed(1);\n    console.log(`進度: ${percent}%`);\n    console.log(`區段: ${currentSegment}/${totalSegments}`);\n\n    // 更新進度條\n    progressBar.style.width = `${progress * 100}%`;\n\n    // 計算處理速度與預估剩餘時間\n    if (currentSegment > 0) {\n      const elapsed = (Date.now() - startTime) / 1000;\n      const processedDuration = (currentSegment / totalSegments) * audioDuration;\n      const speed = processedDuration / elapsed;\n      console.log(`處理速度: ${speed.toFixed(2)}x 即時`);\n\n      const remainingSegments = totalSegments - currentSegment;\n      const avgTimePerSegment = elapsed / currentSegment;\n      const eta = remainingSegments * avgTimePerSegment;\n      console.log(`預估剩餘時間: ${eta.toFixed(0)}秒`);\n    }\n  }\n});\n```\n\n#### onLog - 處理階段日誌回呼\n\n在各個處理階段會觸發此回呼，用於顯示詳細日誌：\n\n```javascript\nconst processor = new DemucsProcessor({\n  ort,\n  onLog: (phase, message) => {\n    const timeStr = new Date().toLocaleTimeString();\n    console.log(`[${timeStr}][${phase}] ${message}`);\n\n    // 常見的 phase 值：\n    // - 'Init': 初始化\n    // - 'Segment': 區段處理\n    // - 'Inference': 模型推論\n    // - 'PostProcess': 後處理\n  }\n});\n```\n\n#### onDownloadProgress - 模型下載進度回呼\n\n在下載模型時觸發，用於顯示下載進度：\n\n```javascript\nconst processor = new DemucsProcessor({\n  ort,\n  onDownloadProgress: (loaded, total) => {\n    const percent = ((loaded / total) * 100).toFixed(1);\n    const loadedMB = (loaded / 1024 / 1024).toFixed(1);\n    const totalMB = (total / 1024 / 1024).toFixed(1);\n    console.log(`下載模型: ${loadedMB}MB / ${totalMB}MB (${percent}%)`);\n\n    // 更新下載進度條\n    downloadBar.style.width = `${(loaded / total) * 100}%`;\n  }\n});\n```\n\n#### sessionOptions - ONNX Runtime Session 設定\n\n可傳入 ONNX Runtime 的 `InferenceSession.SessionOptions` 來自訂模型載入行為。這對於在行動裝置或記憶體受限的環境中降低記憶體使用量特別有用。\n\n```javascript\nconst processor = new DemucsProcessor({\n  ort,\n  sessionOptions: {\n    // 停用 CPU 記憶體競技場，減少記憶體預分配\n    enableCpuMemArena: false,\n    // 停用記憶體模式優化，減少記憶體使用\n    enableMemPattern: false,\n    // 覆蓋預設的執行提供者（預設為 ['webgpu', 'wasm']）\n    // executionProviders: ['wasm'],\n    // 覆蓋圖形優化等級（預設為 'basic'）\n    // graphOptimizationLevel: 'disabled',\n  }\n});\n```\n\n**常用的 sessionOptions：**\n\n| 選項 | 類型 | 預設值 | 說明 |\n|------|------|--------|------|\n| `enableCpuMemArena` | boolean | true | CPU 記憶體競技場。設為 false 可減少記憶體使用 |\n| `enableMemPattern` | boolean | true | 記憶體模式優化。設為 false 可減少記憶體峰值 |\n| `executionProviders` | string[] | ['webgpu', 'wasm'] | 執行提供者優先順序 |\n| `graphOptimizationLevel` | string | 'basic' | 圖形優化等級：'disabled', 'basic', 'extended', 'all' |\n\n**行動裝置建議設定：**\n\n```javascript\n// 針對 iOS/Android 行動裝置的低記憶體設定\nconst processor = new DemucsProcessor({\n  ort,\n  sessionOptions: {\n    enableCpuMemArena: false,\n    enableMemPattern: false,\n  }\n});\n```\n\n### 完整範例\n\n```javascript\nimport * as ort from 'onnxruntime-web';\nimport { DemucsProcessor, CONSTANTS } from 'demucs-web';\n\n// 配置 ONNX Runtime\nort.env.wasm.numThreads = navigator.hardwareConcurrency || 4;\n\n// 檢測 WebGPU 支援\nlet backend = 'wasm';\nif ('gpu' in navigator) {\n  try {\n    const adapter = await navigator.gpu.requestAdapter();\n    if (adapter) {\n      backend = 'webgpu';\n      ort.env.webgpu = { powerPreference: 'high-performance' };\n    }\n  } catch (e) {\n    console.log('WebGPU 不可用，使用 WASM');\n  }\n}\n\n// 建立處理器\nlet startTime;\nconst processor = new DemucsProcessor({\n  ort,\n  onProgress: ({ progress, currentSegment, totalSegments }) => {\n    console.log(`處理中: ${(progress * 100).toFixed(1)}% (${currentSegment}/${totalSegments})`);\n  },\n  onLog: (phase, msg) => {\n    console.log(`[${phase}] ${msg}`);\n  },\n  onDownloadProgress: (loaded, total) => {\n    console.log(`下載: ${(loaded / total * 100).toFixed(1)}%`);\n  }\n});\n\n// 載入模型\nawait processor.loadModel(CONSTANTS.DEFAULT_MODEL_URL);\n\n// 載入音訊（使用 Web Audio API）\nconst audioContext = new AudioContext({ sampleRate: CONSTANTS.SAMPLE_RATE });\nconst response = await fetch('your-audio-file.mp3');\nconst arrayBuffer = await response.arrayBuffer();\nconst audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n\n// 取得聲道資料\nconst leftChannel = audioBuffer.getChannelData(0);\nconst rightChannel = audioBuffer.numberOfChannels > 1\n  ? audioBuffer.getChannelData(1)\n  : leftChannel;  // 單聲道時複製左聲道\n\n// 開始分離\nstartTime = Date.now();\nconst result = await processor.separate(leftChannel, rightChannel);\n\n// 處理結果\nconst totalTime = ((Date.now() - startTime) / 1000).toFixed(1);\nconsole.log(`處理完成，耗時 ${totalTime} 秒`);\n\n// result.drums, result.bass, result.other, result.vocals\n// 每個軌道都有 { left: Float32Array, right: Float32Array }\n```\n\n### 常數\n\n```javascript\nimport { CONSTANTS } from 'demucs-web';\n\nCONSTANTS.SAMPLE_RATE       // 44100\nCONSTANTS.FFT_SIZE          // 4096\nCONSTANTS.HOP_SIZE          // 1024\nCONSTANTS.TRAINING_SAMPLES  // 343980\nCONSTANTS.TRACKS            // ['drums', 'bass', 'other', 'vocals']\n```\n\n### FFT 工具\n\n```javascript\nimport { fft, ifft, stft, istft, reflectPad } from 'demucs-web';\n\n// Cooley-Tukey radix-2 FFT\nfft(realOut, imagOut, realIn, n);\nifft(realOut, imagOut, realIn, imagIn, n);\n\n// Short-Time Fourier Transform\nconst spec = stft(signal, fftSize, hopSize);\nconst reconstructed = istft(spec.real, spec.imag, numFrames, numBins, fftSize, hopSize);\n```\n\n## 技術細節\n\n詳細的移植經驗請參考 [EXPERIENCE_REPORT.md](./EXPERIENCE_REPORT.md)\n\n## License\n\nMIT\n","readmeFilename":"README.md"}