mirror of
https://github.com/w-okada/voice-changer.git
synced 2025-01-23 05:25:01 +03:00
WIP:Internal version
This commit is contained in:
parent
4f534a2c44
commit
e62a140698
11
client/demo/dist/index.html
vendored
11
client/demo/dist/index.html
vendored
@ -1,10 +1 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Voice Changer Client Demo</title>
|
||||
<script defer src="index.js"></script></head>
|
||||
<body style="width: 100%; height: 100%; margin: 0px">
|
||||
<div id="app" style="width: 100%; height: 100%"></div>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>
|
2
client/demo/dist/index.js
vendored
2
client/demo/dist/index.js
vendored
File diff suppressed because one or more lines are too long
3096
client/demo/dist/index.js.LICENSE.txt
vendored
3096
client/demo/dist/index.js.LICENSE.txt
vendored
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,8 @@ import { useVCClient } from "../001_globalHooks/001_useVCClient";
|
||||
import { useAppRoot } from "./001_AppRootProvider";
|
||||
import { useMessageBuilder } from "../hooks/useMessageBuilder";
|
||||
|
||||
import { VoiceChangerJSClient } from "./VoiceChangerJSClient";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
@ -27,6 +29,7 @@ export const AppStateProvider = ({ children }: Props) => {
|
||||
const appRoot = useAppRoot();
|
||||
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext });
|
||||
const messageBuilderState = useMessageBuilder();
|
||||
const voiceChangerJSClient = useRef<VoiceChangerJSClient>();
|
||||
|
||||
useEffect(() => {
|
||||
messageBuilderState.setMessage(__filename, "ioError", {
|
||||
@ -53,15 +56,29 @@ export const AppStateProvider = ({ children }: Props) => {
|
||||
}
|
||||
}, [clientState.clientState.ioErrorCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (clientState.clientState.initialized) {
|
||||
clientState.clientState.setInternalAudioProcessCallback({
|
||||
processAudio: (data: Uint8Array) => {
|
||||
return data;
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [clientState.clientState.initialized]);
|
||||
// useEffect(() => {
|
||||
// if (clientState.clientState.initialized) {
|
||||
// voiceChangerJSClient.current = new VoiceChangerJSClient();
|
||||
// voiceChangerJSClient.current.initialize();
|
||||
// clientState.clientState.setInternalAudioProcessCallback({
|
||||
// processAudio: async (data: Uint8Array) => {
|
||||
// console.log("[CLIENTJS] start --------------------------------------");
|
||||
// const audioF32 = new Float32Array(data.buffer);
|
||||
// const converted = await voiceChangerJSClient.current!.convert(audioF32);
|
||||
|
||||
// let audio_int16_out = new Int16Array(converted.length);
|
||||
// for (let i = 0; i < converted.length; i++) {
|
||||
// audio_int16_out[i] = converted[i] * 32768.0;
|
||||
// }
|
||||
// const res = new Uint8Array(audio_int16_out.buffer);
|
||||
// console.log("AUDIO::::audio_int16_out", audio_int16_out);
|
||||
|
||||
// console.log("[CLIENTJS] end --------------------------------------");
|
||||
// return res;
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// }, [clientState.clientState.initialized]);
|
||||
|
||||
const providerValue: AppStateValue = {
|
||||
audioContext: appRoot.audioContextState.audioContext!,
|
||||
|
149
client/demo/src/001_provider/VoiceChangerJSClient.ts
Normal file
149
client/demo/src/001_provider/VoiceChangerJSClient.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { create, ConverterType } from "@alexanderolsen/libsamplerate-js";
|
||||
import { BlockingQueue } from "./_BlockingQueue";
|
||||
import { WorkerManager, generateConfig, VoiceChangerProcessorInitializeParams, VoiceChangerProcessorConvertParams, FunctionType, VoiceChangerProcessorResult } from "@dannadori/voice-changer-js";
|
||||
|
||||
export class VoiceChangerJSClient {
|
||||
private wm = new WorkerManager();
|
||||
private audioBuffer: Float32Array = new Float32Array(0);
|
||||
private audioInputLength = 24000;
|
||||
|
||||
private inputSamplingRate = 48000;
|
||||
private outputSamplingRate = 48000;
|
||||
private modelInputSamplingRate = 16000;
|
||||
private modelOutputSamplingRate = 40000;
|
||||
private sem = new BlockingQueue<number>();
|
||||
private crossfadeChunks = 1;
|
||||
private solaChunks = 0.5;
|
||||
constructor() {
|
||||
this.sem.enqueue(0);
|
||||
}
|
||||
private lock = async () => {
|
||||
const num = await this.sem.dequeue();
|
||||
return num;
|
||||
};
|
||||
private unlock = (num: number) => {
|
||||
this.sem.enqueue(num + 1);
|
||||
};
|
||||
|
||||
initialize = async () => {
|
||||
console.log("Voice Changer Initializing,,,");
|
||||
const baseUrl = "http://127.0.0.1:18888";
|
||||
|
||||
this.wm = new WorkerManager();
|
||||
const config = generateConfig();
|
||||
config.processorURL = `${baseUrl}/process.js`;
|
||||
config.onnxWasmPaths = `${baseUrl}/`;
|
||||
await this.wm.init(config);
|
||||
|
||||
const initializeParams: VoiceChangerProcessorInitializeParams = {
|
||||
type: FunctionType.initialize,
|
||||
inputLength: 24000,
|
||||
f0_min: 50,
|
||||
f0_max: 1100,
|
||||
embPitchUrl: "http://127.0.0.1:18888/models/emb_pit_24000.bin",
|
||||
rvcv2InputLength: 148,
|
||||
// rvcv2Url: "http://127.0.0.1:18888/models/rvc2v_24000.bin",
|
||||
rvcv2Url: "http://127.0.0.1:18888/models/rvc2vnof0_24000.bin",
|
||||
transfer: [],
|
||||
};
|
||||
|
||||
const res = (await this.wm.execute(initializeParams)) as VoiceChangerProcessorResult;
|
||||
console.log("Voice Changer Initialized..", res);
|
||||
};
|
||||
|
||||
convert = async (audio: Float32Array): Promise<Float32Array> => {
|
||||
console.log("convert start....", audio);
|
||||
const lockNum = await this.lock();
|
||||
//resample
|
||||
const audio_16k = await this.resample(audio, this.inputSamplingRate, this.modelInputSamplingRate);
|
||||
//store data and get target data
|
||||
//// store
|
||||
const newAudioBuffer = new Float32Array(this.audioBuffer.length + audio_16k.length);
|
||||
newAudioBuffer.set(this.audioBuffer);
|
||||
newAudioBuffer.set(audio_16k, this.audioBuffer.length);
|
||||
this.audioBuffer = newAudioBuffer;
|
||||
|
||||
//// Buffering.....
|
||||
if (this.audioBuffer.length < this.audioInputLength * 1) {
|
||||
console.log(`skip covert length:${this.audioBuffer.length}, audio_16k:${audio_16k.length}`);
|
||||
await this.unlock(lockNum);
|
||||
return new Float32Array(1);
|
||||
} else {
|
||||
console.log(`--------------- convert start... length:${this.audioBuffer.length}, audio_16k:${audio_16k.length}`);
|
||||
}
|
||||
|
||||
//// get chunks
|
||||
let chunkIndex = 0;
|
||||
const audioChunks: Float32Array[] = [];
|
||||
while (true) {
|
||||
const chunkOffset = chunkIndex * this.audioInputLength - (this.crossfadeChunks + this.solaChunks) * 320 * chunkIndex;
|
||||
const chunkEnd = chunkOffset + this.audioInputLength;
|
||||
if (chunkEnd > this.audioBuffer.length) {
|
||||
this.audioBuffer = this.audioBuffer.slice(chunkOffset);
|
||||
break;
|
||||
} else {
|
||||
const chunk = this.audioBuffer.slice(chunkOffset, chunkEnd);
|
||||
audioChunks.push(chunk);
|
||||
}
|
||||
chunkIndex++;
|
||||
}
|
||||
|
||||
if (audioChunks.length == 0) {
|
||||
await this.unlock(lockNum);
|
||||
console.log(`skip covert length:${this.audioBuffer.length}, audio_16k:${audio_16k.length}`);
|
||||
return new Float32Array(1);
|
||||
}
|
||||
|
||||
//convert (each)
|
||||
const convetedAudioChunks: Float32Array[] = [];
|
||||
for (let i = 0; i < audioChunks.length; i++) {
|
||||
const convertParams: VoiceChangerProcessorConvertParams = {
|
||||
type: FunctionType.convert,
|
||||
transfer: [audioChunks[i].buffer],
|
||||
};
|
||||
const res = (await this.wm.execute(convertParams)) as VoiceChangerProcessorResult;
|
||||
const converted = new Float32Array(res.transfer[0] as ArrayBuffer);
|
||||
console.log(`converted.length:::${i}:${converted.length}`);
|
||||
|
||||
convetedAudioChunks.push(converted);
|
||||
}
|
||||
|
||||
//concat
|
||||
let totalLength = convetedAudioChunks.reduce((prev, cur) => prev + cur.length, 0);
|
||||
let convetedAudio = new Float32Array(totalLength);
|
||||
let offset = 0;
|
||||
for (let chunk of convetedAudioChunks) {
|
||||
convetedAudio.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
console.log(`converted.length:::convetedAudio:${convetedAudio.length}`);
|
||||
|
||||
//resample
|
||||
// const response = await this.resample(convetedAudio, this.params.modelOutputSamplingRate, this.params.outputSamplingRate);
|
||||
|
||||
const outputDuration = (this.audioInputLength * audioChunks.length - this.crossfadeChunks * 320) / 16000;
|
||||
const outputSamples = outputDuration * this.outputSamplingRate;
|
||||
const convertedOutputRatio = outputSamples / convetedAudio.length;
|
||||
const realOutputSamplingRate = this.modelOutputSamplingRate * convertedOutputRatio;
|
||||
console.log(`realOutputSamplingRate:${realOutputSamplingRate}, `, this.modelOutputSamplingRate, convertedOutputRatio);
|
||||
|
||||
// const response2 = await this.resample(convetedAudio, this.params.modelOutputSamplingRate, realOutputSamplingRate);
|
||||
const response2 = await this.resample(convetedAudio, this.modelOutputSamplingRate, this.outputSamplingRate);
|
||||
|
||||
console.log(`converted from :${audioChunks.length * this.audioInputLength} to:${convetedAudio.length} to:${response2.length}`);
|
||||
console.log(`outputDuration :${outputDuration} outputSamples:${outputSamples}, convertedOutputRatio:${convertedOutputRatio}, realOutputSamplingRate:${realOutputSamplingRate}`);
|
||||
await this.unlock(lockNum);
|
||||
return response2;
|
||||
};
|
||||
|
||||
// Utility
|
||||
resample = async (data: Float32Array, srcSampleRate: number, dstSampleRate: number) => {
|
||||
const converterType = ConverterType.SRC_SINC_BEST_QUALITY;
|
||||
const nChannels = 1;
|
||||
const converter = await create(nChannels, srcSampleRate, dstSampleRate, {
|
||||
converterType: converterType, // default SRC_SINC_FASTEST. see API for more
|
||||
});
|
||||
const res = converter.simple(data);
|
||||
return res;
|
||||
};
|
||||
}
|
41
client/demo/src/001_provider/_BlockingQueue.ts
Normal file
41
client/demo/src/001_provider/_BlockingQueue.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export class BlockingQueue<T> {
|
||||
private _promises: Promise<T>[];
|
||||
private _resolvers: ((t: T) => void)[];
|
||||
|
||||
constructor() {
|
||||
this._resolvers = [];
|
||||
this._promises = [];
|
||||
}
|
||||
|
||||
private _add() {
|
||||
this._promises.push(
|
||||
new Promise((resolve) => {
|
||||
this._resolvers.push(resolve);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
enqueue(t: T) {
|
||||
if (this._resolvers.length == 0) this._add();
|
||||
const resolve = this._resolvers.shift()!;
|
||||
resolve(t);
|
||||
}
|
||||
|
||||
dequeue() {
|
||||
if (this._promises.length == 0) this._add();
|
||||
const promise = this._promises.shift()!;
|
||||
return promise;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this._promises.length == 0;
|
||||
}
|
||||
|
||||
isBlocked() {
|
||||
return this._resolvers.length != 0;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._promises.length - this._resolvers.length;
|
||||
}
|
||||
}
|
@ -64,8 +64,11 @@ module.exports = {
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: "public/models/emb_pit_24000.bin", to: "models/emb_pit_24000.bin" }],
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: "public/models/rvc2v_24000.bin", to: "models/rvc2v_24000.bin" }],
|
||||
}),
|
||||
// new CopyPlugin({
|
||||
// patterns: [{ from: "public/models/rvc2v_24000.bin", to: "models/rvc2v_24000.bin" }],
|
||||
// }),
|
||||
// new CopyPlugin({
|
||||
// patterns: [{ from: "public/models/rvc2vnof0_24000.bin", to: "models/rvc2vnof0_24000.bin" }],
|
||||
// }),
|
||||
],
|
||||
};
|
||||
|
@ -296,7 +296,7 @@ export class VoiceChangerWorkletNode extends AudioWorkletNode {
|
||||
return;
|
||||
}
|
||||
const res = await this.internalCallback.processAudio(newBuffer);
|
||||
if (res.length == 0) {
|
||||
if (res.length < 128 * 2) {
|
||||
return;
|
||||
}
|
||||
if (this.outputNode != null) {
|
||||
|
@ -493,7 +493,8 @@ export type ClientSetting = {
|
||||
};
|
||||
export const DefaultClientSettng: ClientSetting = {
|
||||
workletSetting: {
|
||||
numTrancateTreshold: 512 * 2,
|
||||
// numTrancateTreshold: 512 * 2,
|
||||
numTrancateTreshold: 100,
|
||||
volTrancateThreshold: 0.0005,
|
||||
volTrancateLength: 32,
|
||||
},
|
||||
|
@ -34,7 +34,7 @@ class VoiceChangerWorkletProcessor extends AudioWorkletProcessor {
|
||||
private BLOCK_SIZE = 128;
|
||||
private initialized = false;
|
||||
private volume = 0;
|
||||
private numTrancateTreshold = 100;
|
||||
// private numTrancateTreshold = 100;
|
||||
// private volTrancateThreshold = 0.0005
|
||||
// private volTrancateLength = 32
|
||||
// private volTrancateCount = 0
|
||||
@ -69,7 +69,7 @@ class VoiceChangerWorkletProcessor extends AudioWorkletProcessor {
|
||||
handleMessage(event: any) {
|
||||
const request = event.data as VoiceChangerWorkletProcessorRequest;
|
||||
if (request.requestType === "config") {
|
||||
this.numTrancateTreshold = request.numTrancateTreshold;
|
||||
// this.numTrancateTreshold = request.numTrancateTreshold;
|
||||
// this.volTrancateLength = request.volTrancateLength
|
||||
// this.volTrancateThreshold = request.volTrancateThreshold
|
||||
console.log("[worklet] worklet configured", request);
|
||||
@ -101,12 +101,16 @@ class VoiceChangerWorkletProcessor extends AudioWorkletProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.playBuffer.length > this.numTrancateTreshold) {
|
||||
console.log(`[worklet] Truncate ${this.playBuffer.length} > ${this.numTrancateTreshold}`);
|
||||
const f32Data = request.voice;
|
||||
// if (this.playBuffer.length > this.numTrancateTreshold) {
|
||||
// console.log(`[worklet] Truncate ${this.playBuffer.length} > ${this.numTrancateTreshold}`);
|
||||
// this.trancateBuffer();
|
||||
// }
|
||||
if (this.playBuffer.length > f32Data.length / this.BLOCK_SIZE) {
|
||||
console.log(`[worklet] Truncate ${this.playBuffer.length} > ${f32Data.length / this.BLOCK_SIZE}`);
|
||||
this.trancateBuffer();
|
||||
}
|
||||
|
||||
const f32Data = request.voice;
|
||||
const concatedF32Data = new Float32Array(this.unpushedF32Data.length + f32Data.length);
|
||||
concatedF32Data.set(this.unpushedF32Data);
|
||||
concatedF32Data.set(f32Data, this.unpushedF32Data.length);
|
||||
|
Loading…
Reference in New Issue
Block a user