1use crate::audio::Audio;
2use crate::blip_buf::BlipBuf;
3use crate::channel::{Note, Speed, Volume};
4use crate::oscillator::{Effect, ToneIndex};
5use crate::pyxel::CHANNELS;
6use crate::settings::{
7 CLOCK_RATE, EFFECT_FADEOUT, EFFECT_HALF_FADEOUT, EFFECT_NONE, EFFECT_QUARTER_FADEOUT,
8 EFFECT_SLIDE, EFFECT_VIBRATO, INITIAL_SOUND_SPEED, SAMPLE_RATE, TICKS_PER_SECOND, TONE_NOISE,
9 TONE_PULSE, TONE_SQUARE, TONE_TRIANGLE,
10};
11use crate::utils::simplify_string;
12
13#[derive(Clone)]
14pub struct Sound {
15 pub notes: Vec<Note>,
16 pub tones: Vec<ToneIndex>,
17 pub volumes: Vec<Volume>,
18 pub effects: Vec<Effect>,
19 pub speed: Speed,
20}
21
22pub type SharedSound = shared_type!(Sound);
23
24impl Sound {
25 pub fn new() -> SharedSound {
26 new_shared_type!(Self {
27 notes: Vec::new(),
28 tones: Vec::new(),
29 volumes: Vec::new(),
30 effects: Vec::new(),
31 speed: INITIAL_SOUND_SPEED,
32 })
33 }
34
35 pub fn set(
36 &mut self,
37 note_str: &str,
38 tone_str: &str,
39 volume_str: &str,
40 effect_str: &str,
41 speed: Speed,
42 ) {
43 self.set_notes(note_str);
44 self.set_tones(tone_str);
45 self.set_volumes(volume_str);
46 self.set_effects(effect_str);
47 self.speed = speed;
48 }
49
50 pub fn set_notes(&mut self, note_str: &str) {
51 let note_str = simplify_string(note_str);
52 let mut chars = note_str.chars();
53 self.notes.clear();
54
55 while let Some(c) = chars.next() {
56 let mut note: Note;
57 if ('a'..='g').contains(&c) {
58 note = match c {
59 'c' => 0,
60 'd' => 2,
61 'e' => 4,
62 'f' => 5,
63 'g' => 7,
64 'a' => 9,
65 'b' => 11,
66 _ => panic!("Invalid sound note '{c}'"),
67 };
68
69 let mut c = chars.next().unwrap_or(0 as char);
70 if c == '#' {
71 note += 1;
72 c = chars.next().unwrap_or(0 as char);
73 } else if c == '-' {
74 note -= 1;
75 c = chars.next().unwrap_or(0 as char);
76 }
77
78 if ('0'..='4').contains(&c) {
79 note += (c.to_digit(10).unwrap() as Note) * 12;
80 } else {
81 panic!("Invalid sound note '{c}'");
82 }
83 } else if c == 'r' {
84 note = -1;
85 } else {
86 panic!("Invalid sound note '{c}'");
87 }
88 self.notes.push(note);
89 }
90 }
91
92 pub fn set_tones(&mut self, tone_str: &str) {
93 self.tones.clear();
94 for c in simplify_string(tone_str).chars() {
95 let tone = match c {
96 't' => TONE_TRIANGLE,
97 's' => TONE_SQUARE,
98 'p' => TONE_PULSE,
99 'n' => TONE_NOISE,
100 '0'..='9' => c.to_digit(10).unwrap() as ToneIndex,
101 _ => panic!("Invalid sound tone '{c}'"),
102 };
103 self.tones.push(tone);
104 }
105 }
106
107 pub fn set_volumes(&mut self, volume_str: &str) {
108 self.volumes.clear();
109 for c in simplify_string(volume_str).chars() {
110 if ('0'..='7').contains(&c) {
111 self.volumes.push(c.to_digit(10).unwrap() as Volume);
112 } else {
113 panic!("Invalid sound volume '{c}'");
114 }
115 }
116 }
117
118 pub fn set_effects(&mut self, effect_str: &str) {
119 self.effects.clear();
120 for c in simplify_string(effect_str).chars() {
121 let effect = match c {
122 'n' => EFFECT_NONE,
123 's' => EFFECT_SLIDE,
124 'v' => EFFECT_VIBRATO,
125 'f' => EFFECT_FADEOUT,
126 'h' => EFFECT_HALF_FADEOUT,
127 'q' => EFFECT_QUARTER_FADEOUT,
128 _ => panic!("Invalid sound effect '{c}'"),
129 };
130 self.effects.push(effect);
131 }
132 }
133
134 pub fn save(&self, filename: &str, count: u32, ffmpeg: Option<bool>) {
135 assert!(count > 0);
136 let ticks_per_sound = self.speed * self.notes.len() as u32;
137 let samples_per_sound = ticks_per_sound * SAMPLE_RATE / TICKS_PER_SECOND;
138 let num_samples = samples_per_sound * count;
139
140 if num_samples == 0 {
141 return;
142 }
143
144 let mut samples = vec![0; num_samples as usize];
145 let mut blip_buf = BlipBuf::new(num_samples as usize);
146 blip_buf.set_rates(CLOCK_RATE as f64, SAMPLE_RATE as f64);
147
148 let channels = CHANNELS.lock();
149 channels.iter().for_each(|channel| channel.lock().stop());
150
151 {
152 let mut channels: Vec<_> = channels.iter().map(|channel| channel.lock()).collect();
153 let sounds = vec![new_shared_type!(self.clone())];
154 channels[0].play(sounds, None, true, false);
155 }
156
157 Audio::render_samples(&channels, &mut blip_buf, &mut samples);
158 Audio::save_samples(filename, &samples, ffmpeg.unwrap_or(false));
159 channels.iter().for_each(|channel| channel.lock().stop());
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_sound_new() {
169 let sound = Sound::new();
170 assert_eq!(sound.lock().notes.len(), 0);
171 assert_eq!(sound.lock().tones.len(), 0);
172 assert_eq!(sound.lock().volumes.len(), 0);
173 assert_eq!(sound.lock().effects.len(), 0);
174 assert_eq!(sound.lock().speed, INITIAL_SOUND_SPEED);
175 }
176
177 #[test]
178 fn test_sound_set() {
179 let sound = Sound::new();
180 sound
181 .lock()
182 .set("c0d-0d0d#0", "tspn", "012345", "nsvfhq", 123);
183 assert_eq!(&sound.lock().notes, &vec![0, 1, 2, 3]);
184 assert_eq!(
185 &sound.lock().tones,
186 &vec![TONE_TRIANGLE, TONE_SQUARE, TONE_PULSE, TONE_NOISE]
187 );
188 assert_eq!(&sound.lock().volumes, &vec![0, 1, 2, 3, 4, 5]);
189 assert_eq!(
190 &sound.lock().effects,
191 &vec![
192 EFFECT_NONE,
193 EFFECT_SLIDE,
194 EFFECT_VIBRATO,
195 EFFECT_FADEOUT,
196 EFFECT_HALF_FADEOUT,
197 EFFECT_QUARTER_FADEOUT
198 ]
199 );
200 assert_eq!(sound.lock().speed, 123);
201 }
202
203 #[test]
204 fn test_sound_set_note() {
205 let sound = Sound::new();
206 sound
207 .lock()
208 .set_notes(" c 0 d # 1 r e 2 f 3 g 4 r a - 0 b 1 ");
209 assert_eq!(&sound.lock().notes, &vec![0, 15, -1, 28, 41, 55, -1, 8, 23]);
210 }
211
212 #[test]
213 fn test_sound_set_tone() {
214 let sound = Sound::new();
215 sound.lock().set_tones(" t s p n ");
216 assert_eq!(
217 &sound.lock().tones,
218 &vec![TONE_TRIANGLE, TONE_SQUARE, TONE_PULSE, TONE_NOISE]
219 );
220 }
221
222 #[test]
223 fn test_sound_set_volume() {
224 let sound = Sound::new();
225 sound.lock().set_volumes(" 0 1 2 3 4 5 6 7 ");
226 assert_eq!(&sound.lock().volumes, &vec![0, 1, 2, 3, 4, 5, 6, 7]);
227 }
228
229 #[test]
230 fn test_sound_set_effect() {
231 let sound = Sound::new();
232 sound.lock().set_effects(" n s v f h q");
233 assert_eq!(
234 &sound.lock().effects,
235 &vec![
236 EFFECT_NONE,
237 EFFECT_SLIDE,
238 EFFECT_VIBRATO,
239 EFFECT_FADEOUT,
240 EFFECT_HALF_FADEOUT,
241 EFFECT_QUARTER_FADEOUT
242 ]
243 );
244 }
245}