1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright (c) 2022 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.

use crate::dsp::{
    at, denorm, inp, out_idx, DspNode, GraphFun, LedPhaseVals, NodeContext, NodeGlobalRef, NodeId,
    ProcBuf, SAtom,
};
use crate::nodes::{HxMidiEvent, MidiEventPointer, NodeAudioContext, NodeExecContext};
use synfx_dsp::SlewValue;

#[macro_export]
macro_rules! fa_midicc_cc {
    ($formatter: expr, $v: expr, $denorm_v: expr) => {{
        write!($formatter, "{}", $v.round() as usize)
    }};
}

#[derive(Debug, Clone)]
pub struct MidiCC {
    cur_cc1: f32,
    cur_cc2: f32,
    cur_cc3: f32,
    slew_cc1: SlewValue<f32>,
    slew_cc2: SlewValue<f32>,
    slew_cc3: SlewValue<f32>,
}

impl MidiCC {
    pub fn new(_nid: &NodeId, _node_global: &NodeGlobalRef) -> Self {
        Self {
            cur_cc1: 0.0,
            cur_cc2: 0.0,
            cur_cc3: 0.0,
            slew_cc1: SlewValue::new(),
            slew_cc2: SlewValue::new(),
            slew_cc3: SlewValue::new(),
        }
    }

    pub const chan: &'static str = "MIDI Channel 0 to 15\n";
    pub const slew: &'static str = "Slew limiter for the 3 CCs";
    pub const cc1: &'static str = "MIDI selected CC 1";
    pub const cc2: &'static str = "MIDI selected CC 2";
    pub const cc3: &'static str = "MIDI selected CC 3";

    pub const sig1: &'static str = "CC output channel 1";
    pub const sig2: &'static str = "CC output channel 2";
    pub const sig3: &'static str = "CC output channel 3";

    pub const DESC: &'static str = "MIDI CC Input\n\n\
        This node is an input of MIDI CC events/values into the DSP graph. \
        You get 3 CC value outputs: ~~sig1~~, ~~sig2~~ and ~~sig3~~. To set which CC \
        gets which output you have to set the corresponding ~~cc1~~, ~~cc2~~ and \
        ~~cc3~~ parameters.";
    pub const HELP: &'static str = r#"MIDI CC Input

This node is an input of MIDI CC events/values into the DSP graph.
You get 3 CC value outputs: ~~sig1~~, ~~sig2~~ and ~~sig3~~. To set which CC
gets which output you have to set the corresponding ~~cc1~~, ~~cc2~~ and
~~cc3~~ parameters.

If the CC values change to rapidly or you hear audible artifacts, you can
try to limit the speed of change with the ~~slew~~ limiter.

If you need different ~~slew~~ values for the CCs, I recommend creating other
`MidiCC` instances with different ~~slew~~ settings.
"#;

    pub fn graph_fun() -> Option<GraphFun> {
        None
    }
}

impl DspNode for MidiCC {
    fn set_sample_rate(&mut self, _srate: f32) {}
    fn reset(&mut self) {}

    #[inline]
    fn process(
        &mut self,
        ctx: &mut dyn NodeAudioContext,
        ectx: &mut NodeExecContext,
        _nctx: &NodeContext,
        atoms: &[SAtom],
        inputs: &[ProcBuf],
        outputs: &mut [ProcBuf],
        ctx_vals: LedPhaseVals,
    ) {
        let slew = inp::MidiCC::slew(inputs);
        let chan = at::MidiCC::chan(atoms);
        let cc1 = at::MidiCC::cc1(atoms);
        let cc2 = at::MidiCC::cc2(atoms);
        let cc3 = at::MidiCC::cc3(atoms);
        let sig2_i = out_idx::MidiCC::sig2();
        let (sig1, r) = outputs.split_at_mut(sig2_i);
        let (sig2, sig3) = r.split_at_mut(1);
        let sig1 = &mut sig1[0];
        let sig2 = &mut sig2[0];
        let sig3 = &mut sig3[0];

        let midicc_channel = (chan.i() as usize % 16) as u8;
        let midicc_cc1 = (cc1.i() as usize % 128) as u8;
        let midicc_cc2 = (cc2.i() as usize % 128) as u8;
        let midicc_cc3 = (cc3.i() as usize % 128) as u8;

        let mut ptr = MidiEventPointer::new(&ectx.midi_ccs[..]);

        let mut change = false;

        for frame in 0..ctx.nframes() {
            let slew_ms = denorm::MidiCC::slew(slew, frame);

            while let Some(ev) = ptr.next_at(frame) {
                if let HxMidiEvent::CC { channel, cc, value } = ev {
                    if channel != midicc_channel {
                        continue;
                    }

                    if cc == midicc_cc1 {
                        self.cur_cc1 = value;
                        change = true;
                    } else if cc == midicc_cc2 {
                        self.cur_cc2 = value;
                        change = true;
                    } else if cc == midicc_cc3 {
                        self.cur_cc3 = value;
                        change = true;
                    }
                }
            }

            sig1.write(frame, self.slew_cc1.next(self.cur_cc1, slew_ms));
            sig2.write(frame, self.slew_cc2.next(self.cur_cc2, slew_ms));
            sig3.write(frame, self.slew_cc3.next(self.cur_cc3, slew_ms));
        }

        ctx_vals[0].set(if change { 1.0 } else { 0.0 });
    }
}