apolinario commited on
Commit
c71e540
·
1 Parent(s): 0935499

Voice Changer target: add Library tab to pick from saved voices and presets

Browse files
Files changed (1) hide show
  1. index.html +70 -3
index.html CHANGED
@@ -728,6 +728,16 @@ pre {
728
  @keyframes recPulse { 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.55); } 70% { box-shadow: 0 0 0 14px rgba(239, 68, 68, 0); } 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } }
729
  .record-timer { font-size: 18px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
730
  .record-hint { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
 
 
 
 
 
 
 
 
 
 
731
  </style>
732
  </head>
733
  <body>
@@ -2804,9 +2814,65 @@ window.setInputMode = function(prefix, mode) {
2804
  if (upload) upload.style.display = mode === "upload" ? "" : "none";
2805
  const recordPane = document.getElementById(`${prefix}-record-pane`);
2806
  if (recordPane) recordPane.style.display = mode === "record" ? "" : "none";
2807
- // Optional library pane (Voice Changer target only, added later)
2808
  const libraryPane = document.getElementById(`${prefix}-library-pane`);
2809
- if (libraryPane) libraryPane.style.display = mode === "library" ? "" : "none";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2810
  };
2811
 
2812
  window.toggleInputRecord = async function(prefix) {
@@ -2892,6 +2958,7 @@ function attachAudioInput(prefix, onFile, opts = {}) {
2892
  </div>
2893
  <audio id="${prefix}-preview-audio" controls style="display:none;width:100%;margin-top:10px;border-radius:8px"></audio>
2894
  </div>
 
2895
  `);
2896
  }
2897
 
@@ -3018,7 +3085,7 @@ function updateVcBtn() {
3018
  wireUploadArea("vc-source-upload", "vc-source-input", "vc-source-label", f => { vcSource = f; updateVcBtn(); });
3019
  wireUploadArea("vc-target-upload", "vc-target-input", "vc-target-label", f => { vcTarget = f; updateVcBtn(); });
3020
  attachAudioInput("vc-source", f => { vcSource = f; document.getElementById("vc-source-label").textContent = f.name; document.getElementById("vc-source-upload").classList.add("has-file"); updateVcBtn(); });
3021
- attachAudioInput("vc-target", f => { vcTarget = f; document.getElementById("vc-target-label").textContent = f.name; document.getElementById("vc-target-upload").classList.add("has-file"); updateVcBtn(); });
3022
  setupPlayer("vc");
3023
 
3024
  window.voiceChange = async function() {
 
728
  @keyframes recPulse { 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.55); } 70% { box-shadow: 0 0 0 14px rgba(239, 68, 68, 0); } 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } }
729
  .record-timer { font-size: 18px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
730
  .record-hint { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
731
+
732
+ .lib-list { display: flex; flex-direction: column; gap: 2px; padding: 6px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; max-height: 320px; overflow-y: auto; }
733
+ .lib-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; cursor: pointer; transition: background 0.1s; border: 1px solid transparent; }
734
+ .lib-item:hover { background: var(--surface2); }
735
+ .lib-item.selected { background: var(--surface2); border-color: var(--border-hover); }
736
+ .lib-item .voice-avatar { width: 32px; height: 32px; border-radius: 8px; font-size: 14px; }
737
+ .lib-item-meta { display: flex; flex-direction: column; min-width: 0; }
738
+ .lib-item-name { font-size: 13px; font-weight: 500; color: var(--text); }
739
+ .lib-item-tag { font-size: 11px; color: var(--text-muted); }
740
+ .lib-empty { padding: 24px 12px; text-align: center; color: var(--text-muted); font-size: 13px; }
741
  </style>
742
  </head>
743
  <body>
 
2814
  if (upload) upload.style.display = mode === "upload" ? "" : "none";
2815
  const recordPane = document.getElementById(`${prefix}-record-pane`);
2816
  if (recordPane) recordPane.style.display = mode === "record" ? "" : "none";
 
2817
  const libraryPane = document.getElementById(`${prefix}-library-pane`);
2818
+ if (libraryPane) {
2819
+ libraryPane.style.display = mode === "library" ? "" : "none";
2820
+ if (mode === "library" && prefix === "vc-target") renderVcTargetLibrary();
2821
+ }
2822
+ };
2823
+
2824
+ async function renderVcTargetLibrary() {
2825
+ const list = document.getElementById("vc-target-library-list");
2826
+ if (!list) return;
2827
+ const voices = await getVoices();
2828
+ const usedPresetNames = new Set(voices.filter(v => v.isPreset).map(v => v.fileName.replace(/\.wav$/, "")));
2829
+ const availablePresets = PRESETS.filter(p => !usedPresetNames.has(p.name));
2830
+
2831
+ const userHtml = voices.length ? voices.map(v => `
2832
+ <div class="lib-item" data-id="${v.id}" onclick="pickVcTargetVoice('${v.id}')">
2833
+ <div class="voice-avatar" style="background:${v.color}">${v.name[0].toUpperCase()}</div>
2834
+ <div class="lib-item-meta">
2835
+ <div class="lib-item-name">${v.name}</div>
2836
+ <div class="lib-item-tag">${v.isPreset ? "Preset" : "Your voice"}</div>
2837
+ </div>
2838
+ </div>`).join("") : "";
2839
+
2840
+ const presetHtml = availablePresets.map(p => `
2841
+ <div class="lib-item" data-id="preset_${p.name}" onclick="pickVcTargetPreset('${p.name}')">
2842
+ <div class="voice-avatar" style="background:#d97706">${p.name[0]}</div>
2843
+ <div class="lib-item-meta">
2844
+ <div class="lib-item-name">${p.name.replace("_", " ")}</div>
2845
+ <div class="lib-item-tag">${p.tag}</div>
2846
+ </div>
2847
+ </div>`).join("");
2848
+
2849
+ list.innerHTML = userHtml + presetHtml || `<div class="lib-empty">No voices yet — add one in Voice Clone or Voice Design.</div>`;
2850
+ }
2851
+
2852
+ function markVcTargetSelected(id) {
2853
+ document.querySelectorAll("#vc-target-library-list .lib-item").forEach(el => el.classList.toggle("selected", el.dataset.id === id));
2854
+ }
2855
+
2856
+ window.pickVcTargetVoice = async function(id) {
2857
+ const voices = await getVoices();
2858
+ const v = voices.find(x => x.id === id);
2859
+ if (!v) return;
2860
+ const blob = new Blob([v.audioData], { type: v.audioType || "audio/wav" });
2861
+ vcTarget = new File([blob], v.fileName || `${v.name}.wav`, { type: blob.type });
2862
+ document.getElementById("vc-target-label").textContent = v.name;
2863
+ document.getElementById("vc-target-upload").classList.add("has-file");
2864
+ markVcTargetSelected(id);
2865
+ updateVcBtn();
2866
+ };
2867
+
2868
+ window.pickVcTargetPreset = async function(name) {
2869
+ const res = await fetch(`samples/${name}.wav`);
2870
+ const blob = await res.blob();
2871
+ vcTarget = new File([blob], `${name}.wav`, { type: blob.type || "audio/wav" });
2872
+ document.getElementById("vc-target-label").textContent = name.replace("_", " ");
2873
+ document.getElementById("vc-target-upload").classList.add("has-file");
2874
+ markVcTargetSelected(`preset_${name}`);
2875
+ updateVcBtn();
2876
  };
2877
 
2878
  window.toggleInputRecord = async function(prefix) {
 
2958
  </div>
2959
  <audio id="${prefix}-preview-audio" controls style="display:none;width:100%;margin-top:10px;border-radius:8px"></audio>
2960
  </div>
2961
+ ${opts.includeLibrary ? `<div id="${prefix}-library-pane" style="display:none;margin-bottom:16px"><div class="lib-list" id="${prefix}-library-list"></div></div>` : ""}
2962
  `);
2963
  }
2964
 
 
3085
  wireUploadArea("vc-source-upload", "vc-source-input", "vc-source-label", f => { vcSource = f; updateVcBtn(); });
3086
  wireUploadArea("vc-target-upload", "vc-target-input", "vc-target-label", f => { vcTarget = f; updateVcBtn(); });
3087
  attachAudioInput("vc-source", f => { vcSource = f; document.getElementById("vc-source-label").textContent = f.name; document.getElementById("vc-source-upload").classList.add("has-file"); updateVcBtn(); });
3088
+ attachAudioInput("vc-target", f => { vcTarget = f; document.getElementById("vc-target-label").textContent = f.name; document.getElementById("vc-target-upload").classList.add("has-file"); updateVcBtn(); }, { includeLibrary: true });
3089
  setupPlayer("vc");
3090
 
3091
  window.voiceChange = async function() {