rill-lofi 0.4.0

Lo-fi audio emulation: 8-bit, 12-bit, NES, AY-3-8910, Akai S900
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum ClassicSystem {
    Nes,
    Commodore64,
    SegaGenesis,
    RolandD50,
    AkaiS900,
    EmulatorII,
    FairlightCMI,
    LinnDrum,
    Custom {
        bit_depth: u8,
        sample_rate: f32,
        nonlinear: bool,
        noise_floor: f32,
    },
}

impl ClassicSystem {
    pub fn get_bit_depth(&self) -> u8 {
        match self {
            ClassicSystem::Nes => 7,
            ClassicSystem::Commodore64 => 8,
            ClassicSystem::SegaGenesis => 9,
            ClassicSystem::RolandD50 => 16,
            ClassicSystem::AkaiS900 => 12,
            ClassicSystem::EmulatorII => 8,
            ClassicSystem::FairlightCMI => 8,
            ClassicSystem::LinnDrum => 8,
            ClassicSystem::Custom { bit_depth, .. } => *bit_depth,
        }
    }

    pub fn get_sample_rate(&self) -> f32 {
        match self {
            ClassicSystem::Nes => 44_100.0,
            ClassicSystem::Commodore64 => 44_100.0,
            ClassicSystem::SegaGenesis => 44_100.0,
            ClassicSystem::RolandD50 => 32_000.0,
            ClassicSystem::AkaiS900 => 40_000.0,
            ClassicSystem::EmulatorII => 27_700.0,
            ClassicSystem::FairlightCMI => 16_000.0,
            ClassicSystem::LinnDrum => 44_100.0,
            ClassicSystem::Custom { sample_rate, .. } => *sample_rate,
        }
    }

    pub fn has_nonlinear_encoding(&self) -> bool {
        matches!(
            self,
            ClassicSystem::AkaiS900
                | ClassicSystem::Custom {
                    nonlinear: true,
                    ..
                }
        )
    }

    pub fn get_noise_floor_db(&self) -> f32 {
        match self {
            ClassicSystem::Nes => -42.0,
            ClassicSystem::Commodore64 => -48.0,
            ClassicSystem::AkaiS900 => -72.0,
            ClassicSystem::FairlightCMI => -48.0,
            ClassicSystem::Custom { noise_floor, .. } => *noise_floor,
            _ => -90.0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareEmulation {
    pub bit_depth: u8,
    pub sample_rate: f32,
    pub dac_nonlinearity: bool,
    pub clock_drift: f32,
    pub voltage_drop: f32,
    pub crosstalk: f32,
    pub thermal_noise: f32,
    pub ageing_effect: f32,
}

impl Default for HardwareEmulation {
    fn default() -> Self {
        Self {
            bit_depth: 8,
            sample_rate: 44_100.0,
            dac_nonlinearity: true,
            clock_drift: 0.1,
            voltage_drop: 0.02,
            crosstalk: 0.01,
            thermal_noise: 0.001,
            ageing_effect: 0.05,
        }
    }
}

impl HardwareEmulation {
    pub fn for_system(system: ClassicSystem) -> Self {
        let mut emulation = Self::default();

        match system {
            ClassicSystem::Nes => {
                emulation.bit_depth = 7;
                emulation.clock_drift = 0.5;
                emulation.voltage_drop = 0.05;
                emulation.thermal_noise = 0.005;
            }
            ClassicSystem::Commodore64 => {
                emulation.bit_depth = 8;
                emulation.dac_nonlinearity = true;
                emulation.clock_drift = 0.3;
                emulation.crosstalk = 0.03;
            }
            ClassicSystem::AkaiS900 => {
                emulation.bit_depth = 12;
                emulation.dac_nonlinearity = true;
                emulation.sample_rate = 40_000.0;
                emulation.thermal_noise = 0.001;
            }
            ClassicSystem::FairlightCMI => {
                emulation.bit_depth = 8;
                emulation.sample_rate = 16_000.0;
                emulation.clock_drift = 1.0;
                emulation.voltage_drop = 0.1;
                emulation.thermal_noise = 0.01;
            }
            _ => {
                emulation.bit_depth = system.get_bit_depth();
                emulation.sample_rate = system.get_sample_rate();
            }
        }

        emulation
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LofiConfig {
    pub system: ClassicSystem,
    pub hardware: HardwareEmulation,
    pub enable_bitcrush: bool,
    pub enable_sr_reduction: bool,
    pub enable_noise: bool,
    pub output_gain: f32,
    pub dry_wet: f32,
}

impl Default for LofiConfig {
    fn default() -> Self {
        Self {
            system: ClassicSystem::Custom {
                bit_depth: 8,
                sample_rate: 44_100.0,
                nonlinear: false,
                noise_floor: -48.0,
            },
            hardware: HardwareEmulation::default(),
            enable_bitcrush: true,
            enable_sr_reduction: true,
            enable_noise: true,
            output_gain: 1.0,
            dry_wet: 1.0,
        }
    }
}

impl LofiConfig {
    pub fn for_system(system: ClassicSystem) -> Self {
        Self {
            system,
            hardware: HardwareEmulation::for_system(system),
            ..Default::default()
        }
    }
}