23v commited on
Commit
95c87ec
·
verified ·
1 Parent(s): 17e751e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -8
app.py CHANGED
@@ -565,17 +565,20 @@ def _take_score(i, med):
565
  return s
566
 
567
  def synth_best(gen_fn, text, attempts=1, asr_fn=None, sr=XTTS_SR, seed=1234,
568
- accept_sim=0.90, secs_fn=None, lang="ar", guard_retries=2):
569
  """gen_fn(seed:int) → float32 wav. Returns (wav, info, n_generated).
570
  attempts==1 : cheap self-healing guard — regenerate (≤ guard_retries) only if the
571
- take is obviously broken (silent / absurd speech-rate / clipped).
572
- attempts>1 : best-of-N ranked by ASR similarity + speaker similarity + duration."""
 
 
 
573
  multi = attempts > 1
574
  takes, k = [], 0
575
  while True:
576
  wav = trim_take(gen_fn(seed + 7919 * k), sr)
577
  info = take_info(wav, sr, text, asr_fn if multi else None, lang)
578
- if multi and secs_fn is not None and len(wav) > int(0.3 * sr):
579
  try: info["secs"] = float(secs_fn(wav, sr))
580
  except Exception: info["secs"] = None
581
  takes.append((wav, info)); k += 1
@@ -583,7 +586,8 @@ def synth_best(gen_fn, text, attempts=1, asr_fn=None, sr=XTTS_SR, seed=1234,
583
  if k >= attempts: break
584
  if info["ok"] and info["sim"] is not None and info["sim"] >= accept_sim: break
585
  else:
586
- if (info["ok"] and info["clip"] < 0.005) or k > guard_retries: break
 
587
  if len(takes) == 1: return takes[0][0], takes[0][1], 1
588
  med = float(np.median([i["dur"] for _, i in takes]))
589
  best = max(takes, key=lambda t: _take_score(t[1], med))
@@ -1938,7 +1942,12 @@ def _run_tts_quality(model, text, refs, lang, base_params, out_path, job_id,
1938
 
1939
  d = _model_defaults(model)
1940
  try:
1941
- secs_fn = _secs_fn(model, lat_spk) if attempts > 1 else None
 
 
 
 
 
1942
  except Exception as ex: # scoring is a bonus — never block generation
1943
  print(f"[!] speaker-similarity scoring disabled: {ex}", flush=True)
1944
  secs_fn = None
@@ -1968,10 +1977,16 @@ def _run_tts_quality(model, text, refs, lang, base_params, out_path, job_id,
1968
  secs_fn=secs_fn, lang=lang)
1969
  retries += _n - 1
1970
  wav = _residual_stretch(wav, resid)
 
 
 
 
 
 
1971
  if use_var and emo != "neutral":
1972
- wav = apply_phrase_dynamics(wav, vq.XTTS_SR, emo, variation * 0.5)
1973
  gap = EMOTION_DELTAS.get(emo, EMOTION_DELTAS["neutral"])[3]
1974
- emo_factor = 1.0 + (gap / 110.0 - 1.0) * 0.5 * min(variation, 1.0) if use_var else 1.0
1975
  takes.append(wav); pauses.append(pz * scale * emo_factor)
1976
  job_progress[job_id]["current"] = i + 1
1977
 
 
565
  return s
566
 
567
  def synth_best(gen_fn, text, attempts=1, asr_fn=None, sr=XTTS_SR, seed=1234,
568
+ accept_sim=0.90, secs_fn=None, lang="ar", guard_retries=2, min_secs=0.45):
569
  """gen_fn(seed:int) → float32 wav. Returns (wav, info, n_generated).
570
  attempts==1 : cheap self-healing guard — regenerate (≤ guard_retries) only if the
571
+ take is obviously broken (silent / absurd speech-rate / clipped / off-speaker).
572
+ attempts>1 : best-of-N ranked by ASR similarity + speaker similarity + duration.
573
+ `secs_fn` (speaker-encoder cosine similarity) is a single extra forward pass — cheap
574
+ enough to run even when attempts==1, and it is the only check that catches a take
575
+ that drifted away from the reference speaker's timbre (silence/clip/rate checks don't)."""
576
  multi = attempts > 1
577
  takes, k = [], 0
578
  while True:
579
  wav = trim_take(gen_fn(seed + 7919 * k), sr)
580
  info = take_info(wav, sr, text, asr_fn if multi else None, lang)
581
+ if secs_fn is not None and len(wav) > int(0.3 * sr):
582
  try: info["secs"] = float(secs_fn(wav, sr))
583
  except Exception: info["secs"] = None
584
  takes.append((wav, info)); k += 1
 
586
  if k >= attempts: break
587
  if info["ok"] and info["sim"] is not None and info["sim"] >= accept_sim: break
588
  else:
589
+ secs_ok = info.get("secs") is None or info["secs"] >= min_secs
590
+ if (info["ok"] and info["clip"] < 0.005 and secs_ok) or k > guard_retries: break
591
  if len(takes) == 1: return takes[0][0], takes[0][1], 1
592
  med = float(np.median([i["dur"] for _, i in takes]))
593
  best = max(takes, key=lambda t: _take_score(t[1], med))
 
1942
 
1943
  d = _model_defaults(model)
1944
  try:
1945
+ # One speaker-encoder forward pass per take — cheap, so run it even at attempts==1;
1946
+ # this used to be gated behind attempts>1, which meant the default UI setting
1947
+ # (attempts=1) never actually checked whether a chunk still sounded like the
1948
+ # reference speaker. That gap is the main source of "sounds like a different
1949
+ # speaker sometimes" on normal (non best-of-N) generations.
1950
+ secs_fn = _secs_fn(model, lat_spk)
1951
  except Exception as ex: # scoring is a bonus — never block generation
1952
  print(f"[!] speaker-similarity scoring disabled: {ex}", flush=True)
1953
  secs_fn = None
 
1977
  secs_fn=secs_fn, lang=lang)
1978
  retries += _n - 1
1979
  wav = _residual_stretch(wav, resid)
1980
+ # `conf` (how confident analyze_phrase_emotion was) was computed above but never
1981
+ # reached these two effects — they used to apply full-strength dynamics/pause
1982
+ # changes even on a low-confidence (often false-positive) keyword match, which is
1983
+ # what made ordinary neutral sentences suddenly jump in loudness/pacing next to
1984
+ # their neighbours. Scaling by `conf` makes weak matches barely audible and keeps
1985
+ # strong matches (question marks, "!!", clear keywords) at full strength.
1986
  if use_var and emo != "neutral":
1987
+ wav = apply_phrase_dynamics(wav, vq.XTTS_SR, emo, variation * 0.5 * conf)
1988
  gap = EMOTION_DELTAS.get(emo, EMOTION_DELTAS["neutral"])[3]
1989
+ emo_factor = 1.0 + (gap / 110.0 - 1.0) * 0.5 * min(variation, 1.0) * conf if use_var else 1.0
1990
  takes.append(wav); pauses.append(pz * scale * emo_factor)
1991
  job_progress[job_id]["current"] = i + 1
1992