1use crate::port::{GraphModule, PortDef, PortSpec, PortValues, SignalKind};
7use alloc::sync::Arc;
8use alloc::vec;
9use alloc::vec::Vec;
10use core::sync::atomic::Ordering;
11use portable_atomic::AtomicU64;
14
15#[derive(Debug)]
20pub struct AtomicF64(AtomicU64);
21
22impl AtomicF64 {
23 pub fn new(value: f64) -> Self {
25 Self(AtomicU64::new(value.to_bits()))
26 }
27
28 pub fn get(&self) -> f64 {
30 f64::from_bits(self.0.load(Ordering::Relaxed))
31 }
32
33 pub fn set(&self, value: f64) {
35 self.0.store(value.to_bits(), Ordering::Relaxed);
36 }
37
38 pub fn load(&self, ordering: Ordering) -> f64 {
40 f64::from_bits(self.0.load(ordering))
41 }
42
43 pub fn store(&self, value: f64, ordering: Ordering) {
45 self.0.store(value.to_bits(), ordering);
46 }
47}
48
49impl Default for AtomicF64 {
50 fn default() -> Self {
51 Self::new(0.0)
52 }
53}
54
55impl Clone for AtomicF64 {
56 fn clone(&self) -> Self {
57 Self::new(self.get())
58 }
59}
60
61#[derive(Debug)]
78pub struct AtomicNote(AtomicU64);
79
80impl AtomicNote {
81 pub fn new(pitch: f64, gate: f64) -> Self {
83 Self(AtomicU64::new(Self::pack(pitch, gate)))
84 }
85
86 #[inline]
87 fn pack(pitch: f64, gate: f64) -> u64 {
88 (((pitch as f32).to_bits() as u64) << 32) | ((gate as f32).to_bits() as u64)
89 }
90
91 #[inline]
92 fn unpack(bits: u64) -> (f64, f64) {
93 let pitch = f32::from_bits((bits >> 32) as u32) as f64;
94 let gate = f32::from_bits(bits as u32) as f64;
95 (pitch, gate)
96 }
97
98 #[inline]
100 pub fn publish(&self, pitch: f64, gate: f64) {
101 self.0.store(Self::pack(pitch, gate), Ordering::Release);
102 }
103
104 #[inline]
109 pub fn snapshot(&self) -> (f64, f64) {
110 Self::unpack(self.0.load(Ordering::Acquire))
111 }
112}
113
114impl Default for AtomicNote {
115 fn default() -> Self {
116 Self::new(0.0, 0.0)
117 }
118}
119
120impl Clone for AtomicNote {
121 fn clone(&self) -> Self {
122 let (pitch, gate) = self.snapshot();
123 Self::new(pitch, gate)
124 }
125}
126
127pub struct ExternalInput {
132 value: Arc<AtomicF64>,
133 spec: PortSpec,
134}
135
136impl ExternalInput {
137 pub fn new(value: Arc<AtomicF64>, kind: SignalKind) -> Self {
139 Self {
140 value,
141 spec: PortSpec {
142 inputs: vec![],
143 outputs: vec![PortDef::new(0, "out", kind)],
144 },
145 }
146 }
147
148 pub fn voct(value: Arc<AtomicF64>) -> Self {
150 Self::new(value, SignalKind::VoltPerOctave)
151 }
152
153 pub fn gate(value: Arc<AtomicF64>) -> Self {
155 Self::new(value, SignalKind::Gate)
156 }
157
158 pub fn cv(value: Arc<AtomicF64>) -> Self {
160 Self::new(value, SignalKind::CvUnipolar)
161 }
162
163 pub fn cv_bipolar(value: Arc<AtomicF64>) -> Self {
165 Self::new(value, SignalKind::CvBipolar)
166 }
167
168 pub fn trigger(value: Arc<AtomicF64>) -> Self {
170 Self::new(value, SignalKind::Trigger)
171 }
172
173 pub fn audio(value: Arc<AtomicF64>) -> Self {
175 Self::new(value, SignalKind::Audio)
176 }
177
178 pub fn value_ref(&self) -> &Arc<AtomicF64> {
180 &self.value
181 }
182}
183
184impl GraphModule for ExternalInput {
185 fn port_spec(&self) -> &PortSpec {
186 &self.spec
187 }
188
189 fn tick(&mut self, _inputs: &PortValues, outputs: &mut PortValues) {
190 outputs.set(0, self.value.get());
191 }
192
193 fn reset(&mut self) {}
194
195 fn set_sample_rate(&mut self, _: f64) {}
196
197 fn type_id(&self) -> &'static str {
198 "external_input"
199 }
200}
201
202#[derive(Debug)]
207pub struct MidiState {
208 pub pitch: Arc<AtomicF64>,
217
218 pub gate: Arc<AtomicF64>,
224
225 pub velocity: Arc<AtomicF64>,
227
228 pub mod_wheel: Arc<AtomicF64>,
230
231 pub pitch_bend: Arc<AtomicF64>,
233
234 pub aftertouch: Arc<AtomicF64>,
236
237 pub sustain: Arc<AtomicF64>,
239
240 pub expression: Arc<AtomicF64>,
242
243 pub note: Arc<AtomicNote>,
249
250 held_notes: Vec<u8>,
252}
253
254impl MidiState {
255 pub fn new() -> Self {
257 Self {
258 pitch: Arc::new(AtomicF64::new(0.0)),
259 gate: Arc::new(AtomicF64::new(0.0)),
260 velocity: Arc::new(AtomicF64::new(0.0)),
261 mod_wheel: Arc::new(AtomicF64::new(0.0)),
262 pitch_bend: Arc::new(AtomicF64::new(0.0)),
263 aftertouch: Arc::new(AtomicF64::new(0.0)),
264 sustain: Arc::new(AtomicF64::new(0.0)),
265 expression: Arc::new(AtomicF64::new(10.0)),
266 note: Arc::new(AtomicNote::new(0.0, 0.0)),
267 held_notes: Vec::new(),
268 }
269 }
270
271 pub fn handle_message(&mut self, msg: &[u8]) {
275 if msg.is_empty() {
276 return;
277 }
278
279 let status = msg[0] & 0xF0;
280 let _channel = msg[0] & 0x0F;
281
282 match (status, msg.len()) {
283 (0x90, 3) if msg[2] > 0 => {
285 let note = msg[1];
286 let vel = msg[2];
287 let voct = Self::note_to_voct(note);
288
289 self.held_notes.push(note);
290 self.pitch.set(voct);
297 self.velocity.set(vel as f64 / 127.0 * 10.0);
298 self.gate.set(5.0);
299 self.note.publish(voct, 5.0);
301 }
302
303 (0x80, 3) | (0x90, 3) => {
305 let note = msg[1];
306 self.held_notes.retain(|&n| n != note);
307
308 if self.held_notes.is_empty() {
309 self.gate.set(0.0);
310 self.note.publish(self.pitch.get(), 0.0);
312 } else {
313 let last = *self.held_notes.last().unwrap();
315 let voct = Self::note_to_voct(last);
316 self.pitch.set(voct);
317 self.note.publish(voct, 5.0);
318 }
319 }
320
321 (0xB0, 3) => {
323 let cc = msg[1];
324 let value = msg[2];
325 let v = value as f64 / 127.0 * 10.0;
326
327 match cc {
328 1 => self.mod_wheel.set(v), 11 => self.expression.set(v), 64 => self.sustain.set(if value >= 64 { 5.0 } else { 0.0 }), _ => {}
332 }
333 }
334
335 (0xE0, 3) => {
337 let lsb = msg[1] as u16;
338 let msb = msg[2] as u16;
339 let bend_raw = lsb | (msb << 7);
340 let bend = (bend_raw as f64 - 8192.0) / 8192.0 * (2.0 / 12.0);
342 self.pitch_bend.set(bend);
343 }
344
345 (0xD0, 2) => {
347 let pressure = msg[1];
348 self.aftertouch.set(pressure as f64 / 127.0 * 10.0);
349 }
350
351 (0xA0, 3) => {
353 let pressure = msg[2];
354 self.aftertouch.set(pressure as f64 / 127.0 * 10.0);
355 }
356
357 _ => {}
358 }
359 }
360
361 fn note_to_voct(note: u8) -> f64 {
365 (note as f64 - 60.0) / 12.0
366 }
367
368 pub fn note_snapshot(&self) -> (f64, f64) {
376 self.note.snapshot()
377 }
378
379 pub fn held_notes(&self) -> &[u8] {
381 &self.held_notes
382 }
383
384 pub fn notes_active(&self) -> bool {
386 !self.held_notes.is_empty()
387 }
388
389 pub fn reset(&mut self) {
391 self.pitch.set(0.0);
392 self.gate.set(0.0);
393 self.velocity.set(0.0);
394 self.mod_wheel.set(0.0);
395 self.pitch_bend.set(0.0);
396 self.aftertouch.set(0.0);
397 self.sustain.set(0.0);
398 self.expression.set(10.0);
399 self.note.publish(0.0, 0.0);
400 self.held_notes.clear();
401 }
402
403 pub fn all_notes_off(&mut self) {
405 self.held_notes.clear();
406 self.gate.set(0.0);
407 self.note.publish(self.pitch.get(), 0.0);
408 }
409}
410
411impl Default for MidiState {
412 fn default() -> Self {
413 Self::new()
414 }
415}
416
417impl Clone for MidiState {
418 fn clone(&self) -> Self {
419 Self {
420 pitch: Arc::new(AtomicF64::new(self.pitch.get())),
421 gate: Arc::new(AtomicF64::new(self.gate.get())),
422 velocity: Arc::new(AtomicF64::new(self.velocity.get())),
423 mod_wheel: Arc::new(AtomicF64::new(self.mod_wheel.get())),
424 pitch_bend: Arc::new(AtomicF64::new(self.pitch_bend.get())),
425 aftertouch: Arc::new(AtomicF64::new(self.aftertouch.get())),
426 sustain: Arc::new(AtomicF64::new(self.sustain.get())),
427 expression: Arc::new(AtomicF64::new(self.expression.get())),
428 note: Arc::new((*self.note).clone()),
429 held_notes: self.held_notes.clone(),
430 }
431 }
432}
433
434pub struct ExternalOutput {
438 value: Arc<AtomicF64>,
439 spec: PortSpec,
440}
441
442impl ExternalOutput {
443 pub fn new(value: Arc<AtomicF64>, kind: SignalKind) -> Self {
444 Self {
445 value,
446 spec: PortSpec {
447 inputs: vec![PortDef::new(0, "in", kind)],
448 outputs: vec![],
449 },
450 }
451 }
452
453 pub fn value_ref(&self) -> &Arc<AtomicF64> {
454 &self.value
455 }
456}
457
458impl GraphModule for ExternalOutput {
459 fn port_spec(&self) -> &PortSpec {
460 &self.spec
461 }
462
463 fn tick(&mut self, inputs: &PortValues, _outputs: &mut PortValues) {
464 let value = inputs.get_or(0, 0.0);
465 self.value.set(value);
466 }
467
468 fn reset(&mut self) {
469 self.value.set(0.0);
470 }
471
472 fn set_sample_rate(&mut self, _: f64) {}
473
474 fn type_id(&self) -> &'static str {
475 "external_output"
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_atomic_f64() {
485 let a = AtomicF64::new(3.5);
486 assert!((a.get() - 3.5).abs() < 0.001);
487
488 a.set(2.5);
489 assert!((a.get() - 2.5).abs() < 0.001);
490 }
491
492 #[test]
493 #[cfg(feature = "std")]
494 fn test_atomic_f64_thread_safe() {
495 let a = Arc::new(AtomicF64::new(0.0));
496 let a2 = Arc::clone(&a);
497
498 std::thread::spawn(move || {
499 a2.set(42.0);
500 })
501 .join()
502 .unwrap();
503
504 assert!((a.get() - 42.0).abs() < 0.001);
505 }
506
507 #[test]
508 fn test_external_input() {
509 let value = Arc::new(AtomicF64::new(5.0));
510 let mut input = ExternalInput::voct(value.clone());
511
512 let inputs = PortValues::new();
513 let mut outputs = PortValues::new();
514
515 input.tick(&inputs, &mut outputs);
516 assert!((outputs.get(0).unwrap() - 5.0).abs() < 0.001);
517
518 value.set(10.0);
520 input.tick(&inputs, &mut outputs);
521 assert!((outputs.get(0).unwrap() - 10.0).abs() < 0.001);
522 }
523
524 #[test]
525 fn test_midi_state_note_on_off() {
526 let mut midi = MidiState::new();
527
528 midi.handle_message(&[0x90, 60, 100]);
530 assert!((midi.pitch.get() - 0.0).abs() < 0.001); assert!((midi.gate.get() - 5.0).abs() < 0.001);
532 assert!(midi.velocity.get() > 0.0);
533
534 midi.handle_message(&[0x90, 72, 100]);
536 assert!((midi.pitch.get() - 1.0).abs() < 0.001); midi.handle_message(&[0x80, 72, 0]);
540 assert!((midi.pitch.get() - 0.0).abs() < 0.001);
542 assert!((midi.gate.get() - 5.0).abs() < 0.001); midi.handle_message(&[0x80, 60, 0]);
546 assert!((midi.gate.get() - 0.0).abs() < 0.001); }
548
549 #[test]
550 fn test_midi_state_pitch_bend() {
551 let mut midi = MidiState::new();
552
553 midi.handle_message(&[0xE0, 0, 64]);
555 assert!(midi.pitch_bend.get().abs() < 0.01);
556
557 midi.handle_message(&[0xE0, 127, 127]);
559 assert!(midi.pitch_bend.get() > 0.1);
560
561 midi.handle_message(&[0xE0, 0, 0]);
563 assert!(midi.pitch_bend.get() < -0.1);
564 }
565
566 #[test]
567 fn test_midi_state_cc() {
568 let mut midi = MidiState::new();
569
570 midi.handle_message(&[0xB0, 1, 127]);
572 assert!((midi.mod_wheel.get() - 10.0).abs() < 0.01);
573
574 midi.handle_message(&[0xB0, 64, 127]);
576 assert!((midi.sustain.get() - 5.0).abs() < 0.01);
577
578 midi.handle_message(&[0xB0, 64, 0]);
580 assert!((midi.sustain.get() - 0.0).abs() < 0.01);
581 }
582
583 #[test]
584 fn test_external_output() {
585 let value = Arc::new(AtomicF64::new(0.0));
586 let mut output = ExternalOutput::new(value.clone(), SignalKind::CvUnipolar);
587
588 let mut inputs = PortValues::new();
589 inputs.set(0, 7.5);
590
591 output.tick(&inputs, &mut PortValues::new());
592 assert!((value.get() - 7.5).abs() < 0.001);
593 }
594
595 #[test]
596 fn test_atomic_f64_load_store() {
597 use core::sync::atomic::Ordering;
598 let a = AtomicF64::new(1.0);
599 assert!((a.load(Ordering::SeqCst) - 1.0).abs() < 0.001);
600
601 a.store(99.0, Ordering::SeqCst);
602 assert!((a.load(Ordering::SeqCst) - 99.0).abs() < 0.001);
603 }
604
605 #[test]
606 fn test_external_input_constructors() {
607 let value = Arc::new(AtomicF64::new(0.0));
608
609 let gate = ExternalInput::gate(value.clone());
610 assert!(gate.spec.outputs[0].kind == SignalKind::Gate);
611
612 let cv = ExternalInput::cv(value.clone());
613 assert!(cv.spec.outputs[0].kind == SignalKind::CvUnipolar);
614
615 let cv_bi = ExternalInput::cv_bipolar(value.clone());
616 assert!(cv_bi.spec.outputs[0].kind == SignalKind::CvBipolar);
617
618 let trigger = ExternalInput::trigger(value.clone());
619 assert!(trigger.spec.outputs[0].kind == SignalKind::Trigger);
620
621 let audio = ExternalInput::audio(value.clone());
622 assert!(audio.spec.outputs[0].kind == SignalKind::Audio);
623 }
624
625 #[test]
626 fn test_external_input_value_ref() {
627 let value = Arc::new(AtomicF64::new(42.0));
628 let input = ExternalInput::voct(value.clone());
629 assert!((input.value_ref().get() - 42.0).abs() < 0.001);
630 }
631
632 #[test]
633 fn test_external_input_reset_set_sample_rate() {
634 let value = Arc::new(AtomicF64::new(5.0));
635 let mut input = ExternalInput::voct(value.clone());
636
637 input.reset();
638 input.set_sample_rate(48000.0);
639 assert_eq!(input.type_id(), "external_input");
640 }
641
642 #[test]
643 fn test_external_output_reset_type_id() {
644 let value = Arc::new(AtomicF64::new(5.0));
645 let mut output = ExternalOutput::new(value.clone(), SignalKind::Audio);
646
647 output.reset();
648 assert!((value.get() - 0.0).abs() < 0.001);
649
650 output.set_sample_rate(48000.0);
651 assert_eq!(output.type_id(), "external_output");
652 assert!(output.value_ref().get().abs() < 0.001);
653 }
654
655 #[test]
656 fn test_midi_state_default() {
657 let midi = MidiState::default();
658 assert!(midi.pitch.get().abs() < 0.001);
659 }
660
661 #[test]
662 fn test_midi_state_clone() {
663 let mut midi = MidiState::new();
664 midi.handle_message(&[0x90, 60, 100]);
665
666 let cloned = midi.clone();
667 assert!((cloned.pitch.get() - midi.pitch.get()).abs() < 0.001);
668 }
669
670 #[test]
671 fn test_midi_state_reset() {
672 let mut midi = MidiState::new();
673 midi.handle_message(&[0x90, 60, 100]);
674 midi.handle_message(&[0xB0, 1, 127]);
675
676 midi.reset();
677 assert!(midi.pitch.get().abs() < 0.001);
678 assert!(midi.gate.get().abs() < 0.001);
679 assert!(midi.held_notes.is_empty());
680 }
681
682 #[test]
683 fn test_midi_state_all_notes_off() {
684 let mut midi = MidiState::new();
685 midi.handle_message(&[0x90, 60, 100]);
686 midi.handle_message(&[0x90, 62, 100]);
687
688 assert!(midi.notes_active());
689
690 midi.all_notes_off();
691 assert!(!midi.notes_active());
692 assert!(midi.gate.get().abs() < 0.001);
693 }
694
695 #[test]
696 fn test_midi_state_held_notes() {
697 let mut midi = MidiState::new();
698 midi.handle_message(&[0x90, 60, 100]);
699 midi.handle_message(&[0x90, 62, 100]);
700
701 assert_eq!(midi.held_notes(), &[60, 62]);
702 }
703
704 #[test]
705 fn test_midi_state_channel_aftertouch() {
706 let mut midi = MidiState::new();
707 midi.handle_message(&[0xD0, 100]);
708 assert!(midi.aftertouch.get() > 0.0);
709 }
710
711 #[test]
712 fn test_midi_state_poly_aftertouch() {
713 let mut midi = MidiState::new();
714 midi.handle_message(&[0xA0, 60, 100]);
715 assert!(midi.aftertouch.get() > 0.0);
716 }
717
718 #[test]
719 fn test_midi_state_expression() {
720 let mut midi = MidiState::new();
721 midi.handle_message(&[0xB0, 11, 100]);
722 assert!(midi.expression.get() > 0.0);
723 }
724
725 #[test]
726 fn test_midi_state_note_on_with_zero_velocity() {
727 let mut midi = MidiState::new();
728 midi.handle_message(&[0x90, 60, 100]);
729 assert!(midi.gate.get() > 0.0);
730
731 midi.handle_message(&[0x90, 60, 0]);
733 assert!(midi.gate.get().abs() < 0.001);
734 }
735
736 #[test]
738 fn test_atomic_note_pack_roundtrip() {
739 let note = AtomicNote::new(0.0, 0.0);
740
741 note.publish(0.75, 5.0);
742 let (p, g) = note.snapshot();
743 assert!((p - 0.75).abs() < 1e-6);
744 assert!((g - 5.0).abs() < 1e-6);
745
746 note.publish(-1.25, 0.0);
747 let (p, g) = note.snapshot();
748 assert!((p + 1.25).abs() < 1e-6);
749 assert_eq!(g, 0.0);
750
751 assert_eq!(AtomicNote::default().snapshot(), (0.0, 0.0));
753 assert_eq!(note.clone().snapshot(), note.snapshot());
754 }
755
756 #[test]
757 fn test_midi_state_note_snapshot_coherent() {
758 let mut midi = MidiState::new();
759
760 midi.handle_message(&[0x90, 72, 100]);
762 let (pitch, gate) = midi.note_snapshot();
763 assert!((pitch - 1.0).abs() < 1e-6);
764 assert!((gate - 5.0).abs() < 1e-6);
765
766 midi.handle_message(&[0x80, 72, 0]);
768 let (_pitch, gate) = midi.note_snapshot();
769 assert!(gate.abs() < 1e-6);
770 }
771
772 #[test]
779 fn test_midi_state_legato_snapshot_stays_coherent() {
780 let mut midi = MidiState::new();
781
782 midi.handle_message(&[0x90, 60, 100]);
784 let (p, g) = midi.note_snapshot();
785 assert!((p - 0.0).abs() < 1e-6 && (g - 5.0).abs() < 1e-6);
786
787 midi.handle_message(&[0x90, 72, 100]);
788 let (p, g) = midi.note_snapshot();
789 assert!(
790 (p - 1.0).abs() < 1e-6 && (g - 5.0).abs() < 1e-6,
791 "legato pitch change must pair the new pitch with a held gate, got ({p}, {g})"
792 );
793
794 midi.handle_message(&[0x80, 72, 0]);
797 let (p, g) = midi.note_snapshot();
798 assert!(
799 (p - 0.0).abs() < 1e-6 && (g - 5.0).abs() < 1e-6,
800 "after releasing the top note the held note's pitch pairs with gate 5V, got ({p}, {g})"
801 );
802
803 midi.handle_message(&[0x80, 60, 0]);
805 let (_p, g) = midi.note_snapshot();
806 assert!(g.abs() < 1e-6, "last note off closes the gate");
807 }
808
809 #[test]
810 #[cfg(feature = "std")]
811 fn test_atomic_note_no_tearing_across_threads() {
812 let note = Arc::new(AtomicNote::new(0.0, 0.0));
813 let writer_note = Arc::clone(¬e);
814
815 let writer = std::thread::spawn(move || {
817 for i in 0..200_000u32 {
818 if i % 2 == 0 {
819 writer_note.publish(1.0, 5.0);
820 } else {
821 writer_note.publish(0.0, 0.0);
822 }
823 }
824 });
825
826 for _ in 0..200_000 {
828 let (p, g) = note.snapshot();
829 let coherent = ((p - 1.0).abs() < 1e-6 && (g - 5.0).abs() < 1e-6)
830 || (p.abs() < 1e-6 && g.abs() < 1e-6);
831 assert!(coherent, "torn note snapshot observed: ({p}, {g})");
832 }
833
834 writer.join().unwrap();
835 }
836}