refactor: rename API and frontend projects to Vocero
Rename the project directories, Python distribution and import package, and npm package. Update launcher, Docker, tooling, tests, lockfiles, and documentation while preserving existing API contracts. Make AGENTS.md self-contained and remove external skill and workflow references.
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