refactor: move all localSeparator*.ts into a subfolder

This commit is contained in:
Xiaohan-Tian
2026-05-27 17:32:13 -07:00
parent 7340f7f8f6
commit e2a7d1c285
14 changed files with 52 additions and 52 deletions
+3 -3
View File
@@ -81,7 +81,7 @@ vi.mock('../util/audioUtil', () => ({
sliceAudioToWav: vi.fn(async (_buffer: ArrayBuffer) => _buffer), sliceAudioToWav: vi.fn(async (_buffer: ArrayBuffer) => _buffer),
})); }));
vi.mock('../util/localSeparatorModelCache', () => ({ vi.mock('../util/local-separator/modelCache', () => ({
LocalSeparatorModelCache: { LocalSeparatorModelCache: {
exists: vi.fn(async () => localModelCached), exists: vi.fn(async () => localModelCached),
download: vi.fn(async (url: string, filename: string, onProgress: (progress: unknown) => void) => { download: vi.fn(async (url: string, filename: string, onProgress: (progress: unknown) => void) => {
@@ -95,7 +95,7 @@ vi.mock('../util/localSeparatorModelCache', () => ({
}, },
})); }));
vi.mock('../util/localSeparatorRuntime', () => ({ vi.mock('../util/local-separator/runtime', () => ({
detectLocalRuntimeSupport: () => ({ webgpuExposed: false }), detectLocalRuntimeSupport: () => ({ webgpuExposed: false }),
LocalOrtRuntimeManager: class { LocalOrtRuntimeManager: class {
constructor(private readonly options?: { onProviderChange?: (provider: string) => void }) {} constructor(private readonly options?: { onProviderChange?: (provider: string) => void }) {}
@@ -109,7 +109,7 @@ vi.mock('../util/localSeparatorRuntime', () => ({
}, },
})); }));
vi.mock('../util/localSeparatorRunner', () => ({ vi.mock('../util/local-separator/runner', () => ({
runLocalSeparator: vi.fn(async ({ onProgress, onProviderChange }) => { runLocalSeparator: vi.fn(async ({ onProgress, onProviderChange }) => {
onProviderChange?.('cpu/wasm'); onProviderChange?.('cpu/wasm');
onProgress({ stage: 'main', passLabel: 'Main pass', percent: 100, processedChunks: 1, totalChunks: 1 }); onProgress({ stage: 'main', passLabel: 'Main pass', percent: 100, processedChunks: 1, totalChunks: 1 });
+4 -4
View File
@@ -19,10 +19,10 @@ import {
LOCAL_SEPARATOR_MODEL_CONFIG, LOCAL_SEPARATOR_MODEL_CONFIG,
LOCAL_SEPARATOR_MODEL_FILENAME, LOCAL_SEPARATOR_MODEL_FILENAME,
LOCAL_SEPARATOR_DEFAULT_MODEL_URL, LOCAL_SEPARATOR_DEFAULT_MODEL_URL,
} from '../util/localSeparatorConfig'; } from '../util/local-separator/config';
import { LocalSeparatorModelCache } from '../util/localSeparatorModelCache'; import { LocalSeparatorModelCache } from '../util/local-separator/modelCache';
import { runLocalSeparator } from '../util/localSeparatorRunner'; import { runLocalSeparator } from '../util/local-separator/runner';
import { LocalOrtRuntimeManager, detectLocalRuntimeSupport } from '../util/localSeparatorRuntime'; import { LocalOrtRuntimeManager, detectLocalRuntimeSupport } from '../util/local-separator/runtime';
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
@@ -80,7 +80,7 @@ vi.mock('../../../util/localLLMModelManager', () => ({
}, },
})); }));
vi.mock('../../../util/localSeparatorModelCache', () => ({ vi.mock('../../../util/local-separator/modelCache', () => ({
LocalSeparatorModelCache: localSeparatorModelCacheMock, LocalSeparatorModelCache: localSeparatorModelCacheMock,
})); }));
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { ConfigManager } from '../../../core/config/ConfigManager'; import { ConfigManager } from '../../../core/config/ConfigManager';
import { LocalLLMModelManager, type LocalLLMModelState } from '../../../util/localLLMModelManager'; import { LocalLLMModelManager, type LocalLLMModelState } from '../../../util/localLLMModelManager';
import { LocalSeparatorModelCache } from '../../../util/localSeparatorModelCache'; import { LocalSeparatorModelCache } from '../../../util/local-separator/modelCache';
import { import {
formatLocalLLMContextLength, formatLocalLLMContextLength,
LOCAL_LLM_CONTEXT_LENGTH_OPTIONS, LOCAL_LLM_CONTEXT_LENGTH_OPTIONS,
@@ -12,7 +12,7 @@ import {
normalizeLocalLLMContextLength, normalizeLocalLLMContextLength,
type LocalLLMContextLength, type LocalLLMContextLength,
} from '../../../util/localLLMConfig'; } from '../../../util/localLLMConfig';
import { LOCAL_SEPARATOR_DEFAULT_MODEL_URL } from '../../../util/localSeparatorConfig'; import { LOCAL_SEPARATOR_DEFAULT_MODEL_URL } from '../../../util/local-separator/config';
const GeneralSettings: React.FC = () => { const GeneralSettings: React.FC = () => {
const [llmProvider, setLlmProvider] = useState<string>(LOCAL_LLM_PROVIDER_KEY); const [llmProvider, setLlmProvider] = useState<string>(LOCAL_LLM_PROVIDER_KEY);
+2 -2
View File
@@ -1,9 +1,9 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
import { LocalSeparatorModelCache } from '../../util/localSeparatorModelCache'; import { LocalSeparatorModelCache } from '../../util/local-separator/modelCache';
import { import {
LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES, LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES,
LOCAL_SEPARATOR_MODEL_FILENAME, LOCAL_SEPARATOR_MODEL_FILENAME,
} from '../../util/localSeparatorConfig'; } from '../../util/local-separator/config';
class MockWritableFileStream { class MockWritableFileStream {
private readonly handle: MockFileSystemFileHandle; private readonly handle: MockFileSystemFileHandle;
@@ -1,4 +1,4 @@
import type { LocalSeparatorModelConfig } from './localSeparatorTypes'; import type { LocalSeparatorModelConfig } from './types';
export const LOCAL_SEPARATOR_DEFAULT_MODEL_URL = export const LOCAL_SEPARATOR_DEFAULT_MODEL_URL =
'https://huggingface.co/notabilia/uvr5-models/resolve/main/UVR-MDX-NET-Inst_HQ_3.onnx'; 'https://huggingface.co/notabilia/uvr5-models/resolve/main/UVR-MDX-NET-Inst_HQ_3.onnx';
@@ -1,5 +1,5 @@
import type { LocalSeparatorModelConfig, StereoChannels } from './localSeparatorTypes'; import type { LocalSeparatorModelConfig, StereoChannels } from './types';
import { FFT, createWindowCache, getHannPeriodic, index4d, reflectPad } from './localSeparatorShared'; import { FFT, createWindowCache, getHannPeriodic, index4d, reflectPad } from './shared';
interface SpectrogramPayload { interface SpectrogramPayload {
data: Float32Array; data: Float32Array;
@@ -1,8 +1,8 @@
import type { LocalSeparatorModelConfig } from './localSeparatorTypes'; import type { LocalSeparatorModelConfig } from './types';
import { LocalSeparatorCpuDsp } from './localSeparatorCpuDsp'; import { LocalSeparatorCpuDsp } from './cpuDsp';
import { reflectPad } from './localSeparatorShared'; import { reflectPad } from './shared';
function localSeparatorLog(message: string, payload?: unknown): void { function log(message: string, payload?: unknown): void {
if (payload === undefined) { if (payload === undefined) {
console.log(`[localSeparator] ${message}`); console.log(`[localSeparator] ${message}`);
return; return;
@@ -145,22 +145,22 @@ export class LocalSeparatorGpuDsp {
throw new Error('WebGPU is not available for GPU DSP.'); throw new Error('WebGPU is not available for GPU DSP.');
} }
localSeparatorLog('Requesting WebGPU adapter for GPU DSP.'); log('Requesting WebGPU adapter for GPU DSP.');
const adapter = await (navigator as NavigatorWithGpu).gpu?.requestAdapter({ const adapter = await (navigator as NavigatorWithGpu).gpu?.requestAdapter({
powerPreference: 'high-performance', powerPreference: 'high-performance',
}); });
if (!adapter) { if (!adapter) {
throw new Error('No WebGPU adapter was available for GPU DSP.'); throw new Error('No WebGPU adapter was available for GPU DSP.');
} }
localSeparatorLog('WebGPU adapter acquired for GPU DSP.', { log('WebGPU adapter acquired for GPU DSP.', {
features: typeof adapter.features?.values === 'function' ? Array.from(adapter.features.values()) : undefined, features: typeof adapter.features?.values === 'function' ? Array.from(adapter.features.values()) : undefined,
limits: adapter.limits, limits: adapter.limits,
info: typeof adapter.info === 'object' ? adapter.info : undefined, info: typeof adapter.info === 'object' ? adapter.info : undefined,
}); });
localSeparatorLog('Requesting WebGPU device for GPU DSP.'); log('Requesting WebGPU device for GPU DSP.');
const device = await adapter.requestDevice(); const device = await adapter.requestDevice();
localSeparatorLog('WebGPU device acquired for GPU DSP.'); log('WebGPU device acquired for GPU DSP.');
return new LocalSeparatorGpuDsp(config, device); return new LocalSeparatorGpuDsp(config, device);
} }
@@ -170,7 +170,7 @@ export class LocalSeparatorGpuDsp {
this.nFft = config.metadata.mdx_n_fft_scale_set; this.nFft = config.metadata.mdx_n_fft_scale_set;
this.hopLength = config.defaults.hopLength; this.hopLength = config.defaults.hopLength;
this.trim = Math.floor(this.nFft / 2); this.trim = Math.floor(this.nFft / 2);
localSeparatorLog('Creating GPU DSP compute pipeline.'); log('Creating GPU DSP compute pipeline.');
this.pipeline = device.createComputePipeline({ this.pipeline = device.createComputePipeline({
layout: 'auto', layout: 'auto',
compute: { compute: {
@@ -178,7 +178,7 @@ export class LocalSeparatorGpuDsp {
entryPoint: 'main', entryPoint: 'main',
}, },
}); });
localSeparatorLog('GPU DSP compute pipeline created.'); log('GPU DSP compute pipeline created.');
} }
public async forwardStereo(leftChunk: Float32Array, rightChunk: Float32Array): Promise<{ public async forwardStereo(leftChunk: Float32Array, rightChunk: Float32Array): Promise<{
@@ -1,8 +1,8 @@
import { import {
LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES, LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES,
LOCAL_SEPARATOR_MODEL_FILENAME, LOCAL_SEPARATOR_MODEL_FILENAME,
} from './localSeparatorConfig'; } from './config';
import { OpfsModelCache, type ModelDownloadProgress } from './opfsModelCache'; import { OpfsModelCache, type ModelDownloadProgress } from '../opfsModelCache';
const cache = new OpfsModelCache({ directoryName: 'models' }); const cache = new OpfsModelCache({ directoryName: 'models' });
@@ -1,13 +1,13 @@
import * as ort from 'onnxruntime-web/webgpu'; import * as ort from 'onnxruntime-web/webgpu';
import { LocalSeparatorCpuDsp } from './localSeparatorCpuDsp'; import { LocalSeparatorCpuDsp } from './cpuDsp';
import { LocalSeparatorGpuDsp } from './localSeparatorGpuDsp'; import { LocalSeparatorGpuDsp } from './gpuDsp';
import { LocalSeparatorTimingCollector } from './localSeparatorTiming'; import { LocalSeparatorTimingCollector } from './timing';
import type { import type {
LocalRuntimeProvider, LocalRuntimeProvider,
LocalSeparatorModelConfig, LocalSeparatorModelConfig,
LocalSeparatorProgress, LocalSeparatorProgress,
StereoChannels, StereoChannels,
} from './localSeparatorTypes'; } from './types';
import { import {
concatFloat32, concatFloat32,
createWindowCache, createWindowCache,
@@ -16,11 +16,11 @@ import {
normalizeChannels, normalizeChannels,
scaleChannels, scaleChannels,
sliceChannels, sliceChannels,
} from './localSeparatorShared'; } from './shared';
const SAMPLE_RATE = 44100; const SAMPLE_RATE = 44100;
function localSeparatorLog(message: string, payload?: unknown): void { function log(message: string, payload?: unknown): void {
if (payload === undefined) { if (payload === undefined) {
console.log(`[localSeparator] ${message}`); console.log(`[localSeparator] ${message}`);
return; return;
@@ -108,11 +108,11 @@ class BrowserMdxSeparator {
try { try {
dsp = await timing.measureAsync('dspInit', () => LocalSeparatorGpuDsp.create(config)); dsp = await timing.measureAsync('dspInit', () => LocalSeparatorGpuDsp.create(config));
dspMode = 'gpu-hybrid'; dspMode = 'gpu-hybrid';
localSeparatorLog('GPU DSP initialized successfully.'); log('GPU DSP initialized successfully.');
} catch (error) { } catch (error) {
console.warn('[localSeparator] GPU DSP initialization failed, using CPU DSP.', error); console.warn('[localSeparator] GPU DSP initialization failed, using CPU DSP.', error);
options.onProviderChange?.('webgpu + cpu dsp fallback'); options.onProviderChange?.('webgpu + cpu dsp fallback');
localSeparatorLog( log(
'GPU DSP initialization failed. Inference session may still use WebGPU, but DSP will fall back to CPU.', 'GPU DSP initialization failed. Inference session may still use WebGPU, but DSP will fall back to CPU.',
error, error,
); );
@@ -122,9 +122,9 @@ class BrowserMdxSeparator {
if (!dsp) { if (!dsp) {
dsp = new LocalSeparatorCpuDsp(config); dsp = new LocalSeparatorCpuDsp(config);
if (runtimeProvider === 'webgpu') { if (runtimeProvider === 'webgpu') {
localSeparatorLog('Using CPU DSP while keeping the WebGPU inference provider.'); log('Using CPU DSP while keeping the WebGPU inference provider.');
} else { } else {
localSeparatorLog('Using CPU DSP because the active inference provider is CPU/wasm.'); log('Using CPU DSP because the active inference provider is CPU/wasm.');
} }
} }
@@ -376,7 +376,7 @@ class BrowserMdxSeparator {
} catch (error) { } catch (error) {
if (payloads.length > 1 && this.runtimeBatchSize > 1) { if (payloads.length > 1 && this.runtimeBatchSize > 1) {
console.warn('[localSeparator] Batched inference failed, falling back to batch size 1.', error); console.warn('[localSeparator] Batched inference failed, falling back to batch size 1.', error);
localSeparatorLog('Batched inference failed. Falling back to batch size 1.', error); log('Batched inference failed. Falling back to batch size 1.', error);
this.runtimeBatchSize = 1; this.runtimeBatchSize = 1;
const singleResults: SpectrogramPayload[] = []; const singleResults: SpectrogramPayload[] = [];
for (const payload of payloads) { for (const payload of payloads) {
@@ -486,7 +486,7 @@ export async function runLocalSeparator(options: {
}> { }> {
const timing = new LocalSeparatorTimingCollector('local-separation'); const timing = new LocalSeparatorTimingCollector('local-separation');
const decoded = await timing.measureAsync('decode', () => decodeAudioToStereo(options.audioBuffer)); const decoded = await timing.measureAsync('decode', () => decodeAudioToStereo(options.audioBuffer));
localSeparatorLog(`Running browser MDX separation on ${options.runtimeProvider === 'webgpu' ? 'GPU/WebGPU' : 'CPU/wasm'}...`); log(`Running browser MDX separation on ${options.runtimeProvider === 'webgpu' ? 'GPU/WebGPU' : 'CPU/wasm'}...`);
const separator = await BrowserMdxSeparator.create( const separator = await BrowserMdxSeparator.create(
options.session, options.session,
@@ -522,7 +522,7 @@ export async function runLocalSeparator(options: {
debugSummary: separator.getDebugSummary({ model: options.modelConfig.filename }), debugSummary: separator.getDebugSummary({ model: options.modelConfig.filename }),
}; };
} finally { } finally {
localSeparatorLog('Separation timing summary', separator.getDebugSummary({ model: options.modelConfig.filename })); log('Separation timing summary', separator.getDebugSummary({ model: options.modelConfig.filename }));
separator.dispose(); separator.dispose();
} }
} }
@@ -1,8 +1,8 @@
import * as ort from 'onnxruntime-web/webgpu'; import * as ort from 'onnxruntime-web/webgpu';
import ortWasmAsyncifyUrl from 'onnxruntime-web/ort-wasm-simd-threaded.asyncify.wasm?url'; import ortWasmAsyncifyUrl from 'onnxruntime-web/ort-wasm-simd-threaded.asyncify.wasm?url';
import type { LocalRuntimeState, LocalRuntimeSupport, LocalSeparatorModelConfig } from './localSeparatorTypes'; import type { LocalRuntimeState, LocalRuntimeSupport, LocalSeparatorModelConfig } from './types';
function localSeparatorLog(message: string, payload?: unknown): void { function log(message: string, payload?: unknown): void {
if (payload === undefined) { if (payload === undefined) {
console.log(`[localSeparator] ${message}`); console.log(`[localSeparator] ${message}`);
return; return;
@@ -15,9 +15,9 @@ export function detectLocalRuntimeSupport(): LocalRuntimeSupport {
webgpuExposed: typeof navigator !== 'undefined' && 'gpu' in navigator, webgpuExposed: typeof navigator !== 'undefined' && 'gpu' in navigator,
}; };
if (support.webgpuExposed) { if (support.webgpuExposed) {
localSeparatorLog('WebGPU API is exposed by this browser.'); log('WebGPU API is exposed by this browser.');
} else { } else {
localSeparatorLog('WebGPU API is not exposed by this browser. CPU/wasm will be used.'); log('WebGPU API is not exposed by this browser. CPU/wasm will be used.');
} }
return support; return support;
} }
@@ -46,16 +46,16 @@ export class LocalOrtRuntimeManager {
ort.env.wasm.wasmPaths = { ort.env.wasm.wasmPaths = {
wasm: ortWasmAsyncifyUrl, wasm: ortWasmAsyncifyUrl,
}; };
localSeparatorLog('Configured ONNX Runtime wasm paths.', ort.env.wasm.wasmPaths); log('Configured ONNX Runtime wasm paths.', ort.env.wasm.wasmPaths);
LocalOrtRuntimeManager.wasmPathsConfigured = true; LocalOrtRuntimeManager.wasmPathsConfigured = true;
} }
const providersToTry: Array<'webgpu' | 'wasm'> = []; const providersToTry: Array<'webgpu' | 'wasm'> = [];
if (typeof navigator !== 'undefined' && 'gpu' in navigator) { if (typeof navigator !== 'undefined' && 'gpu' in navigator) {
providersToTry.push('webgpu'); providersToTry.push('webgpu');
localSeparatorLog('navigator.gpu is available, trying WebGPU first.'); log('navigator.gpu is available, trying WebGPU first.');
} else { } else {
localSeparatorLog('navigator.gpu is not available. Falling back to CPU/wasm.'); log('navigator.gpu is not available. Falling back to CPU/wasm.');
} }
providersToTry.push('wasm'); providersToTry.push('wasm');
@@ -64,7 +64,7 @@ export class LocalOrtRuntimeManager {
try { try {
if (provider === 'webgpu' && ort.env?.webgpu) { if (provider === 'webgpu' && ort.env?.webgpu) {
ort.env.webgpu.powerPreference = 'high-performance'; ort.env.webgpu.powerPreference = 'high-performance';
localSeparatorLog('Using WebGPU power preference high-performance.'); log('Using WebGPU power preference high-performance.');
} }
const session = await ort.InferenceSession.create(modelData, { const session = await ort.InferenceSession.create(modelData, {
@@ -75,15 +75,15 @@ export class LocalOrtRuntimeManager {
this.runtime = { provider, session }; this.runtime = { provider, session };
this.currentModel = modelConfig.filename; this.currentModel = modelConfig.filename;
this.onProviderChange(provider === 'wasm' ? 'cpu/wasm' : provider); this.onProviderChange(provider === 'wasm' ? 'cpu/wasm' : provider);
localSeparatorLog(`Using provider: ${provider}`); log(`Using provider: ${provider}`);
return this.runtime; return this.runtime;
} catch (error) { } catch (error) {
lastError = error; lastError = error;
if (provider === 'webgpu') { if (provider === 'webgpu') {
localSeparatorLog('WebGPU session creation failed. Falling back to CPU/wasm.', error); log('WebGPU session creation failed. Falling back to CPU/wasm.', error);
this.onProviderChange('cpu/wasm fallback'); this.onProviderChange('cpu/wasm fallback');
} else { } else {
localSeparatorLog(`Provider failed: ${provider}`, error); log(`Provider failed: ${provider}`, error);
} }
} }
} }
@@ -1,4 +1,4 @@
import type { StereoChannels } from './localSeparatorTypes'; import type { StereoChannels } from './types';
export function index4d(dims: number[], i0: number, i1: number, i2: number, i3: number): number { export function index4d(dims: number[], i0: number, i1: number, i2: number, i3: number): number {
return (((i0 * dims[1] + i1) * dims[2] + i2) * dims[3]) + i3; return (((i0 * dims[1] + i1) * dims[2] + i2) * dims[3]) + i3;