### Ferrosintesis quickstart example: load MIDI and render WAV Source: https://docs.rs/ferrosintesis/latest/src/quickstart/quickstart.rs.html This is the complete quickstart example from the README, compiled as an example file. It loads a MIDI file, prints song metadata, configures reverb, and renders to a WAV file with -18 LUFS loudness normalization and a -1 dBTP ceiling. Run with: cargo run --release --example quickstart -- input.mid output.wav ```rust //! The README's quick-start, as a compiled example. //! //! The README is the crate's landing page on crates.io and its code block is the first //! thing anyone copies. A fenced block in a markdown file is checked by nothing, so this //! file exists to keep it honest: it is the same program, and `cargo build --examples` //! (which `clippy --all-targets` already runs in the gate) fails if the API drifts from //! what the README claims. //! //! Keep the two in sync. If you change this, change the README. //! //! cargo run --release --example quickstart -- input.mid output.wav use ferrosintesis::offline::{self, Normalization, Options}; use std::path::Path; fn main() -> Result<(), Box> { let mut args = std::env::args().skip(1); let input = args.next().unwrap_or_else(|| "input.mid".into()); let output = args.next().unwrap_or_else(|| "output.wav".into()); let song = offline::load(Path::new(&input))?; println!( "{}: {:.1} s, {} events", song.title(), song.seconds(), song.events_len() ); // `Options` has private fields: build it from Default with the with_* builders. let opt = Options::default().with_reverb(0.25); // Render with bounded audio memory, normalize to -18 LUFS (BS.1770-4) // under a -1 dBTP ceiling, and atomically publish the completed WAV. let stats = offline::render_to_wav( &song, &opt, Path::new(&output), Normalization::loudness(-18.0, -1.0), )?; eprintln!("{} voices, peak {:.3}", stats.voices_spawned, stats.peak); Ok(()) } ``` -------------------------------- ### Quickstart example for Song methods Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/offline/struct.Song.html Demonstrates loading a MIDI file and using song.title(), song.seconds(), and song.events_len() to inspect the file before rendering. ```rust 16fn main() -> Result<(), Box> { 17 let mut args = std::env::args().skip(1); 18 let input = args.next().unwrap_or_else(|| "input.mid".into()); 19 let output = args.next().unwrap_or_else(|| "output.wav".into()); 20 21 let song = offline::load(Path::new(&input))?; 22 println!( 23 "{}: {:.1} s, {} events", 24 song.title(), 25 song.seconds(), 26 song.events_len() 27 ); 28 29 // `Options` has private fields: build it from Default with the with_* builders. 30 let opt = Options::default().with_reverb(0.25); 31 32 // Render with bounded audio memory, normalize to -18 LUFS (BS.1770-4) 33 // under a -1 dBTP ceiling, and atomically publish the completed WAV. 34 let stats = offline::render_to_wav( 35 &song, 36 &opt, 37 Path::new(&output), 38 Normalization::loudness(-18.0, -1.0), 39 )?; 40 eprintln!("{} voices, peak {:.3}", stats.voices_spawned, stats.peak); 41 Ok(()) 42} ``` -------------------------------- ### Usage example from examples/quickstart.rs (lines 34-39) Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/offline/fn.render_to_wav.html Example usage of render_to_wav from the repository. It loads a MIDI file, builds Options with default and with_reverb(0.25), then calls render_to_wav with Normalization::loudness(-18.0, -1.0) to render and normalize to -18 LUFS under a -1 dBTP ceiling. The example prints the number of voices spawned and the peak value. ```rust fn main() -> Result<(), Box> { let mut args = std::env::args().skip(1); let input = args.next().unwrap_or_else(|| "input.mid".into()); let output = args.next().unwrap_or_else(|| "output.wav".into()); let song = offline::load(Path::new(&input))?; println!( "{}: {:.1} s, {} events", song.title(), song.seconds(), song.events_len() ); // `Options` has private fields: build it from Default with the with_* builders. let opt = Options::default().with_reverb(0.25); // Render with bounded audio memory, normalize to -18 LUFS (BS.1770-4) // under a -1 dBTP ceiling, and atomically publish the completed WAV. let stats = offline::render_to_wav( &song, &opt, Path::new(&output), Normalization::loudness(-18.0, -1.0), )?; eprintln!("{} voices, peak {:.3}", stats.voices_spawned, stats.peak); Ok(()) } ``` -------------------------------- ### Configuring Options with builders Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/offline/struct.Options.html Example of initializing Options using the default constructor and refining settings with builder methods. ```rust use ferrosintesis::offline::Options; let opt = Options::default() .with_sample_rate(48_000) .with_echo(0.0); // no echo bus assert_eq!(opt.sample_rate(), 48_000); assert_eq!(opt.echo(), 0.0); ``` -------------------------------- ### Configuring RealtimeOptions Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/live/struct.RealtimeOptions.html Example showing how to initialize RealtimeOptions using the default constructor and chain builder methods to set specific parameters. ```rust use ferrosintesis::live::RealtimeOptions; let opt = RealtimeOptions::default() .with_sample_rate(48_000) .with_master_gain(0.5); assert_eq!(opt.sample_rate(), 48_000); ``` -------------------------------- ### Using render_with_progress with a progress callback Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/offline/fn.render_with_progress.html Example of how to use render_with_progress to load a MIDI file and report progress to stderr. ```rust use ferrosintesis::offline::{self, Options}; let song = offline::load(std::path::Path::new("song.mid"))?; let (samples, _stats) = offline::render_with_progress(&song, &Options::default(), &mut |p| { eprintln!("{:.0}% ({} voices)", p.fraction() * 100.0, p.active_voices); }); ``` -------------------------------- ### Call a public function in examples/ex.rs Source: https://docs.rs/ferrosintesis/latest/scrape-examples-help.html Example of calling the previously defined function, which Rustdoc will associate with the function's documentation. ```rust // examples/ex.rs fn main() { a_crate::a_func(); } ``` -------------------------------- ### Define a public function in src/lib.rs Source: https://docs.rs/ferrosintesis/latest/scrape-examples-help.html Example of a public function definition that can be scraped by Rustdoc. ```rust // src/lib.rs pub fn a_func() {} ``` -------------------------------- ### Get voice kind Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/sampler.rs.html Returns the kind of the underlying sustain model, used for testing. ```rust #[cfg(test)] fn kind(&self) -> &'static str { // the LA wrapper is transparent for routing: report the model inside self.sustain.kind() } ``` -------------------------------- ### Render a Standard MIDI File to WAV Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/lib.rs.html Demonstrates how to parse an SMF byte vector and render it to a WAV file using the offline module. The example includes setting a reverb tail and applying loudness normalization. ```rust use ferrosintesis::offline::{self, Normalization, Options}; # fn main() -> Result<(), Box> { // A one-note Standard MIDI File, assembled in memory: format 0, one // track, 480 ticks per quarter; a harp plays middle C for one beat. let mut smf: Vec = Vec::new(); smf.extend(b"MThd"); smf.extend(6u32.to_be_bytes()); // header length smf.extend(0u16.to_be_bytes()); // format 0 smf.extend(1u16.to_be_bytes()); // one track smf.extend(480u16.to_be_bytes()); // ticks per quarter note let track = [ 0x00, 0xC0, 46, // program change: harp 0x00, 0x90, 60, 100, // note on: middle C, velocity 100 0x83, 0x60, 0x80, 60, 0, // 480 ticks later: note off 0x00, 0xFF, 0x2F, 0x00, // end of track ]; smf.extend(b"MTrk"); smf.extend((track.len() as u32).to_be_bytes()); smf.extend(track); let song = offline::parse(&smf)?; let opt = Options::default().with_tail(1.0); // short reverb tail let stats = offline::render_to_wav( &song, &opt, std::path::Path::new("output.wav"), Normalization::loudness(-18.0, -1.0), )?; assert!(stats.voices_spawned >= 1 && stats.peak > 0.0); # Ok(()) # } ``` -------------------------------- ### Get round-robin phase Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/sampler.rs.html Returns the current round-robin index modulo the number of takes. ```rust fn rr_phase(&self) -> Option { self.rr.map(|rr| rr.idx % rr.takes.max(1)) } ``` -------------------------------- ### Render a Standard MIDI File to WAV using offline::render_to_wav Source: https://docs.rs/ferrosintesis This example demonstrates how to parse a Standard MIDI File from memory and render it to a WAV file with normalization and a reverb tail. It requires the ferrosintesis::offline module. ```rust use ferrosintesis::offline::{self, Normalization, Options}; // A one-note Standard MIDI File, assembled in memory: format 0, one // track, 480 ticks per quarter; a harp plays middle C for one beat. let mut smf: Vec = Vec::new(); smf.extend(b"MThd"); smf.extend(6u32.to_be_bytes()); // header length smf.extend(0u16.to_be_bytes()); // format 0 smf.extend(1u16.to_be_bytes()); // one track smf.extend(480u16.to_be_bytes()); // ticks per quarter note let track = [ 0x00, 0xC0, 46, // program change: harp 0x00, 0x90, 60, 100, // note on: middle C, velocity 100 0x83, 0x60, 0x80, 60, 0, // 480 ticks later: note off 0x00, 0xFF, 0x2F, 0x00, // end of track ]; smf.extend(b"MTrk"); smf.extend((track.len() as u32).to_be_bytes()); smf.extend(track); let song = offline::parse(&smf)?; let opt = Options::default().with_tail(1.0); // short reverb tail let stats = offline::render_to_wav( &song, &opt, std::path::Path::new("output.wav"), Normalization::loudness(-18.0, -1.0), )?; assert!(stats.voices_spawned >= 1 && stats.peak > 0.0); ``` -------------------------------- ### get Source: https://docs.rs/ferrosintesis-samples-b1-upright/0.1.1/index.html A function that returns the embedded WAV bytes for an exact (case-sensitive) file name. ```APIDOC ## get ### Description Returns the embedded WAV bytes for an exact (case-sensitive) file name. ### Parameters - **file_name** (string) - Required - The exact file name of the WAV file to retrieve. ### Returns - **bytes** - The embedded WAV bytes for the specified file name. ``` -------------------------------- ### Phase Match Grain Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/sampler.rs.html Refines an integer-period start against the recorded waveform to ensure phase alignment during crossfades. ```rust /// Refine an integer-period start against the actual recorded waveform. /// The measured zone root is only an average: local vibrato can leave two /// nominally equal phases far enough apart to dip during a crossfade. fn phase_match_grain(&self, nominal: usize, grain_len: usize) -> usize { let radius = (0.5 * self.source_period).round() as isize; let fade_start = self.loop_end.saturating_sub(self.grain_crossfade as usize); let compare_len = ((0.006 * 44_100.0) as usize) .min(self.grain_crossfade as usize) .min(self.loop_end.saturating_sub(fade_start)); let candidate_hi = self.grain_body_end.saturating_sub(grain_len); let mut best = nominal; let mut best_corr = f64::NEG_INFINITY; for offset in (-radius..=radius).step_by(2) { let candidate = (nominal as isize + offset) .clamp(self.grain_body_start as isize, candidate_hi as isize) as usize; let mut dot = 0.0f64; let mut a2 = 0.0f64; let mut b2 = 0.0f64; for i in 0..compare_len { let a = self.data[fade_start + i] as f64; let b = self.data[candidate + i] as f64; dot += a * b; a2 += a * a; b2 += b * b; } let corr = dot / (a2 * b2).sqrt().max(1e-20); if corr > best_corr { best_corr = corr; best = candidate; } } best } ``` -------------------------------- ### RealtimeSynth::new Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/live/struct.RealtimeSynth.html Creates a new RealtimeSynth instance with the given options. The sample rate is fixed at creation time. ```APIDOC ## RealtimeSynth::new ### Description Creates a new RealtimeSynth instance. Voices, buses, and reverb are sized for the provided sample rate, so the rate cannot be changed later. ### Method Constructor ### Parameters - **options** (RealtimeOptions) - Required - Configuration options for the synthesizer, including sample rate. ### Returns - **Self** - A new RealtimeSynth instance. ``` -------------------------------- ### render_to_wav_with_progress Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/offline/fn.render_to_wav_with_progress.html Renders a song to a WAV file with progress reporting. The callback reports synthesizer progress roughly ten times, and disk-backed normalization and WAV writing happen after the callback reaches the end. ```APIDOC ## Function render_to_wav_with_progress ### Description Renders a song to a WAV file with progress reporting. The callback reports synthesizer progress roughly ten times, and disk-backed normalization and WAV writing happen after the callback reaches the end. ### Signature pub fn render_to_wav_with_progress( song: &Song, opt: &Options, output: &Path, normalization: Normalization, on_progress: &mut dyn FnMut(Progress), ) -> Result ### Parameters - **song** (&Song) - Required - The song to render. - **opt** (&Options) - Required - Rendering options. - **output** (&Path) - Required - The output WAV file path. - **normalization** (Normalization) - Required - The normalization mode to apply. - **on_progress** (&mut dyn FnMut(Progress)) - Required - A callback that receives progress updates during synthesis. ### Returns - **Result** - The result containing rendering statistics, or an error. ### Errors Returns the same errors as `render_to_wav`. ``` -------------------------------- ### Render drum kit helpers for V1 and V3 Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/drums.rs.html Test helpers to render drum hits for the legacy V1 kit and the shipping default V3 kit. render_drum_v1 calls render_drum_kit with Kit::V1; for_each_kit runs a closure per kit to keep behaviour statements from drifting. render_drum_kit_samples renders into a buffer and sec_window slices a time window. ```Rust fn render_drum_v1(key: u8, vel: u8, secs: f32) -> Vec { render_drum_kit(key, vel, secs, Kit::V1) } ``` ```Rust fn for_each_kit(mut check: impl FnMut(Kit, &str)) { check(Kit::V3, "V3 (engine default)"); check(Kit::V1, "V1 (legacy, PC 25)"); } ``` ```Rust fn render_drum_kit(key: u8, vel: u8, secs: f32, kit: Kit) -> Vec { render_drum_kit_samples(key, vel, secs, kit, false) } ``` ```Rust fn render_drum_kit_samples(key: u8, vel: u8, secs: f32, kit: Kit, samples: bool) -> Vec { let sr = 44100.0; let mut v = make(key, vel, sr, 7, kit, samples, 0).unwrap(); let mut buf = vec![0f32; (sr * secs) as usize]; v.render(&mut buf); buf } ``` ```Rust fn sec_window(s: &[f32], sr: f32, a: f32, b: f32) -> &[f32] { &s[(a * sr) as usize..(b * sr) as usize] } ``` -------------------------------- ### Test Utilities for Realtime Synth Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/live.rs.html Provides helper functions for setting up test environments and simulating voice rendering. ```rust 646#[cfg(test)] 647mod tests { 648 use super::*; 649 650 fn opts() -> RealtimeOptions { 651 RealtimeOptions { 652 sample_rate: 44_100, 653 wet: 0.0, 654 delay_s: 0.0, 655 samples: false, 656 master_gain: 1.0, 657 } 658 } 659 660 /// Spawn `n` distinct (channel, key) melodic voices with no note-offs, then 661 /// render one block so the pending note-ons are applied. Distinct pairs (and 662 /// skipping the drum channel 9) keep every note a fresh voice rather than a 663 /// same-key retrigger. Returns the live voice count after the block. 664 fn spawn_voices_and_render(synth: &mut RealtimeSynth, n: usize) -> usize { ``` -------------------------------- ### get Source: https://docs.rs/ferrosintesis-samples-rain/0.2.0/index.html Returns the embedded sample bytes for an exact (case-sensitive) name. This is the primary function for retrieving the rain sample data. ```APIDOC ## Function: get ### Description Returns the embedded sample bytes for an exact (case-sensitive) name. ### Signature `get(name: &str) -> Option<&'static [u8]>` ### Parameters - **name** (string) - Required - The exact (case-sensitive) name of the sample file. ### Returns - **Option<&'static [u8]>** - The embedded sample bytes if the name matches, otherwise None. ### Example ```rust let bytes = ferrosintesis_samples_rain::get("rain.flac"); ``` ``` -------------------------------- ### load Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/offline/index.html Reads and parses a Standard MIDI File from disk. Returns a Song ready for rendering. ```APIDOC ## load ### Description Reads and parses a Standard MIDI File from disk. ### Function Signature `load(path: impl AsRef) -> Result` ### Parameters - **path** (Path) - Required - Path to the Standard MIDI File. ### Returns - **Song** - Parsed song ready for rendering. - **MidiError** - Error if the file could not be read or parsed. ### Notes - Maximum file size is `MAX_MIDI_FILE_BYTES` (64 MiB). ``` -------------------------------- ### get Source: https://docs.rs/ferrosintesis-samples-ccby/0.2.0/ferrosintesis_samples_ccby Returns the embedded sample bytes for an exact (case-sensitive) name. This function is part of the public API of the ferrosintesis_samples_ccby crate. ```APIDOC ## get ### Description Returns the embedded sample bytes for an exact (case-sensitive) name. ### Function Signature `get(name: &str) -> Option<&'static [u8]>` ### Parameters - **name** (string) - Required - The exact case-sensitive name of the sample file. ### Return Value - **Option<&'static [u8]>** - The embedded sample bytes if the name matches, otherwise None. ### Example ```rust let bytes = ferrosintesis_samples_ccby::get("GM 4 Rhodes.flac"); ``` ``` -------------------------------- ### live::RealtimeOptions Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/all.html Configuration options for realtime synthesis. ```APIDOC ## Struct: live::RealtimeOptions ### Description Configuration options for realtime synthesis. ### Fields - (fields not documented in source) ``` -------------------------------- ### RealtimeOptions::with_samples Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/live/struct.RealtimeOptions.html Enable or disable the embedded PCM attack-sample layer. Default is true. ```APIDOC ## RealtimeOptions::with_samples ### Description Enable or disable the embedded PCM attack-sample layer. Default is true. ### Method pub fn with_samples(self, samples: bool) -> Self ### Parameters - **samples** (bool) - Required - Whether to enable the attack-sample layer. ### Returns Self - The updated RealtimeOptions instance. ``` -------------------------------- ### choose_next_grain method Source: https://docs.rs/ferrosintesis/latest/src/ferrosintesis/sampler.rs.html Calculates the start position and length of the next grain using phase alignment and seeded selection to ensure non-periodic playback. ```rust fn choose_next_grain(&mut self) -> (usize, usize) { let min_periods = ((SAX_GRAIN_MIN_S * 44_100.0) / self.source_period) .ceil() .max(1.0) as usize; let max_periods = ((SAX_GRAIN_MAX_S * 44_100.0) / self.source_period) .floor() .max(min_periods as f32) as usize; let length_unit = (0.5 * (self.rng.white() + 1.0)).clamp(0.0, 0.999_999); let periods = min_periods + (length_unit * (max_periods - min_periods + 1) as f32) as usize; let grain_len = (periods as f32 * self.source_period).round() as usize; let grain_start_hi = self .grain_body_end .saturating_sub(grain_len) .max(self.grain_body_start); let origin = self.loop_start as f32; let first = ((self.grain_body_start as f32 - origin) / self.source_period).ceil() as i32; let last = ((grain_start_hi as f32 - origin) / self.source_period).floor() as i32; if last < first { return (self.grain_body_start, grain_len); } let count = (last - first + 1) as usize; let unit = (0.5 * (self.rng.white() + 1.0)).clamp(0.0, 0.999_999); let mut step = first + (unit * count as f32) as i32; let mut start = (origin + step as f32 * self.source_period).round() as usize; if start.abs_diff(self.loop_start) < (0.5 * self.source_period) as usize { step = if step < last { step + 1 } else { first }; start = (origin + step as f32 * self.source_period).round() as usize; } let nominal = start.clamp(self.grain_body_start, grain_start_hi); (self.phase_match_grain(nominal, grain_len), grain_len) } ``` -------------------------------- ### RealtimeSynth::prewarm_samples Source: https://docs.rs/ferrosintesis/latest/ferrosintesis/live/struct.RealtimeSynth.html Decodes the embedded attack-sample banks on the current thread. This should be called during setup, off the realtime thread, to avoid audio callback deadline issues. ```APIDOC ## RealtimeSynth::prewarm_samples ### Description Decodes the embedded attack-sample banks now, on this thread. The banks decode lazily on first use, which would otherwise happen inside your audio callback and blow the deadline. Call this once during setup, off the realtime thread. A no-op without the `embedded-samples` feature. ### Method Method ### Parameters - **self** (&self) - Required - Immutable reference to the synthesizer. ### Returns - **()** - Nothing. ```