UNPKG

murmuraba

Version:

Real-time audio noise reduction with advanced chunked processing for web applications

200 lines (199 loc) 6.83 kB
import { vi, beforeEach, afterEach } from 'vitest'; export function createMockGainNode(initialValue = 1) { return { gain: { value: initialValue, setValueAtTime: vi.fn().mockReturnThis(), linearRampToValueAtTime: vi.fn().mockReturnThis(), exponentialRampToValueAtTime: vi.fn().mockReturnThis(), setTargetAtTime: vi.fn().mockReturnThis(), setValueCurveAtTime: vi.fn().mockReturnThis(), cancelScheduledValues: vi.fn().mockReturnThis(), cancelAndHoldAtTime: vi.fn().mockReturnThis(), }, connect: vi.fn().mockReturnThis(), disconnect: vi.fn(), numberOfInputs: 1, numberOfOutputs: 1, channelCount: 2, channelCountMode: 'max', channelInterpretation: 'speakers', }; } export function createMockAnalyserNode() { return { fftSize: 2048, frequencyBinCount: 1024, minDecibels: -100, maxDecibels: -30, smoothingTimeConstant: 0.8, getByteFrequencyData: vi.fn((array) => { for (let i = 0; i < array.length; i++) { array[i] = Math.floor(Math.random() * 128) + 64; } }), getByteTimeDomainData: vi.fn((array) => { for (let i = 0; i < array.length; i++) { array[i] = 128 + Math.floor(Math.sin(i * 0.1) * 64); } }), getFloatFrequencyData: vi.fn(), getFloatTimeDomainData: vi.fn(), connect: vi.fn().mockReturnThis(), disconnect: vi.fn(), }; } export function createMockScriptProcessor(bufferSize = 4096, numberOfInputChannels = 1, numberOfOutputChannels = 1) { return { bufferSize, numberOfInputs: 1, numberOfOutputs: 1, onaudioprocess: null, connect: vi.fn().mockReturnThis(), disconnect: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), }; } export function createMockBiquadFilter() { return { type: 'lowpass', frequency: { value: 350, setValueAtTime: vi.fn() }, Q: { value: 1, setValueAtTime: vi.fn() }, gain: { value: 0, setValueAtTime: vi.fn() }, detune: { value: 0, setValueAtTime: vi.fn() }, connect: vi.fn().mockReturnThis(), disconnect: vi.fn(), getFrequencyResponse: vi.fn(), }; } export function createMockMediaStreamSource() { return { connect: vi.fn().mockReturnThis(), disconnect: vi.fn(), mediaStream: null, numberOfInputs: 0, numberOfOutputs: 1, }; } export function createMockMediaStreamDestination() { return { stream: { id: 'mock-output-stream', active: true, getTracks: vi.fn().mockReturnValue([]), getAudioTracks: vi.fn().mockReturnValue([{ kind: 'audio' }]), getVideoTracks: vi.fn().mockReturnValue([]), addEventListener: vi.fn(), removeEventListener: vi.fn(), addTrack: vi.fn(), removeTrack: vi.fn(), clone: vi.fn(), getTrackById: vi.fn(), }, numberOfInputs: 1, numberOfOutputs: 0, }; } export function createMockAudioBuffer(numberOfChannels = 2, length = 48000, sampleRate = 48000) { return { numberOfChannels, length, sampleRate, duration: length / sampleRate, getChannelData: vi.fn(() => new Float32Array(length)), copyFromChannel: vi.fn(), copyToChannel: vi.fn(), }; } export function createMockAudioContext(options = {}) { const { state = 'running', sampleRate = 48000, currentTime = 0, baseLatency = 0.01, outputLatency = 0.02, includeWorklet = false, includeAnalyser = false, includeBiquadFilter = false, includeMediaStreamDestination = false, } = options; const context = { state, sampleRate, currentTime, baseLatency, outputLatency, destination: { maxChannelCount: 2, numberOfInputs: 1, numberOfOutputs: 0, channelCount: 2, channelCountMode: 'max', channelInterpretation: 'speakers', }, listener: { positionX: { value: 0 }, positionY: { value: 0 }, positionZ: { value: 0 }, forwardX: { value: 0 }, forwardY: { value: 0 }, forwardZ: { value: -1 }, upX: { value: 0 }, upY: { value: 1 }, upZ: { value: 0 }, }, createGain: vi.fn(() => createMockGainNode()), createScriptProcessor: vi.fn((buffer, input, output) => createMockScriptProcessor(buffer, input, output)), createMediaStreamSource: vi.fn(() => createMockMediaStreamSource()), createBuffer: vi.fn((channels, length, rate) => createMockAudioBuffer(channels, length, rate)), decodeAudioData: vi.fn().mockImplementation(() => Promise.resolve(createMockAudioBuffer())), close: vi.fn().mockResolvedValue(undefined), suspend: vi.fn().mockResolvedValue(undefined), resume: vi.fn().mockResolvedValue(undefined), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), }; if (includeWorklet) { context.audioWorklet = { addModule: vi.fn().mockResolvedValue(undefined), }; } if (includeAnalyser) { context.createAnalyser = vi.fn(() => createMockAnalyserNode()); } if (includeBiquadFilter) { context.createBiquadFilter = vi.fn(() => createMockBiquadFilter()); } if (includeMediaStreamDestination) { context.createMediaStreamDestination = vi.fn(() => createMockMediaStreamDestination()); } return context; } /** * Setup AudioContext mock globally * Returns the mock instance for further customization */ export function setupAudioContextMock(options) { const mockContext = createMockAudioContext(options); // Store original if exists const original = global.AudioContext; // Setup mock global.AudioContext = vi.fn(() => mockContext); global.webkitAudioContext = global.AudioContext; // Return cleanup function and mock return { context: mockContext, restore: () => { if (original) { global.AudioContext = original; global.webkitAudioContext = original; } }, }; } /** * Helper to use AudioContext mock in beforeEach/afterEach */ export function useAudioContextMock(options) { let mock; beforeEach(() => { mock = setupAudioContextMock(options); }); afterEach(() => { mock?.restore(); vi.clearAllMocks(); }); return () => mock?.context; }