feat: 글자속도·단어간격·문장간격 독립 조절 + atempo 기본 타임스트레치
- 타임스트레치 우선순위를 ffmpeg atempo(기본) → rubberband → librosa 로 변경 - MeloEngine: 문장 단위로 분리 후 문장 사이 무음(sentence_gap), 단어(어절) 사이 무음(word_gap)을 타임스트레치와 독립적으로 삽입. speed 는 글자(음소) 속도만 조절 - API(TTSRequest)에 word_gap/sentence_gap 필드 추가, 전 엔진 시그니처 확장 - 프론트엔드에 '글자 속도/단어 간격/문장 간격' 슬라이더 추가, supports 플래그로 미지원 엔진(supertonic/oute/cosy)에서는 간격 슬라이더 자동 비활성화
This commit is contained in:
@@ -9,6 +9,10 @@ const els = {
|
||||
speaker: $("speaker"),
|
||||
speed: $("speed"),
|
||||
speedVal: $("speedVal"),
|
||||
wordGap: $("wordGap"),
|
||||
wordGapVal: $("wordGapVal"),
|
||||
sentGap: $("sentGap"),
|
||||
sentGapVal: $("sentGapVal"),
|
||||
pitch: $("pitch"),
|
||||
pitchVal: $("pitchVal"),
|
||||
generate: $("generate"),
|
||||
@@ -76,7 +80,9 @@ function updateEngineInfo() {
|
||||
: '<span class="cpu">CPU</span>';
|
||||
const supports = eng.supports || {};
|
||||
const ctrl = [
|
||||
supports.speed ? "속도" : null,
|
||||
supports.speed ? "글자속도" : null,
|
||||
supports.word_gap ? "단어간격" : null,
|
||||
supports.sentence_gap ? "문장간격" : null,
|
||||
supports.pitch ? "피치" : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
@@ -87,9 +93,11 @@ function updateEngineInfo() {
|
||||
`· ${gpu}` +
|
||||
(ctrl ? ` · 조절: ${ctrl}` : "");
|
||||
|
||||
// 지원하지 않는 컨트롤 비활성화
|
||||
// 지원하지 않는 컨트롤 비활성화 (플래그가 명시적으로 false면 끔)
|
||||
els.speed.disabled = supports.speed === false;
|
||||
els.pitch.disabled = supports.pitch === false;
|
||||
els.wordGap.disabled = supports.word_gap !== true;
|
||||
els.sentGap.disabled = supports.sentence_gap !== true;
|
||||
}
|
||||
|
||||
async function loadEngines() {
|
||||
@@ -159,6 +167,8 @@ async function generate() {
|
||||
speaker: els.speaker.value,
|
||||
speed: parseFloat(els.speed.value),
|
||||
pitch: parseFloat(els.pitch.value),
|
||||
word_gap: parseFloat(els.wordGap.value),
|
||||
sentence_gap: parseFloat(els.sentGap.value),
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -189,6 +199,12 @@ els.language.addEventListener("change", populateSpeakers);
|
||||
els.speed.addEventListener("input", () => {
|
||||
els.speedVal.textContent = parseFloat(els.speed.value).toFixed(2) + "x";
|
||||
});
|
||||
els.wordGap.addEventListener("input", () => {
|
||||
els.wordGapVal.textContent = Math.round(parseFloat(els.wordGap.value) * 1000) + " ms";
|
||||
});
|
||||
els.sentGap.addEventListener("input", () => {
|
||||
els.sentGapVal.textContent = parseFloat(els.sentGap.value).toFixed(2) + " s";
|
||||
});
|
||||
els.pitch.addEventListener("input", () => {
|
||||
const v = parseInt(els.pitch.value, 10);
|
||||
els.pitchVal.textContent = (v > 0 ? "+" + v : v) + " 반음";
|
||||
|
||||
Reference in New Issue
Block a user