ferrosintesis/lib.rs
1//! A pure-Rust General MIDI synthesizer. Its DSP code has no third-party Rust
2//! dependencies. Default builds embed separately licensed recorded material
3//! from first-party asset crates, so no external SoundFont or runtime sound-bank
4//! lookup is required. Input is a Standard MIDI File; output is interleaved
5//! stereo `f32`.
6//!
7//! Two surfaces:
8//!
9//! - [`offline`] — load or parse an SMF, then render, normalize, and atomically
10//! write a WAV with bounded audio memory. The path shown below.
11//! - [`live`] — [`live::RealtimeSynth`]: raw MIDI bytes in via
12//! [`write_byte`](live::RealtimeSynth::write_byte), stereo blocks summed
13//! into your output buffer via
14//! [`render_add`](live::RealtimeSynth::render_add).
15//!
16//! ```no_run
17//! use ferrosintesis::offline::{self, Normalization, Options};
18//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
19//! // A one-note Standard MIDI File, assembled in memory: format 0, one
20//! // track, 480 ticks per quarter; a harp plays middle C for one beat.
21//! let mut smf: Vec<u8> = Vec::new();
22//! smf.extend(b"MThd");
23//! smf.extend(6u32.to_be_bytes()); // header length
24//! smf.extend(0u16.to_be_bytes()); // format 0
25//! smf.extend(1u16.to_be_bytes()); // one track
26//! smf.extend(480u16.to_be_bytes()); // ticks per quarter note
27//! let track = [
28//! 0x00, 0xC0, 46, // program change: harp
29//! 0x00, 0x90, 60, 100, // note on: middle C, velocity 100
30//! 0x83, 0x60, 0x80, 60, 0, // 480 ticks later: note off
31//! 0x00, 0xFF, 0x2F, 0x00, // end of track
32//! ];
33//! smf.extend(b"MTrk");
34//! smf.extend((track.len() as u32).to_be_bytes());
35//! smf.extend(track);
36//!
37//! let song = offline::parse(&smf)?;
38//! let opt = Options::default().with_tail(1.0); // short reverb tail
39//! let stats = offline::render_to_wav(
40//! &song,
41//! &opt,
42//! std::path::Path::new("output.wav"),
43//! Normalization::loudness(-18.0, -1.0),
44//! )?;
45//! assert!(stats.voices_spawned >= 1 && stats.peak > 0.0);
46//! # Ok(())
47//! # }
48//! ```
49//!
50//! Rendering is deterministic for a fixed executable, MIDI file, and options.
51//! The default `embedded-samples` feature compiles
52//! roughly 111 MiB of recorded audio — 1156 WAVs across twenty-five
53//! first-party asset crates — into the binary; `default-features = false`
54//! builds the fully modeled synth instead, a far smaller artifact with
55//! synthetic note onsets in place of the recorded ones.
56//! [`embedded_samples_available`] reports which build this is. No unsafe
57//! code, no build scripts.
58//!
59//! Most of that audio is CC0; ten of the twenty-five crates carry an
60//! attribution obligation that travels with a distributed binary. The
61//! `NOTICE` file at the repository root is the index of what you must
62//! reproduce, and each asset crate packages the exact required text.
63//!
64//! All 128 GM melodic program numbers route to a synthesized voice family, but
65//! programs within a family may share an engine and support varies by program.
66//! The GM sound-effects block 120–127 also has explicit voice paths. This is not
67//! a claim of GM conformance or reference-module timbre. The README carries the
68//! program-by-program table; DESIGN.md in the repository carries the full design
69//! essay.
70
71#![forbid(unsafe_code)]
72// The public surface ships to docs.rs. An undocumented public item is a bug, and the
73// gate (`clippy -D warnings`) makes this one fail the build rather than rot quietly.
74#![warn(missing_docs)]
75
76pub mod live;
77pub mod offline;
78
79mod error;
80pub use error::MidiError;
81
82/// Whether this build contains the optional compile-time attack-sample bank.
83///
84/// Disable the crate's default features to build the fully modeled synth without
85/// embedding any of the sample-asset packages. See the README's "Sample provenance
86/// and licensing" section for what a default build embeds and what it obliges a
87/// binary distributor to reproduce.
88pub const fn embedded_samples_available() -> bool {
89 cfg!(feature = "embedded-samples")
90}
91
92pub(crate) mod altbank;
93pub(crate) mod drums;
94pub(crate) mod dsp;
95pub(crate) mod engine;
96// These oracles inspect sibling packages and repository policy. `.cargo/config.toml`
97// enables them in a checkout; the published archive omits both the inputs and the cfg.
98#[cfg(all(test, ferrosintesis_repository_tests))]
99mod inventory;
100
101#[cfg(all(test, ferrosintesis_repository_tests))]
102mod balance;
103#[cfg(all(test, ferrosintesis_repository_tests))]
104mod licensing;
105pub(crate) mod loudness;
106#[cfg(all(test, ferrosintesis_repository_tests))]
107mod manifest;
108pub(crate) mod midi;
109#[cfg(test)]
110mod parse_robustness;
111#[cfg(all(test, ferrosintesis_repository_tests))]
112mod payload;
113#[cfg(all(test, ferrosintesis_repository_tests))]
114mod provenance;
115#[cfg(all(test, ferrosintesis_repository_tests))]
116mod render_profile;
117pub(crate) mod reverb;
118pub(crate) mod sampler;
119pub(crate) mod scratch;
120#[cfg(test)]
121pub(crate) mod testutil;
122#[cfg(test)]
123mod velocity_law;
124pub(crate) mod voices;
125pub(crate) mod wav;