1use std::sync::Arc;
25
26use anyhow::{Context, Result};
27use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
28use cpal::{Device, Stream, StreamConfig};
29use wavecraft_processors::{
30 OscilloscopeFrameConsumer, OscilloscopeTap, create_oscilloscope_channel,
31};
32use wavecraft_protocol::MeterUpdateNotification;
33
34use super::atomic_params::AtomicParameterBridge;
35use super::ffi_processor::DevAudioProcessor;
36
37const GAIN_MULTIPLIER_MIN: f32 = 0.0;
38const GAIN_MULTIPLIER_MAX: f32 = 2.0;
39const INPUT_GAIN_PARAM_ID: &str = "input_gain_level";
41const OUTPUT_GAIN_PARAM_ID: &str = "output_gain_level";
42
43#[derive(Debug, Clone)]
45pub struct AudioConfig {
46 pub sample_rate: f32,
48 pub buffer_size: u32,
50}
51
52pub struct AudioHandle {
55 _input_stream: Stream,
56 _output_stream: Option<Stream>,
57}
58
59pub struct AudioServer {
62 processor: Box<dyn DevAudioProcessor>,
63 config: AudioConfig,
64 input_device: Device,
65 output_device: Device,
66 input_config: StreamConfig,
67 output_config: StreamConfig,
68 param_bridge: Arc<AtomicParameterBridge>,
69}
70
71impl AudioServer {
72 pub fn new(
75 processor: Box<dyn DevAudioProcessor>,
76 config: AudioConfig,
77 param_bridge: Arc<AtomicParameterBridge>,
78 ) -> Result<Self> {
79 let host = cpal::default_host();
80
81 let input_device = host
83 .default_input_device()
84 .context("No input device available")?;
85 tracing::info!("Using input device: {}", input_device.name()?);
86
87 let supported_input = input_device
88 .default_input_config()
89 .context("Failed to get default input config")?;
90 let input_sample_rate = supported_input.sample_rate().0;
91 tracing::info!("Input sample rate: {} Hz", input_sample_rate);
92 let input_config: StreamConfig = supported_input.into();
93
94 let output_device = host
96 .default_output_device()
97 .context("No output device available")?;
98
99 match output_device.name() {
100 Ok(name) => tracing::info!("Using output device: {}", name),
101 Err(_) => tracing::info!("Using output device: (unnamed)"),
102 }
103
104 let supported_output = output_device
105 .default_output_config()
106 .context("Failed to get default output config")?;
107 let output_sr = supported_output.sample_rate().0;
108 tracing::info!("Output sample rate: {} Hz", output_sr);
109 if output_sr != input_sample_rate {
110 tracing::warn!(
111 "Input/output sample rate mismatch ({} vs {}). \
112 Processing at input rate; output device may resample.",
113 input_sample_rate,
114 output_sr
115 );
116 }
117 let output_config: StreamConfig = supported_output.into();
118
119 Ok(Self {
120 processor,
121 config,
122 input_device,
123 output_device,
124 input_config,
125 output_config,
126 param_bridge,
127 })
128 }
129
130 pub fn start(
138 mut self,
139 ) -> Result<(
140 AudioHandle,
141 rtrb::Consumer<MeterUpdateNotification>,
142 OscilloscopeFrameConsumer,
143 )> {
144 let actual_sample_rate = self.input_config.sample_rate.0 as f32;
146 self.processor.set_sample_rate(actual_sample_rate);
147
148 let mut processor = self.processor;
149 let buffer_size = self.config.buffer_size as usize;
150 let input_channels = self.input_config.channels as usize;
151 let output_channels = self.output_config.channels as usize;
152 let param_bridge = Arc::clone(&self.param_bridge);
153
154 let ring_capacity = buffer_size * 2 * 4;
158 let (mut ring_producer, mut ring_consumer) = rtrb::RingBuffer::new(ring_capacity);
159
160 let (mut meter_producer, meter_consumer) =
165 rtrb::RingBuffer::<MeterUpdateNotification>::new(64);
166 let (oscilloscope_producer, oscilloscope_consumer) = create_oscilloscope_channel(8);
167 let mut oscilloscope_tap = OscilloscopeTap::with_output(oscilloscope_producer);
168 oscilloscope_tap.set_sample_rate_hz(actual_sample_rate);
169
170 let mut frame_counter = 0u64;
171 let mut oscillator_phase = 0.0_f32;
172
173 let mut left_buf = vec![0.0f32; buffer_size];
177 let mut right_buf = vec![0.0f32; buffer_size];
178
179 let mut interleave_buf = vec![0.0f32; buffer_size * 2];
181
182 let input_stream = self
183 .input_device
184 .build_input_stream(
185 &self.input_config,
186 move |data: &[f32], _: &cpal::InputCallbackInfo| {
187 frame_counter += 1;
188
189 let num_samples = data.len() / input_channels.max(1);
190 if num_samples == 0 || input_channels == 0 {
191 return;
192 }
193
194 let actual_samples = num_samples.min(left_buf.len());
195 let left = &mut left_buf[..actual_samples];
196 let right = &mut right_buf[..actual_samples];
197
198 left.fill(0.0);
200 right.fill(0.0);
201
202 for i in 0..actual_samples {
203 left[i] = data[i * input_channels];
204 if input_channels > 1 {
205 right[i] = data[i * input_channels + 1];
206 } else {
207 right[i] = left[i];
208 }
209 }
210
211 {
213 let mut channels: [&mut [f32]; 2] = [left, right];
214 processor.process(&mut channels);
215 }
216
217 apply_output_modifiers(
221 left,
222 right,
223 ¶m_bridge,
224 &mut oscillator_phase,
225 actual_sample_rate,
226 );
227
228 let left = &left_buf[..actual_samples];
230 let right = &right_buf[..actual_samples];
231
232 oscilloscope_tap.capture_stereo(left, right);
234
235 let (peak_left, rms_left) = compute_peak_and_rms(left);
237 let (peak_right, rms_right) = compute_peak_and_rms(right);
238
239 if frame_counter.is_multiple_of(2) {
244 let notification = MeterUpdateNotification {
245 timestamp_us: frame_counter,
246 left_peak: peak_left,
247 left_rms: rms_left,
248 right_peak: peak_right,
249 right_rms: rms_right,
250 };
251 let _ = meter_producer.push(notification);
255 }
256
257 let interleave = &mut interleave_buf[..actual_samples * 2];
261 for i in 0..actual_samples {
262 interleave[i * 2] = left[i];
263 interleave[i * 2 + 1] = right[i];
264 }
265
266 for &sample in interleave.iter() {
268 if ring_producer.push(sample).is_err() {
269 break;
270 }
271 }
272 },
273 |err| {
274 tracing::error!("Audio input stream error: {}", err);
275 },
276 None,
277 )
278 .context("Failed to build input stream")?;
279
280 input_stream
281 .play()
282 .context("Failed to start input stream")?;
283 tracing::info!("Input stream started");
284
285 let output_stream = self
287 .output_device
288 .build_output_stream(
289 &self.output_config,
290 move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
291 if output_channels == 0 {
292 data.fill(0.0);
293 return;
294 }
295
296 for frame in data.chunks_mut(output_channels) {
299 let left = ring_consumer.pop().unwrap_or(0.0);
300 let right = ring_consumer.pop().unwrap_or(0.0);
301
302 if output_channels == 1 {
303 frame[0] = 0.5 * (left + right);
304 continue;
305 }
306
307 frame[0] = left;
308 frame[1] = right;
309
310 for channel in frame.iter_mut().skip(2) {
311 *channel = 0.0;
312 }
313 }
314 },
315 |err| {
316 tracing::error!("Audio output stream error: {}", err);
317 },
318 None,
319 )
320 .context("Failed to build output stream")?;
321
322 output_stream
323 .play()
324 .context("Failed to start output stream")?;
325 tracing::info!("Output stream started");
326
327 tracing::info!("Audio server started in full-duplex (input + output) mode");
328
329 Ok((
330 AudioHandle {
331 _input_stream: input_stream,
332 _output_stream: Some(output_stream),
333 },
334 meter_consumer,
335 oscilloscope_consumer,
336 ))
337 }
338
339 pub fn has_output(&self) -> bool {
341 true
342 }
343}
344
345fn apply_output_modifiers(
346 left: &mut [f32],
347 right: &mut [f32],
348 param_bridge: &AtomicParameterBridge,
349 oscillator_phase: &mut f32,
350 sample_rate: f32,
351) {
352 let input_gain = read_gain_multiplier(param_bridge, INPUT_GAIN_PARAM_ID);
353 let output_gain = read_gain_multiplier(param_bridge, OUTPUT_GAIN_PARAM_ID);
354 let combined_gain = input_gain * output_gain;
355
356 if let Some(enabled) = param_bridge.read("oscillator_enabled")
359 && enabled < 0.5
360 {
361 left.fill(0.0);
362 right.fill(0.0);
363 apply_gain(left, right, combined_gain);
364 return;
365 }
366
367 let oscillator_frequency = param_bridge.read("oscillator_frequency");
370 let oscillator_level = param_bridge.read("oscillator_level");
371
372 if let (Some(frequency), Some(level)) = (oscillator_frequency, oscillator_level) {
373 if !sample_rate.is_finite() || sample_rate <= 0.0 {
374 apply_gain(left, right, combined_gain);
375 return;
376 }
377
378 let clamped_frequency = if frequency.is_finite() {
379 frequency.clamp(20.0, 5000.0)
380 } else {
381 440.0
382 };
383 let clamped_level = if level.is_finite() {
384 level.clamp(0.0, 1.0)
385 } else {
386 0.0
387 };
388
389 let phase_delta = clamped_frequency / sample_rate;
390 let mut phase = if oscillator_phase.is_finite() {
391 *oscillator_phase
392 } else {
393 0.0
394 };
395
396 for (left_sample, right_sample) in left.iter_mut().zip(right.iter_mut()) {
397 let sample = (phase * std::f32::consts::TAU).sin() * clamped_level;
398 *left_sample = sample;
399 *right_sample = sample;
400
401 phase += phase_delta;
402 if phase >= 1.0 {
403 phase -= phase.floor();
404 }
405 }
406
407 *oscillator_phase = phase;
408 }
409
410 apply_gain(left, right, combined_gain);
411}
412
413fn read_gain_multiplier(param_bridge: &AtomicParameterBridge, id: &str) -> f32 {
414 if let Some(value) = param_bridge.read(id)
415 && value.is_finite()
416 {
417 return value.clamp(GAIN_MULTIPLIER_MIN, GAIN_MULTIPLIER_MAX);
418 }
419
420 1.0
421}
422
423fn compute_peak_and_rms(samples: &[f32]) -> (f32, f32) {
424 let peak = samples
425 .iter()
426 .copied()
427 .fold(0.0f32, |acc, sample| acc.max(sample.abs()));
428 let rms =
429 (samples.iter().map(|sample| sample * sample).sum::<f32>() / samples.len() as f32).sqrt();
430
431 (peak, rms)
432}
433
434fn apply_gain(left: &mut [f32], right: &mut [f32], gain: f32) {
435 if (gain - 1.0).abs() <= f32::EPSILON {
436 return;
437 }
438
439 for (left_sample, right_sample) in left.iter_mut().zip(right.iter_mut()) {
440 *left_sample *= gain;
441 *right_sample *= gain;
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::apply_output_modifiers;
448 use crate::audio::atomic_params::AtomicParameterBridge;
449 use wavecraft_protocol::{ParameterInfo, ParameterType};
450
451 fn bridge_with_enabled(default_value: f32) -> AtomicParameterBridge {
452 AtomicParameterBridge::new(&[ParameterInfo {
453 id: "oscillator_enabled".to_string(),
454 name: "Enabled".to_string(),
455 param_type: ParameterType::Float,
456 value: default_value,
457 default: default_value,
458 unit: Some("%".to_string()),
459 min: 0.0,
460 max: 1.0,
461 group: Some("Oscillator".to_string()),
462 }])
463 }
464
465 #[test]
466 fn output_modifiers_mute_when_oscillator_disabled() {
467 let bridge = bridge_with_enabled(1.0);
468 bridge.write("oscillator_enabled", 0.0);
469
470 let mut left = [0.25_f32, -0.5, 0.75];
471 let mut right = [0.2_f32, -0.4, 0.6];
472 let mut phase = 0.0;
473 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
474
475 assert!(left.iter().all(|s| s.abs() <= f32::EPSILON));
476 assert!(right.iter().all(|s| s.abs() <= f32::EPSILON));
477 }
478
479 #[test]
480 fn output_modifiers_keep_signal_when_oscillator_enabled() {
481 let bridge = bridge_with_enabled(1.0);
482
483 let mut left = [0.25_f32, -0.5, 0.75];
484 let mut right = [0.2_f32, -0.4, 0.6];
485 let mut phase = 0.0;
486 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
487
488 assert_eq!(left, [0.25, -0.5, 0.75]);
489 assert_eq!(right, [0.2, -0.4, 0.6]);
490 }
491
492 fn oscillator_bridge(
493 frequency: f32,
494 level: f32,
495 enabled: f32,
496 input_gain_level: f32,
497 output_gain_level: f32,
498 ) -> AtomicParameterBridge {
499 AtomicParameterBridge::new(&[
500 ParameterInfo {
501 id: "oscillator_enabled".to_string(),
502 name: "Enabled".to_string(),
503 param_type: ParameterType::Float,
504 value: enabled,
505 default: enabled,
506 unit: Some("%".to_string()),
507 min: 0.0,
508 max: 1.0,
509 group: Some("Oscillator".to_string()),
510 },
511 ParameterInfo {
512 id: "oscillator_frequency".to_string(),
513 name: "Frequency".to_string(),
514 param_type: ParameterType::Float,
515 value: frequency,
516 default: frequency,
517 min: 20.0,
518 max: 5_000.0,
519 unit: Some("Hz".to_string()),
520 group: Some("Oscillator".to_string()),
521 },
522 ParameterInfo {
523 id: "oscillator_level".to_string(),
524 name: "Level".to_string(),
525 param_type: ParameterType::Float,
526 value: level,
527 default: level,
528 unit: Some("%".to_string()),
529 min: 0.0,
530 max: 1.0,
531 group: Some("Oscillator".to_string()),
532 },
533 ParameterInfo {
534 id: "input_gain_level".to_string(),
535 name: "Level".to_string(),
536 param_type: ParameterType::Float,
537 value: input_gain_level,
538 default: input_gain_level,
539 unit: Some("x".to_string()),
540 min: 0.0,
541 max: 2.0,
542 group: Some("InputGain".to_string()),
543 },
544 ParameterInfo {
545 id: "output_gain_level".to_string(),
546 name: "Level".to_string(),
547 param_type: ParameterType::Float,
548 value: output_gain_level,
549 default: output_gain_level,
550 unit: Some("x".to_string()),
551 min: 0.0,
552 max: 2.0,
553 group: Some("OutputGain".to_string()),
554 },
555 ])
556 }
557
558 #[test]
559 fn output_modifiers_generate_runtime_oscillator_from_frequency_and_level() {
560 let bridge = oscillator_bridge(880.0, 0.75, 1.0, 1.0, 1.0);
561 let mut left = [0.0_f32; 128];
562 let mut right = [0.0_f32; 128];
563 let mut phase = 0.0;
564
565 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
566
567 let peak_left = left
568 .iter()
569 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
570 let peak_right = right
571 .iter()
572 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
573
574 assert!(peak_left > 0.2, "expected audible generated oscillator");
575 assert!(peak_right > 0.2, "expected audible generated oscillator");
576 assert_eq!(left, right, "expected in-phase stereo oscillator output");
577 assert!(phase > 0.0, "phase should advance after generation");
578 }
579
580 #[test]
581 fn output_modifiers_level_zero_produces_silence() {
582 let bridge = oscillator_bridge(440.0, 0.0, 1.0, 1.0, 1.0);
583 let mut left = [0.1_f32; 64];
584 let mut right = [0.1_f32; 64];
585 let mut phase = 0.0;
586
587 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
588
589 assert!(left.iter().all(|s| s.abs() <= f32::EPSILON));
590 assert!(right.iter().all(|s| s.abs() <= f32::EPSILON));
591 }
592
593 #[test]
594 fn output_modifiers_frequency_change_changes_waveform() {
595 let low_freq_bridge = oscillator_bridge(220.0, 0.5, 1.0, 1.0, 1.0);
596 let high_freq_bridge = oscillator_bridge(1760.0, 0.5, 1.0, 1.0, 1.0);
597
598 let mut low_left = [0.0_f32; 256];
599 let mut low_right = [0.0_f32; 256];
600 let mut high_left = [0.0_f32; 256];
601 let mut high_right = [0.0_f32; 256];
602
603 let mut low_phase = 0.0;
604 let mut high_phase = 0.0;
605
606 apply_output_modifiers(
607 &mut low_left,
608 &mut low_right,
609 &low_freq_bridge,
610 &mut low_phase,
611 48_000.0,
612 );
613 apply_output_modifiers(
614 &mut high_left,
615 &mut high_right,
616 &high_freq_bridge,
617 &mut high_phase,
618 48_000.0,
619 );
620
621 assert_ne!(
622 low_left, high_left,
623 "frequency updates should alter waveform"
624 );
625 assert_eq!(low_left, low_right);
626 assert_eq!(high_left, high_right);
627 }
628
629 #[test]
630 fn output_modifiers_apply_input_and_output_gain_levels() {
631 let unity_bridge = oscillator_bridge(880.0, 0.5, 1.0, 1.0, 1.0);
632 let boosted_bridge = oscillator_bridge(880.0, 0.5, 1.0, 1.5, 2.0);
633
634 let mut unity_left = [0.0_f32; 256];
635 let mut unity_right = [0.0_f32; 256];
636 let mut boosted_left = [0.0_f32; 256];
637 let mut boosted_right = [0.0_f32; 256];
638
639 let mut unity_phase = 0.0;
640 let mut boosted_phase = 0.0;
641
642 apply_output_modifiers(
643 &mut unity_left,
644 &mut unity_right,
645 &unity_bridge,
646 &mut unity_phase,
647 48_000.0,
648 );
649 apply_output_modifiers(
650 &mut boosted_left,
651 &mut boosted_right,
652 &boosted_bridge,
653 &mut boosted_phase,
654 48_000.0,
655 );
656
657 let unity_peak = unity_left
658 .iter()
659 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
660 let boosted_peak = boosted_left
661 .iter()
662 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
663
664 assert!(boosted_peak > unity_peak * 2.5);
665 assert_eq!(boosted_left, boosted_right);
666 assert_eq!(unity_left, unity_right);
667 }
668
669 #[test]
670 fn output_modifiers_apply_gain_without_oscillator_params() {
671 let bridge = AtomicParameterBridge::new(&[
672 ParameterInfo {
673 id: "input_gain_level".to_string(),
674 name: "Level".to_string(),
675 param_type: ParameterType::Float,
676 value: 1.5,
677 default: 1.5,
678 unit: Some("x".to_string()),
679 min: 0.0,
680 max: 2.0,
681 group: Some("InputGain".to_string()),
682 },
683 ParameterInfo {
684 id: "output_gain_level".to_string(),
685 name: "Level".to_string(),
686 param_type: ParameterType::Float,
687 value: 1.2,
688 default: 1.2,
689 unit: Some("x".to_string()),
690 min: 0.0,
691 max: 2.0,
692 group: Some("OutputGain".to_string()),
693 },
694 ]);
695
696 let mut left = [0.25_f32, -0.5, 0.75];
697 let mut right = [0.2_f32, -0.4, 0.6];
698 let mut phase = 0.0;
699
700 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
701
702 let expected_gain = 1.5 * 1.2;
703 assert_eq!(
704 left,
705 [
706 0.25 * expected_gain,
707 -0.5 * expected_gain,
708 0.75 * expected_gain
709 ]
710 );
711 assert_eq!(
712 right,
713 [
714 0.2 * expected_gain,
715 -0.4 * expected_gain,
716 0.6 * expected_gain
717 ]
718 );
719 }
720
721 #[test]
722 fn output_modifiers_ignore_compact_legacy_gain_ids() {
723 let bridge = AtomicParameterBridge::new(&[
724 ParameterInfo {
725 id: "inputgain_level".to_string(),
726 name: "Level".to_string(),
727 param_type: ParameterType::Float,
728 value: 0.2,
729 default: 0.2,
730 unit: Some("x".to_string()),
731 min: 0.0,
732 max: 2.0,
733 group: Some("InputGain".to_string()),
734 },
735 ParameterInfo {
736 id: "outputgain_level".to_string(),
737 name: "Level".to_string(),
738 param_type: ParameterType::Float,
739 value: 0.2,
740 default: 0.2,
741 unit: Some("x".to_string()),
742 min: 0.0,
743 max: 2.0,
744 group: Some("OutputGain".to_string()),
745 },
746 ]);
747
748 let mut left = [0.5_f32; 16];
749 let mut right = [0.5_f32; 16];
750 let mut phase = 0.0;
751
752 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
753
754 let expected = 0.5;
756 assert!(left.iter().all(|sample| (*sample - expected).abs() < 1e-6));
757 assert!(right.iter().all(|sample| (*sample - expected).abs() < 1e-6));
758 }
759
760 #[test]
761 fn output_modifiers_ignore_legacy_snake_case_gain_suffix_ids() {
762 let bridge = AtomicParameterBridge::new(&[
763 ParameterInfo {
764 id: "input_gain_gain".to_string(),
765 name: "Gain".to_string(),
766 param_type: ParameterType::Float,
767 value: 0.2,
768 default: 0.2,
769 unit: Some("x".to_string()),
770 min: 0.0,
771 max: 2.0,
772 group: Some("InputGain".to_string()),
773 },
774 ParameterInfo {
775 id: "output_gain_gain".to_string(),
776 name: "Gain".to_string(),
777 param_type: ParameterType::Float,
778 value: 0.2,
779 default: 0.2,
780 unit: Some("x".to_string()),
781 min: 0.0,
782 max: 2.0,
783 group: Some("OutputGain".to_string()),
784 },
785 ]);
786
787 let mut left = [0.5_f32; 16];
788 let mut right = [0.5_f32; 16];
789 let mut phase = 0.0;
790
791 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
792
793 let expected = 0.5;
795 assert!(left.iter().all(|sample| (*sample - expected).abs() < 1e-6));
796 assert!(right.iter().all(|sample| (*sample - expected).abs() < 1e-6));
797 }
798
799 #[test]
800 fn output_modifiers_use_canonical_ids_even_when_legacy_variants_exist() {
801 let bridge = AtomicParameterBridge::new(&[
802 ParameterInfo {
803 id: "input_gain_level".to_string(),
804 name: "Level".to_string(),
805 param_type: ParameterType::Float,
806 value: 1.6,
807 default: 1.6,
808 unit: Some("x".to_string()),
809 min: 0.0,
810 max: 2.0,
811 group: Some("InputGain".to_string()),
812 },
813 ParameterInfo {
814 id: "inputgain_level".to_string(),
815 name: "Level".to_string(),
816 param_type: ParameterType::Float,
817 value: 0.4,
818 default: 0.4,
819 unit: Some("x".to_string()),
820 min: 0.0,
821 max: 2.0,
822 group: Some("InputGain".to_string()),
823 },
824 ParameterInfo {
825 id: "output_gain_level".to_string(),
826 name: "Level".to_string(),
827 param_type: ParameterType::Float,
828 value: 1.0,
829 default: 1.0,
830 unit: Some("x".to_string()),
831 min: 0.0,
832 max: 2.0,
833 group: Some("OutputGain".to_string()),
834 },
835 ]);
836
837 let mut left = [0.5_f32; 8];
838 let mut right = [0.5_f32; 8];
839 let mut phase = 0.0;
840
841 apply_output_modifiers(&mut left, &mut right, &bridge, &mut phase, 48_000.0);
842
843 let expected = 0.5 * 1.6;
845 assert!(left.iter().all(|sample| (*sample - expected).abs() < 1e-6));
846 assert!(right.iter().all(|sample| (*sample - expected).abs() < 1e-6));
847 }
848}