refactor(frontend): rename project to s2s-frontend
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
type PlaybackMessage =
|
||||
{ kind: "config"; inputRate: number } | { kind: "audio"; samples: Float32Array } | { kind: "clear" };
|
||||
|
||||
class AudioPlaybackProcessor extends AudioWorkletProcessor {
|
||||
private rate = 16000;
|
||||
private queue: Float32Array[] = [];
|
||||
private offset = 0;
|
||||
private fraction = 0;
|
||||
private playing = false;
|
||||
private fadeIn = 0;
|
||||
private fadeOut = 0;
|
||||
private lastSample = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.port.onmessage = (event: MessageEvent<PlaybackMessage>) => {
|
||||
const data = event.data;
|
||||
switch (data.kind) {
|
||||
case "config":
|
||||
if (data.inputRate > 0) {
|
||||
this.rate = data.inputRate;
|
||||
}
|
||||
break;
|
||||
case "audio":
|
||||
if (data.samples.length) {
|
||||
this.queue.push(data.samples);
|
||||
}
|
||||
break;
|
||||
case "clear":
|
||||
this.queue = [];
|
||||
this.offset = 0;
|
||||
this.fraction = 0;
|
||||
this.fadeOut = 32;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
private bufferedMs() {
|
||||
return (this.queue.reduce((sum, buffer) => sum + buffer.length, -this.offset) / this.rate) * 1000;
|
||||
}
|
||||
private read(): number | null {
|
||||
const head = this.queue[0];
|
||||
if (!head) {
|
||||
return null;
|
||||
}
|
||||
const a = head[this.offset] ?? 0;
|
||||
const b = head[this.offset + 1] ?? this.queue[1]?.[0] ?? a;
|
||||
return a + (b - a) * this.fraction;
|
||||
}
|
||||
private advance() {
|
||||
this.fraction += this.rate / sampleRate;
|
||||
while (this.fraction >= 1) {
|
||||
this.fraction--;
|
||||
this.offset++;
|
||||
}
|
||||
while (this.queue[0] && this.offset >= this.queue[0].length) {
|
||||
this.offset -= this.queue[0].length;
|
||||
this.queue.shift();
|
||||
}
|
||||
}
|
||||
process(_inputs: Float32Array[][], outputs: Float32Array[][]) {
|
||||
const channels = outputs[0];
|
||||
const output = channels?.[0];
|
||||
if (!output) {
|
||||
return true;
|
||||
}
|
||||
for (let index = 0; index < output.length; index++) {
|
||||
let value = 0;
|
||||
if (!this.playing && this.queue.length && this.bufferedMs() >= 90) {
|
||||
this.playing = true;
|
||||
this.fadeIn = 32;
|
||||
this.fadeOut = 0;
|
||||
}
|
||||
if (this.playing) {
|
||||
const sample = this.read();
|
||||
if (sample === null) {
|
||||
value = this.lastSample * (1 - 1 / 32);
|
||||
this.lastSample = value;
|
||||
if (Math.abs(value) < 1e-4) {
|
||||
this.playing = false;
|
||||
this.lastSample = 0;
|
||||
}
|
||||
} else {
|
||||
value = sample;
|
||||
this.lastSample = sample;
|
||||
this.advance();
|
||||
}
|
||||
if (this.fadeIn > 0) {
|
||||
value *= 1 - this.fadeIn-- / 32;
|
||||
}
|
||||
if (this.fadeOut > 0) {
|
||||
value *= this.fadeOut-- / 32;
|
||||
if (!this.fadeOut) {
|
||||
this.playing = false;
|
||||
this.lastSample = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
output[index] = value;
|
||||
const stereo = channels?.[1];
|
||||
if (stereo) {
|
||||
stereo[index] = value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
registerProcessor("audio-playback", AudioPlaybackProcessor);
|
||||
Reference in New Issue
Block a user