psg 1.0.1

Fast and precise AY-3-8910 and YM2149 sound chip emulation
Documentation
/// One of the YM-3-8910/YM2149's tone generator channels.
///
/// A channel represents a single square wave oscillator with configurable period, amplitude, and
/// panning.
///
/// The channel's signal is generated as the sum of the square oscillator and the chip's noise
/// generator, which can both be turned off independently. This signal is then multiplied with the
/// channel amplitude, which can either be a fixed value, or the chip's envelope generator output.
pub struct Channel {
    // Oscillator
    period: u16,
    position: u16,
    value: u8,

    // Flags
    pub(crate) tone_off: bool,
    pub(crate) noise_off: bool,
    pub(crate) envelope_on: bool,

    // Amplitude
    pub(crate) amplitude: u8,

    // Left/right panning
    pub(crate) pan_left: f64,
    pub(crate) pan_right: f64
}

impl Channel {
    /// Initialize a new channel.
    pub(crate) fn new() -> Self {
        Self {
            period: 1,
            position: 0,
            value: 0,

            tone_off: true,
            noise_off: true,
            envelope_on: false,

            amplitude: 0,

            pan_left: 0.5,
            pan_right: 0.5
        }
    }

    /// Produce a new sample for the channel's square wave oscillator.
    pub(crate) fn render(&mut self) -> u8 {
        self.position += 1;

        if self.position >= self.period {
            self.position = 0;
            self.value ^= 1;
        }

        self.value
    }

    /// The channel's tone period.
    ///
    /// This will return a value between 1 and 4095 inclusive.
    pub fn period(&self) -> u16 {
        self.period
    }

    /// Set the channel's tone period to a value between 1 and 4095 inclusive.
    ///
    /// Lower values are set to 1, higher values are wrapped.
    pub fn set_period(&mut self, period: u16) {
        self.period = (period & 0x0fff).max(1);
    }

    /// The most significant byte for the channel's tone period.
    ///
    /// This will return a value between 0 and 15 inclusive.
    pub fn period_msb(&self) -> u8 {
        (self.period >> 8) as u8
    }

    /// Set the most significant byte of the channel's tone period to a value between 0 and 15
    /// inclusive.
    ///
    /// Setting this byte to zero when the least significant byte is also set to zero will result
    /// in the period being set to 1. It is therefore recommended to always set the most
    /// significant byte first.
    ///
    /// Values higher than 15 will be wrapped.
    pub fn set_period_msb(&mut self, period: u8) {
        self.period = ((self.period & 0x00ff) | (((period as u16) & 0x0f) << 8)).max(1);
    }

    /// The least significant byte for the channel's tone period.
    pub fn period_lsb(&self) -> u8 {
        (self.period & 0xff) as u8
    }

    /// Set the least significant byte of the channel's tone period to a value between 0 and 255
    /// inclusive.
    ///
    /// Setting this byte to zero when the most significant byte is also set to zero will result in
    /// the period being set to 1. It is therefore recommended to always set the most significant
    /// byte first.
    pub fn set_period_lsb(&mut self, period: u8) {
        self.period = ((self.period & 0x0f00) | (period as u16)).max(1);
    }

    /// The channel's amplitude.
    ///
    /// This will return a value between 0 and 15 inclusive.
    pub fn amplitude(&self) -> u8 {
        self.amplitude
    }

    /// Set the channel's amplitude to a value between 0 and 15 inclusive.
    ///
    /// Higher values are wrapped.
    pub fn set_amplitude(&mut self, amplitude: u8) {
        self.amplitude = amplitude & 0x0f;
    }

    /// The channel's envelope enabled flag.
    pub fn envelope_enabled(&self) -> bool {
        self.envelope_on
    }

    /// Set the channel's envelope enabled flag.
    pub fn set_envelope_enabled(&mut self, enabled: bool) {
        self.envelope_on = enabled;
    }

    /// The channel's amplitude register.
    ///
    /// This is effectively a combination of the the amplitude and the envelope enabled flag using
    /// a single byte. In this byte the bits 0 through 4 are the amplitude and bit 5 is the
    /// envelope enabled flag.
    pub fn amplitude_and_envelope_enabled(&self) -> u8 {
        ((self.envelope_on as u8) << 4) | self.amplitude
    }

    /// Set the channel's amplitude to a value between 0 and 15 inclusive, taken from bits 0
    /// through 3 from the input value, and set the envelope enabled flag to the value of bit 4
    /// from the input value.
    ///
    /// This is equivalent to writing to the channel's amplitude register on a real PSG.
    pub fn set_amplitude_and_envelope_enabled(&mut self, value: u8) {
        self.amplitude = value & 0x0f;
        self.envelope_on = value & 0x10 != 0;
    }

    /// The channel's tone disabled flag.
    pub fn tone_disabled(&self) -> bool {
        self.tone_off
    }

    /// Set the channel's tone disabled flag.
    pub fn set_tone_disabled(&mut self, disabled: bool) {
        self.tone_off = disabled;
    }

    /// The channel's noise disabled flag.
    pub fn noise_disabled(&self) -> bool {
        self.noise_off
    }

    /// Set the channel's noise disabled flag.
    pub fn set_noise_disabled(&mut self, disabled: bool) {
        self.noise_off = disabled;
    }

    /// The channel's panning, represented as a scaling factor that is applied to the left channel
    /// (first value) and the right channel (second value).
    pub fn panning(&self) -> (f64, f64) {
        (self.pan_left, self.pan_right)
    }

    /// Set the channel's panning to a value between 0.0 (full left) and 1.0 (full right)
    /// inclusive.
    ///
    /// The `equal_power` argument can be set to true to interpret specified balance value as the
    /// ratio between each channel's power instead of amplitude. This effectively takes the square
    /// root of the balance and the square root of one minus the balance and applies these values
    /// as the panning factors.
    pub fn set_panning(&mut self, balance: f64, equal_power: bool) {
        self.pan_left = 1.0 - balance;
        self.pan_right = balance;

        if equal_power {
            self.pan_left = self.pan_left.sqrt();
            self.pan_right = self.pan_right.sqrt();
        }
    }
}