"use strict" window.speech2speechState = { isReadingMic: false, elapsedRecording: 0, s2s_running: false } // Populate the microphone dropdown with the available options navigator.mediaDevices.enumerateDevices().then(devices => { devices = devices.filter(device => device.kind=="audioinput" && device.deviceId!="default" && device.deviceId!="communications") devices.forEach(device => { const option = createElem("option", device.label) option.value = device.deviceId setting_mic_selection.appendChild(option) }) setting_mic_selection.addEventListener("change", () => { window.userSettings.microphone = setting_mic_selection.value window.saveUserSettings() window.initMic() }) if (Object.keys(window.userSettings).includes("microphone")) { setting_mic_selection.value = window.userSettings.microphone } else { window.userSettings.microphone = setting_mic_selection.value window.saveUserSettings() } }) window.initMic = () => { return new Promise(resolve => { const deviceId = window.userSettings.microphone navigator.mediaDevices.getUserMedia({audio: {deviceId: deviceId}}).then(stream => { const audio_context = new AudioContext const input = audio_context.createMediaStreamSource(stream) window.speech2speechState.stream = stream resolve() }).catch(err => { console.log(err) resolve() }) }) } window.initMic() const animateRecordingProgress = () => { const percentDone = (Date.now() - window.speech2speechState.elapsedRecording) / 10000 if (percentDone >= 1 && percentDone!=Infinity) { if (window.speech2speechState.isReadingMic) { window.stopRecord() } } else { const circle = mic_progress_SVG_circle const radius = circle.r.baseVal.value const circumference = radius * 2 * Math.PI const offset = circumference - percentDone * circumference circle.style.strokeDasharray = `${circumference} ${circumference}` circle.style.strokeDashoffset = circumference circle.style.strokeDashoffset = Math.round(offset) requestAnimationFrame(animateRecordingProgress) } } window.clearVCMicSpinnerProgress = (percent=0) => { const circle = mic_progress_SVG_circle circle.style.stroke = "transparent" const radius = circle.r.baseVal.value const circumference = radius * 2 * Math.PI const offset = circumference - percent * circumference circle.style.strokeDasharray = `${circumference} ${circumference}` circle.style.strokeDashoffset = circumference circle.style.strokeDashoffset = Math.floor(offset) } window.clearVCMicSpinnerProgress = clearVCMicSpinnerProgress window.startRecord = async () => { doFetch(`http://localhost:8008/start_microphone_recording`, { method: "Post" }) window.speech2speechState.isReadingMic = true window.speech2speechState.elapsedRecording = Date.now() window.clearVCMicSpinnerProgress() mic_progress_SVG_circle.style.stroke = "red" requestAnimationFrame(animateRecordingProgress) } window.outputS2SRecording = (outPath, callback) => { toggleSpinnerButtons() doFetch(`http://localhost:8008/move_recorded_file`, { method: "Post", body: JSON.stringify({ file_path: outPath }) }).then(r=>r.text()).then(res => { callback() }) } window.useWavFileForspeech2speech = (fileName) => { // let sequence = dialogueInput.value.trim().replace("…", "...") // For some reason, the samplePlay audio element does not update the source when the file name is the same const tempFileNum = `${Math.random().toString().split(".")[1]}` let tempFileLocation = `${path}/output/temp-${tempFileNum}.wav` let style_emb = window.currentModel.audioPreviewPath // Default to the preview audio file, if an embedding can't be found in the json - This shouldn't happen try { style_emb = window.currentModel.games[0].base_speaker_emb // If this fails, the json isn't complete } catch (e) { console.log(e) } if (window.wavesurfer) { window.wavesurfer.stop() wavesurferContainer.style.opacity = 0 } window.tempFileLocation = `${__dirname.replace("/javascript", "").replace("\\javascript", "")}/output/temp-${tempFileNum}.wav` const options = { hz: window.userSettings.audio.hz, padStart: window.userSettings.audio.padStart, padEnd: window.userSettings.audio.padEnd, bit_depth: window.userSettings.audio.bitdepth, amplitude: window.userSettings.audio.amplitude, pitchMult: window.userSettings.audio.pitchMult, tempo: window.userSettings.audio.tempo, deessing: window.userSettings.audio.deessing, nr: window.userSettings.audio.nr, nf: window.userSettings.audio.nf, useNR: window.userSettings.audio.useNR, useSR: useSRCkbx.checked, useCleanup: useCleanupCkbx.checked } doFetch(`http://localhost:8008/runSpeechToSpeech`, { method: "Post", body: JSON.stringify({ input_path: fileName, useSR: useSRCkbx.checked, useCleanup: useCleanupCkbx.checked, isBatchMode: false, style_emb: style_emb_select.value=="default" ? window.currentModel.games[0].base_speaker_emb : style_emb_select.value.split(",").map(v=>parseFloat(v)), audio_out_path: tempFileLocation, doPitchShift: window.userSettings.s2s_prePitchShift, removeNoise: window.userSettings.s2s_removeNoise, // Removed from UI removeNoiseStrength: window.userSettings.s2s_noiseRemStrength, // Removed from UI vc_strength: window.userSettings.vc_strength, n_speakers: undefined, modelPath: undefined, voiceId: undefined, options: JSON.stringify(options) }) }).then(r=>r.text()).then(res => { // This block of code sometimes gets called before the audio file has actually finished flushing to file // I need a better way to make sure that this doesn't get called until it IS finished, but "for now", // I've set up some recursive re-attempts, below doTheRest window.clearVCMicSpinnerProgress() mic_progress_SVG.style.animation = "none" if (res=="TOO_SHORT") { window.toggleSpinnerButtons(true) window.errorModal(`

${window.i18n.VC_TOO_SHORT}

`) return } generateVoiceButton.disabled = true let hasLoaded = false let numRetries = 0 window.toggleSpinnerButtons(true) const doTheRest = () => { if (hasLoaded) { return } // window.wavesurfer = undefined tempFileLocation = tempFileLocation.replaceAll(/\\/g, "/") tempFileLocation = tempFileLocation.replaceAll('/resources/app/resources/app', "/resources/app") tempFileLocation = tempFileLocation.replaceAll('/resources/app', "") dialogueInput.value = "" textEditorElem.innerHTML = "" window.isGenerating = false window.speech2speechState.s2s_running = true if (res.includes("Traceback")) { window.errorModal(`

${window.i18n.SOMETHING_WENT_WRONG}

${res.replaceAll("\n", "
")}`) } else if (res.includes("ERROR:APP_VERSION")) { const speech2speechModelVersion = "v"+res.split(",")[1] window.errorModal(`${window.i18n.ERR_XVASPEECH_MODEL_VERSION.replace("_1", speech2speechModelVersion)} ${window.appVersion}`) } else { keepSampleButton.disabled = false window.tempFileLocation = tempFileLocation // Wavesurfer if (!window.wavesurfer) { window.initWaveSurfer(window.tempFileLocation) } else { window.wavesurfer.load(window.tempFileLocation) } window.wavesurfer.on("ready", () => { hasLoaded = true wavesurferContainer.style.opacity = 1 if (window.userSettings.autoPlayGen) { if (window.userSettings.playChangedAudio) { const playbackStartEnd = window.sequenceEditor.getChangedTimeStamps(start_index, end_index, window.wavesurfer.getDuration()) if (playbackStartEnd) { wavesurfer.play(playbackStartEnd[0], playbackStartEnd[1]) } else { wavesurfer.play() } } else { wavesurfer.play() } window.sequenceEditor.adjustedLetters = new Set() samplePlayPause.innerHTML = window.i18n.PAUSE } }) // Persistance across sessions localStorage.setItem("tempFileLocation", tempFileLocation) generateVoiceButton.innerHTML = window.i18n.GENERATE_VOICE if (window.userSettings.s2s_autogenerate) { speech2speechState.s2s_autogenerate = true generateVoiceButton.click() } keepSampleButton.dataset.newFileLocation = `${window.userSettings[`outpath_${window.currentGame.gameId}`]}/${title.dataset.modelId}/vc_${tempFileNum}.wav` keepSampleButton.disabled = false keepSampleButton.style.display = "block" samplePlayPause.style.display = "block" setTimeout(doTheRest, 100) } } doTheRest() }).catch(e => { console.log(e) window.toggleSpinnerButtons(true) window.errorModal(`

${window.i18n.SOMETHING_WENT_WRONG}

`) mic_progress_SVG.style.animation = "none" }) } window.stopRecord = (cancelled) => { fs.writeFileSync(`${window.path}/python/temp_stop_recording`, "") if (!cancelled) { window.clearVCMicSpinnerProgress(0.35) mic_progress_SVG.style.animation = "spin 1.5s linear infinite" mic_progress_SVG_circle.style.stroke = "white" const fileName = `${__dirname.replace("\\javascript", "").replace("/javascript", "").replace(/\\/g,"/")}/output/recorded_file.wav` window.sequenceEditor.clear() window.outputS2SRecording(fileName, () => { window.useWavFileForspeech2speech(fileName) }) } window.speech2speechState.isReadingMic = false window.speech2speechState.elapsedRecording = 0 window.clearVCMicSpinnerProgress() } window.micClickHandler = (ctrlKey) => { if (window.speech2speechState.isReadingMic) { window.stopRecord() } else { if (window.currentModel && generateVoiceButton.innerHTML == window.i18n.GENERATE_VOICE) { if (window.currentModel.modelType.toLowerCase()=="xvapitch") { window.startRecord() } } else { window.errorModal(window.i18n.LOAD_TARGET_MODEL) } } } mic_SVG.addEventListener("mouseenter", () => { if (!window.currentModel || window.currentModel.modelType.toLowerCase()!="xvapitch") { s2s_voiceId_selected_label.style.display = "inline-block" } }) mic_SVG.addEventListener("mouseleave", () => { s2s_voiceId_selected_label.style.display = "none" }) mic_SVG.addEventListener("click", event => window.micClickHandler(event.ctrlKey)) mic_SVG.addEventListener("contextmenu", () => { if (window.speech2speechState.isReadingMic) { window.stopRecord(true) } else { const audioPreview = createElem("audio", {autoplay: false}, createElem("source", { src: `${__dirname.replace("\\javascript", "").replace("/javascript", "").replace(/\\/g,"/")}/output/recorded_file_post${window.userSettings.s2s_prePitchShift?"_praat":""}.wav` })) audioPreview.setSinkId(window.userSettings.base_speaker) } }) window.clearVCMicSpinnerProgress() // File dragging window.uploadS2SFile = (eType, event) => { if (["dragenter", "dragover"].includes(eType)) { clearVCMicSpinnerProgress(1) mic_progress_SVG_circle.style.stroke = "white" } if (["dragleave", "drop"].includes(eType)) { window.clearVCMicSpinnerProgress() } event.preventDefault() event.stopPropagation() if (eType=="drop") { if (window.currentModel && generateVoiceButton.innerHTML == window.i18n.GENERATE_VOICE) { const dataTransfer = event.dataTransfer const files = Array.from(dataTransfer.files) const file = files[0] if (!file.name.endsWith(".wav")) { window.errorModal(window.i18n.ONLY_WAV_S2S) return } clearVCMicSpinnerProgress(0.35) // mic_progress_SVG.style.animation = "spin 1.5s linear infinite" // mic_progress_SVG_circle.style.stroke = "white" const fileName = `${__dirname.replace("\\javascript", "").replace("/javascript", "").replace(/\\/g,"/")}/output/recorded_file.wav` fs.copyFileSync(file.path, fileName) window.sequenceEditor.clear() toggleSpinnerButtons() window.useWavFileForspeech2speech(fileName) } else { window.errorModal(window.i18n.LOAD_TARGET_MODEL) } } } micContainer.addEventListener("dragenter", event => window.uploadS2SFile("dragenter", event), false) micContainer.addEventListener("dragleave", event => window.uploadS2SFile("dragleave", event), false) micContainer.addEventListener("dragover", event => window.uploadS2SFile("dragover", event), false) micContainer.addEventListener("drop", event => window.uploadS2SFile("drop", event), false) // Disable page navigation on badly dropped file window.document.addEventListener("dragover", event => event.preventDefault(), false) window.document.addEventListener("drop", event => event.preventDefault(), false)