Source browser
examples/signal_filters_checks.hpp
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1/** @file signal_filters_checks.hpp
2 * @brief Independent signal-filter checks for CPU and GPU execution.
3 * Double convolution and direct-form I recurrence provide output oracles for
4 * shaders using float convolution and transposed direct-form II. Frequency
5 * response checks also measure mixed tones after the initial transient.
6 */
7#ifndef SHDR_EXAMPLES_SIGNAL_FILTERS_CHECKS_HPP // Include this example helper only once.
8#define SHDR_EXAMPLES_SIGNAL_FILTERS_CHECKS_HPP // Remember that this example helper is available.
9#include "runner.hpp" // Select CPU execution or the requested graphics profile.
10#include "signal_filters.hpp" // Use the reusable filter designers and pipeline helpers.
11#include <complex> // Evaluate frequency responses with complex arithmetic.
12#include <random> // Create repeatable random fixtures and initial parameters.
13
14namespace shdr_examples { // Keep these example helpers in their own namespace.
15namespace signal_checks { // Keep these example helpers in their own namespace.
16
17inline void require(bool value, const char* message) { // Keep the test checks active in every build configuration.
18 if (!value) // Handle this validation or boundary case explicitly.
19 throw shdr::error(message); // Report the invalid design or failed check clearly.
20} // Finish the require operation.
21template <class Function> void rejects(Function function, const char* message) { // Accept the test function while keeping error checks reusable.
22 bool caught = false; // Remember whether the expected validation error occurs.
23 try { // Run the operation while preserving its failure diagnostic.
24 function(); // Execute the operation whose rejection is being checked.
25 } catch (const shdr::error&) { // Recognize the expected filter validation error.
26 caught = true; // Record that the invalid input was rejected.
27 } // Finish handling this reported failure.
28 require(caught, message); // Require this design property or test invariant.
29} // Finish the rejects operation.
30inline std::complex<double> unit_phase(double frequency, double rate) { // Compute one complex sample of the frequency phase.
31 return std::polar(1.0, -2.0 * signal_detail::pi() * frequency / rate); // Return the unit complex phase for this frequency.
32} // Finish the unit_phase operation.
33inline double fir_gain(const std::vector<float>& coefficients, double frequency, double rate) { // Evaluate FIR gain using complex frequency response.
34 std::complex<double> gain(0, 0), phase(1, 0), step = unit_phase(frequency, rate); // Keep the complex phase and accumulated transfer response.
35 for (float coefficient : coefficients) { // Visit every value needed by this calculation.
36 gain += double(coefficient) * phase; // Add this tap at its complex frequency phase.
37 phase *= step; // Advance to the next tap delay.
38 } // Finish this channel, sample or feature pass.
39 return std::abs(gain); // Return the transfer-function magnitude.
40} // Finish the fir_gain operation.
41inline double biquad_gain(const std::array<float, 5>& coefficients, double frequency, double rate) { // Evaluate the normalized biquad transfer function.
42 const std::complex<double> phase = unit_phase(frequency, rate); // Keep the complex phase and accumulated transfer response.
43 return std::abs( // Return the transfer-function magnitude.
44 (double(coefficients[0]) + double(coefficients[1]) * phase + // Evaluate the feed-forward numerator at this frequency.
45 double(coefficients[2]) * phase * phase) / // Include the second delayed feed-forward coefficient.
46 (1.0 + double(coefficients[3]) * phase + double(coefficients[4]) * phase * phase)); // Divide by the normalized feedback denominator.
47} // Finish the biquad_gain operation.
48inline std::vector<float> fixture(unsigned count, unsigned channels) { // Build separate impulse, step and random test channels.
49 std::vector<float> input(count * channels, 0.0f); // Allocate zero-filled, independent fixture channels.
50 std::mt19937 random(12345u); // Use a fixed seed so every backend receives identical data.
51 std::uniform_real_distribution<float> distribution(-1.0f, 1.0f); // Choose a bounded range for reproducible noise or weights.
52 input[0] = 1.0f; // Put the impulse only in the first channel.
53 for (unsigned i = 0; i < count; ++i) { // Visit every value needed by this calculation.
54 if (channels > 1u) // Keep the step fixture separate from the impulse channel.
55 input[count + i] = 1.0f; // Use a unit step for the second channel.
56 for (unsigned channel = 2u; channel < channels; ++channel) // Process each channel independently.
57 input[channel * count + i] = distribution(random); // Give the remaining channels reproducible random samples.
58 } // Finish this channel, sample or feature pass.
59 return input; // Return the complete channel-major input fixture.
60} // Finish the fixture operation.
61inline std::vector<double> fir_reference(const std::vector<float>& input, // Convolve with double accumulation for the independent oracle.
62 unsigned count, // Receive the validated sample, channel or coefficient count.
63 unsigned channels, // Receive the validated sample, channel or coefficient count.
64 const std::vector<float>& coefficients) { // Use the caller-provided finite FIR coefficients.
65 std::vector<double> output(input.size(), 0.0); // Allocate independent double oracle outputs.
66 for (unsigned channel = 0; channel < channels; ++channel) // Process each channel independently.
67 for (unsigned i = 0; i < count; ++i) // Visit every value needed by this calculation.
68 for (unsigned tap = 0; tap < coefficients.size() && tap <= i; ++tap) // Use only causal taps that lie inside this batch.
69 output[channel * count + i] += // Accumulate this causal tap in double precision.
70 double(coefficients[tap]) * input[channel * count + i - tap]; // Multiply this causal sample and coefficient in double precision.
71 return output; // Return every independently computed filtered sample.
72} // Finish the fir_reference operation.
73inline std::vector<double> biquad_reference(const std::vector<float>& input, // Use a direct-form I double recurrence as an independent oracle.
74 unsigned count, // Receive the validated sample, channel or coefficient count.
75 unsigned channels, // Receive the validated sample, channel or coefficient count.
76 const std::array<float, 5>& coefficients, // Use five normalized biquad coefficients.
77 std::vector<shdr::glsl::vec2>* final_state = 0) { // Keep the independent expected recurrence pair for each channel.
78 std::vector<double> output(input.size()); // Allocate independent double oracle outputs.
79 if (final_state) // Record reference state only when the caller asks for it.
80 final_state->resize(channels); // Reserve one final recurrence pair per channel.
81 for (unsigned channel = 0; channel < channels; ++channel) { // Process each channel independently.
82 double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0; // Start the independent direct-form I recurrence with zero history.
83 for (unsigned i = 0; i < count; ++i) { // Visit every value needed by this calculation.
84 const double x = input[channel * count + i]; // Read this input sample for the double recurrence.
85 const double y = coefficients[0] * x + coefficients[1] * x1 + coefficients[2] * x2 - // Apply the independent direct-form I difference equation.
86 coefficients[3] * y1 - coefficients[4] * y2; // Subtract the two normalized feedback contributions.
87 output[channel * count + i] = y; // Keep this independently computed filtered sample.
88 x2 = x1; // Shift the input history by one sample.
89 x1 = x; // Remember the current input for the next sample.
90 y2 = y1; // Shift the output history by one sample.
91 y1 = y; // Remember the current output for the next sample.
92 } // Finish this channel, sample or feature pass.
93 if (final_state) // Record reference state only when the caller asks for it.
94 (*final_state)[channel] = // Convert direct-form I history into transposed recurrence state.
95 shdr::glsl::vec2(float(coefficients[1] * x1 + coefficients[2] * x2 - // Convert the last input and output histories into the first state value.
96 coefficients[3] * y1 - coefficients[4] * y2), // Include the feedback history in the first state value.
97 float(coefficients[2] * x1 - coefficients[4] * y1)); // Convert the double reference value for the float comparison.
98 } // Finish this channel, sample or feature pass.
99 return output; // Return every independently computed filtered sample.
100} // Finish the biquad_reference operation.
101inline void compare(const shdr::buffer<float>& actual, // Check every filtered output against the double oracle.
102 const std::vector<double>& expected, // Receive the independently accumulated double outputs.
103 const char* label, // Give failures a descriptive check name.
104 float tolerance = 0.00003f) { // Allow only the stated float error in the result check.
105 const float* output = actual.data(); // Read the resource after any required GPU result synchronization.
106 for (unsigned i = 0; i < expected.size(); ++i) // Visit every value needed by this calculation.
107 require_close(output[i], static_cast<float>(expected[i]), label, tolerance); // Compare this result with its independent expected value.
108} // Finish the compare operation.
109inline double tone_amplitude(const shdr::buffer<float>& samples, // Measure a tone by sine and cosine projection.
110 unsigned begin, // Begin assembling at the first sample in each channel.
111 unsigned count, // Receive the validated sample, channel or coefficient count.
112 double frequency, // Receive the tone, cutoff or center frequency.
113 double rate) { // Receive the positive sampling rate in frequency units.
114 const float* values = samples.data(); // Read the resource after any required GPU result synchronization.
115 double real = 0.0, imaginary = 0.0; // Start both tone projection accumulators at zero.
116 for (unsigned i = 0; i < count; ++i) { // Visit every value needed by this calculation.
117 const double angle = 2.0 * signal_detail::pi() * frequency * (begin + i) / rate; // Compute the absolute phase of this tone sample.
118 real += values[begin + i] * std::cos(angle); // Accumulate the cosine projection of the filtered signal.
119 imaginary += values[begin + i] * std::sin(angle); // Accumulate the sine projection of the filtered signal.
120 } // Finish this channel, sample or feature pass.
121 return 2.0 * std::sqrt(real * real + imaginary * imaginary) / count; // Build separate impulse, step and random test channels.
122} // Finish the tone_amplitude operation.
123
124inline void coefficient_checks() { // Check designer gains and invalid parameter handling.
125 const filter_mode modes[] = {filter_mode::low_pass, // List every supported filter shape exercised by the checks.
126 filter_mode::high_pass, // Include the high-pass response in this group of checks.
127 filter_mode::band_pass, // Include the band-pass response in this group of checks.
128 filter_mode::band_stop}; // Include the band-stop response in this group of checks.
129 for (filter_mode mode : modes) { // Check every supported filter shape.
130 const bool band = mode == filter_mode::band_pass || mode == filter_mode::band_stop; // Use two cutoff edges only for band-selective filters.
131 const std::vector<float> fir = // Design the FIR coefficients used by this independent check.
132 fir_coefficients(mode, 48000.0, band ? 3000.0 : 4000.0, band ? 9000.0 : 0.0, 65u); // Design the causal FIR coefficients for this configuration.
133 for (unsigned i = 0; i < fir.size(); ++i) // Visit every value needed by this calculation.
134 require_close(fir[i], fir[fir.size() - 1u - i], "FIR symmetry", 0.0000001f); // Compare this result with its independent expected value.
135 const double dc = fir_gain(fir, 0.0, 48000.0); // Measure the FIR response at zero frequency.
136 require_close(float(dc), // Compare this result with its independent expected value.
137 mode == filter_mode::low_pass || mode == filter_mode::band_stop ? 1.0f : 0.0f, // Expect unit DC gain for low-pass and band-stop designs.
138 "FIR DC gain", // Identify this check clearly in any failure message.
139 0.000002f); // Apply the declared float error allowance.
140 if (mode == filter_mode::low_pass) // Choose the coefficient rule for this filter shape.
141 require(fir_gain(fir, 1000, 48000) > .98 && fir_gain(fir, 12000, 48000) < .015, // Require this design property or test invariant.
142 "low-pass FIR selectivity"); // Identify this check clearly in any failure message.
143 if (mode == filter_mode::high_pass) // Choose the coefficient rule for this filter shape.
144 require(fir_gain(fir, 1000, 48000) < .015 && fir_gain(fir, 12000, 48000) > .98, // Require this design property or test invariant.
145 "high-pass FIR selectivity"); // Identify this check clearly in any failure message.
146 if (mode == filter_mode::band_pass) // Choose the coefficient rule for this filter shape.
147 require(fir_gain(fir, 6000, 48000) > .99 && fir_gain(fir, 1000, 48000) < .015 && // Require this design property or test invariant.
148 fir_gain(fir, 16000, 48000) < .015, // Require attenuation above the band-pass upper edge.
149 "band-pass FIR selectivity"); // Identify this check clearly in any failure message.
150 if (mode == filter_mode::band_stop) // Choose the coefficient rule for this filter shape.
151 require(fir_gain(fir, 6000, 48000) < .000002 && fir_gain(fir, 1000, 48000) > .98 && // Require this design property or test invariant.
152 fir_gain(fir, 16000, 48000) > .98, // Require gain above the rejected band-stop interval.
153 "band-stop FIR selectivity"); // Identify this check clearly in any failure message.
154 const std::array<float, 5> biquad = biquad_coefficients(mode, 48000.0, 6000.0); // Design normalized coefficients for this biquad check.
155 if (mode == filter_mode::low_pass) // Choose the coefficient rule for this filter shape.
156 require_close( // Compare this result with its independent expected value.
157 float(biquad_gain(biquad, 0, 48000)), 1.0f, "biquad low-pass DC", 0.000002f); // Convert the double reference value for the float comparison.
158 if (mode == filter_mode::high_pass) // Choose the coefficient rule for this filter shape.
159 require_close(float(biquad_gain(biquad, 24000, 48000)), // Compare this result with its independent expected value.
160 1.0f, // Expect unit gain at this normalization frequency.
161 "biquad high-pass Nyquist", // Identify this check clearly in any failure message.
162 0.000002f); // Apply the declared float error allowance.
163 if (mode == filter_mode::band_pass) // Choose the coefficient rule for this filter shape.
164 require_close(float(biquad_gain(biquad, 6000, 48000)), // Compare this result with its independent expected value.
165 1.0f, // Expect unit gain at this normalization frequency.
166 "biquad band-pass center", // Identify this check clearly in any failure message.
167 0.000002f); // Apply the declared float error allowance.
168 if (mode == filter_mode::band_stop) // Choose the coefficient rule for this filter shape.
169 require(biquad_gain(biquad, 6000, 48000) < .000002, "biquad notch center"); // Require this design property or test invariant.
170 } // Finish this channel, sample or feature pass.
171 const std::vector<float> average = // Design the FIR coefficients used by this independent check.
172 fir_coefficients(filter_mode::moving_average, 48000, 0, 0, 5); // Design the five equal weights used by the average check.
173 for (float value : average) // Inspect each stored float coefficient.
174 require_close(value, 0.2f, "moving-average coefficients", 0.0f); // Compare this result with its independent expected value.
175 rejects( // Confirm that invalid configuration is rejected before dispatch.
176 [] { // Define one invalid configuration to test.
177 fir_coefficients(filter_mode::low_pass, 48000, 4000, 0, 4); // Try an even tap count that the designer must reject.
178 }, // Finish the invalid-configuration test operation.
179 "even FIR taps accepted"); // Identify this check clearly in any failure message.
180 rejects( // Confirm that invalid configuration is rejected before dispatch.
181 [] { // Define one invalid configuration to test.
182 fir_coefficients(filter_mode::low_pass, 0, 4000); // Try an invalid zero sample rate.
183 }, // Finish the invalid-configuration test operation.
184 "zero sample rate accepted"); // Identify this check clearly in any failure message.
185 rejects( // Confirm that invalid configuration is rejected before dispatch.
186 [] { // Define one invalid configuration to test.
187 fir_coefficients(filter_mode::low_pass, 48000, 24000); // Try a cutoff at Nyquist, outside the supported open interval.
188 }, // Finish the invalid-configuration test operation.
189 "Nyquist cutoff accepted"); // Identify this check clearly in any failure message.
190 rejects( // Confirm that invalid configuration is rejected before dispatch.
191 [] { // Define one invalid configuration to test.
192 fir_coefficients(filter_mode::band_pass, 48000, 9000, 3000); // Try band edges in the wrong order.
193 }, // Finish the invalid-configuration test operation.
194 "descending band edges accepted"); // Identify this check clearly in any failure message.
195 rejects( // Confirm that invalid configuration is rejected before dispatch.
196 [] { // Define one invalid configuration to test.
197 biquad_coefficients(filter_mode::low_pass, 48000, 4000, 0); // Exercise the positive-quality validation with a zero value.
198 }, // Finish the invalid-configuration test operation.
199 "zero Q accepted"); // Identify this check clearly in any failure message.
200 rejects( // Confirm that invalid configuration is rejected before dispatch.
201 [] { // Define one invalid configuration to test.
202 biquad_coefficients(filter_mode::low_pass, 48000, 1e-20); // Try a frequency whose rounded feedback loses strict stability.
203 }, // Finish the invalid-configuration test operation.
204 "unstable rounded biquad accepted"); // Identify this check clearly in any failure message.
205} // Finish the coefficient_checks operation.
206
207inline void fir_outputs(runner& run) { // Check causal FIR outputs and measured mixed-tone gains.
208 const unsigned count = 257, channels = 3; // Choose a non-workgroup-aligned sample count and separate channels.
209 const std::vector<float> source = fixture(count, channels); // Keep the shared impulse, step and seeded random inputs.
210 shdr::buffer<float> input(source.size()), output(source.size()); // Allocate independent float resources for this processing check.
211 std::copy(source.begin(), source.end(), input.data()); // Copy these host values into the shared resource.
212 const filter_mode modes[] = {filter_mode::low_pass, // List every supported filter shape exercised by the checks.
213 filter_mode::high_pass, // Include the high-pass response in this group of checks.
214 filter_mode::band_pass, // Include the band-pass response in this group of checks.
215 filter_mode::band_stop, // Include the band-stop response in this group of checks.
216 filter_mode::moving_average}; // Include equal-weight moving-average filtering.
217 for (filter_mode mode : modes) { // Check every supported filter shape.
218 const bool band = mode == filter_mode::band_pass || mode == filter_mode::band_stop; // Use two cutoff edges only for band-selective filters.
219 const std::vector<float> coefficients = // Design the FIR coefficients used by this independent check.
220 fir_coefficients(mode, 48000, band ? 3000 : 4000, band ? 9000 : 0, 65); // Design the causal FIR coefficients for this configuration.
221 fir_pipeline filter(input, output, count, channels, coefficients); // Connect the FIR check to its designed coefficients.
222 run.run(filter.processing_pipeline()); // Execute the linked stages on the selected backend.
223 compare(output, // Compare every filtered sample with the independent oracle.
224 fir_reference(source, count, channels, coefficients), // Supply the independently accumulated causal convolution.
225 "FIR impulse/step/random oracle"); // Identify this check clearly in any failure message.
226 run.run(filter.processing_pipeline()); // Execute the linked stages on the selected backend.
227 compare( // Compare every filtered sample with the independent oracle.
228 output, fir_reference(source, count, channels, coefficients), "FIR batch prefix reset"); // Verify that another FIR run starts with a fresh zero prefix.
229 rejects( // Confirm that invalid configuration is rejected before dispatch.
230 [&] { // Use the existing resources to test invalid rebinding.
231 filter.bind(input, input, count); // Try unsafe input and output storage aliasing.
232 }, // Finish the invalid-configuration test operation.
233 "FIR aliased storage accepted"); // Identify this check clearly in any failure message.
234 rejects( // Confirm that invalid configuration is rejected before dispatch.
235 [&] { // Use the existing resources to test invalid rebinding.
236 filter.bind(input, output, count + 1u); // Try a count that exceeds the allocated channel capacity.
237 }, // Finish the invalid-configuration test operation.
238 "undersized FIR storage accepted"); // Identify this check clearly in any failure message.
239 } // Finish this channel, sample or feature pass.
240 const unsigned tone_count = 5312; // Reserve a steady-state tone window after the filter transient.
241 shdr::buffer<float> tones(tone_count), filtered(tone_count); // Allocate independent float resources for this processing check.
242 const double frequencies[3] = {1000, 6000, 12000}, amplitudes[3] = {.6, .4, .3}; // Mix separated passband and stopband tones with known amplitudes.
243 for (unsigned i = 0; i < tone_count; ++i) { // Visit each temporal sample in the tone mixture.
244 double value = 0.0; // Start this sample or projected-feature accumulator at zero.
245 for (unsigned tone = 0; tone < 3; ++tone) // Visit each frequency in the mixed-tone fixture.
246 value += amplitudes[tone] * // Add this sinusoidal component to the mixed-tone input.
247 std::cos(2.0 * signal_detail::pi() * frequencies[tone] * i / 48000.0); // Compute this sinusoid at the shared sampling rate.
248 tones.data()[i] = static_cast<float>(value); // Store the same tone mixture for every backend.
249 } // Finish this channel, sample or feature pass.
250 for (filter_mode mode : modes) { // Check every supported filter shape.
251 const bool band = mode == filter_mode::band_pass || mode == filter_mode::band_stop; // Use two cutoff edges only for band-selective filters.
252 const std::vector<float> coefficients = // Design the FIR coefficients used by this independent check.
253 fir_coefficients(mode, 48000, band ? 3000 : 4000, band ? 9000 : 0, 65); // Design the causal FIR coefficients for this configuration.
254 fir_pipeline filter(tones, filtered, tone_count, 1, coefficients); // Connect the FIR check to its designed coefficients.
255 run.run(filter.processing_pipeline()); // Execute the linked stages on the selected backend.
256 for (unsigned tone = 0; tone < 3; ++tone) // Visit each frequency in the mixed-tone fixture.
257 require_close( // Compare this result with its independent expected value.
258 float(tone_amplitude(filtered, 512, 4800, frequencies[tone], 48000)), // Convert the double reference value for the float comparison.
259 float(amplitudes[tone] * fir_gain(coefficients, frequencies[tone], 48000)), // Convert the double reference value for the float comparison.
260 "FIR mixed-tone gain", // Identify this check clearly in any failure message.
261 0.00005f); // Apply the declared float error allowance.
262 } // Finish this channel, sample or feature pass.
263} // Finish the fir_outputs operation.
264
265inline void biquad_outputs(runner& run) { // Check biquad outputs, retained state and chunk boundaries.
266 const unsigned count = 257, channels = 3; // Choose a non-workgroup-aligned sample count and separate channels.
267 const std::vector<float> source = fixture(count, channels); // Keep the shared impulse, step and seeded random inputs.
268 shdr::buffer<float> input(source.size()), output(source.size()); // Allocate independent float resources for this processing check.
269 std::copy(source.begin(), source.end(), input.data()); // Copy these host values into the shared resource.
270 const filter_mode modes[] = {filter_mode::low_pass, // List every supported filter shape exercised by the checks.
271 filter_mode::high_pass, // Include the high-pass response in this group of checks.
272 filter_mode::band_pass, // Include the band-pass response in this group of checks.
273 filter_mode::band_stop}; // Include the band-stop response in this group of checks.
274 for (filter_mode mode : modes) { // Check every supported filter shape.
275 const std::array<float, 5> coefficients = biquad_coefficients(mode, 48000, 6000); // Design normalized coefficients for this biquad check.
276 std::vector<shdr::glsl::vec2> expected_state; // Keep one pair of recurrence values for each channel.
277 const std::vector<double> expected = // Compute the independent full-block double output oracle.
278 biquad_reference(source, count, channels, coefficients, &expected_state); // Compute the independent recurrence output and final state.
279 biquad_pipeline filter(input, output, count, channels, coefficients); // Connect the biquad check with fresh zero history.
280 run.run(filter.processing_pipeline()); // Execute the linked stages on the selected backend.
281 compare(output, expected, "biquad direct-form I oracle"); // Compare every filtered sample with the independent oracle.
282 const shdr::glsl::vec2* state = // Read the resource after any required GPU result synchronization.
283 static_cast<const shdr::buffer<shdr::glsl::vec2>&>(filter.state).data(); // Read the retained state after any required GPU synchronization.
284 for (unsigned channel = 0; channel < channels; ++channel) // Process each channel independently.
285 for (unsigned component = 0; component < 2; ++component) // Visit every value needed by this calculation.
286 require_close(state[channel][component], // Compare this result with its independent expected value.
287 expected_state[channel][component], // Supply the independently converted recurrence value.
288 "biquad state oracle", // Identify this check clearly in any failure message.
289 0.00003f); // Apply the declared float error allowance.
290 filter.reset_state(); // Start a fresh filter history for this independent comparison.
291 run.run(filter.processing_pipeline()); // Execute the linked stages on the selected backend.
292 compare(output, expected, "biquad explicit state reset"); // Compare every filtered sample with the independent oracle.
293
294 shdr::buffer<float> chunk_input(channels), chunk_output(channels); // Allocate independent float resources for this processing check.
295 biquad_pipeline streaming(chunk_input, chunk_output, 1, channels, coefficients); // Keep one stateful filter instance across variable chunks.
296 const unsigned chunks[] = {1, 17, 64, 3, 129, 43}; // Use uneven chunks that exercise state across many boundaries.
297 unsigned begin = 0; // Begin assembling at the first sample in each channel.
298 std::vector<double> assembled(source.size()); // Collect streamed chunks in their full-signal positions.
299 unsigned chunk_index = 0; // Begin the CPU and GPU transition pattern at its first chunk.
300 for (unsigned chunk : chunks) { // Process the next variable-size temporal block.
301 chunk_input = shdr::buffer<float>(chunk * channels); // Allocate this chunk with its channel-major stride.
302 chunk_output = shdr::buffer<float>(chunk * channels); // Allocate this chunk with its channel-major stride.
303 for (unsigned channel = 0; channel < channels; ++channel) // Process each channel independently.
304 for (unsigned i = 0; i < chunk; ++i) // Process the next variable-size temporal block.
305 chunk_input.data()[channel * chunk + i] = source[channel * count + begin + i]; // Copy this channel segment into the next temporal chunk.
306 streaming.bind(chunk_input, chunk_output, chunk); // Connect this temporal chunk while retaining previous channel state.
307 if (run.gpu() && chunk_index % 2u) // Use the requested GPU path or explicit CPU transition.
308 streaming.processing_pipeline().run(); // Process this chunk on the CPU using the same retained state.
309 else // Process this chunk through the selected runner backend.
310 run.run(streaming.processing_pipeline()); // Execute the linked stages on the selected backend.
311 const float* actual = static_cast<const shdr::buffer<float>&>(chunk_output).data(); // Read the resource after any required GPU result synchronization.
312 for (unsigned channel = 0; channel < channels; ++channel) // Process each channel independently.
313 for (unsigned i = 0; i < chunk; ++i) // Process the next variable-size temporal block.
314 assembled[channel * count + begin + i] = actual[channel * chunk + i]; // Place the chunk output in the complete reference signal.
315 begin += chunk; // Advance to the next temporal chunk boundary.
316 ++chunk_index; // Alternate the CPU and GPU transition test on the next chunk.
317 } // Finish this channel, sample or feature pass.
318 require(begin == count, "stream fixture did not cover the entire signal"); // Require this design property or test invariant.
319 for (unsigned i = 0; i < assembled.size(); ++i) // Visit every value needed by this calculation.
320 require_close(float(assembled[i]), // Compare this result with its independent expected value.
321 float(expected[i]), // Convert the computed host value to the shader float type.
322 "biquad streaming chunk equivalence", // Identify this check clearly in any failure message.
323 0.00003f); // Apply the declared float error allowance.
324 } // Finish this channel, sample or feature pass.
325 const unsigned tone_count = 5312; // Reserve a steady-state tone window after the filter transient.
326 shdr::buffer<float> tones(tone_count), filtered(tone_count); // Allocate independent float resources for this processing check.
327 const double frequencies[3] = {1000, 6000, 12000}, amplitudes[3] = {.6, .4, .3}; // Mix separated passband and stopband tones with known amplitudes.
328 for (unsigned i = 0; i < tone_count; ++i) { // Visit each temporal sample in the tone mixture.
329 double value = 0.0; // Start this sample or projected-feature accumulator at zero.
330 for (unsigned tone = 0; tone < 3; ++tone) // Visit each frequency in the mixed-tone fixture.
331 value += amplitudes[tone] * // Add this sinusoidal component to the mixed-tone input.
332 std::cos(2.0 * signal_detail::pi() * frequencies[tone] * i / 48000.0); // Compute this sinusoid at the shared sampling rate.
333 tones.data()[i] = static_cast<float>(value); // Store the same tone mixture for every backend.
334 } // Finish this channel, sample or feature pass.
335 for (filter_mode mode : modes) { // Check every supported filter shape.
336 const std::array<float, 5> coefficients = biquad_coefficients(mode, 48000, 6000); // Design normalized coefficients for this biquad check.
337 biquad_pipeline filter(tones, filtered, tone_count, 1, coefficients); // Connect the biquad check with fresh zero history.
338 run.run(filter.processing_pipeline()); // Execute the linked stages on the selected backend.
339 for (unsigned tone = 0; tone < 3; ++tone) // Visit each frequency in the mixed-tone fixture.
340 require_close( // Compare this result with its independent expected value.
341 float(tone_amplitude(filtered, 512, 4800, frequencies[tone], 48000)), // Convert the double reference value for the float comparison.
342 float(amplitudes[tone] * biquad_gain(coefficients, frequencies[tone], 48000)), // Convert the double reference value for the float comparison.
343 "biquad mixed-tone gain", // Identify this check clearly in any failure message.
344 0.00005f); // Apply the declared float error allowance.
345 } // Finish this channel, sample or feature pass.
346} // Finish the biquad_outputs operation.
347} // namespace signal_checks
348
349/** @brief Check designed gains, causal outputs, independent channels and streamed state. */
350inline void check_signal_filters(runner& run) { // Run all filter design and processing checks.
351 signal_checks::coefficient_checks(); // Run the corresponding independent correctness check.
352 signal_checks::fir_outputs(run); // Run the corresponding independent correctness check.
353 signal_checks::biquad_outputs(run); // Run the corresponding independent correctness check.
354 std::cout // Print the verified result or the reason a check failed.
355 << "FIR low/high/band-pass/band-stop/average and stateful biquads: impulse, step, random and mixed tones verified\n"; // Complete the readable result message.
356} // Finish the check_signal_filters operation.
357} // namespace shdr_examples
358#endif // Finish the conditional compilation block.