-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add recorder mic check and tests * move record templates from src to package root * put mic check in typescript file and add tsc build step to generate js file * add mocks for WebAudioAPI and test coverage for recorder checkMic * ignore test coverage for fixtures
- Loading branch information
1 parent
8eb4c39
commit 4c436ee
Showing
14 changed files
with
752 additions
and
28 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
/** Mock the message port, which is needed by the Audio Worklet Processor. */ | ||
export const msgPort = { | ||
addEventListener: jest.fn(), | ||
start: jest.fn(), | ||
close: jest.fn(), | ||
postMessage: jest.fn(), | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
onmessage: jest.fn(), | ||
} as unknown as MessagePort; | ||
|
||
/** Mock for Media Stream Audio Source Node. */ | ||
export class MediaStreamAudioSourceNodeMock { | ||
/** | ||
* Mock the MediaStreamAudioSourceNode. | ||
* | ||
* @param _destination - Destination | ||
* @returns This | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
public connect(_destination: any): any { | ||
// Return this to support chaining | ||
return this; | ||
} | ||
} | ||
|
||
/** Mock for Audio Worklet Node */ | ||
export class AudioWorkletNodeMock { | ||
/** | ||
* Constructor. | ||
* | ||
* @param _context - Base audio context | ||
* @param _name - Name | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
public constructor(_context: any, _name: string) { | ||
this.port = msgPort; | ||
} | ||
public port: MessagePort; | ||
/** | ||
* Connect. | ||
* | ||
* @param _destination - Destination | ||
* @returns This | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
public connect(_destination: any): any { | ||
return this; | ||
} | ||
} | ||
|
||
// Define a partial mock for the AudioContext | ||
export const audioContextMock = { | ||
audioWorklet: { | ||
addModule: jest.fn(async () => await Promise.resolve()), | ||
}, | ||
createBuffer: jest.fn( | ||
() => | ||
({ | ||
getChannelData: jest.fn(() => new Float32Array(256)), | ||
}) as unknown as AudioBuffer, | ||
), | ||
createBufferSource: jest.fn( | ||
() => | ||
({ | ||
connect: jest.fn(), | ||
}) as unknown as AudioBufferSourceNode, | ||
), | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
createMediaStreamSource: jest.fn( | ||
(_stream: MediaStream) => new MediaStreamAudioSourceNodeMock(), | ||
), | ||
sampleRate: 44100, | ||
destination: new AudioWorkletNodeMock(null, ""), // Mock destination | ||
close: jest.fn(), | ||
decodeAudioData: jest.fn(), | ||
resume: jest.fn(), | ||
suspend: jest.fn(), | ||
state: "suspended", | ||
onstatechange: null as any, | ||
} as unknown as AudioContext; // Cast as AudioContext for compatibility |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
declare module "*"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const SMOOTHING_FACTOR = 0.99; | ||
const SCALING_FACTOR = 5; | ||
/** | ||
* Audio Worklet Processor class for processing audio input streams. This is | ||
* used by the Recorder to run a volume check on the microphone input stream. | ||
* Source: | ||
* https://www.webrtc-developers.com/how-to-know-if-my-microphone-works/#detect-noise-or-silence | ||
*/ | ||
export default class MicCheckProcessor extends AudioWorkletProcessor { | ||
_volume; | ||
_micChecked; | ||
/** Constructor for the mic check processor. */ | ||
constructor() { | ||
super(); | ||
this._volume = 0; | ||
this._micChecked = false; | ||
/** | ||
* Callback to handle a message event on the processor's port. This | ||
* determines how the processor responds when the recorder posts a message | ||
* to the processor with e.g. this.processorNode.port.postMessage({ | ||
* micChecked: true }). | ||
* | ||
* @param event - Message event generated from the 'postMessage' call, which | ||
* includes, among other things, the data property. | ||
* @param event.data - Data sent by the message emitter. | ||
*/ | ||
this.port.onmessage = (event) => { | ||
if ( | ||
event.data && | ||
event.data.micChecked && | ||
event.data.micChecked == true | ||
) { | ||
this._micChecked = true; | ||
} | ||
}; | ||
} | ||
/** | ||
* Process method that implements the audio processing algorithm for the Audio | ||
* Processor Worklet. "Although the method is not a part of the | ||
* AudioWorkletProcessor interface, any implementation of | ||
* AudioWorkletProcessor must provide a process() method." Source: | ||
* https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process | ||
* The process method can take the following arguments: inputs, outputs, | ||
* parameters. Here we are only using inputs. | ||
* | ||
* @param inputs - An array of inputs from the audio stream (microphone) | ||
* connnected to the node. Each item in the inputs array is an array of | ||
* channels. Each channel is a Float32Array containing 128 samples. For | ||
* example, inputs[n][m][i] will access n-th input, m-th channel of that | ||
* input, and i-th sample of that channel. | ||
* @returns Boolean indicating whether or not the Audio Worklet Node should | ||
* remain active, even if the User Agent thinks it is safe to shut down. In | ||
* this case, when the recorder decides that the mic check criteria has been | ||
* met, it will return false (processor should be shut down), otherwise it | ||
* will return true (processor should remain active). | ||
*/ | ||
process(inputs) { | ||
if (this._micChecked) { | ||
return false; | ||
} else { | ||
const input = inputs[0]; | ||
const samples = input[0]; | ||
const sumSquare = samples.reduce((p, c) => p + c * c, 0); | ||
const rms = Math.sqrt(sumSquare / (samples.length || 1)) * SCALING_FACTOR; | ||
this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR); | ||
this.port.postMessage({ volume: this._volume }); | ||
return true; | ||
} | ||
} | ||
} | ||
registerProcessor("mic-check-processor", MicCheckProcessor); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
const SMOOTHING_FACTOR = 0.99; | ||
const SCALING_FACTOR = 5; | ||
|
||
/** | ||
* Audio Worklet Processor class for processing audio input streams. This is | ||
* used by the Recorder to run a volume check on the microphone input stream. | ||
* Source: | ||
* https://www.webrtc-developers.com/how-to-know-if-my-microphone-works/#detect-noise-or-silence | ||
*/ | ||
export default class MicCheckProcessor extends AudioWorkletProcessor { | ||
private _volume: number; | ||
private _micChecked: boolean; | ||
/** Constructor for the mic check processor. */ | ||
public constructor() { | ||
super(); | ||
this._volume = 0; | ||
this._micChecked = false; | ||
/** | ||
* Callback to handle a message event on the processor's port. This | ||
* determines how the processor responds when the recorder posts a message | ||
* to the processor with e.g. this.processorNode.port.postMessage({ | ||
* micChecked: true }). | ||
* | ||
* @param event - Message event generated from the 'postMessage' call, which | ||
* includes, among other things, the data property. | ||
* @param event.data - Data sent by the message emitter. | ||
*/ | ||
this.port.onmessage = (event: MessageEvent) => { | ||
if ( | ||
event.data && | ||
event.data.micChecked && | ||
event.data.micChecked == true | ||
) { | ||
this._micChecked = true; | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Process method that implements the audio processing algorithm for the Audio | ||
* Processor Worklet. "Although the method is not a part of the | ||
* AudioWorkletProcessor interface, any implementation of | ||
* AudioWorkletProcessor must provide a process() method." Source: | ||
* https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process | ||
* The process method can take the following arguments: inputs, outputs, | ||
* parameters. Here we are only using inputs. | ||
* | ||
* @param inputs - An array of inputs from the audio stream (microphone) | ||
* connnected to the node. Each item in the inputs array is an array of | ||
* channels. Each channel is a Float32Array containing 128 samples. For | ||
* example, inputs[n][m][i] will access n-th input, m-th channel of that | ||
* input, and i-th sample of that channel. | ||
* @returns Boolean indicating whether or not the Audio Worklet Node should | ||
* remain active, even if the User Agent thinks it is safe to shut down. In | ||
* this case, when the recorder decides that the mic check criteria has been | ||
* met, it will return false (processor should be shut down), otherwise it | ||
* will return true (processor should remain active). | ||
*/ | ||
public process(inputs: Float32Array[][]) { | ||
if (this._micChecked) { | ||
return false; | ||
} else { | ||
const input = inputs[0]; | ||
const samples = input[0]; | ||
const sumSquare = samples.reduce((p, c) => p + c * c, 0); | ||
const rms = Math.sqrt(sumSquare / (samples.length || 1)) * SCALING_FACTOR; | ||
this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR); | ||
this.port.postMessage({ volume: this._volume }); | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
registerProcessor("mic-check-processor", MicCheckProcessor); |
Oops, something went wrong.