midnote/
note.rs

1use std::fmt;
2use std::sync::atomic::{AtomicU8, Ordering};
3
4use midly::MidiMessage;
5use nodi::{Event, Moment};
6
7static STYLE: AtomicU8 = AtomicU8::new(1);
8
9pub fn toggle_style() {
10	STYLE
11		.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| {
12			Some(n.wrapping_add(1) % (NoteStyle::VALUES.len() as u8))
13		})
14		.unwrap();
15}
16
17pub fn style() -> NoteStyle {
18	NoteStyle::VALUES[STYLE.load(Ordering::Relaxed) as usize]
19}
20
21struct NoteName {
22	abc: &'static str,
23	doremi: &'static str,
24}
25
26const NOTES: [NoteName; 12] = [
27	NoteName {
28		abc: "C",
29		doremi: "Do",
30	},
31	NoteName {
32		abc: "C#",
33		doremi: "Do#",
34	},
35	NoteName {
36		abc: "D",
37		doremi: "Re",
38	},
39	NoteName {
40		abc: "E♭",
41		doremi: "Mi♭",
42	},
43	NoteName {
44		abc: "E",
45		doremi: "Mi",
46	},
47	NoteName {
48		abc: "F",
49		doremi: "Fa",
50	},
51	NoteName {
52		abc: "F#",
53		doremi: "Fa#",
54	},
55	NoteName {
56		abc: "G",
57		doremi: "Sol",
58	},
59	NoteName {
60		abc: "A♭",
61		doremi: "La♭",
62	},
63	NoteName {
64		abc: "A",
65		doremi: "La",
66	},
67	NoteName {
68		abc: "B♭",
69		doremi: "Si♭",
70	},
71	NoteName {
72		abc: "B",
73		doremi: "Si",
74	},
75];
76
77#[derive(Copy, Clone, PartialEq, Eq)]
78pub struct Note {
79	offset: u8,
80	octave: u8,
81}
82
83impl From<u8> for Note {
84	fn from(n: u8) -> Self {
85		let offset = n % 12;
86		let octave = if offset == 0 { n / 12 } else { n / 12 + 1 };
87		Self { offset, octave }
88	}
89}
90
91impl fmt::Display for Note {
92	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93		style().display_note(*self, f)
94	}
95}
96
97pub fn moment_notes(moment: &Moment, shift: i8) -> Option<Vec<Note>> {
98	match &moment {
99		Moment::Empty => None,
100		Moment::Events(events) => {
101			let mut buf = Vec::new();
102			for e in events {
103				if let Event::Midi(m) = e {
104					if m.channel == 9 {
105						// Drum channel, skip.
106						continue;
107					}
108					match m.message {
109						MidiMessage::NoteOn { key, vel } if vel > 0 => {
110							let key = key.as_int() as i32 + shift as i32;
111							if (0..=127).contains(&key) {
112								let k = Note::from(key as u8);
113								if !buf.contains(&k) {
114									buf.push(k);
115								}
116							}
117						}
118						_ => {}
119					};
120				}
121			}
122
123			if buf.is_empty() {
124				None
125			} else {
126				Some(buf)
127			}
128		}
129	}
130}
131
132#[derive(Copy, Clone)]
133pub enum NoteStyle {
134	/// A, B, C
135	Abc,
136	/// A3, B2, E7
137	AbcN,
138	/// Do, Re, Mi
139	Doremi,
140	/// Do 2, Re 5, Mi 7
141	DoremiN,
142}
143
144impl NoteStyle {
145	pub const VALUES: [Self; 4] = [Self::Abc, Self::AbcN, Self::Doremi, Self::DoremiN];
146
147	#[inline]
148	pub fn display_note(self, n: Note, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149		let display = &NOTES[n.offset as usize];
150		match self {
151			Self::Abc => f.write_str(display.abc),
152			Self::AbcN => write!(f, "{}{}", display.abc, n.octave),
153			Self::Doremi => f.write_str(display.doremi),
154			Self::DoremiN => write!(f, "{}{}", display.doremi, n.octave),
155		}
156	}
157}