1use atomic_float::AtomicF64;
7use midir::{MidiInput, MidiInputConnection, MidiInputPort};
8use mimium_lang::{
9 ast::{Expr, Literal},
10 function,
11 interner::{ToSymbol, TypeNodeId},
12 interpreter::Value,
13 log, numeric,
14 plugin::{SysPluginSignature, SystemPlugin, SystemPluginFnType, SystemPluginMacroType},
15 runtime::vm,
16 string_t,
17 types::{PType, RecordTypeField, Type},
18 unit,
19};
20use std::{
21 cell::OnceCell,
22 sync::{Arc, atomic::Ordering},
23};
24use wmidi::MidiMessage;
25
26type NoteCallBack = Arc<dyn Fn(f64, f64) + Send + Sync>;
27
28#[derive(Default)]
29struct NoteCallBacks(pub [Vec<NoteCallBack>; 16]);
30
31impl NoteCallBacks {
32 pub fn invoke_note_callback(&self, chan: u8, note: u8, vel: u8) {
33 if chan < 15 {
34 self.0[chan as usize]
35 .iter()
36 .for_each(|cb| cb(note as f64, vel as f64));
37 };
38 }
39}
40
41struct NoteCell {
42 channel: u8,
43 pitch: AtomicF64,
44 velocity: AtomicF64,
45}
46pub struct MidiPlugin {
48 input: Option<MidiInput>,
49 port: OnceCell<MidiInputPort>,
50 port_name: Option<String>,
51 note_callbacks: Option<NoteCallBacks>,
52 connection: Option<MidiInputConnection<NoteCallBacks>>,
53 midi_note_cells: Vec<Arc<NoteCell>>,
55 midi_note_channels: Vec<u8>,
56}
57
58impl MidiPlugin {
59 const GET_MIDI_NOTE: &'static str = "__get_midi_note";
60
61 pub fn try_new() -> Option<Self> {
62 let input_res = MidiInput::new("mimium midi plugin");
63 match input_res {
64 Ok(input) => Some(Self {
65 input: Some(input),
66 port: OnceCell::new(),
67 port_name: None,
68 note_callbacks: Some(Default::default()),
69 connection: None,
70 midi_note_cells: Vec::new(),
71 midi_note_channels: Vec::new(),
72 }),
73 Err(_e) => None,
74 }
75 }
76 fn add_note_callback(&mut self, chan: u8, cb: NoteCallBack) {
77 match self.note_callbacks.as_mut() {
78 Some(v) if chan < 15 => {
79 v.0[chan as usize].push(cb);
80 }
81 _ => {}
82 }
83 }
84 pub fn set_midi_port(&mut self, vm: &mut vm::Machine) -> vm::ReturnCode {
87 let idx = vm.get_stack(0);
88 let pname = vm.prog.strings[idx as usize].clone();
89
90 self.port_name = Some(pname);
91 0
92 }
93 pub fn midi_note_mono_macro(&mut self, v: &[(Value, TypeNodeId)]) -> Value {
97 let zero = Value::Code(
98 Expr::Literal(Literal::Float(0.0.to_string().to_symbol())).into_id_without_span(),
99 );
100 if v.len() != 3 {
101 log::error!(
102 "midi_note_mono! expects 3 arguments (channel, default_note, default_velocity)"
103 );
104 return zero;
105 }
106
107 let (ch, default_note, default_vel) = match (v[0].0.clone(), v[1].0.clone(), v[2].0.clone())
108 {
109 (Value::Number(ch), Value::Number(note), Value::Number(vel)) => (ch, note, vel),
110 _ => {
111 log::error!("midi_note_mono! arguments must be numbers");
112 return zero;
113 }
114 };
115 let (uid, cell) = if let Some((uid, cell)) = self
116 .midi_note_cells
117 .iter()
118 .enumerate()
119 .find(|(_, c)| c.channel == ch as u8)
120 {
121 (uid, cell.clone())
122 } else {
123 let cell = Arc::new(NoteCell {
124 channel: ch as u8,
125 pitch: AtomicF64::new(default_note),
126 velocity: AtomicF64::new(default_vel),
127 });
128 let uid = self.midi_note_cells.len();
129 self.midi_note_cells.push(cell.clone());
130 (uid, cell)
131 };
132
133 let cell_c = cell.clone();
135 self.add_note_callback(
136 ch as u8,
137 Arc::new(move |note, vel| {
138 cell_c.pitch.store(note, Ordering::Relaxed);
139 cell_c.velocity.store(vel, Ordering::Relaxed);
140 }),
141 );
142
143 Value::Code(
145 Expr::Apply(
146 Expr::Var(Self::GET_MIDI_NOTE.to_symbol()).into_id_without_span(),
147 vec![
148 Expr::Literal(Literal::Float(uid.to_string().to_symbol()))
149 .into_id_without_span(),
150 ],
151 )
152 .into_id_without_span(),
153 )
154 }
155
156 pub fn get_midi_note(&mut self, vm: &mut vm::Machine) -> vm::ReturnCode {
160 let uid = vm::Machine::get_as::<f64>(vm.get_stack(0)) as usize;
161
162 match self.midi_note_cells.get(uid) {
163 Some(cell) => {
164 let pitch = cell.pitch.load(Ordering::Relaxed);
165 let velocity = cell.velocity.load(Ordering::Relaxed);
166 vm.set_stack(0, vm::Machine::to_value(pitch));
168 vm.set_stack(1, vm::Machine::to_value(velocity));
169 2
170 }
171 None => {
172 log::error!("Invalid MIDI note UID: {uid}");
173 vm.set_stack(0, vm::Machine::to_value(0.0));
174 vm.set_stack(1, vm::Machine::to_value(0.0));
175 2
176 }
177 }
178 }
179}
180
181impl Drop for MidiPlugin {
182 fn drop(&mut self) {
183 if let Some(c) = self.connection.take() {
184 c.close();
185 }
186 }
187}
188
189impl SystemPlugin for MidiPlugin {
190 fn after_main(&mut self, _machine: &mut vm::Machine) -> vm::ReturnCode {
191 if self.connection.is_some() {
192 return 0;
193 }
194 let input = self.input.as_ref().unwrap();
195 let ports = input.ports();
196
197 let port_opt = match (&self.port_name, ports.is_empty()) {
198 (Some(pname), false) => ports.iter().find(|port| {
199 let name = input.port_name(port).unwrap_or_default();
200 &name == pname
201 }),
202 (None, false) => {
203 log::info!("trying to connect default MIDI input device...");
204 ports.first()
205 }
206 (_, true) => None,
207 };
208 if let Some(p) = port_opt {
209 let name = input.port_name(p).unwrap_or_default();
210 log::info!("Midi Input: Connected to {name}");
211 let res = self.input.take().unwrap().connect(
212 p,
213 &name,
214 |_stamp, message, cbs: &mut NoteCallBacks| {
215 let msg = MidiMessage::from_bytes(message);
216 if let Ok(m) = msg {
217 match m {
218 MidiMessage::NoteOff(channel, note, _vel) => {
219 cbs.invoke_note_callback(channel.index(), u8::from(note), 0);
220 }
221 MidiMessage::NoteOn(channel, note, vel) => {
222 cbs.invoke_note_callback(
223 channel.index(),
224 u8::from(note),
225 vel.into(),
226 );
227 }
228 _ => {}
229 }
230 }
231 },
232 self.note_callbacks.take().unwrap(),
233 );
234 match res {
235 Ok(c) => self.connection = Some(c),
236 Err(e) => {
237 log::error!("{e}")
238 }
239 }
240 let _ = self.port.set(p.clone());
241 } else {
242 log::warn!("No MIDI devices found.")
243 }
244 0
245 }
246
247 fn gen_interfaces(&self) -> Vec<SysPluginSignature> {
248 let ty = function!(vec![string_t!()], unit!());
250 let fun: SystemPluginFnType<Self> = Self::set_midi_port;
251 let setport = SysPluginSignature::new("set_midi_port", fun, ty);
252
253 let midi_note_macro_f: SystemPluginMacroType<Self> = Self::midi_note_mono_macro;
255 let record_ty = Type::Record(vec![
256 RecordTypeField::new("pitch".to_symbol(), numeric!(), false),
257 RecordTypeField::new("velocity".to_symbol(), numeric!(), false),
258 ])
259 .into_id();
260 let midi_note_macro = SysPluginSignature::new_macro(
261 "midi_note_mono",
262 midi_note_macro_f,
263 function!(
264 vec![numeric!(), numeric!(), numeric!()],
265 Type::Code(record_ty).into_id()
266 ),
267 );
268
269 let get_midi_note_f: SystemPluginFnType<Self> = Self::get_midi_note;
271 let get_midi_note = SysPluginSignature::new(
272 Self::GET_MIDI_NOTE,
273 get_midi_note_f,
274 function!(vec![numeric!()], record_ty),
275 );
276
277 vec![setport, midi_note_macro, get_midi_note]
278 }
279}