AUDIO
Source snapshot · 12 October 2026

MPE expression entry point

examples/tutorials/src/bin/mpe.rs · 83 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 mpe --locked -- --offline
Chapters
  1. Import the voice policy lines 1–4
  2. Validate and apply a literal message lines 5–8
  3. Print occupied voices lines 9–30
  4. Choose live UI or the explicit trace lines 31–40
  5. Negotiate the lower zone and bend range lines 41–55
  6. Start two independent member notes lines 56–64
  7. Add pressure and timbre lines 65–70
  8. Release without immediately freeing a slot lines 71–77
  9. Finish the retained release voice lines 78–83

Import the voice policy

This walkthrough uses a four-slot MpeReceiver and typed short MIDI. It inspects voice expression only; it does not render an oscillator or open a hardware controller.

1//! Negotiate a lower MPE zone and inspect independent per-note expression.

Source note: Negotiate a lower MPE zone and inspect independent per-note expression.

Link to line 1
2use ravet_foundation::MidiMessage;

Bring these module/type names into scope for “Import the voice policy”; the surrounding chapter explains their responsibilities.

Link to line 2
3use ravet_instrument::{MpeReceiver, VoicePhase};

Bring these module/type names into scope for “Import the voice policy”; the surrounding chapter explains their responsibilities.

Link to line 3
4

Blank line separating the surrounding statements.

Link to line 4

Validate and apply a literal message

Convert each fixed byte sequence to MidiMessage, then let the receiver update voice ownership, RPN state and expression. expect treats invalid hard-coded fixture data as a programming error.

5fn apply(receiver: &mut MpeReceiver<4>, bytes: &[u8]) {

Define apply. Convert each fixed byte sequence to MidiMessage, then let the receiver update voice ownership, RPN state and expression. expect treats invalid hard-coded fixture data as a programming error.

Link to line 5
6 receiver.apply(MidiMessage::from_bytes(bytes).expect("fixed valid MIDI message"));

Require the invariant “fixed valid MIDI message”. expect panics if it is broken; external inputs must be validated before relying on it.

Link to line 6
7}

Close the surrounding scope, value or argument list in “Validate and apply a literal message”.

Link to line 7
8

Blank line separating the surrounding statements.

Link to line 8

Print occupied voices

Skip Idle slots, require an occupied note, and print zero-based channel/key plus member bend, master bend, pressure and timbre. Released voices are intentionally included: they can still own an audible release tail in a real DSP consumer.

9fn show(label: &str, receiver: &MpeReceiver<4>) {

Define show. Skip Idle slots, require an occupied note, and print zero-based channel/key plus member bend, master bend, pressure and timbre. Released voices are intentionally included: they can still own an audible release tail in a real DSP consumer.

Link to line 9
10 println!("{label}");

Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.

Link to line 10
11 for voice in receiver

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Print occupied voices”.

Link to line 11
12 .voices()

Call the displayed operation with these arguments as the next step in “Print occupied voices”. The chapter describes its role and validation.

Link to line 12
13 .iter()

Borrow collection entries for iteration without moving or copying the whole collection.

Link to line 13
14 .filter(|v| v.phase != VoicePhase::Idle)

Keep only entries for which this predicate returns true.

Link to line 14
15 {

Close the surrounding scope, value or argument list in “Print occupied voices”.

Link to line 15
16 let note = voice.note.expect("occupied voice");

Bind note to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.

Link to line 16
17 let expression = voice.expression;

Bind expression to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.

Link to line 17
18 println!(

Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.

Link to line 18
19 " channel={} key={} {:?}: member_bend={:.1} master_bend={:.1} pressure={:.3} timbre={:.3}",

The ? operator returns an error immediately on failure and unwraps the success value for the next step.

Link to line 19
20 note.channel,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 20
21 note.key,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 21
22 voice.phase,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 22
23 expression.member_bend,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 23
24 expression.master_bend,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 24
25 expression.pressure,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 25
26 expression.timbre,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Print occupied voices” explanation above.

Link to line 26
27 );

Close the current expression or scope; no additional processing operation occurs on this line.

Link to line 27
28 }

Close the surrounding scope, value or argument list in “Print occupied voices”.

Link to line 28
29}

Close the surrounding scope, value or argument list in “Print occupied voices”.

Link to line 29
30

Blank line separating the surrounding statements.

Link to line 30

Choose live UI or the explicit trace

No arguments and --ui-test go through the common MPE UI launcher. The non-UI path accepts exactly --offline. Incorrect argument combinations return a usage error instead of opening a device implicitly.

31fn main() -> Result<(), Box<dyn std::error::Error>> {

Define main. No arguments and --ui-test go through the common MPE UI launcher. The non-UI path accepts exactly --offline. Incorrect argument combinations return a usage error instead of opening a device implicitly.

Link to line 31
32 if std::env::args_os().len() == 1

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Choose live UI or the explicit trace”.

Link to line 32
33 || std::env::args_os().nth(1).is_some_and(|a| a == "--ui-test")

Accept this alternative condition in the surrounding check; evaluation stops once the combined condition is true.

Link to line 33
34 {

Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.

Link to line 34
35 return ravet_examples::launch("mpe");

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

Link to line 35
36 }

Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.

Link to line 36
37 if std::env::args_os().len() != 2 || std::env::args_os().nth(1).is_none_or(|a| a != "--offline")

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Choose live UI or the explicit trace”.

Link to line 37
38 {

Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.

Link to line 38
39 return Err("usage: mpe [--offline | --ui-test directory]".into());

Exit this path with an explicit error/fault; the caller must not treat this branch as successfully applied state.

Link to line 39
40 }

Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.

Link to line 40

Negotiate the lower zone and bend range

RPN 6 on master channel 0 selects a lower zone with members 1 and 2. CC101/100 select an RPN, CC6 supplies data, and 127/127 nulls the selection. RPN 0 on member channel 1 sets this zone’s member bend range to 12 semitones.

41 let mut receiver = MpeReceiver::<4>::new()?;

Bind mutable receiver to the expression on this and any following continuation lines. The chapter above explains how this intermediate value is used.

Link to line 41
42 // RPN 6: lower master channel 0, two member channels (1 and 2).

Source note: RPN 6: lower master channel 0, two member channels (1 and 2).

Link to line 42
43 // Send Data Entry, then null the RPN selection to avoid accidental later edits.

Source note: Send Data Entry, then null the RPN selection to avoid accidental later edits.

Link to line 43
44 for bytes in [

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Negotiate the lower zone and bend range”.

Link to line 44
45 [0xb0, 101, 0],

Set the RPN most-significant selector to zero on master channel 0. The next selector identifies the specific RPN.

Link to line 45
46 [0xb0, 100, 6],

Select RPN 6, the MPE zone configuration, on the lower-zone master channel.

Link to line 46
47 [0xb0, 6, 2],

Use Data Entry to request two lower-zone member channels, 1 and 2.

Link to line 47
48 [0xb0, 101, 127],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Negotiate the lower zone and bend range” explanation above.

Link to line 48
49 [0xb0, 100, 127],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Negotiate the lower zone and bend range” explanation above.

Link to line 49
50 // RPN 0 on a member: set this zone's member bend range to 12 semitones.

Source note: RPN 0 on a member: set this zone's member bend range to 12 semitones.

Link to line 50
51 [0xb1, 101, 0],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Negotiate the lower zone and bend range” explanation above.

Link to line 51
52 [0xb1, 100, 0],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Negotiate the lower zone and bend range” explanation above.

Link to line 52
53 [0xb1, 6, 12],

Request a 12-semitone member bend range through RPN 0 on channel 1.

Link to line 53
54 [0xb1, 101, 127],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Negotiate the lower zone and bend range” explanation above.

Link to line 54
55 [0xb1, 100, 127],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Negotiate the lower zone and bend range” explanation above.

Link to line 55

Start two independent member notes

Channel 1 plays key 60; channel 2 plays key 64. Maximum channel-1 bend affects only its member note. Minimum master bend on channel 0 affects both. This distinction is the purpose of the expression trace.

56 [0x91, 60, 100],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Start two independent member notes” explanation above.

Link to line 56
57 [0x92, 64, 100],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Start two independent member notes” explanation above.

Link to line 57
58 [0xe1, 127, 127], // member 1 maximum bend; member 2 stays centered

Encode maximum 14-bit member-channel pitch bend. Only member 1’s note receives this bend.

Link to line 58
59 [0xe0, 0, 0], // master bend -2 semitones affects both notes

Encode minimum master-channel pitch bend. The master expression affects both zone members.

Link to line 59
60 ]

Close the current expression or scope; no additional processing operation occurs on this line.

Link to line 60
61 .iter()

Borrow collection entries for iteration without moving or copying the whole collection.

Link to line 61
62 {

Close the surrounding scope, value or argument list in “Start two independent member notes”.

Link to line 62
63 apply(&mut receiver, bytes);

Call the displayed operation with these arguments as the next step in “Start two independent member notes”. The chapter describes its role and validation.

Link to line 63
64 }

Close the surrounding scope, value or argument list in “Start two independent member notes”.

Link to line 64

Add pressure and timbre

Channel pressure 0xd1 applies to member 1; CC74 controls its timbre. Print both notes to show the receiver’s expression snapshot. Actual mapping from pressure/timbre to amplitude/filter is a DSP choice, demonstrated separately by the subtractive synth.

65 apply(&mut receiver, &[0xd1, 100]);

Call the displayed operation with these arguments as the next step in “Add pressure and timbre”. The chapter describes its role and validation.

Link to line 65
66 apply(&mut receiver, &[0xb1, 74, 64]);

Call the displayed operation with these arguments as the next step in “Add pressure and timbre”. The chapter describes its role and validation.

Link to line 66
67 show(

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Add pressure and timbre” explanation above.

Link to line 67
68 "Two notes: member expression is independent; master bend is shared.",

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Add pressure and timbre” explanation above.

Link to line 68
69 &receiver,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Add pressure and timbre” explanation above.

Link to line 69
70 );

Close the current expression or scope; no additional processing operation occurs on this line.

Link to line 70

Release without immediately freeing a slot

Release C4, then recenter member and master bend. Released member expression freezes, while master expression continues to follow the zone. The receiver keeps the released voice occupied until its DSP owner calls finish.

71 apply(&mut receiver, &[0x81, 60, 64]);

Call the displayed operation with these arguments as the next step in “Release without immediately freeing a slot”. The chapter describes its role and validation.

Link to line 71
72 apply(&mut receiver, &[0xe1, 0, 64]); // released member expression freezes

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Release without immediately freeing a slot” explanation above.

Link to line 72
73 apply(&mut receiver, &[0xe0, 0, 64]); // master expression still follows the zone

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Release without immediately freeing a slot” explanation above.

Link to line 73
74 show(

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Release without immediately freeing a slot” explanation above.

Link to line 74
75 "After note-off and recenter: released C4 retains its +12 member bend.",

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Release without immediately freeing a slot” explanation above.

Link to line 75
76 &receiver,

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Release without immediately freeing a slot” explanation above.

Link to line 76
77 );

Close the current expression or scope; no additional processing operation occurs on this line.

Link to line 77

Finish the retained release voice

This inspector calls finish(0) explicitly because it renders no release envelope. A real processor must call it only when its release audio has ended. The final trace confirms the remaining occupied voices.

78 // A DSP owner would render a release envelope before freeing this voice.

Source note: A DSP owner would render a release envelope before freeing this voice.

Link to line 78
79 receiver.finish(0)?;

Explicitly release slot zero after the hypothetical DSP release tail. This inspector has no DSP envelope to wait for.

Link to line 79
80 show("After the DSP owner finishes the release tail:", &receiver);

Call the displayed operation with these arguments as the next step in “Finish the retained release voice”. The chapter describes its role and validation.

Link to line 80
81 println!("Snapshot example only; pressure/timbre mapping to sound belongs to your DSP.");

Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.

Link to line 81
82 Ok(())

Report successful completion without returning a payload.

Link to line 82
83}

Close the surrounding scope, value or argument list in “Finish the retained release voice”.

Link to line 83

Source SHA-256: 54bf3959d8f990dce143903a24e5604dcac3d2b8af9107360e2b0a77f472b235

API: ravet_examples · ravet_foundation · ravet_instrument

All tutorial walkthroughs