AUDIO
Source snapshot · 12 October 2026

Offline rendering and WAV helpers

examples/tutorials/src/support/mod.rs · 133 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 test -p ravet-examples --no-default-features --locked
Chapters
  1. Choose bounded offline constants lines 1–14
  2. Require explicit output selection lines 15–30
  3. Validate the entire clip schedule lines 31–49
  4. Prepare the processor and fixed event scratch lines 50–69
  5. Partition the schedule without losing boundary events lines 70–80
  6. Borrow each block without allocating lines 81–97
  7. End the prepared rendering session lines 98–101
  8. Create a file without overwriting an existing render lines 102–116
  9. Interleave and finalize the WAV stream lines 117–133

Choose bounded offline constants

The helper uses 48 kHz, 256-frame blocks and a 32-event fixed array. Stereo stores two complete f32 clips; that allocation and file I/O occur outside the processing callback. The Result alias lets fixture setup propagate different error types.

1//! Offline teaching helpers. File I/O and whole-clip storage belong outside audio callbacks.

Source note: Offline teaching helpers. File I/O and whole-clip storage belong outside audio callbacks.

Link to line 1
2use ravet_audio_file::{WaveEncoding, WaveFormat, WaveWriter};

Bring these module/type names into scope for “Choose bounded offline constants”; the surrounding chapter explains their responsibilities.

Link to line 2
3use ravet_foundation::{

Bring these module/type names into scope for “Choose bounded offline constants”; the surrounding chapter explains their responsibilities.

Link to line 3
4 AudioBuffer, AudioLayout, Event, EventData, ParameterId, ProcessBlock, ProcessConfig,

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

Link to line 4
5 ProcessStatus, Processor, Transport,

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

Link to line 5
6};

Close the surrounding scope, value or argument list in “Choose bounded offline constants”.

Link to line 6
7use std::{fs::File, io, path::PathBuf};

Bring these module/type names into scope for “Choose bounded offline constants”; the surrounding chapter explains their responsibilities.

Link to line 7
8

Blank line separating the surrounding statements.

Link to line 8
9pub const SAMPLE_RATE: usize = 48_000;

Declare this compile-time constant or fixed array. The value and physical/capacity meaning are given by its source comment and this chapter.

Link to line 9
10const BLOCK_FRAMES: usize = 256;

Declare this compile-time constant or fixed array. The value and physical/capacity meaning are given by its source comment and this chapter.

Link to line 10
11const MAX_EVENTS: usize = 32;

Declare this compile-time constant or fixed array. The value and physical/capacity meaning are given by its source comment and this chapter.

Link to line 11
12pub type Stereo = [Vec<f32>; 2];

Assign the computed value to pub type Stereo. The right-hand expression supplies the new value for this step.

Link to line 12
13pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Choose bounded offline constants” explanation above.

Link to line 13
14

Blank line separating the surrounding statements.

Link to line 14

Require explicit output selection

output_path skips the executable name, requires --render, selects an optional path or the default, and refuses extra arguments. This command-line helper does not enumerate or open audio devices.

15// The only optional argument is the output file; no device or host is opened.

Source note: The only optional argument is the output file; no device or host is opened.

Link to line 15
16pub fn output_path(default: &str) -> Result<PathBuf> {

Define output_path. output_path skips the executable name, requires --render, selects an optional path or the default, and refuses extra arguments. This command-line helper does not enumerate or open audio devices.

Link to line 16
17 let mut args = std::env::args_os().skip(1);

Bind mutable args 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 if !args.next().is_some_and(|a| a == "--render") {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Require explicit output selection”.

Link to line 18
19 return Err("Rendering requires --render".into());

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

Link to line 19
20 }

Close the surrounding scope, value or argument list in “Require explicit output selection”.

Link to line 20
21 let selected = args.next();

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

Link to line 21
22 let path = selected.map_or_else(|| PathBuf::from(default), PathBuf::from);

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 22
23 if args.next().is_some() {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Require explicit output selection”.

Link to line 23
24 return Err(

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

Link to line 24
25 "usage: cargo run -p ravet-examples --bin <name> -- --render [output.wav]".into(),

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Require explicit output selection” explanation above.

Link to line 25
26 );

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

Link to line 26
27 }

Close the surrounding scope, value or argument list in “Require explicit output selection”.

Link to line 27
28 Ok(path)

Construct the success/error variant selected by the surrounding match or function return. The enclosed value becomes its payload.

Link to line 28
29}

Close the surrounding scope, value or argument list in “Require explicit output selection”.

Link to line 29
30

Blank line separating the surrounding statements.

Link to line 30

Validate the entire clip schedule

render rejects zero-length clips, excessive events, offsets beyond the clip, decreasing event order and input channels of the wrong length. These checks precede preparation and rendering. Presence of input selects an effect layout; absence selects an instrument.

31// Events here use absolute clip offsets. Each callback receives block-local offsets.

Source note: Events here use absolute clip offsets. Each callback receives block-local offsets.

Link to line 31
32pub fn render(

Define render. render rejects zero-length clips, excessive events, offsets beyond the clip, decreasing event order and input channels of the wrong length. These checks precede preparation and rendering. Presence of input selects an effect layout; absence selects an instrument.

Link to line 32
33 processor: &mut impl Processor,

Define or initialize processor with the displayed type/value. Its ownership and use are described in “Validate the entire clip schedule”.

Link to line 33
34 input: Option<Stereo>,

Define or initialize input with the displayed type/value. Its ownership and use are described in “Validate the entire clip schedule”.

Link to line 34
35 frames: usize,

Cumulative rendered frame count for telemetry.

Link to line 35
36 events: &[Event],

Define or initialize events with the displayed type/value. Its ownership and use are described in “Validate the entire clip schedule”.

Link to line 36
37) -> Result<Stereo> {

Complete the function’s parameter list and specify the return type. A Result preserves the explicit success/error distinction.

Link to line 37
38 if frames == 0

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Validate the entire clip schedule”.

Link to line 38
39 || events.len() > MAX_EVENTS

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

Link to line 39
40 || events.iter().any(|event| event.frame_offset >= frames)

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

Link to line 40
41 || events

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

Link to line 41
42 .windows(2)

Call the displayed operation with these arguments as the next step in “Validate the entire clip schedule”. The chapter describes its role and validation.

Link to line 42
43 .any(|pair| pair[0].frame_offset > pair[1].frame_offset)

Return true if any entry satisfies this predicate; stop at the first match.

Link to line 43
44 || input

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

Link to line 44
45 .as_ref()

Borrow the contained value without taking ownership; an absent Option stays absent.

Link to line 45
46 .is_some_and(|channels| channels.iter().any(|c| c.len() != frames))

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

Link to line 46
47 {

Close the surrounding scope, value or argument list in “Validate the entire clip schedule”.

Link to line 47
48 return Err("invalid offline clip or event schedule".into());

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

Link to line 48
49 }

Close the surrounding scope, value or argument list in “Validate the entire clip schedule”.

Link to line 49

Prepare the processor and fixed event scratch

Allocate or reuse the complete output clip, then prepare the processor once with the helper’s limits. Keep a peekable ordered event iterator and initialize a fixed 32-entry scratch array; only the occupied prefix is passed to a block.

50 let effect = input.is_some();

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

Link to line 50
51 let mut output = input.unwrap_or_else(|| [vec![0.; frames], vec![0.; frames]]);

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

Link to line 51
52 processor.prepare(ProcessConfig::new(

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Prepare the processor and fixed event scratch” explanation above.

Link to line 52
53 SAMPLE_RATE as f64,

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

Link to line 53
54 BLOCK_FRAMES,

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

Link to line 54
55 MAX_EVENTS,

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

Link to line 55
56 if effect {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Prepare the processor and fixed event scratch”.

Link to line 56
57 AudioLayout::STEREO

Define or initialize AudioLayout with the displayed type/value. Its ownership and use are described in “Prepare the processor and fixed event scratch”.

Link to line 57
58 } else {

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Prepare the processor and fixed event scratch” explanation above.

Link to line 58
59 AudioLayout::STEREO_INSTRUMENT

Define or initialize AudioLayout with the displayed type/value. Its ownership and use are described in “Prepare the processor and fixed event scratch”.

Link to line 59
60 },

Close the surrounding scope, value or argument list in “Prepare the processor and fixed event scratch”.

Link to line 60
61 )?)?;

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

Link to line 61
62 let mut pending = events.iter().peekable();

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

Link to line 62
63 let mut local = [Event {

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

Link to line 63
64 frame_offset: 0,

The event’s timing coordinate, interpreted relative to its current clip/block context.

Link to line 64
65 data: EventData::ParameterValue {

Typed event payload; timing is kept separately in frame_offset.

Link to line 65
66 id: ParameterId(0),

Define or initialize id with the displayed type/value. Its ownership and use are described in “Prepare the processor and fixed event scratch”.

Link to line 66
67 value: 0.,

Define or initialize value with the displayed type/value. Its ownership and use are described in “Prepare the processor and fixed event scratch”.

Link to line 67
68 },

Close the surrounding scope, value or argument list in “Prepare the processor and fixed event scratch”.

Link to line 68
69 }; MAX_EVENTS];

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Prepare the processor and fixed event scratch” explanation above.

Link to line 69

Partition the schedule without losing boundary events

For each exclusive block end, consume events strictly before end and subtract the block start. An event at the boundary remains pending for the next block at offset zero. The struct update preserves the event payload while changing its timing coordinate.

70 for start in (0..frames).step_by(BLOCK_FRAMES) {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Partition the schedule without losing boundary events”.

Link to line 70
71 let end = (start + BLOCK_FRAMES).min(frames);

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

Link to line 71
72 let mut count = 0;

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

Link to line 72
73 while pending.peek().is_some_and(|event| event.frame_offset < end) {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Partition the schedule without losing boundary events”.

Link to line 73
74 let event = *pending.next().expect("peeked event");

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

Link to line 74
75 local[count] = Event {

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

Link to line 75
76 frame_offset: event.frame_offset - start,

The event’s timing coordinate, interpreted relative to its current clip/block context.

Link to line 76
77 ..event

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Partition the schedule without losing boundary events” explanation above.

Link to line 77
78 };

Close the surrounding scope, value or argument list in “Partition the schedule without losing boundary events”.

Link to line 78
79 count += 1;

Add the computed value to count. The right-hand expression supplies the new value for this step.

Link to line 79
80 }

Close the surrounding scope, value or argument list in “Partition the schedule without losing boundary events”.

Link to line 80

Borrow each block without allocating

Split the two owned clip vectors into mutable channel slices. Effects use in-place storage; instruments have empty inputs and separate output storage. Process with default transport and the prepared event prefix. Any non-Continue status deactivates and returns an error.

81 let [left, right] = &mut output;

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Borrow each block without allocating” explanation above.

Link to line 81
82 let mut channels = [&mut left[start..end], &mut right[start..end]];

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

Link to line 82
83 let audio = if effect {

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 83
84 AudioBuffer::in_place(&mut channels, end - start)?

Borrow the active mutable channel slices as both effect input and output. Constructing the view does not allocate audio storage.

Link to line 84
85 } else {

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Borrow each block without allocating” explanation above.

Link to line 85
86 AudioBuffer::separate(&[], &mut channels, end - start)?

Borrow distinct input/output slices. An instrument supplies an empty input slice; output storage already belongs to the caller.

Link to line 86
87 };

Close the surrounding scope, value or argument list in “Borrow each block without allocating”.

Link to line 87
88 let status = processor.process(&mut ProcessBlock::new(

Combine borrowed audio, the active input-event slice and explicit transport into one processing view.

Link to line 88
89 audio,

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

Link to line 89
90 &local[..count],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Borrow each block without allocating” explanation above.

Link to line 90
91 Transport::default(),

Define or initialize Transport with the displayed type/value. Its ownership and use are described in “Borrow each block without allocating”.

Link to line 91
92 ));

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

Link to line 92
93 if status != ProcessStatus::Continue {

Represent a successfully processed block. The offline renderer treats any different status as a failed rendering session.

Link to line 93
94 processor.deactivate();

Call the displayed operation with these arguments as the next step in “Borrow each block without allocating”. The chapter describes its role and validation.

Link to line 94
95 return Err(io::Error::other(format!("offline processing failed: {status:?}")).into());

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

Link to line 95
96 }

Close the surrounding scope, value or argument list in “Borrow each block without allocating”.

Link to line 96
97 }

Close the surrounding scope, value or argument list in “Borrow each block without allocating”.

Link to line 97

End the prepared rendering session

After the complete clip is processed, deactivate the owner and return the rendered storage. This is offline processing: returning a clip does not imply audible device output or a real-time deadline.

98 processor.deactivate();

Call the displayed operation with these arguments as the next step in “End the prepared rendering session”. The chapter describes its role and validation.

Link to line 98
99 Ok(output)

Construct the success/error variant selected by the surrounding match or function return. The enclosed value becomes its payload.

Link to line 99
100}

Close the surrounding scope, value or argument list in “End the prepared rendering session”.

Link to line 100
101

Blank line separating the surrounding statements.

Link to line 101

Create a file without overwriting an existing render

Create a parent directory when needed, then use create_new(true) so an existing output fails. Configure stereo PCM16 at 48 kHz with the same 256-frame worker scratch limit.

102pub fn write_wave(path: &std::path::Path, channels: &Stereo) -> Result<()> {

Define write_wave. Create a parent directory when needed, then use create_new(true) so an existing output fails. Configure stereo PCM16 at 48 kHz with the same 256-frame worker scratch limit.

Link to line 102
103 if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Create a file without overwriting an existing render”.

Link to line 103
104 std::fs::create_dir_all(parent)?;

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

Link to line 104
105 }

Close the surrounding scope, value or argument list in “Create a file without overwriting an existing render”.

Link to line 105
106 // Refuse to overwrite existing files. Use a new path for another render.

Source note: Refuse to overwrite existing files. Use a new path for another render.

Link to line 106
107 let file = File::options().write(true).create_new(true).open(path)?;

Refuse to overwrite an existing file. Use a new output path for a second offline rendering.

Link to line 107
108 let mut writer = WaveWriter::new(

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

Link to line 108
109 file,

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

Link to line 109
110 WaveFormat {

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Create a file without overwriting an existing render” explanation above.

Link to line 110
111 channels: 2,

Define or initialize channels with the displayed type/value. Its ownership and use are described in “Create a file without overwriting an existing render”.

Link to line 111
112 sample_rate: SAMPLE_RATE as u32,

Define or initialize sample_rate with the displayed type/value. Its ownership and use are described in “Create a file without overwriting an existing render”.

Link to line 112
113 encoding: WaveEncoding::Pcm16,

Define or initialize encoding with the displayed type/value. Its ownership and use are described in “Create a file without overwriting an existing render”.

Link to line 113
114 },

Close the surrounding scope, value or argument list in “Create a file without overwriting an existing render”.

Link to line 114
115 BLOCK_FRAMES,

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

Link to line 115
116 )?;

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

Link to line 116

Interleave and finalize the WAV stream

Copy planar left/right samples into an interleaved fixed array for each chunk. Write only the active prefix and call finish to finalize stream headers. Print the written path and frame count after successful finalization.

117 let mut interleaved = [0.; BLOCK_FRAMES * 2];

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

Link to line 117
118 for start in (0..channels[0].len()).step_by(BLOCK_FRAMES) {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Interleave and finalize the WAV stream”.

Link to line 118
119 let count = BLOCK_FRAMES.min(channels[0].len() - start);

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

Link to line 119
120 for frame in 0..count {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Interleave and finalize the WAV stream”.

Link to line 120
121 interleaved[2 * frame] = channels[0][start + frame];

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Interleave and finalize the WAV stream” explanation above.

Link to line 121
122 interleaved[2 * frame + 1] = channels[1][start + frame];

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Interleave and finalize the WAV stream” explanation above.

Link to line 122
123 }

Close the surrounding scope, value or argument list in “Interleave and finalize the WAV stream”.

Link to line 123
124 writer.write_frames(&interleaved[..count * 2])?;

Write the active interleaved frame prefix using worker-side I/O; this call is outside audio processing.

Link to line 124
125 }

Close the surrounding scope, value or argument list in “Interleave and finalize the WAV stream”.

Link to line 125
126 writer.finish()?;

Finalize the WAV stream’s bookkeeping before reporting a successful file write.

Link to line 126
127 println!(

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

Link to line 127
128 "Wrote {} ({} frames, stereo PCM16, 48 kHz)",

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Interleave and finalize the WAV stream” explanation above.

Link to line 128
129 path.display(),

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Interleave and finalize the WAV stream” explanation above.

Link to line 129
130 channels[0].len()

Call the displayed operation with these arguments as the next step in “Interleave and finalize the WAV stream”. The chapter describes its role and validation.

Link to line 130
131 );

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

Link to line 131
132 Ok(())

Report successful completion without returning a payload.

Link to line 132
133}

Close the surrounding scope, value or argument list in “Interleave and finalize the WAV stream”.

Link to line 133

Source SHA-256: 10ad44667ffe3f2f4d2348d1f5ef7e882bbf07e736a25ff9b912349d20e202bd

API: ravet_audio_file · ravet_foundation

All tutorial walkthroughs