mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 08:12:09 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,95 @@
|
|||
// This processor allows access to audio channels directly on the audio rendering thread.
|
||||
// This can be useful for running possibly expensive audio monitoring or processing operations
|
||||
// off the main thread.
|
||||
class ThresholdProcessor extends globalThis.AudioWorkletProcessor {
|
||||
/**
|
||||
* @param {Object} param0 options
|
||||
* @param {Object} param0.processorOptions
|
||||
* @param {Array<number>} param0.processorOptions.frequencyRange array of two numbers that represent the range of
|
||||
frequencies that we want to monitor in hz.
|
||||
* @param {number} [param0.processorOptions.minimumActiveCycles] - how many cycles have to pass since the last time the
|
||||
threshold was exceeded to go back to inactive state. It prevents the microphone to shut down
|
||||
when the user's voice drops in volume mid-sentence.
|
||||
* @param {boolean} [param0.processorOptions.postAllTics] true if we need to postMessage at each tics, this prevents
|
||||
sending events to the main thread on all tics when not necessary.
|
||||
* @param {number} [param0.processorOptions.volumeThreshold] the minimum value for audio detection
|
||||
*/
|
||||
constructor({ processorOptions: { frequencyRange, minimumActiveCycles = 10, postAllTics, volumeThreshold = 0.3 } }) {
|
||||
super();
|
||||
|
||||
// timing variables
|
||||
this.processInterval = 50; // how many ms between each computation
|
||||
this.minimumActiveCycles = minimumActiveCycles;
|
||||
this.intervalInFrames = this.processInterval / 1000 * globalThis.sampleRate;
|
||||
this.nextUpdateFrame = this.processInterval;
|
||||
|
||||
// process variables
|
||||
this.activityBuffer = 0;
|
||||
this.volumeThreshold = volumeThreshold;
|
||||
this.frequencyRange = frequencyRange || [80, 400];
|
||||
this.isAboveThreshold = false;
|
||||
this.postAllTics = postAllTics;
|
||||
this.volume = 0;
|
||||
this.wasAboveThreshold = undefined;
|
||||
}
|
||||
|
||||
process(inputs, outputs, parameters) {
|
||||
const input = inputs[0];
|
||||
if (input.length < 1) {
|
||||
return;
|
||||
}
|
||||
const samples = input[0];
|
||||
|
||||
// throttles down the processing tic rate
|
||||
this.nextUpdateFrame -= samples.length;
|
||||
if (this.nextUpdateFrame >= 0) {
|
||||
return true;
|
||||
}
|
||||
this.nextUpdateFrame += this.intervalInFrames;
|
||||
|
||||
// computes volume and threshold
|
||||
const startIndex = _getFrequencyIndex(this.frequencyRange[0], globalThis.sampleRate, samples.length);
|
||||
const endIndex = _getFrequencyIndex(this.frequencyRange[1], globalThis.sampleRate, samples.length);
|
||||
let sum = 0;
|
||||
for (let i = startIndex; i < endIndex; ++i) {
|
||||
sum += samples[i];
|
||||
}
|
||||
// Normalizing the volume so that volume mostly fits in the [0,1] range.
|
||||
const preNormalizationVolume = sum / (endIndex - startIndex);
|
||||
const preLogarithmVolume = (preNormalizationVolume * 50) + 1;
|
||||
if (preLogarithmVolume <= 0) {
|
||||
this.volume = 0;
|
||||
} else {
|
||||
this.volume = Math.log10(preLogarithmVolume);
|
||||
}
|
||||
|
||||
if (this.volume >= this.volumeThreshold) {
|
||||
this.activityBuffer = this.minimumActiveCycles;
|
||||
} else if (this.volume < this.volumeThreshold && this.activityBuffer > 0) {
|
||||
this.activityBuffer--;
|
||||
}
|
||||
this.isAboveThreshold = this.activityBuffer > 0;
|
||||
|
||||
if (this.wasAboveThreshold !== this.isAboveThreshold) {
|
||||
this.wasAboveThreshold = this.isAboveThreshold;
|
||||
this.port.postMessage({ volume: this.volume, isAboveThreshold: this.isAboveThreshold });
|
||||
return true;
|
||||
}
|
||||
this.postAllTics && this.port.postMessage({ volume: this.volume, isAboveThreshold: this.isAboveThreshold });
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} targetFrequency in Hz
|
||||
* @param {number} sampleRate the sample rate of the audio
|
||||
* @param {number} samplesSize amount of samples in the audio input
|
||||
* @returns {number} the index of the targetFrequency within samplesSize
|
||||
*/
|
||||
function _getFrequencyIndex(targetFrequency, sampleRate, samplesSize) {
|
||||
const index = Math.round(targetFrequency / (sampleRate / 2) * samplesSize);
|
||||
return Math.min(Math.max(0, index), samplesSize);
|
||||
}
|
||||
|
||||
globalThis.registerProcessor("audio-processor", ThresholdProcessor);
|
||||
Loading…
Add table
Add a link
Reference in a new issue