///|
pub(all) struct ClipSpan {
start_frame : Int32
end_frame : Int32
}
///|
pub(all) struct MixMetrics {
peak : Double
rms : Double
clipped_samples : Int32
clip_spans : Array[ClipSpan]
omitted_spans : Int32
}
///|
pub(all) struct PreviewPair {
binaural : Stereo
pan : Stereo
binaural_input : MixMetrics
pan_input : MixMetrics
binaural_gain : Double
pan_gain : Double
matched_rms : Double?
}
///|
fn metrics(audio : Stereo) -> MixMetrics {
let mut peak = 0.0
let mut sum = 0.0
let mut clipped_samples = 0
let mut start = -1
let mut omitted_spans = 0
let clip_spans : Array[ClipSpan] = []
for i in 0..<=audio.left.length() {
let mut clipping = false
if i < audio.left.length() {
let left = audio.left[i].abs()
let right = audio.right[i].abs()
if left > peak {
peak = left
}
if right > peak {
peak = right
}
sum += left * left + right * right
if left > 1.0 {
clipped_samples += 1
clipping = true
}
if right > 1.0 {
clipped_samples += 1
clipping = true
}
}
if clipping && start == -1 {
start = i
}
if !clipping && start != -1 {
if clip_spans.length() < 128 {
clip_spans.push({ start_frame: start, end_frame: i, })
} else {
omitted_spans += 1
}
start = -1
}
}
{
peak,
rms: (sum / (2.0 * audio.left.length().to_double())).sqrt(),
clipped_samples,
clip_spans,
omitted_spans,
}
}
///|
fn pan_gains(direction : Direction) -> (Double, Double) raise AudioError {
// Positive x is to the listener's right; the pan baseline ignores front/back.
let x = normalized(direction).x
(((1.0 - x) * 0.5).sqrt(), ((1.0 + x) * 0.5).sqrt())
}
///|
fn pan_preview(
sources : Array[Source],
frames : Int32,
fade_samples : Int32,
) -> Stereo raise AudioError {
let out : Stereo = {
left: Array::make(frames, 0.0),
right: Array::make(frames, 0.0),
}
for source in sources {
let mut previous = pan_gains(source.keyframes[0].direction)
for f in 0..= fade_samples {
1.0
} else {
elapsed.to_double() / fade_samples.to_double()
}
let x = source.samples[i] * source.gain
let at = source.start + i
out.left[at] += x * ((1.0 - alpha) * old_left + alpha * new_left)
out.right[at] += x * ((1.0 - alpha) * old_right + alpha * new_right)
}
previous = next
}
}
out
}
///|
fn scale(audio : Stereo, gain : Double) -> Unit {
for ear in [audio.left, audio.right] {
for i in 0.. PreviewPair raise AudioError {
let binaural = render_mix(
sources, measurements, sample_rate, hrir_rate, fade_samples,
).audio
let pan = pan_preview(sources, binaural.left.length(), fade_samples)
let binaural_input = metrics(binaural)
let pan_input = metrics(pan)
let (binaural_gain, pan_gain, matched_rms) = if binaural_input.rms > 0.0 &&
pan_input.rms > 0.0 {
let target = @cmp.minimum(
@cmp.minimum(binaural_input.rms, pan_input.rms),
@cmp.minimum(
0.98 * binaural_input.rms / binaural_input.peak,
0.98 * pan_input.rms / pan_input.peak,
),
)
(target / binaural_input.rms, target / pan_input.rms, Some(target))
} else {
(
if binaural_input.peak > 0.98 {
0.98 / binaural_input.peak
} else {
1.0
},
if pan_input.peak > 0.98 {
0.98 / pan_input.peak
} else {
1.0
},
None,
)
}
scale(binaural, binaural_gain)
scale(pan, pan_gain)
{
binaural,
pan,
binaural_input,
pan_input,
binaural_gain,
pan_gain,
matched_rms,
}
}