moont
Roland CM-32L synth emulator, based on Munt,
written in no_std Rust with no code dependencies.
The CM-32L is in the MT-32 family of synthesizers. This hardware predates General MIDI, but many early 1990s PC games directly support these synths.
moont aims to be sample-accurate with Munt. It accomplishes this across a variety of input comparison tests with no known incompatibilities. Inputs for which moont output does not match Munt should be reported as a bug.
Usage
Add the moont crate as dependency in Cargo.toml:
[dependencies]
moont = "1.0"
Loading ROMs
This repository does not distribute the Roland CM-32L ROMs. In the default build configuration, the only option is to load them at runtime:
use ;
let rom = new?;
let mut synth = new;
For situations where redistribution of the ROMs is not a concern, the
bundle-rom feature flag avoids runtime loading:
use ;
let mut synth = new;
Place CM32L_CONTROL.ROM and CM32L_PCM.ROM in the rom/ directory inside
the moont crate (i.e. moont/rom/), or set the MOONT_ROM_DIR environment
variable to a directory containing them. The tools/download-roms.sh script
downloads them to the default location.
This feature goes beyond bundling ROM data as binary blobs: at build time, the
ROMs are validated, parsed, PCM-unscrambled and converted to Rust values. Rust
literals are generated, compiled into the library, and cm32l::Rom::bundled() returns
a handle to this static generated data.
General MIDI Translation
When the input is General MIDI, use cm32l::GmDevice to make it playable through
the CM-32L engine. It does the following:
- Assigns parts 1-8 to MIDI channels 1-8 (rhythm on channel 10).
- Sets pitch bend range to 2 semitones (the GM default) on all parts.
- Translates program changes and drum notes to their CM-32L equivalents.
- Reverses pan (CC#10) to match GM stereo convention.
- Reapplies GM defaults after device reset.
use ;
let mut synth = new;
Rendering Audio
Use play_msg() or play_msg_at() to enqueue MIDI events, and render() to
advance the time and output 32kHz stereo samples to a buffer.
use ;
// Note On: MIDI channel 2, note C4, max velocity.
synth.play_msg;
// 1 second of output.
let mut buf = vec!;
synth.render;
// Note Off: MIDI channel 2, note C4.
synth.play_msg;
Munt Feature Comparison
moont makes major scope limitations compared to mt32emu:
- No support for alternate configurations or other hardware models.
- No C/C++ API, just Rust library crate.
- No display functionality.
- Very limited runtime state introspection.
moont behaves like this libmt32emu configuration:
- CM-32L hardware model.
- Compared to MT-32: 33 extra rhythm sounds.
- None of the MT-32 "quirks".
- 32kHz output sample rate (matching CM-32L).
- 4096 MIDI queue size.
- "Coarse" output mode (no upsampling).
- "Nice" DAC input mode.
- "Nice" amplitude ramp mode.
- "Nice" panning mode.
- "Nice" partial mixing mode.
Related Crates
| Crate | Description |
|---|---|
| moont-render | Render .mid files to .wav |
| moont-live | Real-time ALSA MIDI sink |
| moont-web | WebAssembly wrapper with Web Audio API |
Testing
Since the ROMs are not distributed, most tests are excluded from the published
crate. When built from source, the sample accurate comparison tests require a
patched Munt fork at ./munt in
the workspace root to make its behavior deterministic. It is built and linked
as a static library automatically.
License
moont is a derivative work of Munt. Both are distributed under LGPL 2.1+.
Copyright (C) 2021-2026 Geoff Hill geoff@geoffhill.org
Copyright (C) 2003-2026 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
This library 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.