mtrack 0.12.0

A multitrack audio and MIDI player for live performances.
Documentation
// Copyright (C) 2026 Michael Wilson <mike@mdwn.dev>
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, version 3.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/>.
//

//! YAML output for calibration results.

use super::{ChannelCalibration, CrosstalkCalibration};

/// Writes the calibration results as YAML to stdout.
pub(crate) fn write_yaml(
    device_name: &str,
    sample_rate: u32,
    calibrations: &[ChannelCalibration],
    crosstalk: &CrosstalkCalibration,
) {
    println!("# Auto-calibrated trigger configuration");
    println!("# Generated by: mtrack calibrate-triggers");
    println!("trigger:");
    println!("  device: \"{}\"", device_name);
    println!("  sample_rate: {}", sample_rate);

    if let (Some(window), Some(thresh)) =
        (crosstalk.crosstalk_window_ms, crosstalk.crosstalk_threshold)
    {
        println!("  crosstalk_window_ms: {}", window);
        println!("  crosstalk_threshold: {:.1}", thresh);
    }

    println!("  inputs:");
    for cal in calibrations {
        println!("    - channel: {}", cal.channel);
        println!("      # sample: \"TODO\"");
        println!("      threshold: {:.4}", cal.threshold);
        println!("      gain: {:.2}", cal.gain);
        println!("      scan_time_ms: {}", cal.scan_time_ms);
        println!("      retrigger_time_ms: {}", cal.retrigger_time_ms);
        if let Some(freq) = cal.highpass_freq {
            println!("      highpass_freq: {:.1}", freq);
        }
        if let Some(decay) = cal.dynamic_threshold_decay_ms {
            println!("      dynamic_threshold_decay_ms: {}", decay);
        }
        println!(
            "      # Detected {} hits, noise floor peak: {:.6}, max hit: {:.4}",
            cal.num_hits_detected, cal.noise_floor_peak, cal.max_hit_amplitude
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_yaml_output() {
        let calibrations = vec![
            ChannelCalibration {
                channel: 1,
                threshold: 0.0160,
                gain: 1.32,
                scan_time_ms: 3,
                retrigger_time_ms: 25,
                highpass_freq: Some(80.0),
                dynamic_threshold_decay_ms: Some(40),
                num_hits_detected: 12,
                noise_floor_peak: 0.003200,
                max_hit_amplitude: 0.7200,
            },
            ChannelCalibration {
                channel: 3,
                threshold: 0.0080,
                gain: 1.58,
                scan_time_ms: 2,
                retrigger_time_ms: 20,
                highpass_freq: None,
                dynamic_threshold_decay_ms: None,
                num_hits_detected: 8,
                noise_floor_peak: 0.001600,
                max_hit_amplitude: 0.6000,
            },
        ];
        let crosstalk = CrosstalkCalibration {
            crosstalk_window_ms: Some(4),
            crosstalk_threshold: Some(2.8),
        };

        // Just verify it doesn't panic -- actual output goes to stdout
        write_yaml("UltraLite-mk5", 44100, &calibrations, &crosstalk);
    }

    #[test]
    fn test_write_yaml_without_crosstalk() {
        let calibrations = vec![ChannelCalibration {
            channel: 1,
            threshold: 0.015,
            gain: 1.3,
            scan_time_ms: 3,
            retrigger_time_ms: 25,
            highpass_freq: None,
            dynamic_threshold_decay_ms: None,
            num_hits_detected: 5,
            noise_floor_peak: 0.003,
            max_hit_amplitude: 0.72,
        }];
        let crosstalk = CrosstalkCalibration {
            crosstalk_window_ms: None,
            crosstalk_threshold: None,
        };
        write_yaml("TestDevice", 44100, &calibrations, &crosstalk);
    }

    #[test]
    fn test_write_yaml_empty_calibrations() {
        let crosstalk = CrosstalkCalibration {
            crosstalk_window_ms: None,
            crosstalk_threshold: None,
        };
        write_yaml("EmptyDevice", 48000, &[], &crosstalk);
    }
}