2023-01-11 21:49:22 +03:00
|
|
|
export const RequestType = {
|
|
|
|
"voice": "voice",
|
2023-02-12 07:24:30 +03:00
|
|
|
"config": "config",
|
|
|
|
"startRecording": "startRecording",
|
|
|
|
"stopRecording": "stopRecording"
|
2023-01-11 21:49:22 +03:00
|
|
|
} as const
|
|
|
|
export type RequestType = typeof RequestType[keyof typeof RequestType]
|
|
|
|
|
2023-02-12 07:24:30 +03:00
|
|
|
|
|
|
|
export const ResponseType = {
|
|
|
|
"volume": "volume",
|
|
|
|
"recordData": "recordData"
|
|
|
|
} as const
|
|
|
|
export type ResponseType = typeof ResponseType[keyof typeof ResponseType]
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-01-11 21:49:22 +03:00
|
|
|
export type VoiceChangerWorkletProcessorRequest = {
|
|
|
|
requestType: RequestType,
|
|
|
|
voice: ArrayBuffer,
|
|
|
|
numTrancateTreshold: number
|
|
|
|
volTrancateThreshold: number
|
|
|
|
volTrancateLength: number
|
|
|
|
}
|
2023-01-04 20:28:36 +03:00
|
|
|
|
2023-02-12 07:24:30 +03:00
|
|
|
export type VoiceChangerWorkletProcessorResponse = {
|
|
|
|
responseType: ResponseType,
|
|
|
|
volume?: number,
|
|
|
|
recordData?: Float32Array[]
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:28:36 +03:00
|
|
|
class VoiceChangerWorkletProcessor extends AudioWorkletProcessor {
|
|
|
|
private BLOCK_SIZE = 128
|
|
|
|
private initialized = false;
|
|
|
|
private volume = 0
|
2023-01-11 22:52:01 +03:00
|
|
|
private numTrancateTreshold = 150
|
2023-01-11 21:49:22 +03:00
|
|
|
private volTrancateThreshold = 0.0005
|
|
|
|
private volTrancateLength = 32
|
|
|
|
private volTrancateCount = 0
|
|
|
|
|
2023-02-12 07:24:30 +03:00
|
|
|
private isRecording = false
|
|
|
|
|
2023-01-04 20:28:36 +03:00
|
|
|
playBuffer: Float32Array[] = []
|
2023-02-12 07:24:30 +03:00
|
|
|
recordingBuffer: Float32Array[] = []
|
2023-01-04 20:28:36 +03:00
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.initialized = true;
|
|
|
|
this.port.onmessage = this.handleMessage.bind(this);
|
|
|
|
}
|
|
|
|
|
2023-01-11 21:49:22 +03:00
|
|
|
calcVol = (data: Float32Array, prevVol: number) => {
|
|
|
|
const sum = data.reduce((prev, cur) => {
|
|
|
|
return prev + cur * cur
|
|
|
|
}, 0)
|
|
|
|
const rms = Math.sqrt(sum / data.length)
|
|
|
|
return Math.max(rms, prevVol * 0.95)
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:28:36 +03:00
|
|
|
handleMessage(event: any) {
|
2023-02-12 07:29:50 +03:00
|
|
|
const request = event.data as VoiceChangerWorkletProcessorRequest
|
2023-01-11 21:49:22 +03:00
|
|
|
if (request.requestType === "config") {
|
|
|
|
this.numTrancateTreshold = request.numTrancateTreshold
|
|
|
|
this.volTrancateLength = request.volTrancateLength
|
|
|
|
this.volTrancateThreshold = request.volTrancateThreshold
|
|
|
|
console.log("[worklet] worklet configured", request)
|
|
|
|
return
|
2023-02-12 07:24:30 +03:00
|
|
|
} else if (request.requestType === "startRecording") {
|
|
|
|
if (this.isRecording) {
|
|
|
|
console.warn("[worklet] recoring is already started")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.isRecording = true
|
|
|
|
this.recordingBuffer = []
|
|
|
|
return
|
|
|
|
} else if (request.requestType === "stopRecording") {
|
|
|
|
if (!this.isRecording) {
|
|
|
|
console.warn("[worklet] recoring is not started")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.isRecording = false
|
|
|
|
const recordResponse: VoiceChangerWorkletProcessorResponse = {
|
|
|
|
responseType: ResponseType.recordData,
|
|
|
|
recordData: this.recordingBuffer
|
|
|
|
|
|
|
|
}
|
|
|
|
this.port.postMessage(recordResponse);
|
|
|
|
this.recordingBuffer = []
|
|
|
|
return
|
2023-01-11 21:49:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const arrayBuffer = request.voice
|
2023-01-04 20:28:36 +03:00
|
|
|
// データは(int16)で受信
|
|
|
|
const i16Data = new Int16Array(arrayBuffer)
|
|
|
|
const f32Data = new Float32Array(i16Data.length)
|
2023-01-05 05:45:42 +03:00
|
|
|
// console.log(`[worklet] f32DataLength${f32Data.length} i16DataLength${i16Data.length}`)
|
2023-01-04 20:28:36 +03:00
|
|
|
i16Data.forEach((x, i) => {
|
|
|
|
const float = (x >= 0x8000) ? -(0x10000 - x) / 0x8000 : x / 0x7FFF;
|
|
|
|
f32Data[i] = float
|
|
|
|
})
|
|
|
|
|
2023-01-11 21:49:22 +03:00
|
|
|
if (this.playBuffer.length > this.numTrancateTreshold) {
|
2023-01-04 20:28:36 +03:00
|
|
|
console.log("[worklet] Buffer truncated")
|
|
|
|
while (this.playBuffer.length > 2) {
|
|
|
|
this.playBuffer.shift()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// アップサンプリングしてPlayバッファに蓄積
|
|
|
|
let f32Block: Float32Array
|
|
|
|
for (let i = 0; i < f32Data.length; i++) {
|
|
|
|
const frameIndexInBlock = (i * 2) % this.BLOCK_SIZE //
|
|
|
|
if (frameIndexInBlock === 0) {
|
|
|
|
f32Block = new Float32Array(this.BLOCK_SIZE)
|
|
|
|
}
|
|
|
|
|
|
|
|
const currentFrame = f32Data[i]
|
|
|
|
const nextFrame = i + 1 < f32Data.length ? f32Data[i + 1] : f32Data[i]
|
|
|
|
f32Block![frameIndexInBlock] = currentFrame
|
|
|
|
f32Block![frameIndexInBlock + 1] = (currentFrame + nextFrame) / 2
|
|
|
|
if (f32Block!.length === frameIndexInBlock + 2) {
|
|
|
|
this.playBuffer.push(f32Block!)
|
2023-02-12 07:24:30 +03:00
|
|
|
if (this.isRecording) {
|
|
|
|
this.recordingBuffer.push(f32Block!)
|
|
|
|
}
|
2023-01-04 20:28:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
process(_inputs: Float32Array[][], outputs: Float32Array[][], _parameters: Record<string, Float32Array>) {
|
|
|
|
if (!this.initialized) {
|
|
|
|
console.warn("[worklet] worklet_process not ready");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.playBuffer.length === 0) {
|
2023-01-11 22:52:01 +03:00
|
|
|
// console.log("[worklet] no play buffer")
|
2023-01-04 20:28:36 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-01-11 21:49:22 +03:00
|
|
|
//// 一定期間無音状態が続いている場合はスキップ。
|
|
|
|
let voice: Float32Array | undefined
|
|
|
|
while (true) {
|
|
|
|
voice = this.playBuffer.shift()
|
|
|
|
if (!voice) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
this.volume = this.calcVol(voice, this.volume)
|
|
|
|
if (this.volume < this.volTrancateThreshold) {
|
|
|
|
this.volTrancateCount += 1
|
|
|
|
} else {
|
|
|
|
this.volTrancateCount = 0
|
|
|
|
}
|
2023-01-04 20:28:36 +03:00
|
|
|
|
|
|
|
|
2023-01-11 22:52:01 +03:00
|
|
|
if (this.volTrancateCount < this.volTrancateLength || this.volTrancateLength < 0) {
|
2023-01-11 21:49:22 +03:00
|
|
|
break
|
|
|
|
} else {
|
2023-01-11 22:52:01 +03:00
|
|
|
// console.log("silent...skip")
|
2023-01-11 21:49:22 +03:00
|
|
|
}
|
|
|
|
}
|
2023-01-04 20:28:36 +03:00
|
|
|
|
2023-01-11 21:49:22 +03:00
|
|
|
if (voice) {
|
2023-02-12 07:24:30 +03:00
|
|
|
const volumeResponse: VoiceChangerWorkletProcessorResponse = {
|
|
|
|
responseType: ResponseType.volume,
|
|
|
|
volume: this.volume
|
|
|
|
}
|
|
|
|
this.port.postMessage(volumeResponse);
|
2023-01-11 21:49:22 +03:00
|
|
|
outputs[0][0].set(voice)
|
|
|
|
}
|
2023-01-04 20:28:36 +03:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
registerProcessor("voice-changer-worklet-processor", VoiceChangerWorkletProcessor);
|