No description
Find a file
Johann Queuniet 06e5dc2a36
All checks were successful
CI / stable (pull_request) Successful in 15s
CI / msrv (1.90) (pull_request) Successful in 18s
CI / audit (pull_request) Successful in 9s
CI / msrv (1.90) (push) Successful in 19s
CI / audit (push) Successful in 9s
CI / stable (push) Successful in 15s
chore: tighten crate packaging and document the public API
- Exclude .idea, .forgejo, and deny.toml from the published tarball so
  only source, licenses, README, and the example ship.
- Enable #![warn(missing_docs)] and document every public item: OplOperator/
  OplPatch fields, OplBank methods, MidiMsg variants, TimedEvent/RenderOpts/
  Pcm/StereoPcm fields, Error, and render().

cargo fmt/clippy/test/doc (with -D warnings) all clean.
2026-07-23 09:26:51 +02:00
.forgejo/workflows chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
.idea chore: add IDEA configuration files 2026-07-23 09:13:17 +02:00
examples chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
src chore: tighten crate packaging and document the public API 2026-07-23 09:26:51 +02:00
.gitignore chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
Cargo.lock chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
Cargo.toml chore: tighten crate packaging and document the public API 2026-07-23 09:26:51 +02:00
deny.toml chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
LICENSE-APACHE chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
LICENSE-MIT chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00
README.md chore: standalone-ize after monorepo split 2026-07-23 09:06:11 +02:00

opl-audio

Pure-Rust OPL2 (Yamaha YM3812) and OPL3 (YMF262) FM song renderer.

Renders a list of timed MIDI events against a bank of AdLib FM patches to PCM audio through an original YM3812 emulator. The crate is engine-agnostic: it knows MIDI and FM synthesis — not any specific game, sound driver, or file format. Callers load their own instrument bank (from a .bnk or anywhere else) and feed timed events; the crate returns mono (OPL2) or stereo 18-channel (OPL3) PCM.

  • #![forbid(unsafe_code)]
  • Single dependency: thiserror
  • Register tables are derived from the published OPL2 formulas — no copyleft emulator source is used, so the crate is cleanly licensable under MIT OR Apache-2.0.

What it does

  • OPL2 core — nine 2-operator channels, four waveforms, ADSR envelopes following the published rate table, key-scale rate/level, tremolo and vibrato LFOs.
  • MIDI dynamics — note velocity, CC7 channel volume, CC11 expression, CC64 sustain pedal, ±2-semitone pitch bend, and channel-10 percussion (drum patches played as melodic voices keyed by note).
  • OPL3 mode (render_stereo) — eighteen 2-op channels sharing the same operator pipeline, with CC10 pan quantized to hard L/C/R via the per-channel output-enable bits.

Usage

use opl_audio::{MidiMsg, OplBank, RenderOpts, TimedEvent};

let mut bank = OplBank::new();
bank.insert_program(0, my_patch); // an OplPatch you built or loaded from a .bnk

let events = vec![
    TimedEvent { delta_ticks: 0,  msg: MidiMsg::NoteOn  { ch: 0, note: 69, vel: 127 } },
    TimedEvent { delta_ticks: 96, msg: MidiMsg::NoteOff { ch: 0, note: 69 } },
];

// division = MIDI ticks per quarter note, bpm = tempo
let pcm = opl_audio::render(&events, &bank, 96, 120, RenderOpts::default())?;
// pcm.samples: Vec<f32> in [-1.0, 1.0] at pcm.sample_rate (49716 Hz by default)

For stereo OPL3 output, call opl_audio::render_stereo(..), which returns a StereoPcm { left, right }.

See examples/render_tone.rs for a complete, data-free example:

cargo run --example render_tone

Provenance

Extracted from the dm2-tools Dungeon Master II toolchain, where it renders the game's DOS AdLib soundtrack. The per-crate commit history is preserved from that monorepo.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.