c64 0.1.0-alpha.0

Driver for the Commodore 64 platform
//! Kernal ROM constants and functions.

use core::{marker::PhantomData, ops::Neg};

use ufmt::derive::uDebug;

extern "C" {
    fn kernal_poly1(poly: *const u8);
    fn kernal_poly2(poly: *const u8);
    fn kernal_cos();
    fn kernal_sin();
    fn kernal_tan();
    fn kernal_atn();
    fn kernal_rdtim() -> u32;
}

/// Pointer to the trigonometry constants in the Kernel ROM.
pub const TRIG_CONSTANTS: *const TrigConstants = 0xe2e0 as *const _;

/// The trigonometry constants in the Kernel ROM.
#[repr(C, packed)]
pub struct TrigConstants {
    /// The approximation of π/2.
    pub pi_over_2: F40,
    /// The approximation of 2π.
    pub pi_times_2: F40,
    /// The encoding of 0.25.
    pub zero_point_2_5: F40,
    /// The polynomial table approximating `sin(2πX)` over the domain `[-0.25, 0.25]`.
    pub sin_2_pi_x: Polynomial<OddTerms, 6>,
    _atn_fn: [u8; 0x30],
    /// The polynomial table approximating `arctan(X)` over the domain `[-1, 1]`.
    pub atn: Polynomial<OddTerms, 12>,
}

/// A floating point number, encoded in C64 floating point format.
#[derive(Clone, Copy, uDebug)]
#[repr(C, packed)]
pub struct F40 {
    exp: u8,
    sign_mantissa: [u8; 4],
}

impl F40 {
    /// Returns the encoding of 0.0.
    pub const fn zero() -> Self {
        Self {
            exp: 0,
            sign_mantissa: [0; 4],
        }
    }

    /// Returns the encoding of 1.0.
    pub const fn one() -> Self {
        Self {
            exp: 0x81,
            sign_mantissa: [0; 4],
        }
    }
}

impl Neg for F40 {
    type Output = Self;

    fn neg(mut self) -> Self::Output {
        // Flip the sign bit.
        self.sign_mantissa[0] ^= 0b1000_0000;
        self
    }
}

/// A polynomial table.
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Polynomial<Terms, const N: usize> {
    degree: u8,
    coefficients: [F40; N],
    _terms: PhantomData<Terms>,
}

pub struct AllTerms;
pub struct OddTerms;

impl<const N: usize> Polynomial<AllTerms, N> {
    /// Constructs a polynomial from its coefficients.
    pub const fn new(coefficients: [F40; N]) -> Option<Self> {
        match N.checked_sub(1) {
            Some(degree @ 0..=255) => Some(Self {
                degree: degree as u8,
                coefficients,
                _terms: PhantomData,
            }),
            _ => None,
        }
    }

    /// Sets `FAC` to the evaluation of this polynomial at `FAC`.
    ///
    /// # Safety
    ///
    /// - Do not call this function if the BASIC ROM overlay is disabled.
    #[inline]
    pub unsafe fn poly(&self) {
        // SAFETY: `Polynomial` and `F40` are `repr(C, packed)`, so they are guaranteed to
        // be layed out as contiguous bytes in the correct format.
        unsafe { kernal_poly2(self as *const _ as _) }
    }
}

impl<const N: usize> Polynomial<OddTerms, N> {
    /// Constructs a polynomial that only has non-zero coefficients at odd-powered terms.
    ///
    /// [`Self::poly`] is faster for compatible polynomials than if constructed via
    /// [`Polynomial::new`].
    pub const fn with_odd_terms(coefficients: [F40; N]) -> Option<Self> {
        match N.checked_sub(1) {
            Some(degree @ 0..=255) => Some(Self {
                degree: degree as u8,
                coefficients,
                _terms: PhantomData,
            }),
            _ => None,
        }
    }

    /// Sets `FAC` to the evaluation of this polynomial at `FAC`.
    ///
    /// # Safety
    ///
    /// - Do not call this function if the BASIC ROM overlay is disabled.
    #[inline]
    pub unsafe fn poly(&self) {
        // SAFETY: `Polynomial` and `F40` are `repr(C, packed)`, so they are guaranteed to
        // be layed out as contiguous bytes in the correct format.
        unsafe { kernal_poly1(self as *const _ as _) }
    }
}

/// Sets `FAC = cos(FAC)`.
///
/// # Safety
///
/// - Do not call this function if the Kernal ROM overlay is disabled.
#[inline]
pub unsafe fn cos() {
    unsafe { kernal_cos() }
}

/// Sets `FAC = sin(FAC)`.
///
/// # Safety
///
/// - Do not call this function if the Kernal ROM overlay is disabled.
#[inline]
pub unsafe fn sin() {
    unsafe { kernal_sin() }
}

/// Sets `FAC = tan(FAC)`.
///
/// # Safety
///
/// - Do not call this function if the Kernal ROM overlay is disabled.
#[inline]
pub unsafe fn tan() {
    unsafe { kernal_tan() }
}

/// Sets `FAC = arctan(FAC)`.
///
/// # Safety
///
/// - Do not call this function if the Kernal ROM overlay is disabled.
#[inline]
pub unsafe fn atn() {
    unsafe { kernal_atn() }
}

/// Reads system clock.
///
/// Returns the time in 60ths of a second.
///
/// # Safety
///
/// - Do not call this function if the Kernal ROM overlay is disabled.
#[inline]
pub unsafe fn rdtim() -> u32 {
    unsafe { kernal_rdtim() }
}