Subtractive synth entry point
examples/tutorials/src/bin/midi-synth.rs · 47 source lines
Read the chapters in source order. Select a numbered source line to expand its explanation; the line numbers and code match this snapshot exactly.
cargo run -p ravet-examples --bin midi-synth --locked -- --render target/examples/synth-guide.wavChapters
Import the processor and shared helper
The binary uses the portable SubtractiveSynth, its physical default controls and a prepared SynthPatch. Event and EventData carry messages; MidiMessage validates short MIDI bytes. The support module is shared with the delay binary.
1//! Live subtractive synth UI; explicit rendering remains an offline test fixture.
Source note: Live subtractive synth UI; explicit rendering remains an offline test fixture.
Link to line 12#[path = "../support/mod.rs"]
Apply this Rust attribute to the following item. cfg selects which target/feature/test build contains it; derive generates the named trait implementations.
Link to line 23mod support;
Bring these module/type names into scope for “Import the processor and shared helper”; the surrounding chapter explains their responsibilities.
Link to line 34use ravet_examples::synth::{DEFAULT_VALUES, SubtractiveSynth, SynthPatch};
Bring these module/type names into scope for “Import the processor and shared helper”; the surrounding chapter explains their responsibilities.
Link to line 45use ravet_foundation::{Event, EventData, MidiMessage};
Bring these module/type names into scope for “Import the processor and shared helper”; the surrounding chapter explains their responsibilities.
Link to line 56
Blank line separating the surrounding statements.
Link to line 6Build a timed MIDI event
Decode the supplied fixed byte sequence into a validated short MIDI message. expect is justified by these literal fixture bytes; malformed external bytes would need a handled error. Wrap it with the absolute clip frame offset.
7fn midi(frame_offset: usize, bytes: &[u8]) -> Event {
Define midi. Decode the supplied fixed byte sequence into a validated short MIDI message. expect is justified by these literal fixture bytes; malformed external bytes would need a handled error. Wrap it with the absolute clip frame offset.
Link to line 78 Event {
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Build a timed MIDI event” explanation above.
Link to line 89 frame_offset,
Supply these names as the remaining imports, arguments or fields of the surrounding declaration.
Link to line 910 data: EventData::Midi(MidiMessage::from_bytes(bytes).expect("fixed valid MIDI message")),
Typed event payload; timing is kept separately in frame_offset.
Link to line 1011 }
Close the surrounding scope, value or argument list in “Build a timed MIDI event”.
Link to line 1112}
Close the surrounding scope, value or argument list in “Build a timed MIDI event”.
Link to line 1213
Blank line separating the surrounding statements.
Link to line 13Select interactive or offline operation
Without --render, launch the synth window. The render path is explicitly selected and gets its output path through support::output_path. Construct an inactive synth; preparation is performed later by the renderer.
14fn main() -> support::Result<()> {
Define main. Without --render, launch the synth window. The render path is explicitly selected and gets its output path through support::output_path. Construct an inactive synth; preparation is performed later by the renderer.
Link to line 1415 if !std::env::args_os().nth(1).is_some_and(|a| a == "--render") {
Evaluate this condition or visit this range in source order. The statements in the following scope implement “Select interactive or offline operation”.
Link to line 1516 return ravet_examples::launch("synth");
Return this result to the caller; the remaining statements in the function are skipped.
Link to line 1617 }
Close the surrounding scope, value or argument list in “Select interactive or offline operation”.
Link to line 1718 let path = support::output_path("target/examples/midi-synth.wav")?;
Bind path to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.
Link to line 1819 let mut synth = SubtractiveSynth::new();
Bind mutable synth to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.
Link to line 19Prepare the fixture patch
Copy the nine default physical values. Select sine (waveform index 0), level 0.8, cutoff 20 kHz and zero filter-envelope excursion. Attack/decay/release are 5/10/50 ms and sustain is 0.8. Prepare coefficients at 48 kHz and pass the validated patch to the synth.
20 let mut values = DEFAULT_VALUES;
Bind mutable values to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.
Link to line 2021 values[0] = 0.;
Select the sine waveform for this deterministic rendering fixture. The live default patch selects saw.
Link to line 2122 values[1] = 0.8;
Assign the computed value to values[1]. The right-hand expression supplies the new value for this step.
Link to line 2223 values[2] = 20_000.;
Request a 20 kHz base cutoff; SynthPatch still caps designed coefficients below 45% of the actual sample rate.
Link to line 2324 values[4] = 0.;
Disable the envelope’s positive filter excursion for this fixture. Its VCA envelope still controls amplitude.
Link to line 2425 values[5] = 5.;
Assign the computed value to values[5]. The right-hand expression supplies the new value for this step.
Link to line 2526 values[6] = 10.;
Assign the computed value to values[6]. The right-hand expression supplies the new value for this step.
Link to line 2627 values[7] = 0.8;
Assign the computed value to values[7]. The right-hand expression supplies the new value for this step.
Link to line 2728 values[8] = 50.;
Assign the computed value to values[8]. The right-hand expression supplies the new value for this step.
Link to line 2829 synth.set_patch(&SynthPatch::new(48_000., values)?)?;
The ? operator returns an error immediately on failure and unwraps the success value for the next step.
Link to line 29Schedule notes and sustain
The schedule starts after 2,400 frames of silence. It plays C4, E4 and G4, sends sustain down before G4 note-off, and lifts sustain at frame 60,000. Status bytes 0x90/0x80 identify note-on/off on channel zero; 0xb0 with controller 64 controls sustain.
30 // MIDI channels and keys are zero-based. Notes start after 50 ms of silence.
Source note: MIDI channels and keys are zero-based. Notes start after 50 ms of silence.
Link to line 3031 // The synth supplies its shared ADSR and VCF; this runner supplies MIDI events.
Source note: The synth supplies its shared ADSR and VCF; this runner supplies MIDI events.
Link to line 3132 let events = [
Bind events to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.
Link to line 3233 midi(2_400, &[0x90, 60, 100]), // C4 on, channel 0
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Schedule notes and sustain” explanation above.
Link to line 3334 midi(12_000, &[0x80, 60, 64]),
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Schedule notes and sustain” explanation above.
Link to line 3435 midi(16_000, &[0x90, 64, 100]), // E4
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Schedule notes and sustain” explanation above.
Link to line 3536 midi(28_000, &[0x80, 64, 64]),
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Schedule notes and sustain” explanation above.
Link to line 3637 midi(32_000, &[0x90, 67, 100]), // G4
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Schedule notes and sustain” explanation above.
Link to line 3738 midi(44_000, &[0xb0, 64, 127]), // sustain down
Send sustain-pedal down (controller 64) on zero-based channel 0. A later note-off remains owned until pedal release.
Link to line 3839 midi(46_000, &[0x80, 67, 64]), // retained by sustain
Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Schedule notes and sustain” explanation above.
Link to line 3940 midi(60_000, &[0xb0, 64, 0]), // sustain up, release tail
Send sustain-pedal up on channel 0, allowing the retained voice to enter its latched release envelope.
Link to line 4041 ];
Close the surrounding scope, value or argument list in “Schedule notes and sustain”.
Link to line 41Render the release and save the clip
Render 72,000 frames (1.5 seconds), with no input because this is an instrument. The extra time after pedal-up allows the latched release envelope to finish. Write the clip with the same no-overwrite helper as the effect.
42 let audio = support::render(&mut synth, None, 72_000, &events)?;
Bind audio to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.
Link to line 4243 support::write_wave(&path, &audio)?;
The ? operator returns an error immediately on failure and unwraps the success value for the next step.
Link to line 4344 println!("C4, E4, G4; the final note continues after note-off until pedal-up.");
Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.
Link to line 4445 println!("Launch without arguments for the live UI, waveform switch, shared ADSR and VCF.");
Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.
Link to line 4546 Ok(())
Report successful completion without returning a payload.
Link to line 4647}
Close the surrounding scope, value or argument list in “Render the release and save the clip”.
Link to line 47