moont 1.0.0

Roland CM-32L synthesizer emulator
Documentation
// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
// Copyright (C) 2003-2026 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at
// your option) any later version. Read COPYING.LESSER.txt for details.

//! Bundled ROM data (pre-parsed at compile time).

use crate::param::{
    PairMode, PartialParam, PartialType, RhythmKey, TimbreParam, TvaParam,
    TvfParam, TvpParam,
};
use crate::rom::{Meta, PCM_SAMPLES, PcmMeta, RawTimbreBank, Rom};

const PCM_BYTES: usize = PCM_SAMPLES * 2;
const RAW_TIMBRES_BYTES: usize = 256 * 256;

#[repr(C, align(2))]
struct AlignedPcm([u8; PCM_BYTES]);

static PCM_ALIGNED: AlignedPcm =
    AlignedPcm(*include_bytes!(concat!(env!("OUT_DIR"), "/pcm.bin")));

static PCM: &[i16; PCM_SAMPLES] =
    unsafe { &*(PCM_ALIGNED.0.as_ptr() as *const [i16; PCM_SAMPLES]) };

#[repr(C)]
struct RawTimbresBytes([u8; RAW_TIMBRES_BYTES]);

static RAW_TIMBRES_BYTES_ALIGNED: RawTimbresBytes = RawTimbresBytes(
    *include_bytes!(concat!(env!("OUT_DIR"), "/raw_timbres.bin")),
);

static RAW_TIMBRES: &RawTimbreBank =
    unsafe { &*(RAW_TIMBRES_BYTES_ALIGNED.0.as_ptr() as *const RawTimbreBank) };

include!(concat!(env!("OUT_DIR"), "/rom_static.rs"));

static BUNDLED_META: Meta = Meta {
    pcm_metas: PCM_METAS,
    rhythm_keys: RHYTHM_KEYS,
    rhythm_timbres: RHYTHM_TIMBRES,
    melodic_timbres: MELODIC_TIMBRES,
    default_programs: DEFAULT_PROGRAMS,
    default_panpots: DEFAULT_PANPOTS,
    patch_max_table: PATCH_MAX_TABLE,
    rhythm_max_table: RHYTHM_MAX_TABLE,
    system_max_table: SYSTEM_MAX_TABLE,
    timbre_max_table: TIMBRE_MAX_TABLE,
    reserve_settings: RESERVE_SETTINGS,
};

pub const fn bundled_rom() -> Rom {
    Rom::from_static(&BUNDLED_META, PCM, RAW_TIMBRES)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::param::RHYTHM_KEYS_COUNT;
    use crate::rom::{ControlArray, Error, PCM_META_COUNT, PcmArray};

    const CTRL: &ControlArray = include_bytes!("../rom/CM32L_CONTROL.ROM");
    const PCM_RAW: &PcmArray = include_bytes!("../rom/CM32L_PCM.ROM");

    #[test]
    fn test_small_rom() {
        const TOO_SMALL: &[u8] = &[0; 1024];

        let result = Rom::new(CTRL, TOO_SMALL);
        assert_eq!(result.unwrap_err(), Error::WrongLength);

        let result = Rom::new(TOO_SMALL, PCM_RAW);
        assert_eq!(result.unwrap_err(), Error::WrongLength);

        let result = Rom::new(TOO_SMALL, TOO_SMALL);
        assert_eq!(result.unwrap_err(), Error::WrongLength);
    }

    #[test]
    fn test_new() {
        Rom::new(CTRL, PCM_RAW).unwrap();
    }

    #[test]
    fn test_bundled_rom() {
        let rom = Rom::bundled();
        assert_eq!(rom.pcm().len(), PCM_SAMPLES);
        assert_eq!(rom.meta().pcm_metas.len(), PCM_META_COUNT);
    }

    #[test]
    fn test_bundled_matches_runtime() {
        let runtime = Rom::new(CTRL, PCM_RAW).unwrap();
        assert_eq!(runtime, Rom::bundled());
    }

    #[test]
    fn test_pcm_metas_parsed() {
        let rom = Rom::bundled();
        let meta = &rom.meta().pcm_metas[0];
        assert!(meta.len > 0, "PCM metadata 0 should have non-zero length");
        assert!(
            meta.addr < PCM_SAMPLES,
            "PCM metadata 0 address should be within PCM ROM"
        );
    }

    #[test]
    fn test_rhythm_keys_parsed() {
        let rom = Rom::bundled();

        for i in 0..RHYTHM_KEYS_COUNT {
            let setting = &rom.meta().rhythm_keys[i];

            assert!(
                setting.timbre <= 127,
                "Timbre {} out of range at index {}",
                setting.timbre,
                i
            );
            assert!(
                setting.level <= 100,
                "Output level {} out of range at index {}",
                setting.level,
                i
            );
            assert!(
                setting.panpot <= 14,
                "Panpot {} out of range at index {}",
                setting.panpot,
                i
            );
        }
    }

    #[test]
    fn test_cowbell_rhythm_setting() {
        let rom = Rom::bundled();
        let cowbell_key = 56;
        let rhythm_key_start = 24;
        let rhythm_idx = cowbell_key - rhythm_key_start;
        let setting = &rom.meta().rhythm_keys[rhythm_idx];

        assert!(setting.timbre < 128, "Cowbell timbre should be valid");
        assert!(
            setting.level > 0,
            "Cowbell should have non-zero output level"
        );
    }
}