Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| 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" | |
| }) |