AUDIO
Source snapshot · 12 October 2026

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.wav
Chapters
  1. Import the processor and shared helper lines 1–6
  2. Build a timed MIDI event lines 7–13
  3. Select interactive or offline operation lines 14–19
  4. Prepare the fixture patch lines 20–29
  5. Schedule notes and sustain lines 30–41
  6. Render the release and save the clip lines 42–47

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 1
2#[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 2
3mod support;

Bring these module/type names into scope for “Import the processor and shared helper”; the surrounding chapter explains their responsibilities.

Link to line 3
4use 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 4
5use 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 5
6

Blank line separating the surrounding statements.

Link to line 6

Build 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 7
8 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 8
9 frame_offset,

Supply these names as the remaining imports, arguments or fields of the surrounding declaration.

Link to line 9
10 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 10
11 }

Close the surrounding scope, value or argument list in “Build a timed MIDI event”.

Link to line 11
12}

Close the surrounding scope, value or argument list in “Build a timed MIDI event”.

Link to line 12
13

Blank line separating the surrounding statements.

Link to line 13

Select 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 14
15 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 15
16 return ravet_examples::launch("synth");

Return this result to the caller; the remaining statements in the function are skipped.

Link to line 16
17 }

Close the surrounding scope, value or argument list in “Select interactive or offline operation”.

Link to line 17
18 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 18
19 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 19

Prepare 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 20
21 values[0] = 0.;

Select the sine waveform for this deterministic rendering fixture. The live default patch selects saw.

Link to line 21
22 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 22
23 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 23
24 values[4] = 0.;

Disable the envelope’s positive filter excursion for this fixture. Its VCA envelope still controls amplitude.

Link to line 24
25 values[5] = 5.;

Assign the computed value to values[5]. The right-hand expression supplies the new value for this step.

Link to line 25
26 values[6] = 10.;

Assign the computed value to values[6]. The right-hand expression supplies the new value for this step.

Link to line 26
27 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 27
28 values[8] = 50.;

Assign the computed value to values[8]. The right-hand expression supplies the new value for this step.

Link to line 28
29 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 29

Schedule 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 30
31 // 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 31
32 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 32
33 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 33
34 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 34
35 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 35
36 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 36
37 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 37
38 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 38
39 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 39
40 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 40
41 ];

Close the surrounding scope, value or argument list in “Schedule notes and sustain”.

Link to line 41

Render 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 42
43 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 43
44 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 44
45 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 45
46 Ok(())

Report successful completion without returning a payload.

Link to line 46
47}

Close the surrounding scope, value or argument list in “Render the release and save the clip”.

Link to line 47

Source SHA-256: 42cd4bada784ebf9bb704fd1bab75b68fdd7980b761a16bf91455c9f893981fd

API: ravet_examples · ravet_foundation

All tutorial walkthroughs