///|
fn number(value : Double?) -> String {
match value {
Some(x) => x.to_string()
None => "null"
}
}
///|
// This formula is independently repeated in tools/validate_reference.py.
// Period reduction prevents large-angle/platform-dependent phase drift.
fn sample(id : String, frame : Int32, channel : Int32) -> Double {
let pi = 3.141592653589793
let phase = 2.0 * pi * (frame % 48).to_double() / 48.0
match id {
"steady" => 0.1 * @math.sin(phase)
"gated" => {
let second = frame / 48000
let amplitude = if second < 4 {
0.0
} else if second < 12 {
0.01
} else if second < 20 {
0.1
} else if second < 28 {
0.001
} else if second < 36 {
0.03
} else {
0.0
}
amplitude * @math.sin(phase)
}
"stereo" =>
if channel == 0 {
0.1 * @math.sin(phase)
} else {
0.05 * @math.sin(phase + pi / 2.0)
}
"surround" => if channel == 3 { 0.1 * @math.sin(phase) } else { 0.0 }
"intersample" => {
// 12 kHz, pi/4 offset: samples under-read the continuous sine maximum.
// Linear 100 ms fades suppress start/end transient overshoot.
let remaining = 8 * 48000 - 1 - frame
let envelope = if frame < 4800 {
frame.to_double() / 4800.0
} else if remaining < 4800 {
remaining.to_double() / 4800.0
} else {
1.0
}
0.8 *
envelope *
@math.sin(2.0 * pi * (frame % 4).to_double() / 4.0 + pi / 4.0)
}
_ => panic()
}
}
///|
fn measure(
id : String,
seconds : Int32,
layout : @moonloudness.ChannelLayout,
) -> Unit {
let config = match @moonloudness.MeterConfig::new(48000, layout) {
Ok(value) => value
Err(_) => panic()
}
let analyzer = @moonloudness.Analyzer::new(config)
let mut energy = 0.0
let mut sum = 0.0
// Non-window-aligned chunks also exercise streaming boundary invariance.
let frames = seconds * 48000
let mut start = 0
while start < frames {
let stop = if start + 997 < frames { start + 997 } else { frames }
let pcm : Array[Double] = []
for frame in start.. ()
Err(_) => panic()
}
start = stop
}
let report = analyzer.finish()
println(
"{\"id\":\"" +
id +
"\",\"frames\":" +
report.frame_count().to_string() +
",\"channels\":" +
layout.channels().to_string() +
",\"sum\":" +
sum.to_string() +
",\"energy\":" +
energy.to_string() +
",\"integrated\":" +
number(report.integrated_lufs()) +
",\"lra\":" +
number(report.loudness_range_lu()) +
",\"sp\":" +
number(report.sample_peak_dbfs()) +
",\"tp\":" +
number(report.true_peak_dbtp()) +
"}",
)
}
///|
fn main {
measure("steady", 8, @moonloudness.ChannelLayout::mono())
measure("gated", 40, @moonloudness.ChannelLayout::mono())
measure("stereo", 8, @moonloudness.ChannelLayout::stereo())
measure("surround", 8, @moonloudness.ChannelLayout::five_point_zero())
measure("intersample", 8, @moonloudness.ChannelLayout::mono())
}