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 -- --offlineChapters
- Import the voice policy lines 1–4
- Validate and apply a literal message lines 5–8
- Print occupied voices lines 9–30
- Choose live UI or the explicit trace lines 31–40
- Negotiate the lower zone and bend range lines 41–55
- Start two independent member notes lines 56–64
- Add pressure and timbre lines 65–70
- Release without immediately freeing a slot lines 71–77
- 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 12use ravet_foundation::MidiMessage;
Bring these module/type names into scope for “Import the voice policy”; the surrounding chapter explains their responsibilities.
Link to line 23use 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 34
Blank line separating the surrounding statements.
Link to line 4Validate 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 56 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 67}
Close the surrounding scope, value or argument list in “Validate and apply a literal message”.
Link to line 78
Blank line separating the surrounding statements.
Link to line 8Print 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 910 println!("{label}");
Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.
Link to line 1011 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 1112 .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 1213 .iter()
Borrow collection entries for iteration without moving or copying the whole collection.
Link to line 1314 .filter(|v| v.phase != VoicePhase::Idle)
Keep only entries for which this predicate returns true.
Link to line 1415 {
Close the surrounding scope, value or argument list in “Print occupied voices”.
Link to line 1516 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 1617 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 1718 println!(
Report this explicit fixture result to stdout; printing belongs to the offline/control path, not the audio callback.
Link to line 1819 " 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 1920 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 2021 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 2122 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 2223 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 2324 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 2425 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 2526 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 2627 );
Close the current expression or scope; no additional processing operation occurs on this line.
Link to line 2728 }
Close the surrounding scope, value or argument list in “Print occupied voices”.
Link to line 2829}
Close the surrounding scope, value or argument list in “Print occupied voices”.
Link to line 2930
Blank line separating the surrounding statements.
Link to line 30Choose 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 3132 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 3233 || 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 3334 {
Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.
Link to line 3435 return ravet_examples::launch("mpe");
Return this result to the caller; the remaining statements in the function are skipped.
Link to line 3536 }
Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.
Link to line 3637 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 3738 {
Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.
Link to line 3839 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 3940 }
Close the surrounding scope, value or argument list in “Choose live UI or the explicit trace”.
Link to line 40Negotiate 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 4142 // 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 4243 // 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 4344 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 4445 [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 4546 [0xb0, 100, 6],
Select RPN 6, the MPE zone configuration, on the lower-zone master channel.
Link to line 4647 [0xb0, 6, 2],
Use Data Entry to request two lower-zone member channels, 1 and 2.
Link to line 4748 [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 4849 [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 4950 // 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 5051 [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 5152 [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 5253 [0xb1, 6, 12],
Request a 12-semitone member bend range through RPN 0 on channel 1.
Link to line 5354 [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 5455 [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 55Start 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 5657 [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 5758 [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 5859 [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 5960 ]
Close the current expression or scope; no additional processing operation occurs on this line.
Link to line 6061 .iter()
Borrow collection entries for iteration without moving or copying the whole collection.
Link to line 6162 {
Close the surrounding scope, value or argument list in “Start two independent member notes”.
Link to line 6263 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 6364 }
Close the surrounding scope, value or argument list in “Start two independent member notes”.
Link to line 64Add 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 6566 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 6667 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 6768 "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 6869 &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 6970 );
Close the current expression or scope; no additional processing operation occurs on this line.
Link to line 70Release 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 7172 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 7273 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 7374 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 7475 "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 7576 &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 7677 );
Close the current expression or scope; no additional processing operation occurs on this line.
Link to line 77Finish 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 7879 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 7980 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 8081 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 8182 Ok(())
Report successful completion without returning a payload.
Link to line 8283}
Close the surrounding scope, value or argument list in “Finish the retained release voice”.
Link to line 83