pyxel/
channel.rs

1use std::cmp::max;
2
3use crate::blip_buf::BlipBuf;
4use crate::oscillator::{Effect, Gain, Oscillator, ToneIndex};
5use crate::settings::{
6    EFFECT_NONE, EFFECT_SLIDE, INITIAL_CHANNEL_GAIN, MAX_EFFECT, MAX_NOTE, MAX_TONE, MAX_VOLUME,
7    TONE_TRIANGLE,
8};
9use crate::sound::{SharedSound, Sound};
10
11pub type Note = i16;
12pub type Volume = u16;
13pub type Speed = u32;
14pub type Detune = i32;
15
16pub struct Channel {
17    oscillator: Oscillator,
18    is_first_note: bool,
19    is_playing: bool,
20    should_loop: bool,
21    should_resume: bool,
22    sound_index: u32,
23    note_index: u32,
24    tick_count: u32,
25    resume_sounds: Vec<Sound>,
26    resume_start_tick: u32,
27    resume_should_loop: bool,
28    pub sounds: Vec<Sound>,
29    pub gain: Gain,
30    pub detune: Detune,
31}
32
33pub type SharedChannel = shared_type!(Channel);
34
35impl Channel {
36    pub fn new() -> SharedChannel {
37        new_shared_type!(Self {
38            oscillator: Oscillator::new(),
39            is_first_note: true,
40            is_playing: false,
41            should_loop: false,
42            should_resume: false,
43            sound_index: 0,
44            note_index: 0,
45            tick_count: 0,
46            resume_sounds: Vec::new(),
47            resume_start_tick: 0,
48            resume_should_loop: false,
49            sounds: Vec::new(),
50            gain: INITIAL_CHANNEL_GAIN,
51            detune: 0,
52        })
53    }
54
55    pub fn play(
56        &mut self,
57        sounds: Vec<SharedSound>,
58        start_tick: Option<u32>,
59        should_loop: bool,
60        should_resume: bool,
61    ) {
62        let sounds: Vec<Sound> = sounds.iter().map(|sound| sound.lock().clone()).collect();
63        if sounds.is_empty() || sounds.iter().all(|sound| sound.notes.is_empty()) {
64            return;
65        }
66
67        if !should_resume {
68            self.resume_sounds.clone_from(&sounds);
69            self.resume_should_loop = should_loop;
70            self.resume_start_tick = start_tick.unwrap_or(0);
71        }
72
73        self.sounds = sounds;
74        self.should_loop = should_loop;
75        self.should_resume = self.is_playing && should_resume;
76        self.sound_index = 0;
77        self.note_index = 0;
78        self.tick_count = start_tick.unwrap_or(0);
79
80        loop {
81            let sound = &self.sounds[self.sound_index as usize];
82            let sound_ticks = sound.notes.len() as u32 * sound.speed;
83            if self.tick_count < sound_ticks {
84                self.note_index = self.tick_count / sound.speed;
85                self.tick_count %= sound.speed;
86                break;
87            }
88            self.tick_count -= sound_ticks;
89            self.sound_index += 1;
90            if self.sound_index >= self.sounds.len() as u32 {
91                if should_loop {
92                    self.sound_index = 0;
93                } else {
94                    return;
95                }
96            }
97        }
98
99        self.is_first_note = true;
100        self.is_playing = true;
101    }
102
103    pub fn play1(
104        &mut self,
105        sound: SharedSound,
106        start_tick: Option<u32>,
107        should_loop: bool,
108        should_resume: bool,
109    ) {
110        self.play(vec![sound], start_tick, should_loop, should_resume);
111    }
112
113    pub fn stop(&mut self) {
114        self.is_playing = false;
115        self.oscillator.stop();
116    }
117
118    pub fn play_pos(&mut self) -> Option<(u32, u32)> {
119        if self.is_playing {
120            Some((self.sound_index, self.note_index))
121        } else {
122            None
123        }
124    }
125
126    pub(crate) fn update(&mut self, blip_buf: &mut BlipBuf) {
127        if !self.is_playing {
128            return;
129        }
130
131        let mut sound = &self.sounds[self.sound_index as usize];
132        let speed = max(sound.speed, 1);
133
134        if self.tick_count % speed == 0 {
135            if self.tick_count > 0 {
136                self.note_index += 1;
137            }
138
139            while self.note_index >= sound.notes.len() as u32 {
140                self.is_first_note = true;
141                self.sound_index += 1;
142                self.note_index = 0;
143
144                if self.sound_index >= self.sounds.len() as u32 {
145                    if self.should_loop {
146                        self.sound_index = 0;
147                    } else {
148                        self.stop();
149                        if self.should_resume {
150                            let sounds = self
151                                .resume_sounds
152                                .iter()
153                                .map(|sound| new_shared_type!(sound.clone()))
154                                .collect();
155                            self.play(
156                                sounds,
157                                Some(self.resume_start_tick + 1),
158                                self.resume_should_loop,
159                                false,
160                            );
161                        }
162                        return;
163                    }
164                }
165
166                sound = &self.sounds[self.sound_index as usize];
167            }
168
169            let note = Self::circular_note(&sound.notes, self.note_index);
170            assert!(note <= MAX_NOTE, "invalid sound note {note}");
171            let volume = Self::circular_volume(&sound.volumes, self.note_index);
172            assert!(volume <= MAX_VOLUME, "invalid sound volume {volume}");
173            let tone = Self::circular_tone(&sound.tones, self.note_index);
174            assert!(tone <= MAX_TONE, "invalid sound tone {tone}");
175            let mut effect = Self::circular_effect(&sound.effects, self.note_index);
176            assert!(effect <= MAX_EFFECT, "invalid sound effect {effect}");
177            let speed = max(sound.speed, 1);
178
179            if note >= 0 && volume > 0 {
180                if self.is_first_note {
181                    self.is_first_note = false;
182                    if effect == EFFECT_SLIDE {
183                        effect = EFFECT_NONE;
184                    }
185                }
186                self.oscillator.play(
187                    note as f64 + self.detune as f64 / 200.0,
188                    tone,
189                    self.gain * volume as f64 / MAX_VOLUME as f64,
190                    effect,
191                    speed,
192                );
193            }
194        }
195
196        self.oscillator.update(blip_buf);
197        self.tick_count += 1;
198        self.resume_start_tick += 1;
199    }
200
201    const fn circular_note(notes: &[Note], index: u32) -> Note {
202        let len = notes.len();
203        if len > 0 {
204            notes[index as usize % len]
205        } else {
206            0
207        }
208    }
209
210    const fn circular_tone(tones: &[ToneIndex], index: u32) -> ToneIndex {
211        let len = tones.len();
212        if len > 0 {
213            tones[index as usize % len]
214        } else {
215            TONE_TRIANGLE
216        }
217    }
218
219    const fn circular_volume(volumes: &[Volume], index: u32) -> Volume {
220        let len = volumes.len();
221        if len > 0 {
222            volumes[index as usize % len]
223        } else {
224            MAX_VOLUME
225        }
226    }
227
228    const fn circular_effect(effects: &[Effect], index: u32) -> Effect {
229        let len = effects.len();
230        if len > 0 {
231            effects[index as usize % len]
232        } else {
233            EFFECT_NONE
234        }
235    }
236}