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() | |
} | |
}) |