psg/
math.rs

1//! This module contains useful mathematical operations on frequencies, tone/envelope periods, and
2//! MIDI pitch numbers.
3
4/// Convert a MIDI pitch number into its corresponding frequency.
5///
6/// The pitch number is not required to be an integer.
7pub fn midi_pitch_to_frequency(pitch: f64) -> f64 {
8    (2.0_f64).powf((pitch - 69.0) / 12.0) * 440.0
9}
10
11/// Convert a frequency to its corresponding MIDI pitch number.
12///
13/// The resulting pitch number is not guaranteed to be an integer.
14pub fn frequency_to_midi_pitch(frequency: f64) -> f64 {
15    (frequency / 440.0).log2() * 12.0 + 69.0
16}
17
18/// Convert a MIDI pitch number into a suitable tone period value for the specified clock rate.
19///
20/// The pitch number is not required to be an integer.
21pub fn midi_pitch_to_tone_period(pitch: f64, clock_rate: f64) -> u16 {
22    frequency_to_tone_period(midi_pitch_to_frequency(pitch), clock_rate)
23}
24
25/// Convert a MIDI pitch number into a suitable envelope period value for the specified clock rate.
26///
27/// The pitch number is not required to be an integer.
28pub fn midi_pitch_to_envelope_period(pitch: f64, clock_rate: f64) -> u16 {
29    frequency_to_envelope_period(midi_pitch_to_frequency(pitch), clock_rate)
30}
31
32/// Convert a tone period value into its corresponding MIDI pitch number for the specified clock
33/// rate.
34///
35/// The resulting pitch number is not guaranteed to be an integer.
36pub fn tone_period_to_midi_pitch(period: u16, clock_rate: f64) -> f64 {
37    frequency_to_midi_pitch(tone_period_to_frequency(period, clock_rate))
38}
39
40/// Convert a envelope period value into its corresponding MIDI pitch number for the specified
41/// clock rate.
42///
43/// The resulting pitch number is not guaranteed to be an integer.
44pub fn envelope_period_to_midi_pitch(period: u16, clock_rate: f64) -> f64 {
45    frequency_to_midi_pitch(envelope_period_to_frequency(period, clock_rate))
46}
47
48/// Convert a frequency into its corresponding tone period value for the specified clock rate.
49pub fn frequency_to_tone_period(frequency: f64, clock_rate: f64) -> u16 {
50    (clock_rate / (16.0 * frequency)).round() as u16
51}
52
53/// Convert a frequency into its corresponding envelope period value for the specified clock rate.
54pub fn frequency_to_envelope_period(frequency: f64, clock_rate: f64) -> u16 {
55    (clock_rate / (256.0 * frequency)).round() as u16
56}
57
58/// Convert a tone period value into its corresponding frequency for the specified clock rate.
59pub fn tone_period_to_frequency(period: u16, clock_rate: f64) -> f64 {
60    clock_rate / (period as f64 * 16.0)
61}
62
63/// Convert a envelope period value into its corresponding frequency for the specified clock rate.
64pub fn envelope_period_to_frequency(period: u16, clock_rate: f64) -> f64 {
65    clock_rate / (period as f64 * 256.0)
66}
67
68#[cfg(test)]
69mod tests {
70    #[test]
71    fn midi_pitch_to_frequency() {
72        assert_eq!(super::midi_pitch_to_frequency(81.0), 880.0);
73        assert_eq!(super::midi_pitch_to_frequency(69.0), 440.0);
74        assert_eq!(super::midi_pitch_to_frequency(57.0), 220.0);
75    }
76
77    #[test]
78    fn frequency_to_midi_pitch() {
79        assert_eq!(super::frequency_to_midi_pitch(880.0), 81.0);
80        assert_eq!(super::frequency_to_midi_pitch(440.0), 69.0);
81        assert_eq!(super::frequency_to_midi_pitch(220.0), 57.0);
82    }
83
84    #[test]
85    fn tone_period_midi_conversion() {
86        let period = super::midi_pitch_to_tone_period(57.0, 4400000.0);
87        let pitch = super::tone_period_to_midi_pitch(period, 4400000.0);
88
89        assert_eq!(pitch, 57.0);
90
91        let frequency = super::tone_period_to_midi_pitch(100, 4400000.0);
92        let period = super::midi_pitch_to_tone_period(frequency, 4400000.0);
93
94        assert_eq!(period, 100);
95    }
96
97    #[test]
98    fn envelope_period_midi_conversion() {
99        let period = super::midi_pitch_to_envelope_period(21.0, 4400000.0);
100        let pitch = super::envelope_period_to_midi_pitch(period, 4400000.0);
101
102        assert_eq!(pitch, 21.0);
103
104        let frequency = super::envelope_period_to_midi_pitch(100, 4400000.0);
105        let period = super::midi_pitch_to_envelope_period(frequency, 4400000.0);
106
107        assert_eq!(period, 100);
108    }
109
110    #[test]
111    fn tone_period_conversion() {
112        let period = super::frequency_to_tone_period(100.0, 1000000.0);
113        let frequency = super::tone_period_to_frequency(period, 1000000.0);
114
115        println!("{} {}", period, frequency);
116
117        assert_eq!(frequency, 100.0);
118
119        let frequency = super::tone_period_to_frequency(100, 1000000.0);
120        let period = super::frequency_to_tone_period(frequency, 1000000.0);
121
122        assert_eq!(period, 100);
123    }
124
125    #[test]
126    fn envelop_period_conversion() {
127        let period = super::frequency_to_envelope_period(1.25, 1000000.0);
128        let frequency = super::envelope_period_to_frequency(period, 1000000.0);
129
130        println!("{} {}", period, frequency);
131
132        assert_eq!(frequency, 1.25);
133
134        let frequency = super::envelope_period_to_frequency(100, 1000000.0);
135        let period = super::frequency_to_envelope_period(frequency, 1000000.0);
136
137        assert_eq!(period, 100);
138    }
139}