use std::collections::HashMap;
use std::fmt;
use std::path::Path;
use std::time::Instant;
use taconite_bundle::{Manifest, Store};
pub use taconite::Timing;
#[cfg(feature = "embed")]
pub mod embed;
#[cfg(feature = "encode")]
pub mod encode;
#[cfg(feature = "decode")]
pub mod media;
pub mod model;
pub mod npu;
pub mod stft;
pub mod wav;
use model::Model;
use npu::Npu;
use stft::Stft;
pub const VERSION: u32 = 1;
#[derive(Debug)]
pub enum Error {
Bundle(String),
Npu(String),
Input(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Bundle(m) => write!(f, "bundle: {m}"),
Error::Npu(m) => write!(f, "NPU: {m}"),
Error::Input(m) => write!(f, "input: {m}"),
}
}
}
impl std::error::Error for Error {}
impl From<taconite_bundle::Error> for Error {
fn from(e: taconite_bundle::Error) -> Self {
Error::Bundle(e.to_string())
}
}
impl From<taconite::Error> for Error {
fn from(e: taconite::Error) -> Self {
Error::Npu(e.to_string())
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub blocks: usize,
pub n_fft: usize,
pub hop: usize,
pub win: usize,
pub base_sr: usize,
pub eps: f32,
pub in_eps: f32,
pub pix: usize,
pub padt: usize,
pub skip: usize,
pub rec: usize,
pub yw: usize,
pub rates: Vec<u32>,
pub t_step: usize,
pub t_max: usize,
pub chunk_secs: f32,
pub chunk_hop: f32,
}
impl Config {
fn load(m: &Manifest) -> Result<Self, Error> {
let p = |k: &str| m.param_as::<usize>(k);
let f = |k: &str| m.param_as::<f32>(k);
if p("hid")? != 64 || p("d_state")? != 16 {
return Err(Error::Bundle("not a RE-USE (hid 64, d_state 16) bundle".into()));
}
Ok(Config {
blocks: p("blocks")?,
n_fft: p("n_fft")?,
hop: p("hop")?,
win: p("win")?,
base_sr: p("base_sr")?,
eps: f("eps")?,
in_eps: f("in_eps")?,
pix: p("pix")?,
padt: p("padt")?,
skip: p("skip")?,
rec: p("rec")?,
yw: p("yw")?,
rates: m.list("rates")?,
t_step: p("t_step")?,
t_max: p("t_max")?,
chunk_secs: f("chunk_secs")?,
chunk_hop: f("chunk_hop")?,
})
}
pub fn stft_sizes(&self, sr: u32) -> (usize, usize, usize) {
let even = |v: usize| v + v % 2;
let s = |v: usize| even(v * sr as usize / self.base_sr);
(s(self.n_fft), s(self.hop), s(self.win))
}
pub fn grid_frames(&self, sr: u32, samples: usize) -> usize {
let (_, hop, _) = self.stft_sizes(sr);
(1 + samples / hop + 2 - 1) / 4 + 1
}
}
pub struct Reuse {
pub cfg: Config,
pub npu: Npu,
pub model: Model,
pub manifest: Manifest,
pub store: Store,
stfts: HashMap<u32, Stft>,
pub timing: Timing,
}
impl Reuse {
pub fn load(dir: &Path) -> Result<Self, Error> {
let manifest = Manifest::load(dir, VERSION)?;
let store = Store::load(dir)?;
let cfg = Config::load(&manifest)?;
let npu = Npu::open(&manifest)?;
let model = Model::load(&store, &npu)?;
Ok(Reuse { cfg, npu, model, manifest, store, stfts: HashMap::new(), timing: Timing::default() })
}
pub fn contexts(&self) -> usize {
self.npu.contexts
}
pub fn release_contexts(&mut self) -> usize {
self.npu.release()
}
pub fn enhance(&mut self, wav: &[f32], sr: u32) -> Result<Vec<f32>, Error> {
self.enhance_with(wav, sr, &mut |_| {})
}
pub fn enhance_with(&mut self, wav: &[f32], sr: u32, progress: &mut dyn FnMut(f64)) -> Result<Vec<f32>, Error> {
if !self.cfg.rates.contains(&sr) {
return Err(Error::Input(format!(
"{sr} Hz: this bundle has kernels for {:?} Hz (export_reuse.py --rates; or resample)",
self.cfg.rates
)));
}
self.timing.clear();
if self.cfg.grid_frames(sr, wav.len()) <= self.cfg.t_max {
let y = self.enhance_whole_with(wav, sr, progress)?;
progress(1.0);
return Ok(y);
}
let size = (self.cfg.chunk_secs * sr as f32) as usize;
let hop = (self.cfg.chunk_hop * size as f32) as usize;
let s = wav.len();
let n = (s - size).div_ceil(hop) + 1;
let win: Vec<f32> =
(0..size).map(|i| (0.5 - 0.5 * (2.0 * std::f64::consts::PI * i as f64 / size as f64).cos()).max(1e-7) as f32).collect();
let mut out = vec![0f32; s];
let mut wsum = vec![0f32; s];
for i in 0..n {
let lo = if i < n - 1 { i * hop } else { s - size };
let y = self.enhance_whole_with(&wav[lo..lo + size], sr, &mut |f| progress((i as f64 + f) / n as f64))?;
for (k, &v) in y.iter().enumerate() {
out[lo + k] += v * win[k];
wsum[lo + k] += win[k];
}
}
progress(1.0);
Ok(out.iter().zip(&wsum).map(|(a, b)| a / b).collect())
}
pub fn enhance_whole(&mut self, wav: &[f32], sr: u32) -> Result<Vec<f32>, Error> {
self.enhance_whole_with(wav, sr, &mut |_| {})
}
pub fn enhance_whole_with(&mut self, wav: &[f32], sr: u32, tick: &mut dyn FnMut(f64)) -> Result<Vec<f32>, Error> {
let (n_fft, hop, win) = self.cfg.stft_sizes(sr);
if wav.len() <= n_fft / 2 {
return Err(Error::Input(format!("{} samples: shorter than half an STFT window ({})", wav.len(), n_fft / 2)));
}
let st = self.stfts.entry(sr).or_insert_with(|| Stft::new(n_fft, hop, win));
let t0 = Instant::now();
let (mag, pha) = st.forward(wav);
let (fq, tf) = (st.bins(), st.frames(wav.len()));
self.timing.add("host.stft", t0.elapsed());
let (em, ep) = self.model.forward_with(&self.cfg, &self.npu, &mag, &pha, fq, tf, &mut self.timing, tick)?;
let t0 = Instant::now();
let mut lin = vec![0f32; fq * tf];
for t in 0..tf {
let zero = (0..fq).filter(|&f| em[f * tf + t] <= 0.0).count();
if zero * 2 > fq {
continue; }
for f in 0..fq {
lin[f * tf + t] = em[f * tf + t].max(0.0).exp_m1();
}
}
let st = &self.stfts[&sr];
let mut y = st.inverse(&lin, &ep, tf);
y.resize(wav.len(), 1e-8);
self.timing.add("host.istft", t0.elapsed());
Ok(y)
}
}
pub fn snr(x: &[f32], reference: &[f32]) -> f64 {
let (mut s, mut e) = (0f64, 0f64);
for (a, b) in x.iter().zip(reference) {
s += (*b as f64).powi(2);
e += (*a as f64 - *b as f64).powi(2);
}
10.0 * (s / e).log10()
}