torque_tracker_engine/project/
note_event.rs

1use std::{
2    error::Error,
3    fmt::{Display, Write},
4};
5
6use crate::project::event_command::NoteCommand;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub struct Note(u8);
10
11impl Note {
12    pub fn new(value: u8) -> Result<Note, u8> {
13        if value > 199 {
14            Err(value)
15        } else {
16            Ok(Note(value))
17        }
18    }
19
20    pub const fn get_octave(self) -> u8 {
21        self.0 / 12
22    }
23
24    pub const fn get_note_name(self) -> &'static str {
25        match self.0 % 12 {
26            0 => "C",
27            1 => "C#",
28            2 => "D",
29            3 => "D#",
30            4 => "E",
31            5 => "F",
32            6 => "F#",
33            7 => "G",
34            8 => "G#",
35            9 => "A",
36            10 => "A#",
37            11 => "B",
38            _ => panic!(),
39        }
40    }
41
42    pub fn get_frequency(self) -> f32 {
43        // taken from https://en.wikipedia.org/wiki/MIDI_tuning_standard
44        440. * ((f32::from(self.0) - 69.) / 12.).exp2()
45    }
46
47    pub const fn get(self) -> u8 {
48        self.0
49    }
50}
51
52impl Display for Note {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.write_str(self.get_note_name())?;
55        f.write_char('-')?;
56        self.get_octave().fmt(f)?;
57        Ok(())
58    }
59}
60
61impl Default for Note {
62    fn default() -> Self {
63        Self(60) // C-5
64    }
65}
66
67#[derive(Clone, Copy, Debug, Default)]
68pub struct NoteEvent {
69    pub note: Note,
70    pub sample_instr: u8,
71    pub vol: VolumeEffect,
72    pub command: NoteCommand,
73}
74
75#[derive(Debug, Clone, Copy, Default)]
76pub enum VolumeEffect {
77    FineVolSlideUp(u8),
78    FineVolSlideDown(u8),
79    VolSlideUp(u8),
80    VolSlideDown(u8),
81    PitchSlideUp(u8),
82    PitchSlideDown(u8),
83    SlideToNoteWithSpeed(u8),
84    VibratoWithSpeed(u8),
85    Volume(u8),
86    Panning(u8),
87    /// Uses Instr / Sample Default Volume
88    #[default]
89    None,
90}
91
92#[derive(Debug, Clone, Copy)]
93pub struct InvalidVolumeEffect;
94
95impl Display for InvalidVolumeEffect {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        write!(f, "Invalid Volume Effect")
98    }
99}
100
101impl Error for InvalidVolumeEffect {}
102
103impl TryFrom<u8> for VolumeEffect {
104    type Error = InvalidVolumeEffect;
105
106    /// IT Tracker Format Conversion
107    /// no way to get None, as then it just doesn't get set
108    fn try_from(value: u8) -> Result<Self, Self::Error> {
109        match value {
110            0..=64 => Ok(Self::Volume(value)),
111            65..=74 => Ok(Self::FineVolSlideUp(value - 65)),
112            75..=84 => Ok(Self::FineVolSlideDown(value - 75)),
113            85..=94 => Ok(Self::VolSlideUp(value - 85)),
114            95..=104 => Ok(Self::VolSlideDown(value - 95)),
115            105..=114 => Ok(Self::PitchSlideDown(value - 105)),
116            115..=124 => Ok(Self::PitchSlideUp(value - 115)),
117            128..=192 => Ok(Self::Panning(value - 128)),
118            193..=202 => Ok(Self::SlideToNoteWithSpeed(value - 193)),
119            203..=212 => Ok(Self::VibratoWithSpeed(value - 203)),
120            _ => Err(InvalidVolumeEffect),
121        }
122    }
123}