moont 0.9.2

Roland CM-32L synthesizer emulator
Documentation
// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
//
// 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.

#![allow(dead_code)]

#[path = "src/param.rs"]
mod param;
#[path = "src/rom.rs"]
mod rom;

/// Cargo build system hook to load ROMs and serialize them to Rust literals.
fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=src/param.rs");
    println!("cargo:rerun-if-changed=src/rom.rs");

    #[cfg(feature = "bundle-rom")]
    {
        use param::*;
        use rom::*;
        use std::env;
        use std::fs::File;
        use std::io::Write;
        use std::path::{Path, PathBuf};

        println!("cargo::rerun-if-env-changed=MOONT_ROM_DIR");

        let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
        let out_dir = env::var("OUT_DIR").unwrap();
        let out_path = Path::new(&out_dir);

        let rom_dir = env::var("MOONT_ROM_DIR")
            .map(PathBuf::from)
            .unwrap_or_else(|_| Path::new(&manifest_dir).join("rom"));

        if !rom_dir.is_dir() {
            println!(
                "cargo::warning=ROM directory not found: {}",
                rom_dir.display()
            );
        }

        let control_path = rom_dir.join("CM32L_CONTROL.ROM");
        let pcm_path = rom_dir.join("CM32L_PCM.ROM");

        println!("cargo:rerun-if-changed={}", control_path.display());
        println!("cargo:rerun-if-changed={}", pcm_path.display());

        let control =
            std::fs::read(&control_path).expect("Failed to read control ROM");
        let pcm_raw = std::fs::read(&pcm_path).expect("Failed to read PCM ROM");

        assert_eq!(control.len(), CONTROL_SIZE, "Control ROM has wrong size");
        assert_eq!(pcm_raw.len(), PCM_SIZE, "PCM ROM has wrong size");

        // Unscramble PCM and write as binary.
        let mut pcm_unscrambled = Vec::with_capacity(PCM_SAMPLES * 2);
        for chunk in pcm_raw.chunks_exact(2) {
            let sample = unscramble_pcm(chunk[0], chunk[1]);
            pcm_unscrambled.extend_from_slice(&sample.to_le_bytes());
        }

        let pcm_bin_path = out_path.join("pcm.bin");
        std::fs::write(&pcm_bin_path, &pcm_unscrambled)
            .expect("Failed to write PCM binary");

        // Parse ROM using shared parsing logic.
        let mut meta = Meta {
            pcm_metas: [PcmMeta::default(); PCM_META_COUNT],
            rhythm_keys: [RhythmKey::default(); RHYTHM_KEYS_COUNT],
            rhythm_timbres: [TimbreParam::default(); RHYTHM_TIMBRES_COUNT],
            melodic_timbres: [TimbreParam::default(); MELODIC_TIMBRES_COUNT],
            default_programs: [0; MELODIC_PARTS_COUNT],
            default_panpots: [0; 9],
            patch_max_table: [0; 16],
            rhythm_max_table: [0; 4],
            system_max_table: [0; 23],
            timbre_max_table: [0; 256],
            reserve_settings: [0; 9],
        };
        meta.init(&control).expect("Failed to parse ROM");

        let mut raw_timbres = [[0u8; 256]; 256];
        Meta::fill_raw_timbres(&control, &mut raw_timbres);
        let mut raw_timbres_bytes = Vec::with_capacity(256 * 256);
        for timbre in raw_timbres.iter() {
            raw_timbres_bytes.extend_from_slice(timbre);
        }
        std::fs::write(out_path.join("raw_timbres.bin"), raw_timbres_bytes)
            .expect("Failed to write raw timbres binary");

        // Generate static ROM metadata.
        let mut f = File::create(out_path.join("rom_static.rs"))
            .expect("Failed to create rom_static.rs");

        writeln!(f, "// Auto-generated by build.rs - do not edit\n").unwrap();
        writeln!(
            f,
            "static PCM_METAS: [PcmMeta; {}] = {:#?};\n",
            PCM_META_COUNT, meta.pcm_metas
        )
        .unwrap();
        writeln!(
            f,
            "static RHYTHM_KEYS: [RhythmKey; {}] = {:#?};\n",
            RHYTHM_KEYS_COUNT, meta.rhythm_keys
        )
        .unwrap();
        writeln!(
            f,
            "static RHYTHM_TIMBRES: [TimbreParam; {}] = {:#?};\n",
            RHYTHM_TIMBRES_COUNT, meta.rhythm_timbres
        )
        .unwrap();
        writeln!(
            f,
            "static MELODIC_TIMBRES: [TimbreParam; {}] = {:#?};\n",
            MELODIC_TIMBRES_COUNT, meta.melodic_timbres
        )
        .unwrap();
        writeln!(
            f,
            "static DEFAULT_PROGRAMS: [usize; {}] = {:?};\n",
            MELODIC_PARTS_COUNT, meta.default_programs
        )
        .unwrap();
        writeln!(
            f,
            "static DEFAULT_PANPOTS: [i32; 9] = {:?};\n",
            meta.default_panpots
        )
        .unwrap();
        writeln!(
            f,
            "static PATCH_MAX_TABLE: [u8; 16] = {:?};\n",
            meta.patch_max_table
        )
        .unwrap();
        writeln!(
            f,
            "static RHYTHM_MAX_TABLE: [u8; 4] = {:?};\n",
            meta.rhythm_max_table
        )
        .unwrap();
        writeln!(
            f,
            "static SYSTEM_MAX_TABLE: [u8; 23] = {:?};\n",
            meta.system_max_table
        )
        .unwrap();
        writeln!(
            f,
            "static TIMBRE_MAX_TABLE: [u8; 256] = {:?};\n",
            meta.timbre_max_table
        )
        .unwrap();
        writeln!(
            f,
            "static RESERVE_SETTINGS: [u8; 9] = {:?};",
            meta.reserve_settings
        )
        .unwrap();
    }
}