Files
rustytorch/crates/models/rtx-csm/scripts/pick_context.sh
T
osobhandClaude Opus 4.7 9317047ae6 rtx-csm: pick_context.sh — linear peak penalty (A/B fix)
A/B test 2026-04-29 across mit_2024intro / mcc / carlini manifests
showed the binary peak threshold (≤ -3 dBFS = +0.5) failed to
differentiate hot clips against each other: an mcc clip with input
peak=-1.47 dBFS scored same as one at -3.5 dBFS, and the model output
tracked input amplitude.

Replace with a linear penalty: 0.5 at peak ≤ -9 dBFS, ramping to 0 at
peak = 0 dBFS, clamped. mcc spk0 now produces graduated scores
(1.63 / 1.58 / 1.56 / 1.51) instead of a 1.5 plateau, reordering the
top selection toward the cleaner-peak clip.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-29 03:49:26 -07:00

138 lines
5.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Rank manifest clips by suitability as voice-cloning context for
# CSM-1B's --context-wav / --context-text mechanism.
#
# Findings from 2026-04-29 voice-cloning experiments:
# - context clips < 10 s often produce clipping (output saturates int16)
# - context clips > ~14 s can blow the model's effective context window
# and degrade fidelity on the new prompt
# - clips with healthy RMS (~-15 to -25 dB) and no extreme peaks
# (peak < ~-3 dBFS) yield clean clones
# - speaker labels are PER-FILE in our manifests (diarizer restarts);
# this script preserves that and groups by (source_file, speaker_id)
#
# Usage:
# scripts/pick_context.sh <manifest.jsonl> [top_n=5]
#
# Outputs (per top group, tab-separated):
# score duration_s rms_db peak_db wav_path transcript
#
# Pipe through `head` to grab the top scorer:
# scripts/pick_context.sh manifest.jsonl | head -1 | cut -f5
#
# Then feed the wav + transcript to examples/generate:
# target/release/examples/generate \
# --text "..." \
# --context-wav "<picked.wav>" \
# --context-text "<picked transcript>" \
# --out output.wav
set -euo pipefail
MANIFEST="${1:?usage: $0 <manifest.jsonl> [top_n]}"
TOP_N="${2:-5}"
if [[ ! -f "$MANIFEST" ]]; then
echo "manifest not found: $MANIFEST" >&2
exit 1
fi
# Manifest paths can be absolute or relative to manifest dir.
MANIFEST_DIR="$(cd "$(dirname "$MANIFEST")" && pwd)"
resolve_wav() {
local wav="$1"
if [[ "$wav" = /* ]]; then echo "$wav"; else echo "$MANIFEST_DIR/$wav"; fi
}
# Score one clip:
# duration in [10.0, 13.5] = +1.0 (sweet spot, decreases linearly outside)
# RMS dB in [-25, -15] = +0.5 (clean speech band)
# peak (linear, [-9, 0] dBFS): +0.5 at peak ≤ -9, ramping to 0 at peak ≥ 0
# (CSM tracks input amplitude, so hotter context → hotter / clipping output;
# 2026-04-29 A/B showed binary peak threshold misranks hot clips against
# each other — linear penalty differentiates them)
# Anything outside window: 0 contribution. Total max = 2.0.
score_clip() {
local dur="$1" rms="$2" peak="$3"
local s_dur s_rms s_peak
if (( $(echo "$dur >= 10 && $dur <= 13.5" | bc -l) )); then
s_dur=1.0
elif (( $(echo "$dur >= 8 && $dur < 10" | bc -l) )); then
s_dur=$(echo "scale=3; ($dur - 8) / 2" | bc -l)
elif (( $(echo "$dur > 13.5 && $dur <= 16" | bc -l) )); then
s_dur=$(echo "scale=3; (16 - $dur) / 2.5" | bc -l)
else
s_dur=0
fi
if (( $(echo "$rms >= -25 && $rms <= -15" | bc -l) )); then s_rms=0.5; else s_rms=0; fi
# Linear peak penalty: 0.5 at -9 dBFS or quieter, 0.0 at 0 dBFS, clamped.
if (( $(echo "$peak <= -9" | bc -l) )); then
s_peak=0.5
elif (( $(echo "$peak >= 0" | bc -l) )); then
s_peak=0
else
s_peak=$(echo "scale=4; (- $peak) / 18" | bc -l)
fi
echo "scale=4; $s_dur + $s_rms + $s_peak" | bc -l
}
# Decode int16 max → dBFS roughly: int16_max = 32767 = 0 dBFS.
# Peak in dB = 20 * log10(|peak_int16| / 32767).
peak_to_db() {
local p="$1"
local abs_p
abs_p=$(echo "$p" | awk '{print ($1 < 0) ? -$1 : $1}')
if (( $(echo "$abs_p < 1" | bc -l) )); then echo "-100"; return; fi
echo "scale=2; l($abs_p / 32767) * 20 / l(10)" | bc -l
}
# Collect (group_key, score, dur, rms, peak, wav, transcript) per row.
TMPFILE="$(mktemp)"
trap "rm -f '$TMPFILE'" EXIT
while IFS= read -r line; do
[[ -z "$line" ]] && continue
[[ "$line" =~ ^# ]] && continue
wav=$(echo "$line" | jq -r '.wav')
txt=$(echo "$line" | jq -r '.transcript')
spk=$(echo "$line" | jq -r '.speaker // 0')
abs_wav="$(resolve_wav "$wav")"
[[ ! -f "$abs_wav" ]] && continue
# Use the manifest filename (stem) as part of the group, since speaker
# IDs are per-file.
src_stem="$(basename "$MANIFEST" .jsonl)"
group="${src_stem}/spk${spk}"
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$abs_wav" 2>/dev/null)
[[ -z "$dur" ]] && continue
# ffmpeg astats output: "Max level: <int16>" + "RMS level dB: <float>"
stats=$(ffmpeg -i "$abs_wav" -af "astats=metadata=1:reset=0" -f null - 2>&1 || true)
rms=$(echo "$stats" | grep -m1 "RMS level dB:" | awk -F'RMS level dB:' '{print $2}' | tr -d ' ')
peak_int=$(echo "$stats" | grep -m1 "Max level:" | awk -F'Max level:' '{print $2}' | tr -d ' ')
[[ -z "$rms" || -z "$peak_int" ]] && continue
# ffmpeg might report Inf for silent files
[[ "$rms" =~ "inf" ]] && continue
peak_db=$(peak_to_db "$peak_int")
score=$(score_clip "$dur" "$rms" "$peak_db")
printf "%s\t%s\t%.2f\t%.2f\t%.2f\t%s\t%s\n" \
"$group" "$score" "$dur" "$rms" "$peak_db" "$abs_wav" "$txt" >> "$TMPFILE"
done < "$MANIFEST"
# Group, sort within each group by score DESC, keep top N.
echo "# group score dur(s) rms(dB) peak(dB) wav ← transcript"
sort -t $'\t' -k1,1 -k2,2gr "$TMPFILE" | awk -F'\t' -v n="$TOP_N" '
{
if ($1 != prev) { count = 0; prev = $1 }
if (count < n) {
printf "%s\t%s\t%s\t%s\t%s\t%s\t← %s\n", $1, $2, $3, $4, $5, $6, $7
count++
}
}'