Files
rustytorch/crates/models/rtx-csm/examples/generate_long.rs
T
osobhandClaude Opus 4.7 a5cedfb46a rtx-csm: emotional_speech_guide — CREMA-D vs RAVDESS firdhokk verdict
8-gen bench (4 emotions × 2 corpora) at seed=42 against firdhokk
Whisper-LV3:

  target    RAVDESS              CREMA-D
  happy     happy (0.999) ✓      happy (0.999) ✓
  angry     neutral (0.92)       sad (0.99)
  fearful   happy (0.998)        fearful (0.984) ✓
  sad       angry (0.99)         fearful (0.99)

CREMA-D 2/4 vs RAVDESS 1/4. Larger / more naturalistic corpus
produces more class-pure fearful direction. Neither corpus solves
angry or sad — recipe shifts into 'vague expressivity' rather than
class-specific corners.

Practical: prefer CREMA-D when available; A/B both per emotion if
class precision matters.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-30 00:01:02 -07:00

108 lines
3.3 KiB
Rust

//! Long-form generation with rolling context, post-processing, and
//! optional inline watermark — exercises `Generator::generate_long_to_wav`.
//!
//! Usage:
//! ```
//! cargo run -p rtx-csm --release --features metal --example generate_long -- \
//! --text "Long passage..." \
//! --out /tmp/longform.wav \
//! --watermark-generator /tmp/audioseal_generator.safetensors \
//! --watermark-detector /tmp/audioseal_detector.safetensors
//! ```
use anyhow::Result;
use candle_core::DType;
use clap::Parser;
use rtx_csm::{
GenerateOptions, Generator, PostProcess, audioseal::AudioSealWatermarker,
longform::LongFormConfig, watermark::ResampledWatermarker,
};
use std::path::PathBuf;
const AUDIOSEAL_RATE: u32 = 16_000;
#[derive(Debug, Parser)]
#[command(name = "generate_long")]
struct Cli {
#[arg(long)]
text: String,
#[arg(long, default_value_t = 0)]
speaker: u32,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value_t = 12_000)]
max_audio_ms: u32,
/// Max characters per chunk (sentence packer target).
#[arg(long, default_value_t = 220)]
max_chunk_chars: usize,
#[arg(long, default_value_t = -16.0)]
lufs: f32,
/// Optional AudioSeal watermarker (both flags required to enable).
#[arg(long)]
watermark_generator: Option<PathBuf>,
#[arg(long)]
watermark_detector: Option<PathBuf>,
#[arg(long, default_value = "0xCAFE")]
watermark_message: String,
#[arg(long)]
cpu: bool,
}
fn parse_message(s: &str) -> Result<u16> {
let s = s.trim();
if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
Ok(u16::from_str_radix(rest, 16)?)
} else {
Ok(s.parse::<u16>()?)
}
}
fn main() -> Result<()> {
tracing_subscriber::fmt().init();
let cli = Cli::parse();
let device = if cli.cpu {
candle_core::Device::Cpu
} else {
Generator::default_device()?
};
let mut generator = Generator::load_csm_1b(&device)?;
println!("loaded CSM-1B (sr={} Hz)", generator.config.sample_rate);
if let (Some(g), Some(d)) = (
cli.watermark_generator.as_ref(),
cli.watermark_detector.as_ref(),
) {
let message = parse_message(&cli.watermark_message)?;
let g_vb =
unsafe { candle_nn::VarBuilder::from_mmaped_safetensors(&[g], DType::F32, &device) }?;
let d_vb =
unsafe { candle_nn::VarBuilder::from_mmaped_safetensors(&[d], DType::F32, &device) }?;
let inner = AudioSealWatermarker::from_var_builders(g_vb, d_vb, device.clone(), message)?;
let wm = ResampledWatermarker::new(inner, generator.config.sample_rate, AUDIOSEAL_RATE);
generator.set_watermarker(Box::new(wm));
println!("watermarker installed (message=0x{:04X})", message);
}
let opts = GenerateOptions {
max_audio_ms: cli.max_audio_ms,
..GenerateOptions::default()
};
let post = PostProcess {
lufs_target: Some(cli.lufs),
..PostProcess::default()
};
let cfg = LongFormConfig {
max_chunk_chars: cli.max_chunk_chars,
..LongFormConfig::default()
};
let t = std::time::Instant::now();
generator.generate_long_to_wav(&cli.text, cli.speaker, None, opts, cfg, &post, &cli.out)?;
println!(
"wrote {} in {:.2}s",
cli.out.display(),
t.elapsed().as_secs_f32()
);
Ok(())
}