oplon 1.0.0

A no_std, integer-only OPL2/OPL3 (Yamaha YM3812/YMF262) FM synthesis core with a clean-room, compile-time-generated ROM set
Documentation
//! Hot-path throughput benchmark: render a fully-loaded OPL3 chip and report
//! frames/second and the real-time ratio. Run with `--release`.
//!
//! The workload keeps all 18 channels sounding, with KSL, vibrato, tremolo,
//! feedback and a couple of 4-operator groups active, so every branch of the
//! per-sample path (`render_native` → `Operator::next` → `waveform_output`) is
//! exercised — not a trivial single-sine best case.

#![forbid(unsafe_code)]
use std::time::Instant;

use oplon::Opl2;

fn main() {
    // Optional output rate arg (default 48000; pass 49716 for the native rate,
    // where the resampler is a near-passthrough, to isolate the synthesis core).
    let rate: u32 = std::env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(48_000);
    let mut opl = Opl2::new(rate);
    opl.set_opl3_enabled(true);

    // 4-op groups on channel pairs (0,3) and (9,12); the rest are 2-op.
    opl.write_reg_high(0x04, 0x01 | 0x08);

    // Program every channel across both banks with varied, "bright" patches so
    // KSL / high multiples / feedback / all waveforms are live.
    for bank in 0..2u8 {
        for ch in 0u8..9 {
            let grp = ch / 3;
            let idx = ch % 3;
            let mod_off = grp * 8 + idx;
            let car_off = grp * 8 + 3 + idx;
            for (n, off) in [mod_off, car_off].into_iter().enumerate() {
                let mul = 1 + ((ch + n as u8) & 0x0F);
                let w = |opl: &mut Opl2, r: u8, v: u8| {
                    if bank == 0 { opl.write_reg(r, v) } else { opl.write_reg_high(r, v) }
                };
                w(&mut opl, 0x20 + off, 0xE0 | mul); // AM+VIB+KSR, varied multiple
                w(&mut opl, 0x40 + off, 0x80 | 0x10); // KSL=2, moderate TL
                w(&mut opl, 0x60 + off, 0xF4); // attack 15, decay 4
                w(&mut opl, 0x80 + off, 0x2F); // sustain 2, release 15
                w(&mut opl, 0xE0 + off, (ch + n as u8) & 0x07); // varied waveform 0-7
            }
            let w = |opl: &mut Opl2, r: u8, v: u8| {
                if bank == 0 { opl.write_reg(r, v) } else { opl.write_reg_high(r, v) }
            };
            w(&mut opl, 0xC0 + ch, 0x30 | 0x0E); // pan L+R, feedback 7, FM
            w(&mut opl, 0xA0 + ch, 0x80 + ch);
            w(&mut opl, 0xB0 + ch, 0x20 | ((3 + (ch & 3)) << 2) | 0x01); // key-on
        }
    }

    // Warm up (prime the resampler, reach sustain).
    for _ in 0..rate {
        let _ = opl.render_frame();
    }

    let seconds = 60u32;
    let frames = (rate * seconds) as u64;
    let t0 = Instant::now();
    let mut acc = 0i64;
    for _ in 0..frames {
        let (l, r) = opl.render_frame();
        acc = acc.wrapping_add(l as i64).wrapping_add(r as i64);
    }
    let dt = t0.elapsed().as_secs_f64();

    let fps = frames as f64 / dt;
    println!("rendered {frames} frames in {dt:.3} s");
    println!("  {:.2} M frames/s", fps / 1e6);
    println!("  {:.1}× real time (at {rate} Hz)", fps / rate as f64);
    println!("  {:.2} ns/frame", dt * 1e9 / frames as f64);
    println!("  (checksum {acc})");
}