xVASynth-TTS / javascript /embeddings.js
Pendrokar's picture
xVASynth v3 code for English
19c8b95
raw
history blame
33.8 kB
"use strict"
window.openEmbeddingsWindow = () => {
closeModal(undefined, embeddingsContainer).then(() => {
embeddingsContainer.style.opacity = 0
embeddingsContainer.style.display = "flex"
requestAnimationFrame(() => requestAnimationFrame(() => embeddingsContainer.style.opacity = 1))
requestAnimationFrame(() => requestAnimationFrame(() => chromeBar.style.opacity = 1))
})
}
window.embeddingsState = {
data: {},
allData: {},
clickedObject: undefined,
spritesOn: true,
gendersOn: false,
voiceCheckboxes: [],
isReady: false,
isOpen: false,
sceneData: {},
mouseIsDown: false,
rightMouseIsDown: false,
mousePos: {x: 0, y: 0}
}
function componentToHex(c) {
c = Math.min(255, c)
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
function hexToRgb(hex, normalize) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
const colour = result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
if (normalize && colour) {
colour.r /= 255
colour.g /= 255
colour.b /= 255
}
return colour
}
window.populateGamesList = () => {
embeddingsGamesListContainer.innerHTML = ""
Object.keys(window.gameAssets).sort((a,b)=>a<b?-1:1).forEach(gameId => {
const gameSelectContainer = createElem("div")
const gameCheckbox = createElem(`input#embs_${gameId}`, {type: "checkbox"})
gameCheckbox.checked = true
const gameButton = createElem("button.fixedColour")
gameButton.style.setProperty("background-color", `#${window.embeddingsState.gameColours[gameId]}`, "important")
gameButton.style.display = "flex"
gameButton.style.alignItems = "center"
gameButton.style.margin = "auto"
gameButton.style.marginTop = "8px"
const buttonLabel = createElem("span", window.embeddingsState.gameTitles[gameId])
gameButton.addEventListener("click", e => {
if (e.target==gameButton || e.target==buttonLabel) {
gameCheckbox.click()
window.populateVoicesList()
window.computeEmbsAndDimReduction()
}
})
gameButton.addEventListener("contextmenu", e => {
if (e.target==gameButton || e.target==buttonLabel) {
Array.from(embeddingsGamesListContainer.querySelectorAll("input")).forEach(ckbx => ckbx.checked = false)
gameCheckbox.click()
window.populateVoicesList()
window.computeEmbsAndDimReduction()
}
})
gameCheckbox.addEventListener("click", () => {
window.populateVoicesList()
window.computeEmbsAndDimReduction()
})
gameButton.appendChild(gameCheckbox)
gameButton.appendChild(buttonLabel)
gameSelectContainer.appendChild(gameButton)
embeddingsGamesListContainer.appendChild(gameSelectContainer)
})
}
window.populateVoicesList = () => {
const enabledGames = Array.from(embeddingsGamesListContainer.querySelectorAll("input"))
.map(elem => [elem.checked, elem.id.replace("embs_", "")])
.filter(checkedId => checkedId[0])
.map(checkedId => checkedId[1].replace("embs_", ""))
const checkboxes = []
embeddingsRecordsContainer.innerHTML = ""
Object.keys(window.games).forEach(gameId => {
if (!enabledGames.includes(gameId)) {
return
}
window.games[gameId].models.forEach(model => {
model.variants.forEach(variant => {
const voiceRowElem = createElem("div")
const voiceCkbx = createElem(`input#embsVoice_${variant.voiceId}`, {type: "checkbox"})
voiceCkbx.checked = true
voiceCkbx.addEventListener("click", () => {
window.computeEmbsAndDimReduction()
})
checkboxes.push(voiceCkbx)
const showVoiceBtn = createElem("button.smallButton.fixedColour", "Show")
showVoiceBtn.style.background = `#${window.embeddingsState.gameColours[model.gameId]}`
showVoiceBtn.addEventListener("click", () => {
if (!voiceCkbx.checked) {
return window.errorModal(window.i18n.VEMB_VOICE_NOT_ENABLED)
}
const point = window.embeddingsState.sceneData.points.find(point => point.data.voiceId==variant.voiceId)
window.embeddingsState.sceneData.controls.target.set(point.position.x, point.position.y, point.position.z)
const cameraPos = window.embeddingsState.sceneData.camera.position
const deltaX = (point.position.x - cameraPos.x)
const deltaY = (point.position.y - cameraPos.y)
const deltaZ = (point.position.z - cameraPos.z)
window.embeddingsState.sceneData.camera.position.set(cameraPos.x+deltaX/2, cameraPos.y+deltaY/2, cameraPos.z+deltaZ/2)
})
const nameElem = createElem("div", model.voiceName+(model.variants.length>1?` (${variant.variantName})`:""))
nameElem.title = model.voiceName
const gameElem = createElem("div", window.embeddingsState.gameTitles[model.gameId])
gameElem.title = window.embeddingsState.gameTitles[model.gameId]
const voiceGender = createElem("div", variant.gender)
voiceGender.title = variant.gender
voiceRowElem.appendChild(createElem("div", voiceCkbx))
voiceRowElem.appendChild(createElem("div", showVoiceBtn))
voiceRowElem.appendChild(nameElem)
voiceRowElem.appendChild(gameElem)
voiceRowElem.appendChild(voiceGender)
if (embeddingsSearchBar.value.length && !model.voiceName.toLowerCase().trim().includes(embeddingsSearchBar.value.toLowerCase().trim())) {
return
}
embeddingsRecordsContainer.appendChild(voiceRowElem)
})
})
})
window.embeddingsState.voiceCheckboxes = checkboxes
}
embeddingsSearchBar.addEventListener("keyup", () => window.populateVoicesList())
window.initDataMappings = () => {
window.embeddingsState.voiceIdToModel = {}
window.embeddingsState.gameShortIdToGameId = {}
window.embeddingsState.gameColours = {}
window.embeddingsState.gameTitles = {}
const idToGame = {}
Object.keys(window.gameAssets).forEach(gameId => {
const id = window.gameAssets[gameId].gameCode
idToGame[id] = gameId
})
Object.keys(window.gameAssets).forEach(gameId => {
// Short game ID to full game ID
const gameShortId = window.gameAssets[gameId].gameCode.toLowerCase()
window.embeddingsState.gameShortIdToGameId[gameShortId] = gameId.toLowerCase()
// Game title
const title = window.gameAssets[gameId].gameName
window.embeddingsState.gameTitles[gameId] = title
// Game colour
let colour = window.gameAssets[gameId].themeColourPrimary
colour = colour.length==3 ? `${colour[0]}${colour[0]}${colour[1]}${colour[1]}${colour[2]}${colour[2]}` : colour
window.embeddingsState.gameColours[gameId] = colour
})
Object.keys(window.games).forEach(gameId => {
// Voice Id to model data
window.games[gameId].models.forEach(model => {
model.variants.forEach(variant => {
window.embeddingsState.voiceIdToModel[variant.voiceId] = JSON.parse(JSON.stringify(model))
if (model.variants.length>1) {
let voiceName = window.embeddingsState.voiceIdToModel[variant.voiceId].voiceName
voiceName = `${voiceName} (${variant.variantName})`
window.embeddingsState.voiceIdToModel[variant.voiceId].voiceName = voiceName
}
})
})
})
}
window.initEmbeddingsScene = () => {
window.initDataMappings()
window.populateGamesList()
window.populateVoicesList()
embeddingsSceneContainer.addEventListener("mousedown", (event) => {
window.embeddingsState.mousePos.x = parseInt(event.layerX)
window.embeddingsState.mousePos.y = parseInt(event.layerY)
})
embeddingsSceneContainer.addEventListener("mouseup", (event) => {
const mouseX = parseInt(event.layerX)
const mouseY = parseInt(event.layerY)
if (event.button==0) {
if (Math.abs(mouseX-window.embeddingsState.mousePos.x)<10 && Math.abs(mouseY-window.embeddingsState.mousePos.y)<10) {
window.embeddingsState.mouseIsDown = true
setTimeout(() => {window.embeddingsState.mouseIsDown = false}, 100)
}
} else if (event.button==2) {
if (Math.abs(mouseX-window.embeddingsState.mousePos.x)<10 && Math.abs(mouseY-window.embeddingsState.mousePos.y)<10) {
window.embeddingsState.rightMouseIsDown = true
setTimeout(() => {window.embeddingsState.rightMouseIsDown = false}, 100)
}
}
})
window.embeddingsState.isReady = false
const SPHERE_RADIUS = 3
const SPHERE_V_COUNT = 50
// Renderer
window.embeddingsState.renderer = new THREE.WebGLRenderer({alpha: true, antialias: true})
window.embeddingsState.renderer.setPixelRatio( window.devicePixelRatio )
window.embeddingsState.renderer.setSize(embeddingsSceneContainer.offsetWidth, embeddingsSceneContainer.offsetHeight)
embeddingsSceneContainer.appendChild(window.embeddingsState.renderer.domElement)
// Scene and camera
const scene = new THREE.Scene()
window.embeddingsState.sceneData.camera = new THREE.PerspectiveCamera(60, embeddingsSceneContainer.offsetWidth/embeddingsSceneContainer.offsetHeight, 0.001, 100000)
window.embeddingsState.sceneData.camera.position.set( -100, 0, 0 )
// Controls
window.embeddingsState.sceneData.controls = new THREE.OrbitControls(window.embeddingsState.sceneData.camera, window.embeddingsState.renderer.domElement)
window.embeddingsState.sceneData.controls2 = new THREE.TrackballControls(window.embeddingsState.sceneData.camera, window.embeddingsState.renderer.domElement)
window.embeddingsState.sceneData.controls.target.set(window.embeddingsState.sceneData.camera.position.x+0.15, window.embeddingsState.sceneData.camera.position.y, window.embeddingsState.sceneData.camera.position.z)
window.embeddingsState.sceneData.controls.enableDamping = true
window.embeddingsState.sceneData.controls.dampingFactor = 0.025
window.embeddingsState.sceneData.controls.screenSpacePanning = true
window.embeddingsState.sceneData.controls.rotateSpeed = 1/6
window.embeddingsState.sceneData.controls.panSpeed = 1
window.embeddingsState.sceneData.controls.minDistance = 50
window.embeddingsState.sceneData.controls.maxDistance = 500
window.embeddingsState.sceneData.controls2.noRotate = true
window.embeddingsState.sceneData.controls2.noPan = true
window.embeddingsState.sceneData.controls2.noZoom = true
window.embeddingsState.sceneData.controls2.zoomSpeed = 1/2// 1.5
window.embeddingsState.sceneData.controls2.dynamicDampingFactor = 0.2
const light = new THREE.DirectionalLight( 0xffffff, 0.5 )
light.position.set( -1, 1, 1 ).normalize()
scene.add(light)
scene.add(new THREE.AmbientLight( 0xffffff, 0.5 ))
// Mouse event ray caster
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
window.embeddingsState.renderer.domElement.addEventListener("mousemove", event => {
const sizeY = event.target.height
const sizeX = event.target.width
mouse.x = event.offsetX / sizeX * 2 - 1
mouse.y = -event.offsetY / sizeY * 2 + 1
}, false)
window.embeddingsState.sceneData.sprites = []
window.embeddingsState.sceneData.points = []
window.refreshEmbeddingsScenePoints = () => {
const enabledGames = Array.from(embeddingsGamesListContainer.querySelectorAll("input"))
.map(elem => [elem.checked, elem.id.replace("embs_", "")])
.filter(checkedId => checkedId[0])
.map(checkedId => checkedId[1].replace("embs_", ""))
const data = window.embeddingsState.data
const newDataNames = Object.keys(data)
const oldDataKept = []
const newSprites = []
const newPoints = []
// Remove any existing data
;[window.embeddingsState.sceneData.points, window.embeddingsState.sceneData.sprites].forEach(dataList => {
dataList.forEach(object => {
const objectName = object.data.voiceId
if (newDataNames.includes(objectName)) {
oldDataKept.push(objectName)
const coords = {
x: parseFloat(data[objectName][0]),
y: parseFloat(data[objectName][1])-(object.data.type=="text"?SPHERE_RADIUS*1.5:0),
z: parseFloat(data[objectName][2])
}
object.data.isMoving = true
object.data.newPos = coords
if (object.data.type=="text") {
newSprites.push(object)
} else {
newPoints.push(object)
}
} else {
scene.remove(object)
}
})
})
// Add the new data
window.embeddingsState.sceneData.sprites = newSprites
window.embeddingsState.sceneData.points = newPoints
Object.keys(data).forEach(voiceId => {
if (oldDataKept.includes(voiceId)) {
return
}
const game = Object.keys(window.embeddingsState.gameShortIdToGameId).includes(voiceId.split("_")[0]) ? window.embeddingsState.gameShortIdToGameId[voiceId.split("_")[0]] : "other"
let gender
if (Object.keys(window.embeddingsState.voiceIdToModel).includes(voiceId)) {
gender = window.embeddingsState.voiceIdToModel[voiceId].gender || window.embeddingsState.voiceIdToModel[voiceId].variants[0].gender
} else {
gender = window.embeddingsState.allData[voiceId].voiceGender
}
gender = gender ? gender.toLowerCase() : "other"
// if (!enabledGames.includes(game)) {
// return
// }
// Filter out data by gender
// if (gender=="male" && !embeddingsMalesCkbx.checked) {
// return
// }
// if (gender=="female" && !embeddingsFemalesCkbx.checked) {
// return
// }
// if (gender=="other" && !embeddingsOtherGendersCkbx.checked) {
// return
// }
// Colour dict
const colour = hexToRgb("#"+window.embeddingsState.gameColours[game])
const genderColours = {
"f": {r: 200, g: 0, b: 0},
"m": {r: 0, g: 0, b: 200},
"o": {r: 85, g: 85, b: 85},
}
const coords = {
x: parseFloat(data[voiceId][0]),
y: parseFloat(data[voiceId][1]),
z: parseFloat(data[voiceId][2])
}
const genderColour = gender=="female" ? genderColours["f"] : (gender=="male" ? genderColours["m"] : genderColours["o"])
const pointGeometry = new THREE.SphereGeometry(SPHERE_RADIUS, SPHERE_V_COUNT, SPHERE_V_COUNT)
const pointMaterial = new THREE.MeshLambertMaterial({
color: window.embeddingsState.gendersOn ? rgbToHex(genderColour.r, genderColour.g, genderColour.b) : "#"+window.embeddingsState.gameColours[game],
transparent: true
})
pointMaterial.emissive.emissiveIntensity = 1
// Point sphere
const point = new THREE.Mesh(pointGeometry, pointMaterial)
point.position.x = coords.x
point.position.y = coords.y
point.position.z = coords.z
point.name = `point|${voiceId}`
point.data = {
type: "point",
voiceId: voiceId,
game: game,
gameColour: {r: colour.r, g: colour.g, b: colour.b},
genderColour: genderColour
}
window.embeddingsState.sceneData.points.push(point)
scene.add(point)
let voiceName
if (Object.keys(window.embeddingsState.voiceIdToModel).includes(voiceId)) {
voiceName = window.embeddingsState.voiceIdToModel[voiceId].voiceName
} else {
voiceName = window.embeddingsState.allData[voiceId].voiceName
}
// Text sprite
const sprite = new THREE.TextSprite({
text: voiceName,
fontFamily: 'Helvetica, sans-serif',
fontSize: 2,
strokeColor: '#ffffff',
strokeWidth: 0,
color: '#24ff00',
material: {color: "white"}
})
sprite.position.x = coords.x
sprite.position.y = coords.y-SPHERE_RADIUS*1.5
sprite.position.z = coords.z
sprite.name = `sprite|${voiceId}`
sprite.data = {type: "text", voiceId: voiceId, game: game}
window.embeddingsState.sceneData.sprites.push(sprite)
scene.add(sprite)
})
}
window.refreshEmbeddingsScenePoints()
let hoveredObject = undefined
let clickedObject = undefined
window.embeddings_render = () => {
if (!window.embeddingsState.isReady) {
return
}
requestAnimationFrame(window.embeddings_render)
const target = window.embeddingsState.sceneData.controls.target
window.embeddingsState.sceneData.controls.update()
window.embeddingsState.sceneData.controls2.target.set(target.x, target.y, target.z)
window.embeddingsState.sceneData.controls2.update()
window.embeddingsState.sceneData.camera.updateMatrixWorld()
raycaster.setFromCamera( mouse, window.embeddingsState.sceneData.camera );
// Move objects
[window.embeddingsState.sceneData.points, window.embeddingsState.sceneData.sprites].forEach(dataList => {
dataList.forEach(object => {
if (object.data.isMoving) {
if (Math.abs(object.position.x-object.data.newPos.x)>0.005) {
object.position.x += (object.data.newPos.x - object.position.x) / 20
object.position.y += (object.data.newPos.y - object.position.y) / 20
object.position.z += (object.data.newPos.z - object.position.z) / 20
} else {
object.data.isMoving = false
object.data.newPos = undefined
}
}
})
})
// Handle mouse events
let intersects = raycaster.intersectObjects(scene.children, true)
if (intersects.length) {
if (intersects.length>2) {
intersects = [intersects.find(it => it.object.data.type=="point")]
}
if (intersects.length==0 || intersects[0]==undefined || intersects[0].object==undefined || intersects[0].object.data.type=="text") {
window.embeddingsState.renderer.render(scene, window.embeddingsState.sceneData.camera)
return
}
window.embeddingsState.renderer.domElement.style.cursor = "pointer"
if (hoveredObject != undefined && hoveredObject.object.data.voiceId!=intersects[0].object.data.voiceId) {
const colour = window.embeddingsState.gendersOn ? hoveredObject.object.data.genderColour : hoveredObject.object.data.gameColour
hoveredObject.object.material.color.r = Math.min(1, colour.r/255)
hoveredObject.object.material.color.g = Math.min(1, colour.g/255)
hoveredObject.object.material.color.b = Math.min(1, colour.b/255)
}
hoveredObject = intersects[0]
const colour = window.embeddingsState.gendersOn ? hoveredObject.object.data.genderColour : hoveredObject.object.data.gameColour
hoveredObject.object.material.color.r = Math.min(1, colour.r/255*1.5)
hoveredObject.object.material.color.g = Math.min(1, colour.g/255*1.5)
hoveredObject.object.material.color.b = Math.min(1, colour.b/255*1.5)
// Right click does voice audio preview
if (window.embeddingsState.rightMouseIsDown) {
window.embeddingsState.rightMouseIsDown = false
const voiceId = hoveredObject.object.data.voiceId
const gameId = hoveredObject.object.data.game
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
const audioPreviewPath = `${modelsPathForGame}/${voiceId}.wav`
if (fs.existsSync(audioPreviewPath)) {
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
src: audioPreviewPath
}))
audioPreview.setSinkId(window.userSettings.base_speaker)
} else {
window.errorModal(window.i18n.VEMB_NO_PREVIEW)
}
}
// Left click does voice click
if (window.embeddingsState.mouseIsDown) {
if (window.embeddingsState.clickedObject==undefined || window.embeddingsState.clickedObject.voiceId!=hoveredObject.object.data.voiceId) {
if (window.embeddingsState.clickedObject!=undefined) {
window.embeddingsState.clickedObject.object.material.emissive.setRGB(0,0,0)
}
window.embeddingsState.clickedObject = {
voiceId: hoveredObject.object.data.voiceId,
game: hoveredObject.object.data.game,
object: hoveredObject.object
}
hoveredObject.object.material.emissive.setRGB(0, 1, 0)
const voiceId = window.embeddingsState.clickedObject.object.data.voiceId
if (Object.keys(window.embeddingsState.voiceIdToModel).includes(voiceId)) {
embeddingsVoiceGameDisplay.innerHTML = window.embeddingsState.gameTitles[window.embeddingsState.clickedObject.object.data.game]
embeddingsVoiceNameDisplay.innerHTML = window.embeddingsState.voiceIdToModel[voiceId].voiceName
embeddingsVoiceGenderDisplay.innerHTML = window.embeddingsState.voiceIdToModel[voiceId].gender || window.embeddingsState.voiceIdToModel[voiceId].variants[0].gender
} else {
embeddingsVoiceGameDisplay.innerHTML = window.embeddingsState.gameTitles[window.embeddingsState.clickedObject.object.data.game]
embeddingsVoiceNameDisplay.innerHTML = window.embeddingsState.allData[voiceId].voiceName
embeddingsVoiceGenderDisplay.innerHTML = window.embeddingsState.allData[voiceId].voiceGender
}
}
}
} else {
window.embeddingsState.renderer.domElement.style.cursor = "default"
if (hoveredObject != undefined) {
const colour = window.embeddingsState.gendersOn ? hoveredObject.object.data.genderColour : hoveredObject.object.data.gameColour
hoveredObject.object.material.color.r = Math.min(1, colour.r/255)
hoveredObject.object.material.color.g = Math.min(1, colour.g/255)
hoveredObject.object.material.color.b = Math.min(1, colour.b/255)
}
if (window.embeddingsState.mouseIsDown && window.embeddingsState.clickedObject!=undefined) {
window.embeddingsState.clickedObject.object.material.emissive.setRGB(0,0,0)
window.embeddingsState.clickedObject = undefined
embeddingsVoiceGameDisplay.innerHTML = ""
embeddingsVoiceNameDisplay.innerHTML = ""
embeddingsVoiceGenderDisplay.innerHTML = ""
}
}
window.embeddingsState.renderer.render(scene, window.embeddingsState.sceneData.camera)
}
window.embeddingsState.isReady = true
window.embeddings_render()
window.toggleSprites = () => {
window.embeddingsState.spritesOn = !window.embeddingsState.spritesOn
window.embeddingsState.sceneData.sprites.forEach(sprite => {
sprite.material.visible = window.embeddingsState.spritesOn
})
}
window.toggleGenders = () => {
window.embeddingsState.gendersOn = !window.embeddingsState.gendersOn
window.embeddingsState.sceneData.points.forEach(point => {
const colour = window.embeddingsState.gendersOn ? point.data.genderColour : point.data.gameColour
point.material.color.r = Math.min(1, colour.r/255)
point.material.color.g = Math.min(1, colour.g/255)
point.material.color.b = Math.min(1, colour.b/255)
})
}
window.addEventListener("resize", () => {
if (window.embeddingsState.isOpen) {
window.embeddings_updateSize()
}
})
}
window.embeddings_updateSize = () => {
if (window.embeddingsState.isReady) {
window.embeddingsState.sceneData.camera.aspect = embeddingsSceneContainer.offsetWidth/embeddingsSceneContainer.offsetHeight
window.embeddingsState.sceneData.camera.updateProjectionMatrix()
window.embeddingsState.renderer.setSize(embeddingsSceneContainer.offsetWidth, embeddingsSceneContainer.offsetHeight)
}
}
embeddingsPreviewButton.addEventListener("click", () => {
if (window.embeddingsState.clickedObject) {
const voiceId = window.embeddingsState.clickedObject.object.data.voiceId
const gameId = window.embeddingsState.clickedObject.object.data.game
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
const audioPreviewPath = `${modelsPathForGame}/${voiceId}.wav`
if (fs.existsSync(audioPreviewPath)) {
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
src: audioPreviewPath
}))
audioPreview.setSinkId(window.userSettings.base_speaker)
} else {
window.errorModal(window.i18n.VEMB_NO_PREVIEW)
}
} else {
window.errorModal(window.i18n.VEMB_SELECT_VOICE_FIRST)
}
})
embeddingsLoadButton.addEventListener("click", () => {
if (window.embeddingsState.clickedObject) {
const voiceId = window.embeddingsState.clickedObject.object.data.voiceId
const gameId = window.embeddingsState.clickedObject.object.data.game
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
const modelPath = `${modelsPathForGame}/${voiceId}.pt`
if (fs.existsSync(modelPath)) {
window.changeGame(window.gameAssets[gameId])
// Simulate voice loading through the UI
let voiceName
window.games[gameId].models.forEach(model => {
model.variants.forEach(variant => {
if (variant.voiceId==voiceId) {
voiceName = model.voiceName
}
})
})
const voiceButton = Array.from(voiceTypeContainer.children).find(button => button.innerHTML==voiceName)
voiceButton.click()
closeModal().then(() => {
generateVoiceButton.click()
})
} else {
window.errorModal(window.i18n.VEMB_NO_MODEL)
}
} else {
window.errorModal(window.i18n.VEMB_SELECT_VOICE_FIRST)
}
})
embeddingsKey.addEventListener("change", () => {
if (embeddingsKey.value=="embsKey_game" && window.embeddingsState.gendersOn) {
window.toggleGenders()
} else if (embeddingsKey.value=="embsKey_gender" && !window.embeddingsState.gendersOn) {
window.toggleGenders()
}
})
embeddingsMalesCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
embeddingsFemalesCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
embeddingsOtherGendersCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
embeddingsOnlyInstalledCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
embeddingsAlgorithm.addEventListener("change", () => window.computeEmbsAndDimReduction())
window.computeEmbsAndDimReduction = (includeAllVoices=false) => {
const enabledGames = Array.from(embeddingsGamesListContainer.querySelectorAll("input"))
.map(elem => [elem.checked, elem.id.replace("embs_", "")])
.filter(checkedId => checkedId[0])
.map(checkedId => checkedId[1].replace("embs_", ""))
const enabledVoices = window.embeddingsState.voiceCheckboxes
.map(elem => [elem.checked, elem.id.replace("embsVoice_", "")])
.filter(checkedId => checkedId[0])
.map(checkedId => checkedId[1].replace("embsVoice_", ""))
if (enabledVoices.length<=2) {
return window.errorModal(window.i18n.EMBEDDINGS_NEED_AT_LEAST_3)
}
// Get together a list of voiceId->.wav path mappings
const mappings = []
if (includeAllVoices) {
Object.keys(window.games).forEach(gameId => {
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
window.games[gameId].models.forEach(model => {
model.variants.forEach(variant => {
const audioPreviewPath = `${modelsPathForGame}/${variant.voiceId}.wav`
if (fs.existsSync(audioPreviewPath)) {
mappings.push(`${variant.voiceId}=${audioPreviewPath}=${model.voiceName}=${variant.gender}=${gameId}`)
}
})
})
})
} else {
Object.keys(window.embeddingsState.allData).forEach(voiceId => {
try {
const voiceMeta = window.embeddingsState.allData[voiceId]
const gender = voiceMeta.voiceGender.toLowerCase()
// Filter game-level voices
if (!enabledGames.includes(voiceMeta.gameId)) {
return
}
// Filter out by voice
if (!enabledVoices.includes(voiceId)) {
return
}
// Filter out data by gender
if (gender=="male" && !embeddingsMalesCkbx.checked) {
return
}
if (gender=="female" && !embeddingsFemalesCkbx.checked) {
return
}
if (gender=="other" && !embeddingsOtherGendersCkbx.checked) {
return
}
const modelsPathForGame = window.userSettings[`modelspath_${voiceMeta.gameId}`]
let audioPreviewPath = `${modelsPathForGame}/${voiceId}.wav`
if (!fs.existsSync(audioPreviewPath)) {
audioPreviewPath = ""
}
mappings.push(`${voiceId}=${audioPreviewPath}=${voiceMeta.voiceName}=${voiceMeta.voiceGender.toLowerCase()}=${voiceMeta.gameId}`)
} catch (e) {console.log(e)}
})
}
if (mappings.length<=2) {
return window.errorModal(window.i18n.EMBEDDINGS_NEED_AT_LEAST_3)
}
window.spinnerModal(window.i18n.VEMB_RECOMPUTING)
doFetch(`http://localhost:8008/computeEmbsAndDimReduction`, {
method: "Post",
body: JSON.stringify({
mappings: mappings.join("\n"),
onlyInstalled: embeddingsOnlyInstalledCkbx.checked,
algorithm: embeddingsAlgorithm.value.split("_")[1],
includeAllVoices
})
}).then(r=>r.text()).then(res => {
window.embeddingsState.data = {}
res.split("\n").forEach(voiceMetaAndCoords => {
const voiceId = voiceMetaAndCoords.split("=")[0]
const voiceName = voiceMetaAndCoords.split("=")[1]
const voiceGender = voiceMetaAndCoords.split("=")[2]
const gameId = voiceMetaAndCoords.split("=")[3]
const coords = voiceMetaAndCoords.split("=")[4].split(",").map(v => parseFloat(v))
window.embeddingsState.data[voiceId] = coords
if (includeAllVoices) {
window.embeddingsState.allData[voiceId] = {
voiceName,
voiceGender,
coords,
gameId
}
}
})
window.refreshEmbeddingsScenePoints()
closeModal(undefined, embeddingsContainer)
}).catch(e => {
console.log(e)
if (e.code =="ENOENT") {
closeModal(null, modalContainer).then(() => {
window.errorModal(window.i18n.ERR_SERVER)
})
}
})
}
embeddingsCloseHelpUI.addEventListener("click", () => {
embeddingsHelpUI.style.display = "none"
})