AUDIO
Source snapshot · 12 October 2026

Portable stereo delay processor

examples/tutorials/src/delay.rs · 191 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 --test delay --no-default-features --locked
Chapters
  1. Control types and physical bounds lines 1–22
  2. Reject invalid physical values lines 23–28
  3. Own prepared delay state lines 29–44
  4. Construct an inactive owner lines 45–54
  5. Validate complete control patches before mutation lines 55–73
  6. Apply only recognized parameter IDs lines 74–83
  7. Prepare two seconds of history per channel lines 84–110
  8. Validate a block and its entire control stream lines 111–138
  9. Apply automation before each sample lines 139–153
  10. Advance smoothers and read both rings lines 154–159
  11. Write stereo or cross-channel feedback lines 160–163
  12. Choose dry bypass or the wet/dry blend lines 164–173
  13. Reset history without reallocating lines 174–191

Control types and physical bounds

Foundation supplies lifecycle and event validation; signal supplies delay rings and smoothers. DEFAULT_VALUES and RANGES use the same five-element order. The dedicated control error has Display and Error implementations so setup can propagate it.

1//! Prepared stereo teaching delay built from RAVET delay lines and smoothers.

Source note: Prepared stereo teaching delay built from RAVET delay lines and smoothers.

Link to line 1
2use ravet_foundation::{

Bring these module/type names into scope for “Control types and physical bounds”; the surrounding chapter explains their responsibilities.

Link to line 2
3 AudioLayout, EventData, EventError, PrepareError, ProcessBlock, ProcessConfig, ProcessError,

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

Link to line 3
4 ProcessStatus, Processor, Tail,

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

Link to line 4
5};

Close the surrounding scope, value or argument list in “Control types and physical bounds”.

Link to line 5
6use ravet_signal::{DelayLine, LinearSmoother, smoothing_samples};

Bring these module/type names into scope for “Control types and physical bounds”; the surrounding chapter explains their responsibilities.

Link to line 6
7

Blank line separating the surrounding statements.

Link to line 7
8/// Physical controls: time (ms), feedback, wet mix, bypass and ping-pong.

Source note: Physical controls: time (ms), feedback, wet mix, bypass and ping-pong.

Link to line 8
9pub const DEFAULT_VALUES: [f32; 5] = [375., 0.45, 0.35, 0., 0.];

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
10/// Inclusive physical bounds; bypass and ping-pong accept only zero or one.

Source note: Inclusive physical bounds; bypass and ping-pong accept only zero or one.

Link to line 10
11pub const RANGES: [(f32, f32); 5] = [(1., 2000.), (0., 0.95), (0., 1.), (0., 1.), (0., 1.)];

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
12

Blank line separating the surrounding statements.

Link to line 12
13/// Reject invalid controls without changing requested values or ramps.

Source note: Reject invalid controls without changing requested values or ramps.

Link to line 13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]

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 14
15pub struct InvalidDelayControl;

Declare the state/type or implementation used by this chapter. Foundation supplies lifecycle and event validation; signal supplies delay rings and smoothers. DEFAULT_VALUES and RANGES use the same five-element order. The dedicated control error has Display and Error implementations so setup can propagate it.

Link to line 15
16impl std::fmt::Display for InvalidDelayControl {

Declare the state/type or implementation used by this chapter. Foundation supplies lifecycle and event validation; signal supplies delay rings and smoothers. DEFAULT_VALUES and RANGES use the same five-element order. The dedicated control error has Display and Error implementations so setup can propagate it.

Link to line 16
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

Define fmt. Foundation supplies lifecycle and event validation; signal supplies delay rings and smoothers. DEFAULT_VALUES and RANGES use the same five-element order. The dedicated control error has Display and Error implementations so setup can propagate it.

Link to line 17
18 f.write_str("invalid delay control")

Call the displayed operation with these arguments as the next step in “Control types and physical bounds”. The chapter describes its role and validation.

Link to line 18
19 }

Close the surrounding scope, value or argument list in “Control types and physical bounds”.

Link to line 19
20}

Close the surrounding scope, value or argument list in “Control types and physical bounds”.

Link to line 20
21impl std::error::Error for InvalidDelayControl {}

Declare the state/type or implementation used by this chapter. Foundation supplies lifecycle and event validation; signal supplies delay rings and smoothers. DEFAULT_VALUES and RANGES use the same five-element order. The dedicated control error has Display and Error implementations so setup can propagate it.

Link to line 21
22

Blank line separating the surrounding statements.

Link to line 22

Reject invalid physical values

Every control must be finite and within its inclusive range. The first three controls accept continuous values; bypass and ping-pong additionally require exactly zero or one. Validation is reused by whole-patch edits and event preflight.

23fn valid(index: usize, value: f32) -> bool {

Define valid. Every control must be finite and within its inclusive range. The first three controls accept continuous values; bypass and ping-pong additionally require exactly zero or one. Validation is reused by whole-patch edits and event preflight.

Link to line 23
24 value.is_finite()

Reject NaN and either infinity before consulting the control’s physical range.

Link to line 24
25 && (RANGES[index].0..=RANGES[index].1).contains(&value)

Check the value against both inclusive bounds for this control index.

Link to line 25
26 && (index < 3 || value == 0. || value == 1.)

Continuous controls occupy indices 0–2. Bypass and ping-pong must be exactly zero or one.

Link to line 26
27}

Close the surrounding scope, value or argument list in “Reject invalid physical values”.

Link to line 27
28

Blank line separating the surrounding statements.

Link to line 28

Own prepared delay state

Store an optional active configuration, two optional f64 delay lines, the latest physical values and four smoothers. The documentation states supported rates/layouts/capacities, ignored events and the absence of a limiter or tempo sync.

29/// Stereo f32 effect with two seconds of prepared storage per channel.

Source note: Stereo f32 effect with two seconds of prepared storage per channel.

Link to line 29
30///

Source note:

Link to line 30
31/// Supports mono/stereo, 8..192 kHz, up to 1024 frames and 128 input events.

Source note: Supports mono/stereo, 8..192 kHz, up to 1024 frames and 128 input events.

Link to line 31
32/// IDs 1..5 correspond to [`DEFAULT_VALUES`]. Known parameter values are

Source note: IDs 1..5 correspond to [`DEFAULT_VALUES`]. Known parameter values are

Link to line 32
33/// preflighted before any history changes; other events are ignored. Time glides

Source note: preflighted before any history changes; other events are ignored. Time glides

Link to line 33
34/// over 20 ms (with a pitch change); feedback, mix and ping-pong routing ramp over

Source note: over 20 ms (with a pitch change); feedback, mix and ping-pong routing ramp over

Link to line 34
35/// 5 ms. Bypass is immediate dry output while the delay continues internally.

Source note: 5 ms. Bypass is immediate dry output while the delay continues internally.

Link to line 35
36/// Reset clears history and snaps to requested controls without allocating.

Source note: Reset clears history and snaps to requested controls without allocating.

Link to line 36
37/// Preparation allocates two f64 delay lines; processing allocates nothing.

Source note: Preparation allocates two f64 delay lines; processing allocates nothing.

Link to line 37
38/// Nonfinite input samples become zero. There is no limiter or tempo sync.

Source note: Nonfinite input samples become zero. There is no limiter or tempo sync.

Link to line 38
39pub struct DelayProcessor {

Declare the state/type or implementation used by this chapter. Store an optional active configuration, two optional f64 delay lines, the latest physical values and four smoothers. The documentation states supported rates/layouts/capacities, ignored events and the absence of a limiter or tempo sync.

Link to line 39
40 config: Option<ProcessConfig>,

Processing limits. An optional configuration in processor-owned state is None until preparation succeeds.

Link to line 40
41 lines: Option<[DelayLine; 2]>,

The two allocated delay rings, present only after preparation.

Link to line 41
42 values: [f32; 5],

Requested physical controls in the order documented for this processor.

Link to line 42
43 smooth: [LinearSmoother; 4],

Four sample-counted ramps; the delay’s bypass control is excluded.

Link to line 43
44}

Close the surrounding scope, value or argument list in “Own prepared delay state”.

Link to line 44

Construct an inactive owner

No delay history is allocated by Default. Initial smoother slots map time, feedback, mix and ping-pong indices [0,1,2,4]; bypass is deliberately excluded. Their zero ramp durations are replaced during prepare.

45impl Default for DelayProcessor {

Declare the state/type or implementation used by this chapter. No delay history is allocated by Default. Initial smoother slots map time, feedback, mix and ping-pong indices [0,1,2,4]; bypass is deliberately excluded. Their zero ramp durations are replaced during prepare.

Link to line 45
46 fn default() -> Self {

Define default. No delay history is allocated by Default. Initial smoother slots map time, feedback, mix and ping-pong indices [0,1,2,4]; bypass is deliberately excluded. Their zero ramp durations are replaced during prepare.

Link to line 46
47 Self {

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Construct an inactive owner” explanation above.

Link to line 47
48 config: None,

Processing limits. An optional configuration in processor-owned state is None until preparation succeeds.

Link to line 48
49 lines: None,

The two allocated delay rings, present only after preparation.

Link to line 49
50 values: DEFAULT_VALUES,

Requested physical controls in the order documented for this processor.

Link to line 50
51 smooth: [0, 1, 2, 4].map(|i| LinearSmoother::new(DEFAULT_VALUES[i], 0).unwrap()),

Four sample-counted ramps; the delay’s bypass control is excluded.

Link to line 51
52 }

Close the surrounding scope, value or argument list in “Construct an inactive owner”.

Link to line 52
53 }

Close the surrounding scope, value or argument list in “Construct an inactive owner”.

Link to line 53
54}

Close the surrounding scope, value or argument list in “Construct an inactive owner”.

Link to line 54

Validate complete control patches before mutation

set_controls scans all five values before changing values or targets, then updates the four corresponding ramps. controls returns requested values, which may differ from effective values while smoothing.

55impl DelayProcessor {

Declare the state/type or implementation used by this chapter. set_controls scans all five values before changing values or targets, then updates the four corresponding ramps. controls returns requested values, which may differ from effective values while smoothing.

Link to line 55
56 /// Set the complete physical patch transactionally, including while inactive.

Source note: Set the complete physical patch transactionally, including while inactive.

Link to line 56
57 /// Prepared changes begin ramps on the next processed sample.

Source note: Prepared changes begin ramps on the next processed sample.

Link to line 57
58 pub fn set_controls(&mut self, values: [f32; 5]) -> Result<(), InvalidDelayControl> {

Define set_controls. set_controls scans all five values before changing values or targets, then updates the four corresponding ramps. controls returns requested values, which may differ from effective values while smoothing.

Link to line 58
59 if values.iter().enumerate().any(|(i, v)| !valid(i, *v)) {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Validate complete control patches before mutation”.

Link to line 59
60 return Err(InvalidDelayControl);

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

Link to line 60
61 }

Close the surrounding scope, value or argument list in “Validate complete control patches before mutation”.

Link to line 61
62 self.values = values;

Commit the validated whole control array. Validation above completes before this assignment.

Link to line 62
63 for (smoother, index) in self.smooth.iter_mut().zip([0, 1, 2, 4]) {

Pair the four smoothers with time, feedback, mix and ping-pong. Bypass at index 3 remains immediate.

Link to line 63
64 smoother

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Validate complete control patches before mutation” explanation above.

Link to line 64
65 .set_target(values[index])

Set the requested smoother target; the effective value reaches it over the prepared sample ramp.

Link to line 65
66 .expect("validated finite value");

Require the invariant “validated finite value”. expect panics if it is broken; external inputs must be validated before relying on it.

Link to line 66
67 }

Close the surrounding scope, value or argument list in “Validate complete control patches before mutation”.

Link to line 67
68 Ok(())

Report successful completion without returning a payload.

Link to line 68
69 }

Close the surrounding scope, value or argument list in “Validate complete control patches before mutation”.

Link to line 69
70 /// Latest requested physical controls, independently of active ramps.

Source note: Latest requested physical controls, independently of active ramps.

Link to line 70
71 pub fn controls(&self) -> [f32; 5] {

Define controls. set_controls scans all five values before changing values or targets, then updates the four corresponding ramps. controls returns requested values, which may differ from effective values while smoothing.

Link to line 71
72 self.values

Return a copy of the fixed-size requested control array; this does not advance a smoother.

Link to line 72
73 }

Close the surrounding scope, value or argument list in “Validate complete control patches before mutation”.

Link to line 73

Apply only recognized parameter IDs

Known IDs 1–5 select array entries by subtracting one. Clone the fixed patch, change one entry and reuse set_controls. The expect relies on earlier complete event preflight; unsupported valid events are ignored.

74 fn apply(&mut self, data: EventData) {

Define apply. Known IDs 1–5 select array entries by subtracting one. Clone the fixed patch, change one entry and reuse set_controls. The expect relies on earlier complete event preflight; unsupported valid events are ignored.

Link to line 74
75 if let EventData::ParameterValue { id, value } = data

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Apply only recognized parameter IDs”.

Link to line 75
76 && (1..=5).contains(&id.0)

Limit this control handling to the delay’s recognized parameter IDs 1 through 5.

Link to line 76
77 {

Close the surrounding scope, value or argument list in “Apply only recognized parameter IDs”.

Link to line 77
78 let mut values = self.values;

Copy the requested control array into a mutable candidate. Fixed-array copying does not allocate.

Link to line 78
79 values[id.0 as usize - 1] = value;

Convert the one-based parameter ID into a zero-based physical-control index and replace that candidate entry.

Link to line 79
80 self.set_controls(values).expect("preflighted control");

Require the invariant “preflighted control”. expect panics if it is broken; external inputs must be validated before relying on it.

Link to line 80
81 }

Close the surrounding scope, value or argument list in “Apply only recognized parameter IDs”.

Link to line 81
82 }

Close the surrounding scope, value or argument list in “Apply only recognized parameter IDs”.

Link to line 82
83}

Close the surrounding scope, value or argument list in “Apply only recognized parameter IDs”.

Link to line 83

Prepare two seconds of history per channel

Clear active configuration first. Check 8–192 kHz, at most 1,024 frames/128 input events, mono/stereo effects, no auxiliary buses and no emitted-event capacity. Allocate two rings sized ceil(rate × 2) + 1. Build 20 ms time smoothing and 5 ms other ramps, then mark the owner prepared.

84impl Processor for DelayProcessor {

Declare the state/type or implementation used by this chapter. Clear active configuration first. Check 8–192 kHz, at most 1,024 frames/128 input events, mono/stereo effects, no auxiliary buses and no emitted-event capacity. Allocate two rings sized ceil(rate × 2) + 1. Build 20 ms time smoothing and 5 ms other ramps, then mark the owner prepared.

Link to line 84
85 fn prepare(&mut self, config: ProcessConfig) -> Result<(), PrepareError> {

Define prepare. Clear active configuration first. Check 8–192 kHz, at most 1,024 frames/128 input events, mono/stereo effects, no auxiliary buses and no emitted-event capacity. Allocate two rings sized ceil(rate × 2) + 1. Build 20 ms time smoothing and 5 ms other ramps, then mark the owner prepared.

Link to line 85
86 self.config = None;

Mark the processor inactive before preparation or after deactivation; a failed setup must not retain an apparently valid configuration.

Link to line 86
87 if !(8000.0..=192000.).contains(&config.sample_rate()) {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Prepare two seconds of history per channel”.

Link to line 87
88 return Err(PrepareError::InvalidSampleRate);

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

Link to line 88
89 }

Close the surrounding scope, value or argument list in “Prepare two seconds of history per channel”.

Link to line 89
90 if config.maximum_frames() > 1024 || config.maximum_events() > 128 {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Prepare two seconds of history per channel”.

Link to line 90
91 return Err(PrepareError::InvalidMaximumFrames);

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

Link to line 91
92 }

Close the surrounding scope, value or argument list in “Prepare two seconds of history per channel”.

Link to line 92
93 if ![AudioLayout::MONO, AudioLayout::STEREO].contains(&config.layout())

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Prepare two seconds of history per channel”.

Link to line 93
94 || !config.auxiliary_buses().is_empty()

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

Link to line 94
95 || config.maximum_output_events() != 0

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

Link to line 95
96 {

Close the surrounding scope, value or argument list in “Prepare two seconds of history per channel”.

Link to line 96
97 return Err(PrepareError::UnsupportedLayout);

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

Link to line 97
98 }

Close the surrounding scope, value or argument list in “Prepare two seconds of history per channel”.

Link to line 98
99 let length = (config.sample_rate() * 2.).ceil() as usize + 1;

Reserve two seconds of history rounded upward, plus one sample of interpolation headroom.

Link to line 99
100 self.lines = Some(std::array::from_fn(|_| DelayLine::new(length).unwrap()));

Allocate both delay rings during preparation and store them as prepared state. The checked length makes construction valid.

Link to line 100
101 self.smooth = [0, 1, 2, 4].map(|i| {

Transform each iterator entry, or the successful value of an Option/Result, using the supplied closure.

Link to line 101
102 LinearSmoother::new(

Define or initialize LinearSmoother with the displayed type/value. Its ownership and use are described in “Prepare two seconds of history per channel”.

Link to line 102
103 self.values[i],

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Prepare two seconds of history per channel” explanation above.

Link to line 103
104 smoothing_samples(if i == 0 { 20. } else { 5. }, config.sample_rate()).unwrap(),

Convert a time in milliseconds into a validated, sample-counted ramp duration at the configured rate.

Link to line 104
105 )

Close the surrounding scope, value or argument list in “Prepare two seconds of history per channel”.

Link to line 105
106 .unwrap()

Call the displayed operation with these arguments as the next step in “Prepare two seconds of history per channel”. The chapter describes its role and validation.

Link to line 106
107 });

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

Link to line 107
108 self.config = Some(config);

Mark preparation successful only after the required state and storage have been established.

Link to line 108
109 Ok(())

Report successful completion without returning a payload.

Link to line 109
110 }

Close the surrounding scope, value or argument list in “Prepare two seconds of history per channel”.

Link to line 110

Validate a block and its entire control stream

Require a prepared configuration and valid borrowed block. A fault clears active outputs. Scan all recognized parameter edits before changing history; a bad later value rejects the complete block. Offset-zero events in empty blocks change controls without advancing sample state.

111 fn process(&mut self, block: &mut ProcessBlock<'_, '_, '_>) -> ProcessStatus {

Define process. Require a prepared configuration and valid borrowed block. A fault clears active outputs. Scan all recognized parameter edits before changing history; a bad later value rejects the complete block. Offset-zero events in empty blocks change controls without advancing sample state.

Link to line 111
112 let config = match self

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

Link to line 112
113 .config

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Validate a block and its entire control stream” explanation above.

Link to line 113
114 .ok_or(ProcessError::NotPrepared)

Convert missing preparation into a typed fault instead of processing with unknown limits.

Link to line 114
115 .and_then(|c| c.validate_block(block).map(|()| c))

Validate the borrowed block, then retain its prepared configuration only on success.

Link to line 115
116 {

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 116
117 Ok(config) => config,

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

Link to line 117
118 Err(error) => {

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

Link to line 118
119 block.clear_outputs();

Silence active output samples and discard emitted events. This is the documented failure path, and also clears unselected device channels before routing.

Link to line 119
120 return ProcessStatus::Fault(error);

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

Link to line 120
121 }

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 121
122 };

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 122
123 for (index, event) in block.events.iter().enumerate() {

Scan every input event and retain its index so a later invalid value can be reported precisely.

Link to line 123
124 if let EventData::ParameterValue { id, value } = event.data

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Validate a block and its entire control stream”.

Link to line 124
125 && (1..=5).contains(&id.0)

Limit this control handling to the delay’s recognized parameter IDs 1 through 5.

Link to line 125
126 && !valid(id.0 as usize - 1, value)

Reject a recognized parameter value that violates the delay’s physical bounds or discrete toggle rule.

Link to line 126
127 {

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 127
128 block.clear_outputs();

Silence active output samples and discard emitted events. This is the documented failure path, and also clears unselected device channels before routing.

Link to line 128
129 return ProcessStatus::Fault(ProcessError::Events(EventError::InvalidValue {

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

Link to line 129
130 index,

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

Link to line 130
131 }));

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

Link to line 131
132 }

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 132
133 }

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 133
134 if block.audio.frames() == 0 {

Handle event-only blocks separately: controls or note state can change without rendering a sample.

Link to line 134
135 for event in block.events {

Visit the supplied event slice in order. No temporary event collection is created.

Link to line 135
136 self.apply(event.data);

Apply this validated event payload to the processor’s control or voice state.

Link to line 136
137 }

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 137
138 }

Close the surrounding scope, value or argument list in “Validate a block and its entire control stream”.

Link to line 138

Apply automation before each sample

Track the next event index and consume all edits at the current frame before reading input. Mono input is duplicated into the internal pair. Nonfinite samples become zero; finite samples are promoted to f64.

139 let mut next = 0;

Start the cursor at the first input event; the cursor advances as frame-local edits are consumed.

Link to line 139
140 for frame in 0..block.audio.frames() {

Render each active frame once. This is the outer sample loop, independent of output channel count.

Link to line 140
141 while next < block.events.len() && block.events[next].frame_offset == frame {

Consume all events scheduled for this frame before its sample is computed, preserving their supplied order.

Link to line 141
142 self.apply(block.events[next].data);

Apply the next event before rendering the sample at its exact block-local offset.

Link to line 142
143 next += 1;

Advance the event cursor so the same edit cannot be applied twice.

Link to line 143
144 }

Close the surrounding scope, value or argument list in “Apply automation before each sample”.

Link to line 144
145 let mono = block.audio.input_channels() == 1;

Choose whether both internal delay channels should read the single mono input.

Link to line 145
146 let dry: [f64; 2] = std::array::from_fn(|c| {

Create the current two-channel dry frame in fixed stack storage, with f64 internal arithmetic.

Link to line 146
147 let sample = block.audio.input(if mono { 0 } else { c }).unwrap()[frame];

Read input channel zero for mono, or the matching stereo channel, at the current frame.

Link to line 147
148 if sample.is_finite() {

Keep finite input; the alternate branch replaces NaN or infinity with zero.

Link to line 148
149 f64::from(sample)

Promote the f32 device/sample value into the delay’s f64 internal arithmetic.

Link to line 149
150 } else {

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Apply automation before each sample” explanation above.

Link to line 150
151 0.

Supply this literal numeric entry to the array or argument list being constructed. Its units/addressing follow the containing fixture or configuration.

Link to line 151
152 }

Close the surrounding scope, value or argument list in “Apply automation before each sample”.

Link to line 152
153 });

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

Link to line 153

Advance smoothers and read both rings

Convert the smoothed milliseconds to fractional samples. Advance feedback, mix and routing once for the frame. Read both wet samples before either line is modified, preventing iteration order from changing feedback.

154 let delay = f64::from(self.smooth[0].next_value()) * config.sample_rate() / 1000.;

Advance a sample-counted smoother once. In the stereo delay this happens once per frame, not once per output channel.

Link to line 154
155 let feedback = f64::from(self.smooth[1].next_value());

Advance a sample-counted smoother once. In the stereo delay this happens once per frame, not once per output channel.

Link to line 155
156 let mix = f64::from(self.smooth[2].next_value());

Advance a sample-counted smoother once. In the stereo delay this happens once per frame, not once per output channel.

Link to line 156
157 let cross = f64::from(self.smooth[3].next_value());

Advance a sample-counted smoother once. In the stereo delay this happens once per frame, not once per output channel.

Link to line 157
158 let lines = self.lines.as_mut().expect("prepared delay lines");

Borrow the two prepared rings mutably. The earlier configuration check establishes their existence.

Link to line 158
159 let wet = lines.each_ref().map(|line| line.read(delay));

Read both delayed samples before either ring is written, preserving channel-independent feedback timing.

Link to line 159

Write stereo or cross-channel feedback

Mix the same-channel and opposite-channel delayed samples according to the smoothed cross value, multiply by feedback and add dry input. Write and advance both rings. At cross zero repeats remain independent; at cross one they alternate channels.

160 for c in 0..2 {

Visit the two internal delay channels, even when the external layout is mono.

Link to line 160
161 lines[c].write(dry[c] + feedback * ((1. - cross) * wet[c] + cross * wet[1 - c]));

Blend same-channel and opposite-channel delayed samples for feedback. cross=0 is stereo; cross=1 is ping-pong. Add dry input before writing the ring.

Link to line 161
162 lines[c].advance();

Advance this ring’s write position by one sample after writing the new feedback value.

Link to line 162
163 }

Close the surrounding scope, value or argument list in “Write stereo or cross-channel feedback”.

Link to line 163

Choose dry bypass or the wet/dry blend

Bypass outputs dry input immediately while delay history continues to update. Otherwise use the linear blend (1−mix)×dry + mix×wet. Initialize every active output sample and return Continue after the block.

164 for c in 0..block.audio.output_channels() {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Choose dry bypass or the wet/dry blend”.

Link to line 164
165 block.audio.output_mut(c).unwrap()[frame] = if self.values[3] == 1. {

Test the immediate bypass control. Delay history has already advanced for the frame, so bypass preserves the internal tail.

Link to line 165
166 dry[c] as f32

Output the untouched dry sample for bypass, converting the internal f64 value back to f32.

Link to line 166
167 } else {

Continue the surrounding value or expression with this exact operand. Read it together with the preceding statement and the “Choose dry bypass or the wet/dry blend” explanation above.

Link to line 167
168 ((1. - mix) * dry[c] + mix * wet[c]) as f32

Use a linear dry/wet mix. Conversion to f32 occurs only at the output; this expression is not a limiter.

Link to line 168
169 };

Close the surrounding scope, value or argument list in “Choose dry bypass or the wet/dry blend”.

Link to line 169
170 }

Close the surrounding scope, value or argument list in “Choose dry bypass or the wet/dry blend”.

Link to line 170
171 }

Close the surrounding scope, value or argument list in “Choose dry bypass or the wet/dry blend”.

Link to line 171
172 ProcessStatus::Continue

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

Link to line 172
173 }

Close the surrounding scope, value or argument list in “Choose dry bypass or the wet/dry blend”.

Link to line 173

Reset history without reallocating

Clear each existing ring and snap all smoothers to their latest target. Preserve requested physical controls and prepared configuration. deactivate invokes reset then clears the configuration; tail returns the conservative Infinite declaration.

174 fn reset(&mut self) {

Define reset. Clear each existing ring and snap all smoothers to their latest target. Preserve requested physical controls and prepared configuration. deactivate invokes reset then clears the configuration; tail returns the conservative Infinite declaration.

Link to line 174
175 if let Some(lines) = &mut self.lines {

Evaluate this condition or visit this range in source order. The statements in the following scope implement “Reset history without reallocating”.

Link to line 175
176 for line in lines {

Visit the already allocated rings to clear signal history without replacing their storage.

Link to line 176
177 line.reset();

Clear this delay ring and reset its cursor while retaining its allocated capacity.

Link to line 177
178 }

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 178
179 }

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 179
180 for smoother in &mut self.smooth {

Borrow each of the four smoothers mutably without constructing another collection.

Link to line 180
181 smoother.reset();

Snap this smoother to its requested target and clear any remaining ramp.

Link to line 181
182 }

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 182
183 }

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 183
184 fn deactivate(&mut self) {

Define deactivate. Clear each existing ring and snap all smoothers to their latest target. Preserve requested physical controls and prepared configuration. deactivate invokes reset then clears the configuration; tail returns the conservative Infinite declaration.

Link to line 184
185 self.reset();

Clear the processor’s signal history through its existing reset implementation.

Link to line 185
186 self.config = None;

Mark the processor inactive before preparation or after deactivation; a failed setup must not retain an apparently valid configuration.

Link to line 186
187 }

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 187
188 fn tail(&self) -> Tail {

Define tail. Clear each existing ring and snap all smoothers to their latest target. Preserve requested physical controls and prepared configuration. deactivate invokes reset then clears the configuration; tail returns the conservative Infinite declaration.

Link to line 188
189 Tail::Infinite

Declare a conservative unbounded tail. This is a host scheduling hint, not a promise of endless audible output.

Link to line 189
190 }

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 190
191}

Close the surrounding scope, value or argument list in “Reset history without reallocating”.

Link to line 191

Source SHA-256: 25aa94f0ced5b4f12bd95d73c3a662a298533816f38261c44ce2ad60966e3e12

API: ravet_foundation · ravet_signal

All tutorial walkthroughs