- app.js → app.ts:添加完整类型标注、接口定义 - audio-processor.js → audio-processor.ts:AudioWorklet 类型化 - vite.config.js → vite.config.ts - 添加 tsconfig.json、vite-env.d.ts - 集成 Biome 默认配置(lint + format),通过全部检查 - package.json 添加 check/typecheck 脚本 - index.html 修复无障碍问题(button type、SVG title)
89 lines
2.1 KiB
TypeScript
89 lines
2.1 KiB
TypeScript
/**
|
|
* AudioWorklet processor for VoicePaste.
|
|
*
|
|
* Captures raw Float32 PCM from the microphone, accumulates samples into
|
|
* ~200ms frames, and posts them to the main thread for resampling + WS send.
|
|
*
|
|
* Communication:
|
|
* Main → Processor: { command: "start" | "stop" }
|
|
* Processor → Main: { type: "audio", samples: Float32Array, sampleRate: number }
|
|
*/
|
|
|
|
// AudioWorkletGlobalScope globals (not in standard lib)
|
|
declare const sampleRate: number;
|
|
declare class AudioWorkletProcessor {
|
|
readonly port: MessagePort;
|
|
constructor();
|
|
process(
|
|
inputs: Float32Array[][],
|
|
outputs: Float32Array[][],
|
|
parameters: Record<string, Float32Array>,
|
|
): boolean;
|
|
}
|
|
declare function registerProcessor(
|
|
name: string,
|
|
ctor: new () => AudioWorkletProcessor,
|
|
): void;
|
|
|
|
class VoicePasteProcessor extends AudioWorkletProcessor {
|
|
private recording = false;
|
|
private buffer: Float32Array[] = [];
|
|
private bufferLen = 0;
|
|
private readonly frameSize: number;
|
|
|
|
constructor() {
|
|
super();
|
|
// ~200ms worth of samples at current sample rate
|
|
this.frameSize = Math.floor(sampleRate * 0.2);
|
|
|
|
this.port.onmessage = (e: MessageEvent) => {
|
|
if (e.data.command === "start") {
|
|
this.recording = true;
|
|
this.buffer = [];
|
|
this.bufferLen = 0;
|
|
} else if (e.data.command === "stop") {
|
|
if (this.bufferLen > 0) {
|
|
this.flush();
|
|
}
|
|
this.recording = false;
|
|
}
|
|
};
|
|
}
|
|
|
|
process(inputs: Float32Array[][]): boolean {
|
|
if (!this.recording) return true;
|
|
|
|
const input = inputs[0];
|
|
if (!input || !input[0]) return true;
|
|
|
|
const channelData = input[0];
|
|
this.buffer.push(new Float32Array(channelData));
|
|
this.bufferLen += channelData.length;
|
|
|
|
if (this.bufferLen >= this.frameSize) {
|
|
this.flush();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private flush(): void {
|
|
const merged = new Float32Array(this.bufferLen);
|
|
let offset = 0;
|
|
for (const chunk of this.buffer) {
|
|
merged.set(chunk, offset);
|
|
offset += chunk.length;
|
|
}
|
|
|
|
this.port.postMessage(
|
|
{ type: "audio", samples: merged, sampleRate: sampleRate },
|
|
[merged.buffer],
|
|
);
|
|
|
|
this.buffer = [];
|
|
this.bufferLen = 0;
|
|
}
|
|
}
|
|
|
|
registerProcessor("audio-processor", VoicePasteProcessor);
|