Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| // This doesn't seem to work anymore. TODO, make it work again | |
| function getCaretPosition() { | |
| var sel = document.selection, range, rect; | |
| var x = 0, y = 0; | |
| if (sel) { | |
| if (sel.type != "Control") { | |
| range = sel.createRange(); | |
| range.collapse(true); | |
| x = range.boundingLeft; | |
| y = range.boundingTop; | |
| } | |
| } else if (window.getSelection) { | |
| sel = window.getSelection(); | |
| if (sel.rangeCount) { | |
| range = sel.getRangeAt(0).cloneRange(); | |
| if (range.getClientRects) { | |
| range.collapse(true); | |
| if (range.getClientRects().length>0){ | |
| rect = range.getClientRects()[0]; | |
| x = rect.left; | |
| y = rect.top; | |
| } | |
| } | |
| // Fall back to inserting a temporary element | |
| if (x == 0 && y == 0) { | |
| var span = document.createElement("span"); | |
| if (span.getClientRects) { | |
| // Ensure span has dimensions and position by | |
| // adding a zero-width space character | |
| span.appendChild( document.createTextNode("\u200b") ); | |
| range.insertNode(span); | |
| rect = span.getClientRects()[0]; | |
| x = rect.left; | |
| y = rect.top; | |
| var spanParent = span.parentNode; | |
| spanParent.removeChild(span); | |
| // Glue any broken text nodes back together | |
| spanParent.normalize(); | |
| } | |
| } | |
| } | |
| } | |
| return { x: x, y: y }; | |
| } | |
| window.ARPABET_SYMBOLS_v2 = [ | |
| "AA0", "AA1", "AA2", "AE0", "AE1", "AE2", "AH0", "AH1", "AH2", "AO0", "AO1","AO2", "AW0", "AW1", "AW2", "AY0", "AY1", "AY2", "B", "CH", "D", "DH", | |
| "EH0", "EH1", "EH2", "ER0", "ER1", "ER2", "EY0", "EY1", "EY2", "F", "G", "HH", "IH0", "IH1", "IH2", "IY0", "IY1", "IY2", "JH", "K", "L", "M", | |
| "N", "NG", "OW0", "OW1", "OW2", "OY0", "OY1", "OY2", "P", "R", "S", "SH", "T", "TH", "UH0", "UH1", "UH2", "UW0", "UW1", "UW2", "V", "W", "Y", "Z", "ZH" | |
| ] | |
| let ARPABET_SYMBOLS = [ | |
| 'AA0', 'AA1', 'AA2', 'AA', 'AE0', 'AE1', 'AE2', 'AE', 'AH0', 'AH1', 'AH2', 'AH', | |
| 'AO0', 'AO1', 'AO2', 'AO', 'AW0', 'AW1', 'AW2', 'AW', 'AY0', 'AY1', 'AY2', 'AY', | |
| 'B', 'CH', 'D', 'DH', 'EH0', 'EH1', 'EH2', 'EH', 'ER0', 'ER1', 'ER2', 'ER', | |
| 'EY0', 'EY1', 'EY2', 'EY', 'F', 'G', 'HH', 'IH0', 'IH1', 'IH2', 'IH', 'IY0', 'IY1', | |
| 'IY2', 'IY', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW0', 'OW1', 'OW2', 'OW', 'OY0', | |
| 'OY1', 'OY2', 'OY', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH0', 'UH1', 'UH2', 'UH', | |
| 'UW0', 'UW1', 'UW2', 'UW', 'V', 'W', 'Y', 'Z', 'ZH' | |
| ] | |
| let extra_arpabet_symbols = [ | |
| "AX", | |
| "AXR", | |
| "IX", | |
| "UX", | |
| "DX", | |
| "EL", | |
| "EM", | |
| "EN0", | |
| "EN1", | |
| "EN2", | |
| "EN", | |
| "NX", | |
| "Q", | |
| "WH", | |
| ] | |
| let new_arpabet_symbols = [ | |
| "RRR", | |
| "HR", | |
| "OE", | |
| "RH", | |
| "TS", | |
| "RR", | |
| "UU", | |
| "OO", | |
| "KH", | |
| "SJ", | |
| "HJ", | |
| "BR", | |
| ] | |
| window.ARPABET_SYMBOLS_v3 = ARPABET_SYMBOLS.concat(extra_arpabet_symbols).concat(new_arpabet_symbols).sort((a,b)=>a<b?-1:1) | |
| const preprocess = (caretStart) => { | |
| const defaultLang = "en" | |
| let text = dialogueInput.value | |
| const finishedParts = [] | |
| // const parts = text.split(/\\lang\{[a-z]{0,2}\}\{/gi) | |
| const parts = text.split(/\\lang\[[a-z]{0,2}\]\[/gi) | |
| // const matchIterator = text.matchAll(/\\lang\{[a-z]{0,2}\}\{/gi) | |
| const matchIterator = text.matchAll(/\\lang\[[a-z]{0,2}\]\[/gi) | |
| finishedParts.push([defaultLang, parts[0]]) | |
| const langStack = [] | |
| // console.log("parts", parts) | |
| let textCounter = parts[0].length | |
| let caretInARPAbet = false | |
| parts.forEach((part,pi) => { | |
| if (pi) { | |
| const match = matchIterator.next().value[0] | |
| const langCode = match.split("lang[")[1].split("]")[0] | |
| langStack.push(langCode) | |
| // console.log("add langStack", langStack) | |
| textCounter += match.length | |
| let unescaped_part = "" | |
| part.split("]").forEach(sub_part => { | |
| // console.log("sub_part", sub_part, textCounter, textCounter+sub_part.length) | |
| if (caretStart > textCounter && caretStart <textCounter+sub_part.length) { | |
| caretInARPAbet = true | |
| // If backspace-ing a "{", and the next non-space character is "}", then delete that also | |
| // Add ctrl+<space> to open the auto-complete | |
| } | |
| // if (sub_part.includes("[")) { | |
| // unescaped_part += sub_part+"]" | |
| // return | |
| // } | |
| sub_part = unescaped_part+sub_part | |
| unescaped_part = "" | |
| if (part.includes("]")) { | |
| finishedParts.push([langStack.pop()||defaultLang, sub_part]) | |
| } else { | |
| finishedParts.push([langStack[langStack.length-1], sub_part]) | |
| } | |
| // finishedParts.push([langStack.pop()||defaultLang, sub_part]) | |
| }) | |
| } | |
| }) | |
| return caretInARPAbet | |
| } | |
| window.hideAutocomplete = () => { | |
| textEditorTooltip.style.display = "none" | |
| textEditorTooltip.innerHTML = "" | |
| autocomplete_callback = undefined | |
| } | |
| let textWrittenSinceAutocompleteWasShown = "" | |
| const filterOrHideAutocomplete = () => { | |
| if (textEditorTooltip.style.display=="flex") { | |
| highlightedAutocompleteIndex = 0 | |
| let childrenShown = 0 | |
| Array.from(textEditorTooltip.children).forEach(child => { | |
| if (child.classList.contains("autocomplete_option_active")) { | |
| child.classList.toggle("autocomplete_option_active") | |
| } | |
| if (child.innerText.toLowerCase().startsWith(textWrittenSinceAutocompleteWasShown) || textWrittenSinceAutocompleteWasShown.length==0) { | |
| child.style.display = "flex" | |
| childrenShown += 1 | |
| } else { | |
| child.style.display = "none" | |
| } | |
| }) | |
| if (childrenShown==0) { | |
| hideAutocomplete() | |
| return | |
| } | |
| setHighlightedAutocomplete(0, true) | |
| } | |
| } | |
| let autocomplete_callback = undefined | |
| const showAutocomplete = (options, callback) => { | |
| const position = getCaretPosition(dialogueInput) | |
| // The getCaretPosition function doesn't work anymore. At least center it | |
| position.x = window.visualViewport.width/2 | |
| textEditorTooltip.style.left = position.x + "px" | |
| textEditorTooltip.style.top = position.y + "px" | |
| highlightedAutocompleteIndex = 0 | |
| textWrittenSinceAutocompleteWasShown = "" | |
| autocomplete_callback = callback | |
| options.forEach(option => { | |
| const optElem = createElem("div.autocomplete_option", option[0]) | |
| optElem.dataset.autocomplete_return = option.length>1 ? option[1] : option[0] | |
| optElem.addEventListener("click", () => { | |
| callback(optElem.dataset.autocomplete_return) | |
| hideAutocomplete() | |
| refreshText() | |
| }) | |
| textEditorTooltip.appendChild(optElem) | |
| }) | |
| textEditorTooltip.style.display = "flex" | |
| setHighlightedAutocomplete(0, true) | |
| return | |
| } | |
| let highlightedAutocompleteIndex = 0 | |
| const setHighlightedAutocomplete = (delta, override=false) => { | |
| let NEW_highlightedAutocompleteIndex = Math.min(textEditorTooltip.children.length-1, Math.max(0, highlightedAutocompleteIndex+delta)) | |
| if (override || NEW_highlightedAutocompleteIndex != highlightedAutocompleteIndex) { | |
| if (!override) { | |
| textEditorTooltip.children[highlightedAutocompleteIndex].classList.toggle("autocomplete_option_active") | |
| } | |
| textEditorTooltip.children[override ? delta : NEW_highlightedAutocompleteIndex].classList.toggle("autocomplete_option_active") | |
| highlightedAutocompleteIndex = NEW_highlightedAutocompleteIndex | |
| // textEditorTooltip.children[highlightedAutocompleteIndex].scrollIntoView() | |
| } | |
| } | |
| dialogueInput.addEventListener("keydown", event => { | |
| if (event.key=="Tab" || event.key=="Enter") { | |
| event.preventDefault() | |
| if (autocomplete_callback!==undefined) { | |
| autocomplete_callback(textEditorTooltip.children[highlightedAutocompleteIndex].dataset.autocomplete_return) | |
| hideAutocomplete() | |
| refreshText() | |
| return | |
| } | |
| let cursorIndex = dialogueInput.selectionStart | |
| if (cursorIndex) { | |
| // Move into the next [] bracket if already wrote down language | |
| let textPreCursor = dialogueInput.value.slice(0, cursorIndex) | |
| let textPostCursor = dialogueInput.value.slice(cursorIndex, dialogueInput.value.length) | |
| if (textPreCursor.slice(textPreCursor.length-8, 7)=="\\lang[") { | |
| dialogueInput.setSelectionRange(cursorIndex+2,cursorIndex+2) | |
| // Move out of [] if at the end | |
| } else if (textEditorTooltip.style.display=="none" && (textPostCursor.startsWith("]") || textPostCursor.startsWith("}"))) { | |
| console.log("moving one") | |
| dialogueInput.setSelectionRange(cursorIndex+1,cursorIndex+1) | |
| } | |
| } | |
| } | |
| }) | |
| const splitWords = (sequence, addSpace) => { | |
| const words = [] | |
| // const sequenceProcessed = sequence | |
| const sequenceProcessed = [] // Do further processing to also split on { } symbols, not just spaces | |
| sequence.forEach(word => { | |
| if (word.includes("{")) { | |
| word.split("{").forEach((w, wi) => { | |
| sequenceProcessed.push(wi ? ["{"+w, addSpace] : [w, false]) | |
| }) | |
| } else if (word.includes("}")) { | |
| word.split("}").forEach((w, wi) => { | |
| sequenceProcessed.push(wi ? [w, addSpace] : [w+"}", false]) | |
| }) | |
| } else { | |
| sequenceProcessed.push([word, addSpace]) | |
| } | |
| }) | |
| sequenceProcessed.forEach(([word, addSpace]) => { | |
| if (word.startsWith("\\lang[")) { | |
| words.push(word.split("][")[0]+"][") | |
| word = word.split("][")[1] | |
| } | |
| ["}","]","[","{"].forEach(char => { | |
| if (word.startsWith(char)) { | |
| words.push(char) | |
| word = word.slice(1,word.length) | |
| } | |
| }) | |
| const endExtras = []; | |
| ["}","]","[","{"].forEach(char => { | |
| if (word.endsWith(char)) { | |
| endExtras.push(char) | |
| word = word.slice(0,word.length-1) | |
| } | |
| }) | |
| words.push(word) | |
| endExtras.reverse().forEach(extra => words.push(extra)) | |
| // if (word.startsWith("{")) { | |
| // split_words.push("{") | |
| // word = word.slice(1,word.length) | |
| // } | |
| // if (word.endsWith("}")) { | |
| // split_words.push("{") | |
| // word = word.slice(1,word.length) | |
| // } | |
| if (addSpace) { | |
| words.push(" ") | |
| } | |
| }) | |
| return words | |
| } | |
| window.refreshText = () => { | |
| let all_text = dialogueInput.value | |
| textEditorElem.innerHTML = "" | |
| let openedCurlys = 0 | |
| let openedLangs = 0 | |
| let split_words = splitWords(all_text.split(" "), true) | |
| split_words = splitWords(split_words) | |
| split_words = splitWords(split_words) | |
| split_words = splitWords(split_words) | |
| // console.log("split_words", split_words) | |
| // dfgd() | |
| let caretCounter = 0 | |
| let caretInARPAbet = false | |
| let firstOpenCurly = undefined | |
| let lastOpenCurly = undefined | |
| split_words.forEach(word => { | |
| if (caretCounter<=dialogueInput.selectionStart && (caretCounter+word.length)>dialogueInput.selectionStart) { | |
| // console.log(`caret (${dialogueInput.selectionStart}) in counter (${caretCounter}): `, word, openedCurlys, openedLangs) | |
| caretInARPAbet = openedCurlys > 0 | |
| } | |
| caretCounter += word.length | |
| const spanElem = createElem("span.manyWhitespace", word) | |
| if (word.startsWith("\\lang[")) { | |
| openedLangs += 1 | |
| } | |
| if (word.startsWith("{")) { | |
| openedCurlys += 1 | |
| if (!caretInARPAbet) { | |
| firstOpenCurly = spanElem | |
| } | |
| } | |
| if (openedCurlys) { | |
| spanElem.style.fontWeight = "bold" | |
| spanElem.style.fontStyle = "italic" | |
| } | |
| if (openedLangs) { | |
| spanElem.style.background = "rgba(50, 150, 250, 0.2)" | |
| } | |
| ///==== | |
| if (word.includes("part-highlighted")) { | |
| spanElem.style.textDecoration = "underline dotted red" | |
| } else if (word.includes("highlighted")) { | |
| spanElem.style.textDecoration = "underline solid red" | |
| } | |
| ///==== | |
| if (word.endsWith("]")) { | |
| openedLangs -= 1 | |
| } | |
| if (word.endsWith("}")) { | |
| openedCurlys -= 1 | |
| if (caretInARPAbet && lastOpenCurly===undefined) { | |
| lastOpenCurly = spanElem | |
| } | |
| } | |
| textEditorElem.appendChild(spanElem) | |
| }) | |
| preprocess(dialogueInput.selectionStart) | |
| return [caretInARPAbet, firstOpenCurly, lastOpenCurly] | |
| } | |
| const languagesList = Object.keys(window.supportedLanguages) | |
| const insertText = (inputTextArea, textToInsert, caretOffset=0) => { | |
| let cursorIndex = inputTextArea.selectionStart | |
| inputTextArea.value = inputTextArea.value.slice(0, cursorIndex) + textToInsert + inputTextArea.value.slice(cursorIndex, inputTextArea.value.length) | |
| caretOffset += textToInsert.length | |
| inputTextArea.setSelectionRange(cursorIndex+caretOffset,cursorIndex+caretOffset) | |
| refreshText() | |
| } | |
| dialogueInput.addEventListener("keydown", event => { | |
| generateVoiceButton.disabled = window.currentModel==undefined || !dialogueInput.value.length | |
| if (event.key=="Enter") { | |
| event.stopPropagation() | |
| event.preventDefault() | |
| return | |
| } | |
| if (textEditorTooltip.style.display=="flex" && (event.key=="ArrowDown" || event.key=="ArrowUp" || (!window.shiftKeyIsPressed && event.key=="ArrowLeft") || (!window.shiftKeyIsPressed && event.key=="ArrowRight"))) { | |
| // if (textEditorTooltip.style.display=="flex" && (event.key=="ArrowDown" || event.key=="ArrowUp")) { | |
| event.stopPropagation() | |
| event.preventDefault() | |
| return | |
| } | |
| if (event.key=="}") { | |
| if (dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.value.length-1).startsWith("}")) { | |
| dialogueInput.setSelectionRange(dialogueInput.selectionStart+1, dialogueInput.selectionStart+1) | |
| event.stopPropagation() | |
| event.preventDefault() | |
| return | |
| } | |
| } | |
| if (event.key=="]") { | |
| if (dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.value.length-1).startsWith("]")) { | |
| dialogueInput.setSelectionRange(dialogueInput.selectionStart+1, dialogueInput.selectionStart+1) | |
| event.stopPropagation() | |
| event.preventDefault() | |
| return | |
| } | |
| } | |
| }) | |
| let is_doing_gp2 = false | |
| window.get_g2p = (text_to_g2p) => { | |
| return new Promise(resolve => { | |
| doFetch("http://localhost:8008/getG2P", {method: "Post", body: JSON.stringify({base_lang: base_lang_select.value, text: text_to_g2p})}) | |
| .then(r=>r.text()).then(res => { | |
| is_doing_gp2 = false | |
| resolve(res) | |
| }) | |
| }) | |
| } | |
| const handleTextUpdate = (event) => { | |
| generateVoiceButton.disabled = window.currentModel==undefined || !dialogueInput.value.length | |
| window.shiftKeyIsPressed = event.shiftKey | |
| if (textEditorTooltip.style.display=="flex" && (event.type=="click" || (!window.shiftKeyIsPressed && event.key=="ArrowDown") || (!window.shiftKeyIsPressed && event.key=="ArrowRight"))) { | |
| event.stopPropagation() | |
| event.preventDefault() | |
| setHighlightedAutocomplete(1) | |
| return | |
| } | |
| if (textEditorTooltip.style.display=="flex" && (event.type=="click" || (!window.shiftKeyIsPressed && event.key=="ArrowLeft") || (!window.shiftKeyIsPressed && event.key=="ArrowUp"))) { | |
| event.stopPropagation() | |
| event.preventDefault() | |
| setHighlightedAutocomplete(-1) | |
| return | |
| } | |
| if (event.type!="click" && (event.key=="Shift" || event.key=="Control")) { | |
| event.stopPropagation() | |
| event.preventDefault() | |
| return | |
| } | |
| const [caretInARPAbet, firstOpenCurly, lastOpenCurly] = refreshText() | |
| if (caretInARPAbet) { | |
| firstOpenCurly && (firstOpenCurly.style.color = "red") | |
| lastOpenCurly && (lastOpenCurly.style.color = "red") | |
| } | |
| textEditorElem.scrollTop = dialogueInput.scrollTop | |
| if (dialogueInput.selectionStart!=dialogueInput.selectionEnd && !is_doing_gp2) { | |
| hideAutocomplete() | |
| showAutocomplete([["<Convert to phonemes>"]], () => { | |
| const text_to_g2p = dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.selectionEnd) | |
| is_doing_gp2 = true | |
| get_g2p(text_to_g2p).then(phonemes => { | |
| const initialStart = dialogueInput.selectionStart | |
| dialogueInput.value = dialogueInput.value.slice(0, dialogueInput.selectionStart) + dialogueInput.value.slice(dialogueInput.selectionEnd, dialogueInput.value.length) | |
| dialogueInput.selectionStart = initialStart | |
| insertText(dialogueInput, phonemes, 0) | |
| }) | |
| }) | |
| } else | |
| // } else { | |
| if (event.type!="click" && event.key.length==1 && event.key.match(/[a-z]/i)) { | |
| textWrittenSinceAutocompleteWasShown += event.key.toLowerCase() | |
| filterOrHideAutocomplete() | |
| } else if (event.type!="click" && event.key=="Backspace") { | |
| if (textWrittenSinceAutocompleteWasShown.length==0) { | |
| hideAutocomplete() | |
| } else { | |
| textWrittenSinceAutocompleteWasShown = textWrittenSinceAutocompleteWasShown.slice(0,textWrittenSinceAutocompleteWasShown.length-1) | |
| filterOrHideAutocomplete() | |
| } | |
| } else { | |
| hideAutocomplete() | |
| } | |
| const ctrlSpace = event.ctrlKey && event.code=="Space" | |
| if (event.type!="click" && (event.key=="{" || event.key=="") || ctrlSpace) { | |
| if (!ctrlSpace && event.key!="") { | |
| insertText(dialogueInput, "}", -1) | |
| } | |
| if (caretInARPAbet) { | |
| let symbols = window.ARPABET_SYMBOLS_v3 | |
| if (window.currentModel&&window.currentModel.modelType=="FastPitch1.1") { | |
| symbols = window.ARPABET_SYMBOLS_v2 | |
| } else if (window.currentModel&&window.currentModel.modelType=="FastPitch") { | |
| symbols = ["<ARPAbet only available for v2+ models>"] | |
| } | |
| showAutocomplete(symbols.map(v=>{return [v]}), option => { | |
| if (symbols.length>1) { | |
| insertText(dialogueInput, option.slice(textWrittenSinceAutocompleteWasShown.length, option.length)+" ", 0) | |
| } | |
| }) | |
| } | |
| if (event.key!="") { | |
| handleTextUpdate({type: "keydown", key: ""}) | |
| } | |
| } | |
| if (event.type!="click" && event.key=="\\") { | |
| // showAutocomplete([["\\lang[language][text]", "\\lang[][]"], ["\\sil[milliseconds]", "\\sil[]"]], (option) => { | |
| showAutocomplete([["\\lang[language][text]", "\\lang[][]"]], (option) => { | |
| if (option.includes("lang")) { | |
| insertText(dialogueInput, option.slice(1, option.length), -3) | |
| } else { | |
| insertText(dialogueInput, option.slice(1, option.length), -1) | |
| } | |
| setTimeout(() => { | |
| showAutocomplete(languagesList.map(v=>{return [v]}), option => { | |
| insertText(dialogueInput, option.slice(textWrittenSinceAutocompleteWasShown.length, option.length), 2) | |
| }) | |
| }, 100) | |
| }) | |
| } | |
| // } | |
| // }) | |
| } | |
| dialogueInput.addEventListener("click", event => handleTextUpdate(event)) | |
| dialogueInput.addEventListener("keyup", event => handleTextUpdate(event)) | |
| refreshText() | |
| setTimeout(window.refreshText, 500) | |
| window.addEventListener("click", event => { | |
| if (event.target && event.target!=textEditorTooltip && event.target.className && event.target.className.includes && !event.target.className.includes("autocomplete_option")) { | |
| hideAutocomplete() | |
| } | |
| }) |