piper-demo / js /app.js
Michael Hansen
First working version
0c6d0de
raw
history blame contribute delete
8.81 kB
import { setVoice, textToWavAudio } from "./piper.js";
const voiceRoot = "https:huggingface.co/rhasspy/piper-voices/resolve/main";
let languageToSelect = null;
let voiceToSelect = null;
let qualityToSelect = null;
let voices = {};
const qualitySort = {
x_low: 0,
low: 1,
medium: 2,
high: 3,
};
let voiceId = "";
let voiceUrl = "";
let loadedVoiceId = "";
async function main() {
loadVoices();
const buttonSpeak = document.getElementById("buttonSpeak");
const audioTTS = document.getElementById("audioTTS");
const textInput = document.getElementById("textInput");
const status = document.getElementById("status");
const speakerSelect = document.getElementById("speaker");
const inputLengthScale = document.getElementById("lengthScale");
const inputNoiseScale = document.getElementById("noiseScale");
const inputNoiseWScale = document.getElementById("noiseWScale");
buttonSpeak.addEventListener("click", async () => {
const text = textInput.value;
if (!text) {
return;
}
if (!voiceId) {
console.log("ERROR: No voice id");
return;
}
if (voiceId != loadedVoiceId) {
if (!voiceUrl) {
console.log("ERROR: No voice URL");
return;
}
status.innerHTML = "Loading voice...";
await setVoice(
`${voiceUrl}/${voiceId}.onnx?download=true`,
`${voiceUrl}/${voiceId}.onnx.json?download=true`,
);
loadedVoiceId = voiceId;
}
let speakerId = null;
if (speakerSelect.selectedIndex > 0) {
speakerId = parseInt(speakerSelect.value);
}
let lengthScale = parseFloat(inputLengthScale.value);
if (isNaN(lengthScale)) {
lengthScale = null;
}
let noiseScale = parseFloat(inputNoiseScale.value);
if (isNaN(noiseScale)) {
noiseScale = null;
}
let noiseWScale = parseFloat(inputNoiseWScale.value);
if (isNaN(noiseWScale)) {
noiseWScale = null;
}
status.innerHTML = "Synthesizing audio...";
const wavAudio = await textToWavAudio(
text,
speakerId,
lengthScale,
noiseScale,
noiseWScale,
);
const audioURL = URL.createObjectURL(wavAudio);
audioTTS.src = audioURL;
audioTTS.play();
status.innerHTML = "Ready";
});
textInput.disabled = false;
buttonSpeak.disabled = false;
window.setLanguage = setLanguage;
window.setVoiceName = setVoiceName;
window.setQuality = setQuality;
window.setSpeaker = setSpeaker;
}
document.addEventListener("DOMContentLoaded", () => {
main();
});
// ----------------------------------------------------------------------------
export function setLanguage() {
var language = document.getElementById("languages").value;
if (language.length > 0) {
var voiceSelect = document.getElementById("voice");
while (voiceSelect.options.length > 1) {
voiceSelect.remove(voiceSelect.options.length - 1);
}
let names = [];
for (let key in voices) {
let voice = voices[key];
if (voice.language.code == language) {
names.push(voice.name);
}
}
names = Array.from(new Set(names)).sort();
for (let i in names) {
let name = names[i];
let option = document.createElement("option");
option.text = name;
option.value = name;
voiceSelect.add(option);
}
if (voiceToSelect) {
voiceSelect.value = voiceToSelect;
voiceToSelect = null;
setVoiceName();
} else if (voiceSelect.options.length > 1) {
// Select first voice
voiceSelect.selectedIndex = 1;
setVoiceName();
}
}
}
function setVoiceName() {
var language = document.getElementById("languages").value;
var voiceName = document.getElementById("voice").value;
if (voiceName.length > 0) {
var qualitySelect = document.getElementById("quality");
while (qualitySelect.options.length > 1) {
qualitySelect.remove(qualitySelect.options.length - 1);
}
let qualities = [];
for (let key in voices) {
let voice = voices[key];
if (voice.language.code == language && voice.name == voiceName) {
qualities.push(voice.quality);
}
}
qualities = Array.from(new Set(qualities)).sort(
(a, b) => qualitySort[a] - qualitySort[b],
);
for (let i in qualities) {
let quality = qualities[i];
let option = document.createElement("option");
option.text = quality;
option.value = quality;
qualitySelect.add(option);
}
if (qualityToSelect) {
qualitySelect.value = qualityToSelect;
qualityToSelect = null;
setQuality();
} else if (qualitySelect.options.length > 1) {
// Select highest quality
qualitySelect.selectedIndex = qualitySelect.options.length - 1;
setQuality();
}
}
}
function setQuality() {
var language = document.getElementById("languages").value;
var voiceName = document.getElementById("voice").value;
var quality = document.getElementById("quality").value;
if (quality.length > 0) {
var speakerSelect = document.getElementById("speaker");
while (speakerSelect.options.length > 1) {
speakerSelect.remove(speakerSelect.options.length - 1);
}
var numSpeakers = 1;
var speakerIdMap = {};
for (let key in voices) {
let voice = voices[key];
if (
voice.language.code == language &&
voice.name == voiceName &&
voice.quality == quality
) {
numSpeakers = voice.num_speakers;
speakerIdMap = voice.speaker_id_map;
break;
}
}
if (numSpeakers <= 1) {
// Single speaker model
let option = document.createElement("option");
option.text = "default";
option.value = "0";
speakerSelect.add(option);
} else {
// Multi-speaker model
let sortedSpeakers = Object.keys(speakerIdMap).sort(
(a, b) => speakerIdMap[a] - speakerIdMap[b],
);
for (let i in sortedSpeakers) {
let speaker = sortedSpeakers[i];
let option = document.createElement("option");
option.text = speaker + " (" + i.toString() + ")";
option.value = i.toString();
speakerSelect.add(option);
}
}
if (speakerSelect.options.length > 1) {
// Select first speaker
speakerSelect.selectedIndex = 1;
setSpeaker();
}
voiceId = `${language}-${voiceName}-${quality}`;
window.location.hash = voiceId;
}
}
function setSpeaker() {
var language = document.getElementById("languages").value;
let languageFamily = language.split("_")[0];
var voiceName = document.getElementById("voice").value;
var quality = document.getElementById("quality").value;
var speaker = document.getElementById("speaker").value;
if (speaker.length > 0) {
for (let key in voices) {
let voice = voices[key];
if (
voice.language.code == language &&
voice.name == voiceName &&
voice.quality == quality
) {
voiceUrl = `${voiceRoot}/${languageFamily}/${language}/${voiceName}/${quality}`;
let aKey = document.getElementById("key");
aKey.innerHTML = key;
aKey.href = voiceUrl;
fetch(`txt/${languageFamily}.txt`)
.then((response) => response.text())
.then((text) => {
document.getElementById("textInput").innerHTML = text;
});
}
}
}
}
function loadVoices() {
let hash = window.location.hash;
if (hash.length > 0) {
let voiceIdRegexp = RegExp("^#([^-]+)-([^-]+)-([^-]+)$");
let match = voiceIdRegexp.exec(hash);
if (match) {
languageToSelect = match[1];
voiceToSelect = match[2];
qualityToSelect = match[3];
}
}
fetch(`${voiceRoot}/voices.json?download=true`)
.then((response) => response.json())
.then((response_obj) => {
voices = response_obj;
let voiceLanguages = [];
let languageNames = {};
for (let key in voices) {
let voice = voices[key];
voiceLanguages.push(voice.language.code);
languageNames[voice.language.code] =
voice.language.name_native +
" (" +
voice.language.name_english +
", " +
voice.language.country_english +
")";
}
let sortedLanguages = Array.from(new Set(voiceLanguages)).sort();
let languagesSelect = document.getElementById("languages");
for (let i in sortedLanguages) {
let language = sortedLanguages[i];
let option = document.createElement("option");
option.text = languageNames[language];
option.value = language;
languagesSelect.add(option);
}
if (languageToSelect) {
languagesSelect.value = languageToSelect;
languageToSelect = null;
setLanguage();
}
});
}