// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
// See README.md and COPYING for details.

mod common;
use common::*;

#[test]
fn check_matrix_sine() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(2);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, sin.out("sig"), None));
    matrix.place(1, 0, Cell::empty(out).input(None, out.inp("ch1"), None));
    matrix.sync().unwrap();

    let (mut out_l, out_r) = run_no_input(&mut node_exec, 4.0);

    let sum_l: f32 = out_l.iter().map(|v| v.abs()).sum();
    let sum_r: f32 = out_r.iter().map(|v| v.abs()).sum();
    assert_float_eq!(sum_l.floor(), 112301.0);
    assert_float_eq!(sum_r, 0.0);

    save_wav("check_matrix_sine.wav", &out_l);

    let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], 1000.0);
    for i in 0..4 {
        assert_float_eq!(rms_mimax[i].0, 0.5);
        assert_float_eq!(rms_mimax[i].1, -0.9999999);
        assert_float_eq!(rms_mimax[i].2, 0.9999999);
    }

    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 100, 0.0);
    assert_eq!(fft_res[0], (431, 248));
    assert_eq!(fft_res[1], (474, 169));

    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 100, 1000.0);
    assert_eq!(fft_res[0], (431, 248));
    assert_eq!(fft_res[1], (474, 169));

    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 100, 1500.0);
    assert_eq!(fft_res[0], (431, 248));
    assert_eq!(fft_res[1], (474, 169));

    let sin_led_val = matrix.led_value_for(&sin);
    let out_led_val = matrix.led_value_for(&out);

    assert_float_eq!(sin_led_val, 0.54018);
    assert_float_eq!(out_led_val, 0.54018);
}

#[test]
fn check_matrix_atom_set() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(2);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, sin.out("sig"), None));
    matrix.place(1, 0, Cell::empty(out).input(None, out.inp("ch1"), None));
    matrix.sync().unwrap();

    let mono_param = out.inp_param("mono").unwrap();

    matrix.set_param(mono_param, SAtom::setting(1));

    let (out_l, out_r) = run_no_input(&mut node_exec, 4.0);

    let sum_l: f32 = out_l.iter().map(|v| v.abs()).sum();
    let sum_r: f32 = out_r.iter().map(|v| v.abs()).sum();
    assert_float_eq!(sum_l.floor(), 112301.0);
    assert_float_eq!(sum_r.floor(), 112301.0);

    let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], 1000.0);
    for i in 0..4 {
        assert_float_eq!(rms_mimax[i].0, 0.5);
        assert_float_eq!(rms_mimax[i].1, -0.9999999);
        assert_float_eq!(rms_mimax[i].2, 0.9999999);
    }

    let rms_mimax = calc_rms_mimax_each_ms(&out_r[..], 1000.0);
    for i in 0..4 {
        assert_float_eq!(rms_mimax[i].0, 0.5);
        assert_float_eq!(rms_mimax[i].1, -0.9999999);
        assert_float_eq!(rms_mimax[i].2, 0.9999999);
    }
}

#[test]
fn check_sine_pitch_change() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, sin.out("sig"), None));
    matrix.place(1, 0, Cell::empty(out).input(None, out.inp("ch1"), None));
    matrix.sync().unwrap();

    let (mut out_l, _out_r) = run_no_input(&mut node_exec, 0.2);

    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 200, 0.0);
    assert_eq!(fft_res[0], (431, 248));

    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F64, 20, 100.0);
    assert_eq!(fft_res[0], (0, 22));

    let freq_param = sin.inp_param("freq").unwrap();

    matrix.set_param(freq_param, SAtom::param(freq_param.norm(4400.0)));

    let (mut out_l, _out_r) = run_no_input(&mut node_exec, 1.0);

    // Test at the start of the slope (~ 690 Hz):
    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F64, 15, 0.0);
    assert_eq!(fft_res[0], (0, 18));
    assert_eq!(fft_res[1], (689, 15));

    // In the middle (~ 2067 Hz):
    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F64, 10, 5.0);
    assert_eq!(fft_res[0], (1378, 14));
    assert_eq!(fft_res[1], (2067, 12));

    // Goal (~ 4134 Hz)
    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F64, 14, 10.0);
    assert_eq!(fft_res[0], (4134, 14));

    // Test the freq after the slope in high res (closer to 4400 Hz):
    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 200, 400.0);
    assert_eq!(fft_res[0], (4393, 251));
}

#[test]
fn check_detune_parameter() {
    let sin = NodeId::Sin(0);
    let det_param = sin.inp_param("det").unwrap();
    assert_float_eq!(det_param.norm(12.0), 0.1);
    assert_float_eq!(det_param.norm(-12.0), -0.1);
    assert_float_eq!(det_param.norm(24.0), 0.2);
    assert_float_eq!(det_param.norm(-24.0), -0.2);
}

#[test]
fn check_sine_freq_detune() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, sin.out("sig"), None));
    matrix.place(1, 0, Cell::empty(out).input(None, out.inp("ch1"), None));
    matrix.sync().unwrap();

    let freq_param = sin.inp_param("freq").unwrap();
    let det_param = sin.inp_param("det").unwrap();

    run_no_input(&mut node_exec, 50.0);

    let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0);
    assert_float_eq!(cfreq.floor(), 440.0);

    matrix.set_param(freq_param, SAtom::param(freq_param.norm(4400.0)));
    run_no_input(&mut node_exec, 50.0);
    let cfreq = run_and_get_counted_freq(&mut node_exec, 2000.0);
    assert_float_eq!(cfreq.floor(), 4400.0);

    matrix.set_param(freq_param, SAtom::param(freq_param.norm(50.0)));
    run_no_input(&mut node_exec, 50.0);
    let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0);
    assert_float_eq!(cfreq.floor(), 50.0);

    matrix.set_param(freq_param, SAtom::param(freq_param.norm(440.0)));
    matrix.set_param(det_param, SAtom::param(det_param.norm(12.0)));
    run_no_input(&mut node_exec, 50.0);
    let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0);
    assert_float_eq!(cfreq.floor(), 880.0);

    matrix.set_param(det_param, SAtom::param(det_param.norm(1.0)));
    run_no_input(&mut node_exec, 50.0);
    let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0);
    assert_float_eq!(cfreq.floor(), 466.0);

    matrix.set_param(det_param, SAtom::param(det_param.norm(-1.0)));
    run_no_input(&mut node_exec, 50.0);
    let cfreq = run_and_get_counted_freq(&mut node_exec, 2000.0);
    assert_float_eq!(cfreq.floor(), 415.0);

    matrix.set_param(det_param, SAtom::param(det_param.norm(-14.0)));
    run_no_input(&mut node_exec, 50.0);
    let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0);
    assert_float_eq!(cfreq.floor(), 196.0);
}

#[test]
fn check_matrix_monitor() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(2);
    let out = NodeId::Out(0);
    matrix.place(
        0,
        0,
        Cell::empty(sin).input(sin.inp("freq"), sin.inp("freq"), sin.inp("freq")).out(
            sin.out("sig"),
            sin.out("sig"),
            sin.out("sig"),
        ),
    );
    matrix.place(1, 0, Cell::empty(out).input(None, out.inp("ch1"), None));
    matrix.sync().unwrap();

    // Go to 220Hz
    let freq_param = sin.inp_param("freq").unwrap();
    matrix.set_param(freq_param, SAtom::param(-0.1));

    matrix.monitor_cell(*matrix.get(0, 0).unwrap());

    let (mut out_l, _out_r) = run_realtime_no_input(&mut node_exec, 0.3, true);

    // Give the MonitorProcessor some time to work on the buffers.
    std::thread::sleep(std::time::Duration::from_millis(100));

    //assert!(false);
    for i in 0..3 {
        let sl = matrix.get_minmax_monitor_samples(i);
        //d// println!("SL={:?}", sl);
        //d// println!("=> {}", i);

        assert_eq!((sl[sl.len() - 1].0 * 10000.0) as i64, -1000);
        assert_eq!((sl[sl.len() - 1].1 * 10000.0) as i64, -1000);
        assert_eq!((sl[sl.len() - 13].0 * 10000.0) as i64, -1000);
        // Here we see that the paramter is smoothed in:
        assert_eq!((sl[sl.len() - 14].1 * 10000.0) as i64, -2);
        assert_eq!((sl[sl.len() - 15].0 * 10000.0) as i64, 0);
        assert_eq!((sl[sl.len() - 15].1 * 10000.0) as i64, 0);
    }

    for i in 3..6 {
        let sl = matrix.get_minmax_monitor_samples(i);
        //d// println!("SL={:?}", sl);
        //d// println!("=> {}", i);

        assert_eq!((sl[sl.len() - 1].0 * 10000.0) as i64, -9999);
        assert_eq!((sl[sl.len() - 1].1 * 10000.0) as i64, 9999);
        assert_eq!((sl[sl.len() - 14].0 * 10000.0) as i64, -9999);
        assert_eq!((sl[sl.len() - 14].1 * 10000.0) as i64, 9999);
        assert_eq!((sl[sl.len() - 15].0 * 10000.0) as i64, 0);
        assert_eq!((sl[sl.len() - 15].1 * 10000.0) as i64, 0);
    }

    let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], 50.0);
    assert_float_eq!(rms_mimax[0].0, 0.49901);

    // let ta = std::time::Instant::now();

    // Test the freq after the slope in high res (closer to 4400 Hz):
    let fft_res = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 200, 50.0);

    // let ta = std::time::Instant::now().duration_since(ta);
    // println!("ta Elapsed: {:?}", ta);
    // assert!(false);

    // 220Hz is one Octave below 440Hz
    assert_eq!(fft_res[0], (215, 253));
}

#[test]
fn check_matrix_monitor_bug_1() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let amp = NodeId::Amp(1);
    matrix.place(0, 0, Cell::empty(sin).out(None, sin.out("sig"), None));
    matrix.place(
        1,
        0,
        Cell::empty(amp).out(None, None, amp.out("sig")).input(None, amp.inp("inp"), None),
    );
    matrix.sync().unwrap();

    matrix.monitor_cell(*matrix.get(1, 0).unwrap());

    let (_out_l, _out_r) = run_realtime_no_input(&mut node_exec, 0.2, true);

    std::thread::sleep(std::time::Duration::from_millis(100));

    for i in [0, 2, 3, 4].iter() {
        let sl = matrix.get_minmax_monitor_samples(*i);
        assert_eq!((sl[sl.len() - 1].0 * 10000.0) as i64, 0);
        assert_eq!((sl[sl.len() - 1].1 * 10000.0) as i64, 0);
    }

    for i in [1, 5].iter() {
        let sl = matrix.get_minmax_monitor_samples(*i);
        assert_eq!((sl[sl.len() - 1].0 * 10000.0) as i64, -9999);
        assert_eq!((sl[sl.len() - 1].1 * 10000.0) as i64, 9999);
    }
}

#[test]
fn check_matrix_out_config_bug1() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    matrix.place(0, 0, Cell::empty(NodeId::Sin(0)).out(None, Some(0), None));
    matrix.place(
        1,
        0,
        Cell::empty(NodeId::Out(0)).input(None, Some(0), None).out(None, None, Some(0)),
    );

    matrix.place(0, 1, Cell::empty(NodeId::Sin(1)).out(None, Some(0), None));
    matrix.place(
        1,
        2,
        Cell::empty(NodeId::Sin(0)).input(None, Some(0), None).out(None, None, Some(0)),
    );
    matrix.place(
        1,
        1,
        Cell::empty(NodeId::Out(0)).input(Some(1), Some(0), None).out(None, None, Some(0)),
    );

    assert!(matrix.sync().is_err());

    let (_out_l, _out_r) = run_no_input(&mut node_exec, 0.2);
}

#[test]
fn check_matrix_out_config_bug1_reduced() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    matrix.place(
        1,
        0,
        Cell::empty(NodeId::Out(0)).input(Some(0), None, None).out(None, None, Some(0)),
    );
    matrix.place(
        1,
        2,
        Cell::empty(NodeId::Out(0)).input(Some(0), None, None).out(None, None, None),
    );

    matrix.sync().unwrap();

    let (_out_l, _out_r) = run_no_input(&mut node_exec, 0.2);
}

#[test]
fn check_matrix_out_config_bug1b_reduced() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    matrix.place(1, 0, Cell::empty(NodeId::Out(0)).out(None, None, Some(0)));
    matrix.place(1, 1, Cell::empty(NodeId::Out(0)).input(Some(0), None, None));

    assert!(matrix.sync().is_err());

    let (_out_l, _out_r) = run_no_input(&mut node_exec, 0.2);
}

#[test]
fn check_matrix_out_config_bug1c_reduced() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    matrix.place(1, 0, Cell::empty(NodeId::Sin(0)).out(None, None, Some(0)));
    matrix.place(1, 1, Cell::empty(NodeId::Out(0)).input(Some(9), None, None));

    matrix.sync().unwrap();

    let (_out_l, _out_r) = run_no_input(&mut node_exec, 0.2);
}

macro_rules! simple_sine_output_test {
    ($matrix: ident, $block: tt) => {
        let (node_conf, mut node_exec) = new_node_engine();
        let mut $matrix = Matrix::new(node_conf, 7, 7);

        $block;

        $matrix.sync().unwrap();

        let (out_l, _out_r) = run_no_input(&mut node_exec, 0.2);

        let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], 50.0);
        assert_float_eq!(rms_mimax[0].0, 0.5);
        assert_float_eq!(rms_mimax[0].1, -0.9999999);
        assert_float_eq!(rms_mimax[0].2, 0.9999999);
    };
}

#[test]
fn check_matrix_connect_even_top_left() {
    simple_sine_output_test!(matrix, {
        matrix.place(1, 0, Cell::empty(NodeId::Sin(0)).out(None, Some(0), None));
        matrix.place(2, 1, Cell::empty(NodeId::Out(0)).input(None, Some(0), None));
    });
}

#[test]
fn check_matrix_connect_even_bottom_left() {
    simple_sine_output_test!(matrix, {
        matrix.place(1, 1, Cell::empty(NodeId::Sin(0)).out(Some(0), None, None));
        matrix.place(2, 1, Cell::empty(NodeId::Out(0)).input(None, None, Some(0)));
    });
}

#[test]
fn check_matrix_connect_even_top() {
    simple_sine_output_test!(matrix, {
        matrix.place(0, 0, Cell::empty(NodeId::Sin(0)).out(None, None, Some(0)));
        matrix.place(0, 1, Cell::empty(NodeId::Out(0)).input(Some(0), None, None));
    });
}

#[test]
fn check_matrix_connect_odd_top_left() {
    simple_sine_output_test!(matrix, {
        matrix.place(0, 0, Cell::empty(NodeId::Sin(0)).out(None, Some(0), None));
        matrix.place(1, 0, Cell::empty(NodeId::Out(0)).input(None, Some(0), None));
    });
}

#[test]
fn check_matrix_connect_odd_bottom_left() {
    simple_sine_output_test!(matrix, {
        matrix.place(0, 1, Cell::empty(NodeId::Sin(0)).out(Some(0), None, None));
        matrix.place(1, 0, Cell::empty(NodeId::Out(0)).input(None, None, Some(0)));
    });
}

#[test]
fn check_matrix_connect_odd_top() {
    simple_sine_output_test!(matrix, {
        matrix.place(1, 0, Cell::empty(NodeId::Sin(0)).out(None, None, Some(0)));
        matrix.place(1, 1, Cell::empty(NodeId::Out(0)).input(Some(0), None, None));
    });
}

#[test]
fn check_matrix_adj_odd() {
    let (node_conf, _node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    /*
            _____
        I2 / I1  \ O1
          /       \
          \       /
        I3 \_____/  O2
             O3

          0     1    2      3
         ___         ___
       0/   \  ___ 0/   \  ___
        \___/0/S2 \ \___/0/   \
         ___  \___/       \___/
       1/S1 \        ___
        \___/  ___ 1/S3 \  ___
         ___ 1/S0 \ \___/1/   \
       2/S6 \ \___/       \___/
        \___/        ___
               ___ 2/S4 \  ___
             2/S5 \ \___/2/   \
              \___/       \___/
    */

    matrix.place(
        1,
        1,
        Cell::empty(NodeId::Sin(0)).out(Some(0), Some(0), Some(0)).input(Some(0), Some(0), Some(0)),
    );

    matrix.place(0, 1, Cell::empty(NodeId::Sin(1)).out(None, Some(0), None));
    matrix.place(1, 0, Cell::empty(NodeId::Sin(2)).out(None, None, Some(0)));
    matrix.place(2, 1, Cell::empty(NodeId::Sin(3)).input(None, None, Some(0)));
    matrix.place(2, 2, Cell::empty(NodeId::Sin(4)).input(None, Some(0), None));
    matrix.place(1, 2, Cell::empty(NodeId::Sin(5)).input(Some(0), None, None));
    matrix.place(0, 2, Cell::empty(NodeId::Sin(6)).out(Some(0), None, None));
    matrix.sync().unwrap();

    assert_eq!(matrix.get_adjacent(1, 1, CellDir::B).unwrap().node_id(), NodeId::Sin(5));
    assert_eq!(matrix.get_adjacent(1, 1, CellDir::BR).unwrap().node_id(), NodeId::Sin(4));
    assert_eq!(matrix.get_adjacent(1, 1, CellDir::TR).unwrap().node_id(), NodeId::Sin(3));

    assert_eq!(matrix.get_adjacent(1, 1, CellDir::T).unwrap().node_id(), NodeId::Sin(2));
    assert_eq!(matrix.get_adjacent(1, 1, CellDir::TL).unwrap().node_id(), NodeId::Sin(1));
    assert_eq!(matrix.get_adjacent(1, 1, CellDir::BL).unwrap().node_id(), NodeId::Sin(6));
}

#[test]
fn check_matrix_adj_even() {
    let (node_conf, _node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    /*
            _____
        I2 / I1  \ O1
          /       \
          \       /
        I3 \_____/  O2
             O3

          0     1    2      3
         ___         ___
       0/   \  ___ 0/S2 \  ___
        \___/0/S1 \ \___/0/S3 \
         ___  \___/       \___/
       1/   \        ___
        \___/  ___ 1/S0 \  ___
         ___ 1/S6 \ \___/1/S4 \
       2/   \ \___/       \___/
        \___/        ___
               ___ 2/S5 \  ___
             2/   \ \___/2/   \
              \___/       \___/
    */

    matrix.place(
        2,
        1,
        Cell::empty(NodeId::Sin(0)).out(Some(0), Some(0), Some(0)).input(Some(0), Some(0), Some(0)),
    );

    matrix.place(1, 0, Cell::empty(NodeId::Sin(1)).out(None, Some(0), None));
    matrix.place(2, 0, Cell::empty(NodeId::Sin(2)).out(None, None, Some(0)));
    matrix.place(3, 0, Cell::empty(NodeId::Sin(3)).input(None, None, Some(0)));
    matrix.place(3, 1, Cell::empty(NodeId::Sin(4)).input(None, Some(0), None));
    matrix.place(2, 2, Cell::empty(NodeId::Sin(5)).input(Some(0), None, None));
    matrix.place(1, 1, Cell::empty(NodeId::Sin(6)).out(Some(0), None, None));
    matrix.sync().unwrap();

    assert_eq!(matrix.get_adjacent(2, 1, CellDir::B).unwrap().node_id(), NodeId::Sin(5));
    assert_eq!(matrix.get_adjacent(2, 1, CellDir::BR).unwrap().node_id(), NodeId::Sin(4));
    assert_eq!(matrix.get_adjacent(2, 1, CellDir::TR).unwrap().node_id(), NodeId::Sin(3));

    assert_eq!(matrix.get_adjacent(2, 1, CellDir::T).unwrap().node_id(), NodeId::Sin(2));
    assert_eq!(matrix.get_adjacent(2, 1, CellDir::TL).unwrap().node_id(), NodeId::Sin(1));
    assert_eq!(matrix.get_adjacent(2, 1, CellDir::BL).unwrap().node_id(), NodeId::Sin(6));
}

#[test]
fn check_matrix_out_twice_assignment() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    matrix.place(0, 0, Cell::empty(NodeId::Sin(0)).out(None, Some(0), None));
    matrix.place(0, 1, Cell::empty(NodeId::Sin(0)).out(Some(0), None, None));
    matrix.place(
        1,
        0,
        Cell::empty(NodeId::Out(0)).input(None, Some(0), Some(0)).out(None, None, None),
    );

    matrix.sync().unwrap();

    let (_out_l, _out_r) = run_no_input(&mut node_exec, 0.2);
}

#[test]
fn check_matrix_amp() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let amp = NodeId::Amp(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(
        0,
        1,
        Cell::empty(amp).input(out.inp("ch1"), None, None).out(None, None, sin.out("sig")),
    );
    matrix.place(0, 2, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    let att_param = amp.inp_param("att").unwrap();
    matrix.set_param(att_param, SAtom::param(0.5));

    let (rms, _, _) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
    assert_float_eq!(rms, 0.031249225);

    matrix.set_param(att_param, SAtom::param(1.0));
    let (rms, _, _) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
    assert_float_eq!(rms, 0.49998704);

    matrix.set_param(att_param, SAtom::param(0.0));
    let (rms, _, _) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
    assert_float_eq!(rms, 0.0);

    let gain_param = amp.inp_param("gain").unwrap();

    matrix.set_param(att_param, SAtom::param(1.0));
    matrix.set_param(gain_param, SAtom::param(0.5));
    let (rms, min, max) = run_and_get_l_rms_mimax(&mut node_exec, 50.0);
    assert_float_eq!(rms, 0.12499);
    assert_float_eq!(min, -0.5);
    assert_float_eq!(max, 0.5);
}

#[test]
fn check_matrix_clear() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(0, 1, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    let freq_param = sin.inp_param("freq").unwrap();
    matrix.set_param(freq_param, SAtom::param(-0.2));

    let fft = run_and_get_fft4096(&mut node_exec, 800, 0.0);
    // slightly lower counts than later, because we have a slight
    // frequency slope after setting the frequency to 110Hz
    assert_eq!(fft[0], (108, 989));

    let fft = run_and_get_fft4096(&mut node_exec, 800, 10.0);
    assert_eq!(fft[0], (108, 993));

    matrix.clear();

    let fft = run_and_get_fft4096(&mut node_exec, 1, 50.0);
    assert_eq!(fft.len(), 0);

    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(0, 1, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    let fft = run_and_get_fft4096(&mut node_exec, 800, 50.0);
    assert_eq!(fft[0], (441, 1012));
}

#[test]
fn check_matrix_serialize() {
    {
        let (node_conf, mut node_exec) = new_node_engine();
        let mut matrix = Matrix::new(node_conf, 3, 3);

        let sin = NodeId::Sin(0);
        let out = NodeId::Out(0);
        matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
        matrix.place(0, 1, Cell::empty(out).input(out.inp("ch1"), None, None));
        matrix.sync().unwrap();

        let freq_param = sin.inp_param("freq").unwrap();
        matrix.set_param(freq_param, SAtom::param(-0.2));

        let fft = run_and_get_fft4096(&mut node_exec, 800, 10.0);
        assert_eq!(fft[0], (108, 993));

        hexodsp::save_patch_to_file(&mut matrix, "check_matrix_serialize.hxy").unwrap();
    }

    {
        let (node_conf, mut node_exec) = new_node_engine();
        let mut matrix = Matrix::new(node_conf, 3, 3);

        hexodsp::load_patch_from_file(&mut matrix, "check_matrix_serialize.hxy").unwrap();

        let fft = run_and_get_fft4096(&mut node_exec, 800, 10.0);
        assert_eq!(fft[0], (108, 993));
    }
}

#[test]
fn check_matrix_tseq() {
    use hexodsp::dsp::tracker::UIPatternModel;

    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let tsq = NodeId::TSeq(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(
        0,
        1,
        Cell::empty(tsq).input(tsq.inp("clock"), None, None).out(None, None, tsq.out("trk1")),
    );
    matrix.place(0, 2, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    pset_n(&mut matrix, sin, "freq", -0.978);
    pset_s(&mut matrix, tsq, "cmode", 1);

    let pat = matrix.get_pattern_data(0).unwrap();
    {
        let mut pr = pat.lock().unwrap();
        pr.set_rows(16);
        pr.set_cell_value(0, 0, 0xFFF);
        pr.set_cell_value(15, 0, 0x000);
    }

    for _ in 0..10 {
        matrix.check_pattern_data(0);
    }

    // We let the clock mode tune in:
    run_and_undersample(&mut node_exec, 10000.0, 1);

    // Take some real samples:
    let samples = run_and_undersample(&mut node_exec, 2000.0, 10);

    assert_vec_feq!(
        samples,
        vec![
            0.5322106,
            0.4255343,
            0.318858,
            0.21218172,
            0.105505496,
            0.017571526,
            // then start at the beginning:
            0.958819,
            0.8521427,
            0.7454664,
            0.63879013
        ]
    );

    // switch to row trigger:
    pset_s(&mut matrix, tsq, "cmode", 0);
    let samples = run_and_undersample(&mut node_exec, 2000.0, 5);

    assert_vec_feq!(samples, vec![0.5011433, 0.7011613, 0.9011793, 0.9932535, 0.97991896]);

    // set to phase mode:
    pset_s(&mut matrix, tsq, "cmode", 2);
    let samples = run_and_undersample(&mut node_exec, 1000.0, 5);

    assert_float_eq!(samples[0], 0.2491);
    assert_float_eq!(samples[1], 0.0026);
    assert_float_eq!(samples[2], 0.1616);
    assert_float_eq!(samples[3], 0.6655);
    assert_float_eq!(samples[4], 0.8104);
}

#[test]
fn check_matrix_tseq_trig() {
    use hexodsp::dsp::tracker::UIPatternModel;

    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let tsq = NodeId::TSeq(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(
        0,
        1,
        Cell::empty(tsq).input(tsq.inp("clock"), None, None).out(None, None, tsq.out("trk1")),
    );
    matrix.place(0, 2, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    pset_n(&mut matrix, sin, "freq", -0.978);
    pset_s(&mut matrix, tsq, "cmode", 1);

    let pat = matrix.get_pattern_data(0).unwrap();
    {
        let mut pr = pat.lock().unwrap();
        pr.set_rows(16);
        pr.set_cell_value(0, 0, 0xFFF);
        pr.set_cell_value(15, 0, 0x000);
    }

    for _ in 0..10 {
        matrix.check_pattern_data(0);
    }

    // We let the clock mode tune in:
    run_and_undersample(&mut node_exec, 10000.0, 1);

    // Take some real samples:
    let samples = run_and_undersample(&mut node_exec, 2000.0, 10);

    assert_vec_feq!(
        samples,
        vec![
            0.5322106,
            0.4255343,
            0.318858,
            0.21218172,
            0.105505496,
            0.017571526,
            0.958819,
            0.8521427,
            0.7454664,
            0.63879013
        ]
    );

    pset_n(&mut matrix, tsq, "trig", 1.0);

    // Take some real samples:
    let samples = run_and_undersample(&mut node_exec, 2000.0, 10);

    assert_vec_feq!(
        samples,
        vec![
            0.5321138,
            // Then trigger happens:
            0.96263915,
            0.8559629,
            0.74928665,
            0.6426103,
            0.53593403,
            0.42925775,
            0.32258147,
            0.21590519,
            0.109228894
        ]
    );
}

#[test]
fn check_matrix_tseq_gate() {
    use hexodsp::dsp::tracker::UIPatternModel;

    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let tsq = NodeId::TSeq(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(
        0,
        1,
        Cell::empty(tsq).input(tsq.inp("clock"), None, None).out(None, None, tsq.out("trk1")),
    );
    matrix.place(0, 2, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    let freq_param = sin.inp_param("freq").unwrap();
    matrix.set_param(freq_param, SAtom::param(-0.978));
    let cmode_param = tsq.inp_param("cmode").unwrap();
    matrix.set_param(cmode_param, SAtom::setting(1));

    let pat = matrix.get_pattern_data(0).unwrap();
    {
        let mut pr = pat.lock().unwrap();
        pr.set_rows(16);
        pr.set_col_gate_type(0);
        // pulse_width:
        //      0xF  - Gate is on for full row
        //      0x0  - Gate is on for a very short burst
        // row_div:
        //      0xF  - Row has 1  Gate
        //      0x0  - Row is divided up into 16 Gates
        // probability:
        //      0xF  - Row is always triggered
        //      0x7  - Row fires only in 50% of the cases
        //      0x0  - Row fires only in ~6% of the cases
        pr.set_cell_value(5, 0, 0xFFF);
        pr.set_cell_value(7, 0, 0xFF0);
        pr.set_cell_value(9, 0, 0xF00);
    }

    for _ in 0..10 {
        matrix.check_pattern_data(0);
    }

    // We let the clock mode tune in:
    run_and_undersample(&mut node_exec, 11100.0, 1);

    // Take some real samples:
    let samples = run_and_undersample(&mut node_exec, 2000.0, 2000);
    let changes = collect_gates(&samples[..]);

    assert_eq!(
        changes,
        vec![
            (524, 126),
            (775, 8),
            (1033, 1),
            (1041, 1),
            (1049, 1),
            (1080, 1),
            (1088, 1),
            (1119, 1),
            (1127, 1),
            (1135, 1)
        ]
    );
}

#[test]
fn check_matrix_tseq_2col_gate_bug() {
    use hexodsp::dsp::tracker::UIPatternModel;

    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let tsq = NodeId::TSeq(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(
        0,
        1,
        Cell::empty(tsq).input(tsq.inp("clock"), None, None).out(None, None, tsq.out("trk2")),
    );
    matrix.place(0, 2, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    let freq_param = sin.inp_param("freq").unwrap();
    matrix.set_param(freq_param, SAtom::param(0.0));

    let cmode_param = tsq.inp_param("cmode").unwrap();
    matrix.set_param(cmode_param, SAtom::setting(1));

    let pat = matrix.get_pattern_data(0).unwrap();
    {
        let mut pr = pat.lock().unwrap();
        pr.set_rows(2);
        pr.set_col_value_type(0);
        pr.set_col_gate_type(1);

        // pulse_width:
        //      0xF  - Gate is on for full row
        //      0x0  - Gate is on for a very short burst
        // row_div:
        //      0xF  - Row has 1  Gate
        //      0x0  - Row is divided up into 16 Gates
        // probability:
        //      0xF  - Row is always triggered
        //      0x7  - Row fires only in 50% of the cases
        //      0x0  - Row fires only in ~6% of the cases
        pr.set_cell_value(0, 0, 0xFFF);
        pr.set_cell_value(1, 0, 0x000);

        pr.set_cell_value(0, 1, 0x0FF);
        pr.set_cell_value(1, 1, 0x000);
    }

    for _ in 0..10 {
        matrix.check_pattern_data(0);
    }

    let samples = run_and_undersample(&mut node_exec, 10000.0, 100000);

    let mut any_non_zero = false;
    for s in samples.iter() {
        if *s > 0.0 {
            any_non_zero = true;
        }
    }

    assert!(any_non_zero);
}

#[test]
fn check_matrix_output_feedback() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let amp = NodeId::Amp(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(0, 1, Cell::empty(amp).input(amp.inp("inp"), None, None));
    matrix.sync().unwrap();

    let gain_p = amp.inp_param("gain").unwrap();
    matrix.set_param(gain_p, SAtom::param(0.25));

    for _ in 0..10 {
        node_exec.test_run(0.11, true, &[]);
        matrix.update_filters();
        matrix.filtered_out_fb_for(&sin, sin.out("sig").unwrap());
        matrix.filtered_out_fb_for(&amp, amp.out("sig").unwrap());
    }

    let o_sin = matrix.out_fb_for(&sin, sin.out("sig").unwrap()).unwrap();
    let o_amp = matrix.out_fb_for(&amp, amp.out("sig").unwrap()).unwrap();
    let fo_sin = matrix.filtered_out_fb_for(&sin, sin.out("sig").unwrap());
    let fo_amp = matrix.filtered_out_fb_for(&amp, amp.out("sig").unwrap());

    assert_float_eq!(o_sin, -0.061266);
    assert_float_eq!(o_amp, -0.007658);

    assert_float_eq!(fo_sin.0, 0.96846);
    assert_float_eq!(fo_sin.1, 0.9302191);
    assert_float_eq!(fo_amp.0, 0.12105);
    assert_float_eq!(fo_amp.1, 0.11627);
}

#[test]
fn check_matrix_node_feedback() {
    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 7, 7);

    let sin = NodeId::Sin(0);
    let sin2 = NodeId::Sin(1);
    let wr = NodeId::FbWr(0);
    let rd = NodeId::FbRd(0);
    let wr2 = NodeId::FbWr(1);
    let rd2 = NodeId::FbRd(1);
    let out = NodeId::Out(0);

    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(0, 1, Cell::empty(wr).input(wr.inp("inp"), None, None));
    matrix.place(1, 0, Cell::empty(rd).out(None, None, rd.out("sig")));
    matrix.place(1, 1, Cell::empty(out).input(out.inp("ch1"), None, None));

    matrix.place(0, 2, Cell::empty(sin2).out(None, None, sin2.out("sig")));
    matrix.place(0, 3, Cell::empty(wr2).input(wr2.inp("inp"), None, None));
    matrix.place(1, 2, Cell::empty(rd2).out(None, None, rd2.out("sig")));
    matrix.place(1, 3, Cell::empty(out).input(out.inp("ch2"), None, None));
    matrix.sync().unwrap();

    let freq_param = sin2.inp_param("freq").unwrap();
    matrix.set_param(freq_param, SAtom::param(freq_param.norm(880.0)));

    let (out_l, out_r) = run_for_ms(&mut node_exec, 10.0);
    assert_decimated_feq!(
        out_l,
        15,
        vec![
            // The initial zeros are the feedback delays:
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.68328893,
            0.9925844,
            0.48698083,
            -0.4184115,
            -0.9803018,
            -0.73738277,
            0.110905044,
            0.8681419,
            0.9126584,
            0.20790927,
            -0.6675302,
            -0.99494797,
            -0.50553185,
            0.39891028,
            0.97586703,
            0.7516482,
            -0.089641616,
            -0.8573498,
            -0.9211795,
            -0.22875604
        ]
    );
    assert_decimated_feq!(
        out_r,
        15,
        vec![
            // The initial zeros are the feedback delays:
            // The frequency will be established a bit later because
            // the parameter setting of 880 Hz will be smoothed:
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.0,
            0.8791775,
            0.8898413,
            0.10330327,
            -0.79178804,
            -0.92698133,
            -0.11967586,
            0.8259115,
            0.86836,
            -0.09246742,
            -0.9534301,
            -0.62676203,
            0.5235326,
            0.9718173,
            0.04517236,
            -0.9560416,
            -0.49554884,
            0.7601789,
            0.75973713,
            -0.5529301,
            -0.8783003
        ]
    );

    // Let the frequency settle...
    run_for_ms(&mut node_exec, 80.0);

    let (mut out_l, mut out_r) = run_for_ms(&mut node_exec, 50.0);
    let fft_res_l = fft_thres_at_ms(&mut out_l[..], FFT::F1024, 100, 0.0);
    assert_eq!(fft_res_l[0], (431, 245));
    assert_eq!(fft_res_l[1], (474, 170));

    let fft_res_r = fft_thres_at_ms(&mut out_r[..], FFT::F1024, 100, 0.0);
    assert_eq!(fft_res_r[0], (861, 224));
    assert_eq!(fft_res_r[1], (904, 206));
}

#[test]
fn check_matrix_tseq_perf() {
    use hexodsp::dsp::tracker::UIPatternModel;

    let (node_conf, mut node_exec) = new_node_engine();
    let mut matrix = Matrix::new(node_conf, 3, 3);

    let sin = NodeId::Sin(0);
    let tsq = NodeId::TSeq(0);
    let out = NodeId::Out(0);
    matrix.place(0, 0, Cell::empty(sin).out(None, None, sin.out("sig")));
    matrix.place(
        0,
        1,
        Cell::empty(tsq).input(tsq.inp("clock"), None, None).out(None, None, tsq.out("trk1")),
    );
    matrix.place(0, 2, Cell::empty(out).input(out.inp("ch1"), None, None));
    matrix.sync().unwrap();

    let freq_param = sin.inp_param("freq").unwrap();
    //    matrix.set_param(freq_param, SAtom::param(-0.978));
    matrix.set_param(freq_param, SAtom::param(0.0));
    let cmode_param = tsq.inp_param("cmode").unwrap();
    matrix.set_param(cmode_param, SAtom::setting(0));
    //    matrix.set_param(cmode_param, SAtom::setting(2));

    let pat = matrix.get_pattern_data(0).unwrap();
    {
        let mut pr = pat.lock().unwrap();
        pr.set_rows(16);
        pr.set_col_note_type(0);
        pr.set_col_gate_type(1);
        pr.set_col_gate_type(2);

        pr.set_cell_value(0, 0, 0x0F7);
        pr.set_cell_value(4, 0, 0x100);
        pr.set_cell_value(8, 0, 0x10F);
        pr.set_cell_value(12, 0, 0x0F7);

        pr.set_cell_value(0, 1, 0xFF1);
        pr.set_cell_value(4, 1, 0xFF1);
        pr.set_cell_value(8, 1, 0xFF1);
        pr.set_cell_value(12, 1, 0xFF1);

        pr.set_cell_value(0, 2, 0xFF1);
        pr.set_cell_value(2, 2, 0xFF1);
        pr.set_cell_value(4, 2, 0xFF1);
        pr.set_cell_value(6, 2, 0xFF1);
        pr.set_cell_value(8, 2, 0xFF1);
        pr.set_cell_value(10, 2, 0xFF1);
        pr.set_cell_value(12, 2, 0xFF1);
        pr.set_cell_value(14, 2, 0xFF1);
    }

    for _ in 0..100 {
        matrix.check_pattern_data(0);
    }

    let mut prev: i64 = 0;
    let mut first: i64 = 0;
    for _ in 0..10 {
        let ta = std::time::Instant::now();
        run_for_ms(&mut node_exec, 10000.0);
        let dur = std::time::Instant::now().duration_since(ta);
        if prev > 0 {
            let now = dur.as_millis() as i64;
            if first <= 0 {
                first = now;
            }

            //d// println!("{},{}", prev, now);
            assert!((first - now).abs() < (first / 2));
        }
        prev = dur.as_millis() as i64;
    }
}
