1use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
6
7use crossbeam_channel::Sender;
8use phosphor_midi::message::{MidiMessage, MidiMessageType};
9use phosphor_midi::ring::MidiRingReceiver;
10use phosphor_plugin::{MidiEvent, Plugin};
11
12use crate::mixer::{Mixer, MixerCommand, mixer_command_channel};
13use crate::project::TrackHandle;
14use crate::transport::Transport;
15use crate::EngineConfig;
16
17#[derive(Debug)]
20pub struct VuLevels {
21 pub peak_l: AtomicU32,
23 pub peak_r: AtomicU32,
24}
25
26impl Default for VuLevels {
27 fn default() -> Self { Self::new() }
28}
29
30impl VuLevels {
31 pub fn new() -> Self {
32 Self {
33 peak_l: AtomicU32::new(0),
34 peak_r: AtomicU32::new(0),
35 }
36 }
37
38 pub fn set(&self, l: f32, r: f32) {
39 self.peak_l.store(l.to_bits(), Ordering::Relaxed);
40 self.peak_r.store(r.to_bits(), Ordering::Relaxed);
41 }
42
43 pub fn get(&self) -> (f32, f32) {
44 (
45 f32::from_bits(self.peak_l.load(Ordering::Relaxed)),
46 f32::from_bits(self.peak_r.load(Ordering::Relaxed)),
47 )
48 }
49}
50
51pub struct EngineShared {
57 pub config: EngineConfig,
58 pub transport: Arc<Transport>,
59 pub panic_flag: Arc<AtomicBool>,
60 pub vu_levels: Arc<VuLevels>,
62 pub mixer_command_tx: Sender<MixerCommand>,
64 pub track_handles: Vec<Arc<TrackHandle>>,
66}
67
68impl EngineShared {
69 pub fn new(config: EngineConfig) -> Self {
70 let (tx, _rx) = mixer_command_channel();
71 Self {
72 config,
73 transport: Arc::new(Transport::new(120.0)),
74 panic_flag: Arc::new(AtomicBool::new(false)),
75 vu_levels: Arc::new(VuLevels::new()),
76 mixer_command_tx: tx,
77 track_handles: Vec::new(),
78 }
79 }
80
81 pub fn with_command_tx(config: EngineConfig, tx: Sender<MixerCommand>) -> Self {
83 Self {
84 config,
85 transport: Arc::new(Transport::new(120.0)),
86 panic_flag: Arc::new(AtomicBool::new(false)),
87 vu_levels: Arc::new(VuLevels::new()),
88 mixer_command_tx: tx,
89 track_handles: Vec::new(),
90 }
91 }
92
93 pub fn panic(&self) {
95 self.panic_flag.store(true, Ordering::Relaxed);
96 }
97}
98
99pub struct EngineAudio {
101 channels: u16,
102 sample_rate: u32,
103 synth: Box<dyn Plugin>,
104 midi_rx: Option<MidiRingReceiver>,
105 panic_flag: Arc<AtomicBool>,
106 vu_levels: Arc<VuLevels>,
107 midi_scratch: Vec<MidiMessage>,
109 plugin_events: Vec<MidiEvent>,
111 plugin_buf_l: Vec<f32>,
113 plugin_buf_r: Vec<f32>,
114 mixer: Option<Mixer>,
116}
117
118impl EngineAudio {
119 pub fn new(
120 config: &EngineConfig,
121 synth: Box<dyn Plugin>,
122 midi_rx: Option<MidiRingReceiver>,
123 panic_flag: Arc<AtomicBool>,
124 vu_levels: Arc<VuLevels>,
125 ) -> Self {
126 let buf_size = config.buffer_size as usize;
127 let mut s = Self {
128 channels: 2,
129 sample_rate: config.sample_rate,
130 synth,
131 midi_rx,
132 panic_flag,
133 vu_levels,
134 midi_scratch: Vec::with_capacity(256),
135 plugin_events: Vec::with_capacity(256),
136 plugin_buf_l: vec![0.0; buf_size],
137 plugin_buf_r: vec![0.0; buf_size],
138 mixer: None,
139 };
140 s.synth.init(config.sample_rate as f64, buf_size);
141 s
142 }
143
144 pub fn with_mixer(
146 config: &EngineConfig,
147 mixer: Mixer,
148 midi_rx: Option<MidiRingReceiver>,
149 panic_flag: Arc<AtomicBool>,
150 vu_levels: Arc<VuLevels>,
151 ) -> Self {
152 let buf_size = config.buffer_size as usize;
153 use phosphor_plugin::{ParameterInfo, PluginCategory, PluginInfo};
156 struct NullPlugin;
157 impl Plugin for NullPlugin {
158 fn info(&self) -> PluginInfo {
159 PluginInfo {
160 name: "null".into(),
161 version: "0".into(),
162 author: "".into(),
163 category: PluginCategory::Utility,
164 }
165 }
166 fn init(&mut self, _sr: f64, _bs: usize) {}
167 fn process(
168 &mut self,
169 _i: &[&[f32]],
170 _o: &mut [&mut [f32]],
171 _m: &[MidiEvent],
172 ) {
173 }
174 fn parameter_count(&self) -> usize { 0 }
175 fn parameter_info(&self, _: usize) -> Option<ParameterInfo> { None }
176 fn get_parameter(&self, _: usize) -> f32 { 0.0 }
177 fn set_parameter(&mut self, _: usize, _: f32) {}
178 fn reset(&mut self) {}
179 }
180
181 Self {
182 channels: 2,
183 sample_rate: config.sample_rate,
184 synth: Box::new(NullPlugin),
185 midi_rx,
186 panic_flag,
187 vu_levels,
188 midi_scratch: Vec::with_capacity(256),
189 plugin_events: Vec::with_capacity(256),
190 plugin_buf_l: vec![0.0; buf_size],
191 plugin_buf_r: vec![0.0; buf_size],
192 mixer: Some(mixer),
193 }
194 }
195
196 pub fn flush_midi(&mut self) {
199 if let Some(rx) = &mut self.midi_rx {
200 self.midi_scratch.clear();
201 rx.drain_into(&mut self.midi_scratch);
202 if !self.midi_scratch.is_empty() {
203 tracing::info!("Flushed {} stale MIDI events", self.midi_scratch.len());
204 }
205 self.midi_scratch.clear();
206 }
207 }
208
209 pub fn process(&mut self, output: &mut [f32], transport: &Transport) {
213 if self.panic_flag.swap(false, Ordering::Relaxed) {
215 self.synth.reset();
216 if let Some(ref mut mixer) = self.mixer {
217 mixer.reset_all();
218 }
219 output.fill(0.0);
220 return;
221 }
222
223 let num_frames = output.len() / self.channels as usize;
224
225 self.midi_scratch.clear();
227 if let Some(rx) = &mut self.midi_rx {
228 rx.drain_into(&mut self.midi_scratch);
229 }
230
231 if let Some(ref mut mixer) = self.mixer {
233 mixer.process(output, &self.midi_scratch, transport);
234 transport.advance(num_frames as u32, self.sample_rate);
236 return;
237 }
238
239 if self.plugin_buf_l.len() < num_frames {
242 self.plugin_buf_l.resize(num_frames, 0.0);
243 self.plugin_buf_r.resize(num_frames, 0.0);
244 }
245
246 self.plugin_events.clear();
248 for msg in &self.midi_scratch {
249 if let Some(ev) = midi_to_plugin_event(msg) {
250 self.plugin_events.push(ev);
251 }
252 }
253
254 self.plugin_buf_l[..num_frames].fill(0.0);
256 self.plugin_buf_r[..num_frames].fill(0.0);
257
258 {
260 let mut outputs: [&mut [f32]; 2] = [
261 &mut self.plugin_buf_l[..num_frames],
262 &mut self.plugin_buf_r[..num_frames],
263 ];
264 self.synth.process(&[], &mut outputs, &self.plugin_events);
265 }
266
267 let mut peak_l = 0.0f32;
269 let mut peak_r = 0.0f32;
270 for i in 0..num_frames {
271 let idx = i * self.channels as usize;
272 let l = self.plugin_buf_l[i];
273 output[idx] = l;
274 peak_l = peak_l.max(l.abs());
275 if self.channels >= 2 {
276 let r = self.plugin_buf_r[i];
277 output[idx + 1] = r;
278 peak_r = peak_r.max(r.abs());
279 }
280 }
281
282 let (old_l, old_r) = self.vu_levels.get();
284 let decay = 0.85f32; let new_l = if peak_l > old_l { peak_l } else { old_l * decay };
286 let new_r = if peak_r > old_r { peak_r } else { old_r * decay };
287 self.vu_levels.set(new_l, new_r);
288
289 transport.advance(num_frames as u32, self.sample_rate);
291 }
292}
293
294fn midi_to_plugin_event(msg: &MidiMessage) -> Option<MidiEvent> {
296 match msg.message_type {
299 MidiMessageType::NoteOn { .. }
300 | MidiMessageType::NoteOff { .. }
301 | MidiMessageType::ControlChange { .. }
302 | MidiMessageType::PitchBend { .. } => Some(MidiEvent {
303 sample_offset: 0,
304 status: msg.raw[0],
305 data1: msg.raw[1],
306 data2: msg.raw[2],
307 }),
308 _ => None,
309 }
310}
311
312pub struct Engine {
316 pub shared: EngineShared,
317}
318
319impl Engine {
320 pub fn new(config: EngineConfig) -> Self {
321 let (tx, _rx) = mixer_command_channel();
322 Self {
323 shared: EngineShared::with_command_tx(config, tx),
324 }
325 }
326
327 pub fn with_command_tx(config: EngineConfig, tx: Sender<MixerCommand>) -> Self {
329 Self {
330 shared: EngineShared::with_command_tx(config, tx),
331 }
332 }
333
334 pub fn transport(&self) -> &Transport {
336 &self.shared.transport
337 }
338}
339
340impl std::ops::Deref for Engine {
342 type Target = EngineShared;
343 fn deref(&self) -> &Self::Target {
344 &self.shared
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use phosphor_dsp::synth::PhosphorSynth;
352 use phosphor_midi::ring::midi_ring_buffer;
353 use phosphor_midi::message::MidiMessage;
354
355 fn test_engine(midi_rx: Option<MidiRingReceiver>) -> (EngineAudio, Arc<Transport>) {
356 let config = EngineConfig { buffer_size: 64, sample_rate: 44100 };
357 let transport = Arc::new(Transport::new(120.0));
358 let panic_flag = Arc::new(AtomicBool::new(false));
359 let vu_levels = Arc::new(VuLevels::new());
360 let synth = Box::new(PhosphorSynth::new());
361 let engine = EngineAudio::new(&config, synth, midi_rx, panic_flag, vu_levels);
362 (engine, transport)
363 }
364
365 #[test]
366 fn engine_produces_silence_with_no_midi() {
367 let (mut engine, transport) = test_engine(None);
368 let mut output = vec![0.0f32; 128]; engine.process(&mut output, &transport);
370 assert!(output.iter().all(|&s| s == 0.0));
371 }
372
373 #[test]
374 fn engine_produces_sound_from_midi() {
375 let (mut tx, rx) = midi_ring_buffer();
376 let (mut engine, transport) = test_engine(Some(rx));
377
378 let msg = MidiMessage::from_bytes(&[0x90, 60, 100], 0).unwrap();
380 tx.push(msg);
381
382 let mut output = vec![0.0f32; 512]; engine.process(&mut output, &transport);
384
385 let peak = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
386 assert!(peak > 0.01, "Should produce sound from MIDI, peak={peak}");
387 }
388
389 #[test]
390 fn engine_output_is_stereo() {
391 let (mut tx, rx) = midi_ring_buffer();
392 let (mut engine, transport) = test_engine(Some(rx));
393
394 let msg = MidiMessage::from_bytes(&[0x90, 60, 100], 0).unwrap();
395 tx.push(msg);
396
397 let mut output = vec![0.0f32; 128];
398 engine.process(&mut output, &transport);
399
400 for i in 0..64 {
402 assert_eq!(output[i * 2], output[i * 2 + 1], "L/R mismatch at frame {i}");
403 }
404 }
405
406 #[test]
407 fn engine_output_always_finite() {
408 let (mut tx, rx) = midi_ring_buffer();
409 let (mut engine, transport) = test_engine(Some(rx));
410
411 let msg = MidiMessage::from_bytes(&[0x90, 60, 127], 0).unwrap();
412 tx.push(msg);
413
414 for _ in 0..1000 {
415 let mut output = vec![0.0f32; 128];
416 engine.process(&mut output, &transport);
417 assert!(output.iter().all(|s| s.is_finite()));
418 }
419 }
420
421 #[test]
422 fn engine_note_off_leads_to_silence() {
423 let (mut tx, rx) = midi_ring_buffer();
424 let (mut engine, transport) = test_engine(Some(rx));
425
426 tx.push(MidiMessage::from_bytes(&[0x90, 60, 100], 0).unwrap());
428 let mut output = vec![0.0f32; 128];
429 engine.process(&mut output, &transport);
430
431 tx.push(MidiMessage::from_bytes(&[0x80, 60, 0], 0).unwrap());
433
434 for _ in 0..500 {
436 output.fill(0.0);
437 engine.process(&mut output, &transport);
438 }
439
440 let peak = output.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
442 assert!(peak < 0.001, "Should be silent after release, peak={peak}");
443 }
444
445 #[test]
446 fn engine_advances_transport() {
447 let (mut engine, transport) = test_engine(None);
448 transport.play();
449 let mut output = vec![0.0f32; 128];
450 engine.process(&mut output, &transport);
451 assert!(transport.position_ticks() > 0);
452 }
453}