pyxel-engine 1.8.2

Core engine for Pyxel, a retro game engine for Python
Documentation
use std::cmp::max;

use crate::blipbuf::BlipBuf;
use crate::oscillator::Oscillator;
use crate::settings::{
    EFFECT_NONE, MAX_EFFECT, MAX_NOTE, MAX_TONE, MAX_VOLUME, NUM_CHANNELS, TONE_TRIANGLE,
};
use crate::sound::{SharedSound, Sound};
use crate::types::{Effect, Note, Tone, Volume};

pub struct Channel {
    oscillator: Oscillator,
    sounds: Vec<Sound>,
    is_playing: bool,
    should_loop: bool,
    sound_index: u32,
    note_index: u32,
    tick_count: u32,
    pub gain: u8,
}

pub type SharedChannel = shared_type!(Channel);

impl Channel {
    pub fn new() -> SharedChannel {
        new_shared_type!(Self {
            oscillator: Oscillator::new(),
            sounds: Vec::new(),
            is_playing: false,
            should_loop: false,
            sound_index: 0,
            note_index: 0,
            tick_count: 0,
            gain: u8::MAX / NUM_CHANNELS as u8,
        })
    }

    pub fn play_pos(&mut self) -> Option<(u32, u32)> {
        if self.is_playing {
            Some((self.sound_index, self.note_index))
        } else {
            None
        }
    }

    pub fn play(&mut self, sounds: Vec<SharedSound>, start_tick: Option<u32>, should_loop: bool) {
        let sounds: Vec<Sound> = sounds.iter().map(|sound| sound.lock().clone()).collect();
        if sounds.is_empty() || sounds.iter().all(|sound| sound.notes.is_empty()) {
            return;
        }
        self.sounds = sounds;
        self.should_loop = should_loop;
        self.sound_index = 0;
        self.note_index = 0;
        self.tick_count = start_tick.unwrap_or(0);
        loop {
            let sound = &self.sounds[self.sound_index as usize];
            let sound_ticks = sound.notes.len() as u32 * sound.speed;
            if self.tick_count < sound_ticks {
                self.note_index = self.tick_count / sound.speed;
                self.tick_count %= sound.speed;
                break;
            }
            self.tick_count -= sound_ticks;
            self.sound_index += 1;
            if self.sound_index >= self.sounds.len() as u32 {
                if should_loop {
                    self.sound_index = 0;
                } else {
                    return;
                }
            }
        }
        self.is_playing = true;
    }

    pub fn play1(&mut self, sound: SharedSound, start_tick: Option<u32>, should_loop: bool) {
        self.play(vec![sound], start_tick, should_loop);
    }

    pub fn stop(&mut self) {
        self.is_playing = false;
        self.oscillator.stop();
    }

    pub(crate) fn update(&mut self, blip_buf: &mut BlipBuf) {
        if !self.is_playing {
            return;
        }
        let mut sound = &self.sounds[self.sound_index as usize];
        let speed = max(sound.speed, 1);
        if self.tick_count % speed == 0 {
            if self.tick_count > 0 {
                self.note_index += 1;
            }
            while self.note_index >= sound.notes.len() as u32 {
                self.sound_index += 1;
                self.note_index = 0;
                if self.sound_index >= self.sounds.len() as u32 {
                    if self.should_loop {
                        self.sound_index = 0;
                    } else {
                        self.stop();
                        return;
                    }
                }
                sound = &self.sounds[self.sound_index as usize];
            }

            let note = Self::circular_note(&sound.notes, self.note_index);
            assert!(note <= MAX_NOTE, "invalid sound note {}", note);
            let volume = Self::circular_volume(&sound.volumes, self.note_index);
            assert!(volume <= MAX_VOLUME, "invalid sound volume {}", volume);
            let tone = Self::circular_tone(&sound.tones, self.note_index);
            assert!(tone <= MAX_TONE, "invalid sound tone {}", tone);
            let effect = Self::circular_effect(&sound.effects, self.note_index);
            assert!(effect <= MAX_EFFECT, "invalid sound effect {}", effect);
            let speed = max(sound.speed, 1);

            if note >= 0 && volume > 0 {
                self.oscillator.play(
                    note as f64,
                    tone,
                    (self.gain as f64 * volume as f64) / (u8::MAX as f64 * MAX_VOLUME as f64),
                    effect,
                    speed,
                );
            }
        }
        self.oscillator.update(blip_buf);
        self.tick_count += 1;
    }

    const fn circular_note(notes: &[Note], index: u32) -> Note {
        let len = notes.len();
        if len > 0 {
            notes[index as usize % len]
        } else {
            0
        }
    }

    const fn circular_tone(tones: &[Tone], index: u32) -> Tone {
        let len = tones.len();
        if len > 0 {
            tones[index as usize % len]
        } else {
            TONE_TRIANGLE
        }
    }

    const fn circular_volume(volumes: &[Volume], index: u32) -> Volume {
        let len = volumes.len();
        if len > 0 {
            volumes[index as usize % len]
        } else {
            MAX_VOLUME
        }
    }

    const fn circular_effect(effects: &[Effect], index: u32) -> Effect {
        let len = effects.len();
        if len > 0 {
            effects[index as usize % len]
        } else {
            EFFECT_NONE
        }
    }
}