tono_core/render/
output.rs1use super::Signal;
5use crate::dsl::{Normalize, Stereo};
6use crate::dsp::{
7 db_to_lin, loudness_lufs, loudness_lufs_gated, peak_limit, true_peak, true_peak_oversampled,
8};
9use std::f32::consts::FRAC_PI_2;
10
11pub fn make_loop_buffer(
21 samples: &[f32],
22 sr: u32,
23 start_secs: f32,
24 end_secs: Option<f32>,
25 crossfade_secs: f32,
26) -> Signal {
27 let len = samples.len();
28 let s = ((start_secs * sr as f32) as usize).min(len);
29 let e = end_secs
30 .map(|x| (x * sr as f32) as usize)
31 .unwrap_or(len)
32 .min(len);
33 if e <= s {
34 return samples.to_vec();
35 }
36 let region = &samples[s..e];
37 let l = region.len();
38 let x = ((crossfade_secs * sr as f32) as usize).min(l / 2);
39 if x == 0 {
40 return region.to_vec();
41 }
42 let out_len = l - x;
43 let mut out = region[..out_len].to_vec();
44 for (i, o) in out.iter_mut().take(x).enumerate() {
45 let t = (i as f32 + 0.5) / x as f32;
46 let fade_in = (FRAC_PI_2 * t).sin();
47 let fade_out = (FRAC_PI_2 * t).cos();
48 *o = region[i] * fade_in + region[out_len + i] * fade_out;
49 }
50 out
51}
52
53pub fn loop_seam_db(samples: &[f32]) -> f32 {
56 if samples.len() < 2 {
57 return -120.0;
58 }
59 let jump = (samples[0] - samples[samples.len() - 1]).abs();
60 20.0 * jump.max(1e-9).log10()
61}
62
63pub(super) fn normalize_output(samples: &mut [f32], nz: &Normalize) {
70 let ceil = db_to_lin(nz.ceiling_dbtp);
71 if let Some(target) = nz.target_lufs {
72 for _ in 0..2 {
73 let cur = loudness_lufs(samples);
74 if cur <= -120.0 {
75 break;
76 }
77 let g = db_to_lin(target - cur);
78 for x in samples.iter_mut() {
79 *x *= g;
80 }
81 soft_limit(samples, ceil);
82 }
83 }
84 true_peak_limit(samples, nz.ceiling_dbtp);
86 peak_limit(&mut [samples]);
87}
88
89pub(super) fn normalize_output_v4(channels: &mut [&mut [f32]], nz: &Normalize, sr: u32) {
96 let ceil = db_to_lin(nz.ceiling_dbtp);
97 if let Some(target) = nz.target_lufs {
98 for _ in 0..2 {
99 let cur = {
100 let views: Vec<&[f32]> = channels.iter().map(|c| &**c).collect();
101 loudness_lufs_gated(&views, sr)
102 };
103 if cur <= -120.0 {
104 break;
105 }
106 let g = db_to_lin(target - cur);
107 for c in channels.iter_mut() {
108 for x in c.iter_mut() {
109 *x *= g;
110 }
111 soft_limit(c, ceil);
112 }
113 }
114 }
115 let tp = channels
117 .iter()
118 .map(|c| true_peak_oversampled(c))
119 .fold(0.0f32, f32::max);
120 if tp > ceil && tp > 0.0 {
121 let g = ceil / tp;
122 for c in channels.iter_mut() {
123 for x in c.iter_mut() {
124 *x *= g;
125 }
126 }
127 }
128 peak_limit(channels);
129}
130
131fn soft_limit(samples: &mut [f32], ceil: f32) {
134 const KNEE: f32 = 0.7;
135 let ceil = ceil.max(1e-9);
137 for x in samples.iter_mut() {
138 let v = *x / ceil;
139 let a = v.abs();
140 if a > KNEE {
141 let compressed = KNEE + (1.0 - KNEE) * ((a - KNEE) / (1.0 - KNEE)).tanh();
142 *x = v.signum() * compressed * ceil;
143 }
144 }
145}
146
147fn true_peak_limit(samples: &mut [f32], ceiling_dbtp: f32) {
150 let ceil = db_to_lin(ceiling_dbtp);
151 let tp = true_peak(samples);
152 if tp > ceil && tp > 0.0 {
153 let g = ceil / tp;
154 for x in samples.iter_mut() {
155 *x *= g;
156 }
157 }
158}
159
160pub fn stereoize(mono: &[f32], stereo: Stereo, sr: u32) -> (Vec<f32>, Vec<f32>) {
164 let (mut l, mut r) = match stereo {
165 Stereo::Mono => (mono.to_vec(), mono.to_vec()),
166 Stereo::Haas { ms, pan } => {
167 let d = ((ms / 1000.0) * sr as f32) as usize;
168 let delayed: Vec<f32> = (0..mono.len())
169 .map(|i| if i >= d { mono[i - d] } else { 0.0 })
170 .collect();
171 if pan >= 0.0 {
173 (delayed, mono.to_vec())
174 } else {
175 (mono.to_vec(), delayed)
176 }
177 }
178 Stereo::Wide { amount } => {
179 let dec = allpass_decorrelate(mono, sr);
180 let a = amount.clamp(0.0, 1.0);
181 let mut l = Vec::with_capacity(mono.len());
182 let mut r = Vec::with_capacity(mono.len());
183 for i in 0..mono.len() {
184 let mid = mono[i];
185 let side = a * (mono[i] - dec[i]) * 0.5;
186 l.push(mid + side);
187 r.push(mid - side);
188 }
189 (l, r)
190 }
191 };
192 peak_limit(&mut [&mut l, &mut r]);
193 (l, r)
194}
195
196fn allpass_decorrelate(input: &[f32], sr: u32) -> Vec<f32> {
199 let scale = sr as f32 / 44_100.0;
200 let mut sig = input.to_vec();
201 for &tune in &[225usize, 556, 441] {
202 let len = ((tune as f32 * scale) as usize).max(1);
203 let mut buf = vec![0.0f32; len];
204 let mut idx = 0usize;
205 let g = 0.7;
206 for s in sig.iter_mut() {
207 let buffered = buf[idx];
208 let y = -*s * g + buffered;
209 buf[idx] = *s + buffered * g;
210 idx = (idx + 1) % len;
211 *s = y;
212 }
213 }
214 sig
215}