harp-dev Claude Opus 5.5 commited on
Commit
9547ca2
·
verified ·
1 Parent(s): 135e9ab

Scale a result past full scale down to fit, return an AIFF input as WAV, and reword the interface text

Browse files

The result could overshoot full scale, and writing it to a 16-bit file flattened every peak above, which the validation measured as clipping. A result whose peak exceeds full scale is now scaled down to fit, and any other is left as it is.

HARP accepts .aif, the extension Logic Pro gives AIFF, but soundfile cannot write a file named with it, so such an input failed when its result was written, inside the GPU call. Gradio also converts an AIFF output to 16-bit WAV, since browsers cannot play AIFF, so keeping that container gained nothing. Lossless inputs now keep WAV and FLAC alone, and AIFF comes back as WAV at the bit depth this Space writes.

Some of the description and help text joined clauses with a semicolon or ended a sentence with a preposition, and each such sentence is reworded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +2 -2
  2. unison/pipelines/infer.py +9 -2
app.py CHANGED
@@ -337,7 +337,7 @@ def run(
337
  # A lossless input keeps its container. A lossy one comes back as WAV, since encoding
338
  # the edit lossily again would expose the artifacts of the first encode and add a second.
339
  input_ext = Path(input_audio_path).suffix.lower()
340
- if input_ext in {".flac", ".aiff", ".aif"}:
341
  data, rate = sf.read(out_path, always_2d=True)
342
  out_path = str(Path(out_path).with_suffix(input_ext))
343
  sf.write(out_path, data, rate)
@@ -421,7 +421,7 @@ if __name__ == "__main__":
421
  gr.Textbox(label="Prompt",
422
  info="Generate: describe the sound, or what's said. Edit: describe the change. Clone Voice: text to speak."),
423
  gr.Textbox(label="Background (optional)",
424
- info="\"Speech + Background\" sound type only: the background sound to mix in."),
425
  gr.Textbox(label="Reference Transcript (optional)",
426
  info="Only used in Clone Voice mode. Leave blank to auto-transcribe the input audio."),
427
  gr.Dropdown(choices=list(VARIANTS), value="Balanced (44kHz)", label="Model",
 
337
  # A lossless input keeps its container. A lossy one comes back as WAV, since encoding
338
  # the edit lossily again would expose the artifacts of the first encode and add a second.
339
  input_ext = Path(input_audio_path).suffix.lower()
340
+ if input_ext in {".flac"}:
341
  data, rate = sf.read(out_path, always_2d=True)
342
  out_path = str(Path(out_path).with_suffix(input_ext))
343
  sf.write(out_path, data, rate)
 
421
  gr.Textbox(label="Prompt",
422
  info="Generate: describe the sound, or what's said. Edit: describe the change. Clone Voice: text to speak."),
423
  gr.Textbox(label="Background (optional)",
424
+ info="\"Speech + Background\" sound type only: the background sound to mix with the speech."),
425
  gr.Textbox(label="Reference Transcript (optional)",
426
  info="Only used in Clone Voice mode. Leave blank to auto-transcribe the input audio."),
427
  gr.Dropdown(choices=list(VARIANTS), value="Balanced (44kHz)", label="Model",
unison/pipelines/infer.py CHANGED
@@ -252,6 +252,13 @@ def sample_latents(
252
  # -------------------------
253
  # Decode & save
254
  # -------------------------
 
 
 
 
 
 
 
255
  @torch.no_grad()
256
  def decode_and_save(audio_vae, latents, durations, output_paths, sample_rate=None):
257
  if sample_rate is None:
@@ -269,7 +276,7 @@ def decode_and_save(audio_vae, latents, durations, output_paths, sample_rate=Non
269
  wav = wav.mean(0)
270
  wav = wav[..., :max_smp]
271
  os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
272
- sf.write(path, wav.float().numpy(), sample_rate)
273
  print(f" Saved: {path} ({dur:.1f}s)")
274
 
275
 
@@ -320,7 +327,7 @@ def decode_and_save_full(audio_vae, latents_full, ref_samples,
320
  if fade_len > 1:
321
  target_wav[:fade_len] *= torch.linspace(0.0, 1.0, fade_len)
322
  os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
323
- sf.write(path, target_wav.float().numpy(), sample_rate)
324
  print(f" Saved (target): {path} ({dur:.1f}s, crop@{crop_start} = {crop_start_s:.3f}s, fade={fade_in_ms}ms)")
325
 
326
  # Optionally save the truncated ref waveform used as conditioning
 
252
  # -------------------------
253
  # Decode & save
254
  # -------------------------
255
+ def _within_full_scale(wav: torch.Tensor) -> torch.Tensor:
256
+ """Scale a result whose peak exceeds full scale down to fit, since writing it to a 16-bit
257
+ file would flatten every peak above."""
258
+ peak = wav.abs().max()
259
+ return wav / peak if peak > 1.0 else wav
260
+
261
+
262
  @torch.no_grad()
263
  def decode_and_save(audio_vae, latents, durations, output_paths, sample_rate=None):
264
  if sample_rate is None:
 
276
  wav = wav.mean(0)
277
  wav = wav[..., :max_smp]
278
  os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
279
+ sf.write(path, _within_full_scale(wav.float()).numpy(), sample_rate)
280
  print(f" Saved: {path} ({dur:.1f}s)")
281
 
282
 
 
327
  if fade_len > 1:
328
  target_wav[:fade_len] *= torch.linspace(0.0, 1.0, fade_len)
329
  os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
330
+ sf.write(path, _within_full_scale(target_wav.float()).numpy(), sample_rate)
331
  print(f" Saved (target): {path} ({dur:.1f}s, crop@{crop_start} = {crop_start_s:.3f}s, fade={fade_in_ms}ms)")
332
 
333
  # Optionally save the truncated ref waveform used as conditioning