1use lazy_static::lazy_static;
142use once_cell::sync::OnceCell;
143use regex::Regex;
144use serde::{Deserialize, Serialize};
145use std::collections::HashMap;
146
147static _NOTES_MAP: OnceCell<NotesMap> = OnceCell::new();
149
150#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Serialize, Deserialize)]
159pub struct ResolvedNote {
160 pub note: NoteName,
161 pub accidental: Accidental,
162 pub octave: Octave,
163 pub midi: u8,
164}
165
166impl ResolvedNote {
167 pub fn new(note: NoteName, accidental: Accidental, octave: Octave, midi: u8) -> Self {
168 Self {
169 note,
170 accidental,
171 octave,
172 midi,
173 }
174 }
175 pub fn from_str(name: &str) -> Option<ResolvedNote> {
177 lazy_static! {
178 static ref RESOLVED_NOTE_RE: Regex = Regex::new(r"(\w*)(-?\d)").unwrap();
179 }
180 for cap in RESOLVED_NOTE_RE.captures_iter(name) {
181 let tonic = note_from_str(&cap[1])?;
182 let (note, accidental) = tonic;
183 let mut octave = Octave::from(&cap[2]);
184 if note == NoteName::B
185 && (accidental == Accidental::Sharp || accidental == Accidental::DoubleSharp)
186 {
187 octave = Octave::new(octave.raw() + 1);
188 } else if note == NoteName::C
189 && (accidental == Accidental::Flat || accidental == Accidental::DoubleFlat)
190 {
191 octave = Octave::new(octave.raw() - 1);
192 }
193 return Some(Self {
194 note,
195 accidental,
196 octave,
197 midi: octave.apply_to_midi_note(NotesMap::get().get_by_note(note, accidental)),
198 });
199 }
200 None
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Serialize, Deserialize)]
205pub struct Octave {
206 octave: u8,
207}
208impl Octave {
209 pub fn new(octave: u8) -> Self {
216 Self { octave }
217 }
218 pub fn raw(&self) -> u8 {
219 self.octave
220 }
221 pub fn as_midi(&self) -> i8 {
224 self.octave as i8 - 2
225 }
226 pub fn from_midi(midi: u8) -> Self {
227 Self { octave: midi / 12 }
228 }
229 pub fn split_midi(midi: u8) -> (u8, Self) {
231 (midi % 12, Self::from_midi(midi))
232 }
233 pub fn apply_to_midi_note(&self, note_idx: u8) -> u8 {
235 self.octave * 12 + (note_idx % 12)
236 }
237}
238impl From<&str> for Octave {
239 fn from(value: &str) -> Self {
240 let nr = value.parse::<i8>().unwrap();
241 return Self {
242 octave: (nr + 2) as u8,
243 };
244 }
245}
246
247pub fn midi_to_note(midi: u8, key: Key, accidental: Option<Accidental>) -> ResolvedNote {
252 Note::from_midi(midi, accidental).resolve(key)
253}
254
255#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Serialize, Deserialize)]
257pub struct Note {
258 midi: u8,
259 accidental: Option<Accidental>,
260 octave: Octave,
261}
262impl Note {
263 pub fn from_midi(midi: u8, accidental: Option<Accidental>) -> Self {
264 Self {
265 midi,
266 accidental,
267 octave: Octave::from_midi(midi),
268 }
269 }
270 pub fn resolve(&self, key: Key) -> ResolvedNote {
271 let (midi_note, octave) = Octave::split_midi(self.midi);
272 let notes_map = NotesMap::get();
274 let res = self.resolve_by_accident(¬es_map, midi_note, octave);
275 if res.is_some() {
276 return res.unwrap();
277 }
278 let scale = key.resolve_scale(¬es_map);
279
280 scale.resolve_pitch(notes_map, midi_note, octave)
281 }
282 fn resolve_by_accident(
283 &self,
284 notes_map: &NotesMap,
285 midi_note: u8,
286 octave: Octave,
287 ) -> Option<ResolvedNote> {
288 if self.accidental.is_some() {
289 let acc = self.accidental.as_ref().unwrap();
290 let note = notes_map.get_by_midi(&midi_note).get(&acc);
291 if note.is_some() {
292 return Some(ResolvedNote::new(
293 note.unwrap().clone(),
294 acc.clone(),
295 octave,
296 octave.apply_to_midi_note(midi_note),
297 ));
298 }
299 }
300 None
301 }
302 pub fn midi(&self) -> u8 {
303 self.midi
304 }
305 pub fn set_midi(&mut self, midi: u8) {
306 self.midi = midi;
307 }
308 pub fn accidental(&self) -> Option<Accidental> {
309 self.accidental.clone()
310 }
311 pub fn set_accidental(&mut self, accidental: Option<Accidental>) {
312 self.accidental = accidental;
313 }
314}
315impl From<u8> for Note {
316 fn from(midi: u8) -> Self {
318 Self::from_midi(midi, None)
319 }
320}
321impl From<ResolvedNote> for Note {
322 fn from(value: ResolvedNote) -> Self {
323 Self {
324 midi: value.midi,
325 accidental: Some(value.accidental),
326 octave: value.octave,
327 }
328 }
329}
330
331#[derive(Debug, PartialEq, PartialOrd, Hash, Eq, Clone, Copy, Serialize, Deserialize)]
332pub enum Accidental {
333 White,
334 Sharp,
335 DoubleSharp,
336 Flat,
337 DoubleFlat,
338}
339impl Accidental {
340 pub fn to_string_by_note(&self, note: NoteName) -> String {
341 match self {
342 Self::DoubleFlat | Self::Flat => {
343 if note.need_trunk() {
344 let mut s = self.to_string();
345 s.remove(0);
346 s
347 } else {
348 self.to_string()
349 }
350 }
351 _ => self.to_string(),
352 }
353 }
354 pub fn from_str(name: &str) -> Option<Self> {
356 match name.to_lowercase().as_str() {
357 "s" | "es" => Some(Self::Flat),
358 "eses" => Some(Self::DoubleFlat),
359 "is" => Some(Self::Sharp),
360 "isis" => Some(Self::DoubleSharp),
361 "white" => Some(Self::White),
362 _ => None,
363 }
364 }
365}
366impl Default for Accidental {
367 fn default() -> Self {
368 Self::White
369 }
370}
371impl ToString for Accidental {
372 fn to_string(&self) -> String {
373 match self {
374 Accidental::White => "white".to_string(),
375 Accidental::Sharp => "is".to_string(),
376 Accidental::DoubleSharp => "isis".to_string(),
377 Accidental::Flat => "es".to_string(),
378 Accidental::DoubleFlat => "eses".to_string(),
379 }
380 }
381}
382
383#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Serialize, Deserialize)]
384pub struct Key {
385 pub tonic: (NoteName, Accidental),
386 pub scale: Scale,
387}
388impl Key {
389 pub fn new(note: NoteName, accidental: Accidental, scale: Scale) -> Self {
390 Self {
391 tonic: (note, accidental),
392 scale,
393 }
394 }
395 pub fn get_root(&self) -> [u8; 2] {
400 let (tonic_note, tonic_acc) = self.tonic.clone();
401 let root_midi = NotesMap::get().get_by_note(tonic_note.clone(), tonic_acc);
402 let root_idx = tonic_note.index();
403 [root_midi, root_idx]
404 }
405 fn resolve_scale(&self, notes_map: &NotesMap) -> ResolvedScale {
407 self.scale.resolve_for_key(notes_map, self)
408 }
409 pub fn from_str(name: &str, scale: Scale) -> Option<Self> {
411 Some(Self {
412 tonic: note_from_str(name)?,
413 scale,
414 })
415 }
416}
417impl Default for Key {
418 fn default() -> Self {
419 Self::new(Default::default(), Default::default(), Default::default())
420 }
421}
422impl From<(&String, Scale)> for Key {
423 fn from(value: (&String, Scale)) -> Self {
424 let (name, scale) = value;
425 Self {
426 tonic: note_from_str(name).unwrap(),
427 scale,
428 }
429 }
430}
431
432fn note_from_str(name: &str) -> Option<(NoteName, Accidental)> {
433 let note = &name[0..1];
434 match NoteName::from_str(note) {
435 Some(note) => {
436 if name.len() <= 1 {
437 return Some((note, Accidental::White));
438 } else {
439 match Accidental::from_str(&name[1..]) {
440 Some(acc) => Some((note, acc)),
441 None => None,
442 }
443 }
444 }
445 None => None,
446 }
447}
448
449#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Serialize, Deserialize)]
450pub enum NoteName {
451 C,
452 D,
453 E,
454 F,
455 G,
456 A,
457 B,
458}
459impl NoteName {
460 fn need_trunk(&self) -> bool {
461 match self {
462 Self::A | Self::E => true,
463 _ => false,
464 }
465 }
466 fn index(self) -> u8 {
467 match self {
468 Self::C => 0,
469 Self::D => 1,
470 Self::E => 2,
471 Self::F => 3,
472 Self::G => 4,
473 Self::A => 5,
474 Self::B => 6,
475 }
476 }
477 pub fn from_str(name: &str) -> Option<Self> {
478 match name.to_uppercase().as_str() {
479 "C" => Some(Self::C),
480 "D" => Some(Self::D),
481 "E" => Some(Self::E),
482 "F" => Some(Self::F),
483 "G" => Some(Self::G),
484 "A" => Some(Self::A),
485 "B" => Some(Self::B),
486 _ => None,
487 }
488 }
489 fn by_index(mut index: u8) -> Self {
490 let names = [
491 Self::C,
492 Self::D,
493 Self::E,
494 Self::F,
495 Self::G,
496 Self::A,
497 Self::B,
498 ];
499 if index > 6 {
500 index -= 7;
501 }
502 names[index as usize].clone()
503 }
504}
505impl Default for NoteName {
506 fn default() -> Self {
507 Self::C
508 }
509}
510impl ToString for NoteName {
511 fn to_string(&self) -> String {
512 match self {
513 Self::C => String::from("c"),
514 Self::D => String::from("d"),
515 Self::E => String::from("e"),
516 Self::F => String::from("f"),
517 Self::G => String::from("g"),
518 Self::A => String::from("a"),
519 Self::B => String::from("b"),
520 }
521 }
522}
523
524#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
528pub struct ResolvedScale {
529 pub key: Key,
530 pub degree_midi: [u8; 7],
534 pub degree_notes: [(NoteName, Accidental); 7],
536 pub used_accidentals: Vec<Accidental>,
540}
541impl ResolvedScale {
542 pub fn resolve_pitch(
543 &self,
544 notes_map: &NotesMap,
545 midi_note: u8,
546 octave: Octave,
547 ) -> ResolvedNote {
548 let note: (NoteName, Accidental);
549 let key_root_midi = self.key.get_root()[0];
550 match self.degree_midi.binary_search(&midi_note) {
551 Ok(note_index) => {
552 note = self.degree_notes[note_index];
553 }
555 Err(_err) => {
556 if midi_note == key_root_midi + 1 && self.key.scale == Scale::Minor {
557 note = notes_map
559 .resolve_note_for_midi(self.degree_notes[1 as usize], midi_note)
560 .unwrap();
561 } else if midi_note == (key_root_midi + 8) % 12 && self.key.scale == Scale::Major {
562 let candidate_note = self.degree_notes[5 as usize];
564 note = notes_map
565 .resolve_note_for_midi(candidate_note, midi_note)
566 .unwrap();
567 } else {
568 note = notes_map
570 .resolve_enharmonic(self.used_accidentals.last().copied(), midi_note);
571 }
572 }
573 }
574 ResolvedNote::new(note.0, note.1, octave, octave.apply_to_midi_note(midi_note))
575 }
576}
577
578#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
579pub enum Scale {
580 Major,
581 Minor,
582 Dorian,
583 Phrygian,
584 Lydian,
585 Mixolidyan,
586 Locrian,
587}
588impl Scale {
589 pub fn structure(&self) -> [u8; 6] {
591 match self {
592 Self::Major => [2, 2, 1, 2, 2, 2],
593 Self::Minor => [2, 1, 2, 2, 1, 2],
594 Self::Dorian => [2, 1, 2, 2, 2, 1],
595 Self::Phrygian => [1, 2, 2, 2, 1, 2],
596 Self::Lydian => [2, 2, 2, 1, 2, 2],
597 Self::Mixolidyan => [2, 2, 1, 2, 2, 1],
598 Self::Locrian => [1, 2, 2, 1, 2, 2],
599 }
600 }
601 fn resolve_for_key(&self, notes_map: &NotesMap, key: &Key) -> ResolvedScale {
603 let root = key.get_root();
604 let (root_midi, _root_idx) = (root[0], root[1]);
605 let mut degree_midi = [root_midi; 7];
606 let mut degree_notes = [key.tonic; 7];
607 let mut used_accidentals = Vec::with_capacity(10);
608 for (idx, interval) in self.structure().iter().enumerate() {
609 let mut next_midi = degree_midi[idx];
610 next_midi += interval;
611 if next_midi > 11 {
612 next_midi %= 12;
613 }
614 let index = degree_notes[idx].0.index() + 1;
615 let candidate_note = NoteName::by_index(index);
616 let next_note = notes_map
621 .resolve_note_for_midi((candidate_note, Accidental::White), next_midi)
622 .unwrap();
623 degree_notes[idx + 1] = next_note;
624 degree_midi[idx + 1] = next_midi;
625 if next_note.1 != Accidental::White {
626 used_accidentals.push(next_note.1);
627 }
628 }
633 ResolvedScale {
634 key: *key,
635 degree_midi,
636 degree_notes,
637 used_accidentals,
638 }
639 }
640}
641impl Default for Scale {
642 fn default() -> Self {
643 Self::Major
644 }
645}
646impl ToString for Scale {
647 fn to_string(&self) -> String {
648 match self {
649 Self::Major => "major".to_string(),
650 Self::Minor => "minor".to_string(),
651 Self::Dorian => "dorian".to_string(),
652 Self::Phrygian => "phrygian".to_string(),
653 Self::Lydian => "lydian".to_string(),
654 Self::Mixolidyan => "mixolidyan".to_string(),
655 Self::Locrian => "locrian".to_string(),
656 }
657 }
658}
659
660#[derive(Debug)]
669pub struct NotesMap {
670 note_index: HashMap<(NoteName, Accidental), u8>,
671 midi_index: HashMap<u8, HashMap<Accidental, NoteName>>,
672}
673impl NotesMap {
674 pub fn get() -> &'static NotesMap {
675 if _NOTES_MAP.get().is_none() {
676 _NOTES_MAP.set(Self::new()).unwrap();
677 }
678 _NOTES_MAP.get().expect("logger is not initialized")
679 }
680 fn new() -> Self {
682 let mut obj = Self {
683 note_index: HashMap::with_capacity(35),
684 midi_index: HashMap::with_capacity(35),
685 };
686 obj.add(NoteName::C, Accidental::White, 0);
687 obj.add(NoteName::B, Accidental::Sharp, 0);
688 obj.add(NoteName::D, Accidental::DoubleFlat, 0);
689 obj.add(NoteName::C, Accidental::Sharp, 1);
691 obj.add(NoteName::B, Accidental::DoubleSharp, 1);
692 obj.add(NoteName::D, Accidental::Flat, 1);
693 obj.add(NoteName::D, Accidental::White, 2);
695 obj.add(NoteName::C, Accidental::DoubleSharp, 2);
696 obj.add(NoteName::E, Accidental::DoubleFlat, 2);
697 obj.add(NoteName::D, Accidental::Sharp, 3);
699 obj.add(NoteName::E, Accidental::Flat, 3);
700 obj.add(NoteName::F, Accidental::DoubleFlat, 3);
701 obj.add(NoteName::E, Accidental::White, 4);
703 obj.add(NoteName::D, Accidental::DoubleSharp, 4);
704 obj.add(NoteName::F, Accidental::Flat, 4);
705 obj.add(NoteName::F, Accidental::White, 5);
707 obj.add(NoteName::E, Accidental::Sharp, 5);
708 obj.add(NoteName::G, Accidental::DoubleFlat, 5);
709 obj.add(NoteName::F, Accidental::Sharp, 6);
711 obj.add(NoteName::E, Accidental::DoubleSharp, 6);
712 obj.add(NoteName::G, Accidental::Flat, 6);
713 obj.add(NoteName::G, Accidental::White, 7);
715 obj.add(NoteName::F, Accidental::DoubleSharp, 7);
716 obj.add(NoteName::A, Accidental::DoubleFlat, 7);
717 obj.add(NoteName::G, Accidental::Sharp, 8);
719 obj.add(NoteName::A, Accidental::Flat, 8);
720 obj.add(NoteName::A, Accidental::White, 9);
722 obj.add(NoteName::G, Accidental::DoubleSharp, 9);
723 obj.add(NoteName::B, Accidental::DoubleFlat, 9);
724 obj.add(NoteName::A, Accidental::Sharp, 10);
726 obj.add(NoteName::B, Accidental::Flat, 10);
727 obj.add(NoteName::C, Accidental::DoubleFlat, 10);
728 obj.add(NoteName::B, Accidental::White, 11);
730 obj.add(NoteName::A, Accidental::DoubleSharp, 11);
731 obj.add(NoteName::C, Accidental::Flat, 11);
732 obj
734 }
735 fn add(&mut self, note: NoteName, acc: Accidental, midi: u8) {
736 self.note_index.insert((note.clone(), acc.clone()), midi);
737 match self.midi_index.get_mut(&midi) {
738 Some(map) => {
739 map.insert(acc, note);
740 }
741 None => {
742 let mut map = HashMap::with_capacity(4);
743 map.insert(acc, note);
744 self.midi_index.insert(midi, map);
745 }
746 }
747 }
748 pub fn get_by_midi(&self, midi: &u8) -> &HashMap<Accidental, NoteName> {
749 let midi = midi % 12;
750 self.midi_index.get(&midi).unwrap()
751 }
752 pub fn get_by_note(&self, note: NoteName, acc: Accidental) -> u8 {
753 *self.note_index.get(&(note, acc)).unwrap()
754 }
755 pub fn resolve_note_for_midi(
766 &self,
767 note: (NoteName, Accidental),
768 midi: u8,
769 ) -> Option<(NoteName, Accidental)> {
770 let notes = self.get_by_midi(&midi);
771 for (acc, candidate) in notes.iter() {
772 if candidate == ¬e.0 {
773 return Some((*candidate, *acc));
774 }
775 }
776 None
777 }
778 pub fn resolve_enharmonic(
779 &self,
780 accidental: Option<Accidental>,
781 midi: u8,
782 ) -> (NoteName, Accidental) {
783 let notes = self.get_by_midi(&(midi % 12));
784 let acc_final: Accidental;
789 match accidental {
790 Some(acc) => {
791 if notes.contains_key(&acc) {
792 acc_final = acc;
793 } else {
794 acc_final = Accidental::White;
795 }
796 }
797 None => {
798 acc_final = Accidental::White;
799 }
800 }
801 (notes[&acc_final], acc_final)
802 }
803}