1#[derive(Debug, Clone)]
2pub struct AudioContext {
3 inner: Arc<Mutex<GraphInner>>,
4}
5
6#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub struct AudioContextOptions {
8 pub sample_rate: Option<u32>,
9 pub latency_hint: Option<AudioContextLatencyHint>,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum AudioContextLatencyHint {
14 Interactive,
15 Balanced,
16 Playback,
17 Seconds(f64),
18}
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct AudioTimestamp {
22 pub context_time: f64,
23 pub performance_time: f64,
24}
25
26#[derive(Debug, Clone)]
27pub struct AudioListener {
28 graph: Arc<Mutex<GraphInner>>,
29}
30
31impl AudioListener {
32 #[must_use]
33 pub fn position_x(&self) -> AudioParamHandle {
34 self.param_handle(ParamKind::PositionX)
35 }
36
37 #[must_use]
38 pub fn position_y(&self) -> AudioParamHandle {
39 self.param_handle(ParamKind::PositionY)
40 }
41
42 #[must_use]
43 pub fn position_z(&self) -> AudioParamHandle {
44 self.param_handle(ParamKind::PositionZ)
45 }
46
47 #[must_use]
48 pub fn forward_x(&self) -> AudioParamHandle {
49 self.param_handle(ParamKind::ForwardX)
50 }
51
52 #[must_use]
53 pub fn forward_y(&self) -> AudioParamHandle {
54 self.param_handle(ParamKind::ForwardY)
55 }
56
57 #[must_use]
58 pub fn forward_z(&self) -> AudioParamHandle {
59 self.param_handle(ParamKind::ForwardZ)
60 }
61
62 #[must_use]
63 pub fn up_x(&self) -> AudioParamHandle {
64 self.param_handle(ParamKind::UpX)
65 }
66
67 #[must_use]
68 pub fn up_y(&self) -> AudioParamHandle {
69 self.param_handle(ParamKind::UpY)
70 }
71
72 #[must_use]
73 pub fn up_z(&self) -> AudioParamHandle {
74 self.param_handle(ParamKind::UpZ)
75 }
76
77 #[must_use]
78 pub fn position_value(&self) -> [f32; 3] {
79 self.graph
80 .lock()
81 .expect("graph mutex poisoned")
82 .listener
83 .position_value()
84 }
85
86 #[must_use]
87 pub fn forward_value(&self) -> [f32; 3] {
88 self.graph
89 .lock()
90 .expect("graph mutex poisoned")
91 .listener
92 .forward_value()
93 }
94
95 #[must_use]
96 pub fn up_value(&self) -> [f32; 3] {
97 self.graph
98 .lock()
99 .expect("graph mutex poisoned")
100 .listener
101 .up_value()
102 }
103
104 fn param_handle(&self, param: ParamKind) -> AudioParamHandle {
105 AudioParamHandle {
106 graph: Arc::clone(&self.graph),
107 id: ParamId {
108 node: LISTENER_PARAM_NODE,
109 param,
110 },
111 }
112 }
113}
114
115impl AudioContext {
116 #[must_use]
117 pub fn new() -> Self {
118 Self::try_new_with_options(AudioContextOptions::default())
119 .expect("default AudioContext options are valid")
120 }
121
122 pub fn try_new_with_sample_rate(sample_rate: u32) -> Result<Self, GraphError> {
123 Self::try_new_with_options(AudioContextOptions {
124 sample_rate: Some(sample_rate),
125 ..Default::default()
126 })
127 }
128
129 pub fn try_new_with_options(options: AudioContextOptions) -> Result<Self, GraphError> {
130 let sample_rate = options.sample_rate.unwrap_or(44_100);
131 if !(3_000..=384_000).contains(&sample_rate) {
132 return Err(GraphError::InvalidAudioBuffer);
133 }
134 if let Some(AudioContextLatencyHint::Seconds(seconds)) = options.latency_hint
135 && (!seconds.is_finite() || seconds < 0.0) {
136 return Err(GraphError::InvalidAutomationValue);
137 }
138 Ok(Self::with_sample_rate_and_destination_channels(
139 sample_rate,
140 2,
141 options.latency_hint,
142 ))
143 }
144
145 fn with_sample_rate_and_destination_channels(
146 sample_rate: u32,
147 destination_channels: usize,
148 latency_hint: Option<AudioContextLatencyHint>,
149 ) -> Self {
150 let mut inner = GraphInner {
151 sample_rate,
152 latency_hint,
153 ..GraphInner::default()
154 };
155 inner
156 .nodes
157 .push(NodeDef::destination(destination_channels.max(1)));
158 Self {
159 inner: Arc::new(Mutex::new(inner)),
160 }
161 }
162
163 #[must_use]
164 pub fn destination(&self) -> AudioDestinationNode {
165 AudioDestinationNode {
166 id: NodeId(0),
167 graph: Arc::clone(&self.inner),
168 }
169 }
170
171 #[must_use]
172 pub fn sample_rate(&self) -> u32 {
173 self.inner.lock().expect("graph mutex poisoned").sample_rate
174 }
175
176 #[must_use]
177 pub fn current_time(&self) -> f64 {
178 self.inner
179 .lock()
180 .expect("graph mutex poisoned")
181 .current_time
182 }
183
184 #[must_use]
185 pub fn get_output_timestamp(&self) -> AudioTimestamp {
186 let current_time = self.current_time();
187 AudioTimestamp {
188 context_time: current_time,
189 performance_time: current_time,
190 }
191 }
192
193 #[must_use]
194 pub fn render_quantum_size(&self) -> usize {
195 RENDER_QUANTUM_SIZE_USIZE
196 }
197
198 #[must_use]
199 pub fn latency_hint(&self) -> Option<AudioContextLatencyHint> {
200 self.inner
201 .lock()
202 .expect("graph mutex poisoned")
203 .latency_hint
204 }
205
206 #[must_use]
207 pub fn state(&self) -> OfflineAudioContextState {
208 self.inner.lock().expect("graph mutex poisoned").state
209 }
210
211 #[must_use]
212 pub fn base_latency(&self) -> f64 {
213 0.0
214 }
215
216 #[must_use]
217 pub fn output_latency(&self) -> f64 {
218 0.0
219 }
220
221 pub fn suspend(&mut self) -> Result<(), GraphError> {
222 let mut inner = self.inner.lock().expect("graph mutex poisoned");
223 if inner.state == OfflineAudioContextState::Closed {
224 return Err(GraphError::ContextClosed);
225 }
226 inner.state = OfflineAudioContextState::Suspended;
227 Ok(())
228 }
229
230 pub fn resume(&mut self) -> Result<(), GraphError> {
231 let mut inner = self.inner.lock().expect("graph mutex poisoned");
232 if inner.state == OfflineAudioContextState::Closed {
233 return Err(GraphError::ContextClosed);
234 }
235 inner.state = OfflineAudioContextState::Running;
236 Ok(())
237 }
238
239 pub fn close(&mut self) -> Result<(), GraphError> {
240 let mut inner = self.inner.lock().expect("graph mutex poisoned");
241 if inner.state == OfflineAudioContextState::Closed {
242 return Err(GraphError::ContextClosed);
243 }
244 inner.state = OfflineAudioContextState::Closed;
245 Ok(())
246 }
247
248 pub fn try_create_buffer(
249 &self,
250 number_of_channels: usize,
251 length: usize,
252 sample_rate: u32,
253 ) -> Result<AudioBuffer, GraphError> {
254 if number_of_channels == 0
255 || number_of_channels > 32
256 || length == 0
257 || !(3_000..=384_000).contains(&sample_rate)
258 {
259 return Err(GraphError::InvalidAudioBuffer);
260 }
261 Ok(AudioBuffer::from_channels(
262 sample_rate,
263 length,
264 (0..number_of_channels).map(|_| vec![0.0; length]),
265 ))
266 }
267
268 pub fn create_buffer(
269 &self,
270 number_of_channels: usize,
271 length: usize,
272 sample_rate: u32,
273 ) -> Result<AudioBuffer, GraphError> {
274 self.try_create_buffer(number_of_channels, length, sample_rate)
275 }
276
277 pub fn try_create_buffer_with_options(
278 &self,
279 options: AudioBufferOptions,
280 ) -> Result<AudioBuffer, GraphError> {
281 self.try_create_buffer(
282 options.number_of_channels,
283 options.length,
284 options.sample_rate,
285 )
286 }
287
288 pub fn try_create_periodic_wave(
289 &self,
290 real: impl IntoIterator<Item = f32>,
291 imag: impl IntoIterator<Item = f32>,
292 ) -> Result<PeriodicWave, GraphError> {
293 self.try_create_periodic_wave_with_options(real, imag, PeriodicWaveOptions::default())
294 }
295
296 pub fn create_periodic_wave(
297 &self,
298 real: impl IntoIterator<Item = f32>,
299 imag: impl IntoIterator<Item = f32>,
300 ) -> Result<PeriodicWave, GraphError> {
301 self.try_create_periodic_wave(real, imag)
302 }
303
304 pub fn try_create_periodic_wave_with_options(
305 &self,
306 real: impl IntoIterator<Item = f32>,
307 imag: impl IntoIterator<Item = f32>,
308 options: PeriodicWaveOptions,
309 ) -> Result<PeriodicWave, GraphError> {
310 PeriodicWave::try_new_with_options(real, imag, options)
311 }
312
313 #[must_use]
314 pub fn listener(&self) -> AudioListener {
315 AudioListener {
316 graph: Arc::clone(&self.inner),
317 }
318 }
319
320 #[must_use]
321 fn oscillator_with_type(&mut self, waveform: Waveform) -> OscillatorNode {
322 let mut inner = self.inner.lock().expect("graph mutex poisoned");
323 let id = NodeId(inner.nodes.len());
324 inner.nodes.push(NodeDef::oscillator(waveform));
325 OscillatorNode {
326 id,
327 graph: Arc::clone(&self.inner),
328 }
329 }
330
331 #[must_use]
332 pub fn create_oscillator(&mut self) -> OscillatorNode {
333 self.oscillator_with_type(Waveform::Sine)
334 }
335
336 pub fn try_create_oscillator_with_options(
337 &mut self,
338 options: OscillatorOptions,
339 ) -> Result<OscillatorNode, GraphError> {
340 let oscillator = self.create_oscillator();
341 match options.oscillator_type {
342 OscillatorType::Basic(waveform) => oscillator.set_type(waveform),
343 OscillatorType::Custom(wave) => oscillator.set_periodic_wave(wave),
344 }
345 oscillator.frequency().set_value(options.frequency)?;
346 oscillator.detune().set_value(options.detune)?;
347 Ok(oscillator)
348 }
349
350 #[must_use]
351 fn constant_with_offset(&mut self, value: f32) -> ConstantSourceNode {
352 let mut inner = self.inner.lock().expect("graph mutex poisoned");
353 let id = NodeId(inner.nodes.len());
354 inner.nodes.push(NodeDef::constant(value));
355 ConstantSourceNode {
356 id,
357 graph: Arc::clone(&self.inner),
358 }
359 }
360
361 #[must_use]
362 pub fn create_constant_source(&mut self) -> ConstantSourceNode {
363 self.constant_with_offset(1.0)
364 }
365
366 pub fn try_create_constant_source_with_options(
367 &mut self,
368 options: ConstantSourceOptions,
369 ) -> Result<ConstantSourceNode, GraphError> {
370 let source = self.create_constant_source();
371 source.offset().set_value(options.offset)?;
372 Ok(source)
373 }
374
375 #[must_use]
376 fn gain(&mut self) -> GainNode {
377 let mut inner = self.inner.lock().expect("graph mutex poisoned");
378 let id = NodeId(inner.nodes.len());
379 inner.nodes.push(NodeDef::gain());
380 GainNode {
381 id,
382 graph: Arc::clone(&self.inner),
383 }
384 }
385
386 #[must_use]
387 pub fn create_gain(&mut self) -> GainNode {
388 self.gain()
389 }
390
391 pub fn try_create_gain_with_options(
392 &mut self,
393 options: GainOptions,
394 ) -> Result<GainNode, GraphError> {
395 let gain = self.create_gain();
396 gain.gain().set_value(options.gain)?;
397 Ok(gain)
398 }
399
400 #[must_use]
401 pub fn create_buffer_source(&mut self) -> AudioBufferSourceNode {
402 let id = self.push_node(NodeDef::new(NodeKind::AudioBufferSource {
403 buffer: None,
404 buffer_assigned: false,
405 acquired_buffer: None,
406 playback_rate: ParamTimeline::new(1.0)
407 .with_nominal_range(f32::MIN, f32::MAX)
408 .with_automation_rate(AutomationRate::KRate),
409 detune: ParamTimeline::new(0.0)
410 .with_nominal_range(f32::MIN, f32::MAX)
411 .with_automation_rate(AutomationRate::KRate),
412 looping: false,
413 loop_range: None,
414 start_time: 0.0,
415 stop_time: None,
416 start_scheduled: false,
417 stop_scheduled: false,
418 ended: Arc::new(AtomicBool::new(false)),
419 offset: 0.0,
420 duration: None,
421 }));
422 AudioBufferSourceNode {
423 id,
424 graph: Arc::clone(&self.inner),
425 }
426 }
427
428 pub fn try_create_buffer_source_with_options(
429 &mut self,
430 options: AudioBufferSourceOptions,
431 ) -> Result<AudioBufferSourceNode, GraphError> {
432 let source = self.create_buffer_source();
433 if let Some(buffer) = options.buffer {
434 source.try_set_buffer(buffer)?;
435 }
436 source.playback_rate().set_value(options.playback_rate)?;
437 source.detune().set_value(options.detune)?;
438 source.set_looping(options.looping);
439 source.try_loop_range(options.loop_start, options.loop_end)?;
440 Ok(source)
441 }
442
443 #[must_use]
444 pub fn create_sound_data_source<D>(&mut self, data: D) -> SoundDataSourceNode
445 where
446 D: SoundData + Send + 'static,
447 D::Error: fmt::Debug + Send + Sync + 'static,
448 {
449 let id = self.push_node(NodeDef::new(NodeKind::ExternalSound {
450 data: ExternalSoundDataNode::new(data),
451 start_time: 0.0,
452 stop_time: None,
453 start_scheduled: false,
454 stop_scheduled: false,
455 ended: Arc::new(AtomicBool::new(false)),
456 }));
457 SoundDataSourceNode {
458 id,
459 graph: Arc::clone(&self.inner),
460 }
461 }
462
463 #[must_use]
464 fn stereo_panner(&mut self) -> StereoPannerNode {
465 let id = self.push_node(NodeDef::fixed_clamped_max(NodeKind::StereoPanner {
466 pan: ParamTimeline::new(0.0).with_nominal_range(-1.0, 1.0),
467 }));
468 StereoPannerNode {
469 id,
470 graph: Arc::clone(&self.inner),
471 }
472 }
473
474 #[must_use]
475 pub fn create_stereo_panner(&mut self) -> StereoPannerNode {
476 self.stereo_panner()
477 }
478
479 pub fn try_create_stereo_panner_with_options(
480 &mut self,
481 options: StereoPannerOptions,
482 ) -> Result<StereoPannerNode, GraphError> {
483 let panner = self.create_stereo_panner();
484 panner.pan().set_value(options.pan)?;
485 Ok(panner)
486 }
487
488 #[must_use]
489 fn biquad_filter(&mut self, kind: BiquadFilterType) -> BiquadFilterHandle {
490 let id = self.push_node(NodeDef::new(NodeKind::BiquadFilter {
491 kind,
492 frequency: ParamTimeline::new(350.0),
493 detune: ParamTimeline::new(0.0)
494 .with_nominal_range(-DETUNE_NOMINAL_LIMIT, DETUNE_NOMINAL_LIMIT),
495 q: ParamTimeline::new(1.0),
496 gain: ParamTimeline::new(0.0).with_nominal_range(f32::NEG_INFINITY, BIQUAD_GAIN_MAX),
497 }));
498 BiquadFilterHandle {
499 id,
500 graph: Arc::clone(&self.inner),
501 }
502 }
503
504 #[must_use]
505 pub fn create_biquad_filter(&mut self) -> BiquadFilterHandle {
506 self.biquad_filter(BiquadFilterType::Lowpass)
507 }
508
509 pub fn try_create_biquad_filter_with_options(
510 &mut self,
511 options: BiquadFilterOptions,
512 ) -> Result<BiquadFilterHandle, GraphError> {
513 let filter = self.create_biquad_filter();
514 filter.set_type(options.filter_type);
515 filter.frequency().set_value(options.frequency)?;
516 filter.detune().set_value(options.detune)?;
517 filter.q().set_value(options.q)?;
518 filter.gain().set_value(options.gain)?;
519 Ok(filter)
520 }
521
522 #[must_use]
523 fn iir_filter(
524 &mut self,
525 feedforward: impl IntoIterator<Item = f32>,
526 feedback: impl IntoIterator<Item = f32>,
527 ) -> IirFilterNode {
528 let id = self.push_node(NodeDef::new(NodeKind::IirFilter {
529 feedforward: feedforward.into_iter().collect(),
530 feedback: feedback.into_iter().collect(),
531 }));
532 IirFilterNode {
533 id,
534 graph: Arc::clone(&self.inner),
535 }
536 }
537
538 pub fn try_create_iir_filter(
539 &mut self,
540 feedforward: impl IntoIterator<Item = f32>,
541 feedback: impl IntoIterator<Item = f32>,
542 ) -> Result<IirFilterNode, GraphError> {
543 let feedforward = feedforward.into_iter().collect::<Vec<_>>();
544 let feedback = feedback.into_iter().collect::<Vec<_>>();
545 validate_iir_coefficients(&feedforward, &feedback)?;
546
547 Ok(self.iir_filter(feedforward, feedback))
548 }
549
550 pub fn create_iir_filter(
551 &mut self,
552 feedforward: impl IntoIterator<Item = f32>,
553 feedback: impl IntoIterator<Item = f32>,
554 ) -> Result<IirFilterNode, GraphError> {
555 self.try_create_iir_filter(feedforward, feedback)
556 }
557
558 pub fn try_create_iir_filter_with_options(
559 &mut self,
560 options: IirFilterOptions,
561 ) -> Result<IirFilterNode, GraphError> {
562 self.try_create_iir_filter(options.feedforward, options.feedback)
563 }
564
565 #[must_use]
566 fn delay_with_max_delay_time(&mut self, max_delay_time: f64) -> DelayNodeHandle {
567 let id = self.push_node(NodeDef::new(NodeKind::Delay {
568 delay_time: ParamTimeline::new(0.0).with_nominal_range(0.0, max_delay_time as f32),
569 max_delay_time: Some(max_delay_time.max(0.0) as f32),
570 }));
571 DelayNodeHandle {
572 id,
573 graph: Arc::clone(&self.inner),
574 }
575 }
576
577 #[must_use]
578 pub fn create_delay(&mut self) -> DelayNodeHandle {
579 self.delay_with_max_delay_time(1.0)
580 }
581
582 pub fn try_create_delay(&mut self, max_delay_time: f64) -> Result<DelayNodeHandle, GraphError> {
583 if !(0.0..180.0).contains(&max_delay_time) || max_delay_time == 0.0 {
584 return Err(GraphError::InvalidDelayTime);
585 }
586 Ok(self.delay_with_max_delay_time(max_delay_time))
587 }
588
589 pub fn try_create_delay_with_options(
590 &mut self,
591 options: DelayOptions,
592 ) -> Result<DelayNodeHandle, GraphError> {
593 let delay = self.try_create_delay(options.max_delay_time)?;
594 delay.delay_time().set_value(options.delay_time)?;
595 Ok(delay)
596 }
597
598 #[must_use]
599 pub fn create_wave_shaper(&mut self) -> WaveShaperNode {
600 let id = self.push_node(NodeDef::new(NodeKind::WaveShaper {
601 curve: None,
602 oversample: Oversample::None,
603 }));
604 WaveShaperNode {
605 id,
606 graph: Arc::clone(&self.inner),
607 }
608 }
609
610 pub fn try_create_wave_shaper_with_options(
611 &mut self,
612 options: WaveShaperOptions,
613 ) -> Result<WaveShaperNode, GraphError> {
614 let shaper = self.create_wave_shaper();
615 shaper.set_oversample(options.oversample);
616 if let Some(curve) = options.curve {
617 shaper.try_curve(curve)?;
618 }
619 Ok(shaper)
620 }
621
622 #[must_use]
623 fn dynamics_compressor(&mut self) -> DynamicsCompressorNode {
624 let id = self.push_node(NodeDef::fixed_clamped_max(NodeKind::DynamicsCompressor {
625 threshold: ParamTimeline::new(-24.0)
626 .with_nominal_range(-100.0, 0.0)
627 .with_automation_rate(AutomationRate::KRate),
628 knee: ParamTimeline::new(30.0)
629 .with_nominal_range(0.0, 40.0)
630 .with_automation_rate(AutomationRate::KRate),
631 ratio: ParamTimeline::new(12.0)
632 .with_nominal_range(1.0, 20.0)
633 .with_automation_rate(AutomationRate::KRate),
634 attack: ParamTimeline::new(0.003)
635 .with_nominal_range(0.0, 1.0)
636 .with_automation_rate(AutomationRate::KRate),
637 release: ParamTimeline::new(0.25)
638 .with_nominal_range(0.0, 1.0)
639 .with_automation_rate(AutomationRate::KRate),
640 reduction: Arc::new(AtomicU32::new(0.0f32.to_bits())),
641 }));
642 DynamicsCompressorNode {
643 id,
644 graph: Arc::clone(&self.inner),
645 }
646 }
647
648 #[must_use]
649 pub fn create_dynamics_compressor(&mut self) -> DynamicsCompressorNode {
650 self.dynamics_compressor()
651 }
652
653 pub fn try_create_dynamics_compressor_with_options(
654 &mut self,
655 options: DynamicsCompressorOptions,
656 ) -> Result<DynamicsCompressorNode, GraphError> {
657 let compressor = self.create_dynamics_compressor();
658 compressor.threshold().set_value(options.threshold)?;
659 compressor.knee().set_value(options.knee)?;
660 compressor.ratio().set_value(options.ratio)?;
661 compressor.attack().set_value(options.attack)?;
662 compressor.release().set_value(options.release)?;
663 Ok(compressor)
664 }
665
666 #[must_use]
667 pub fn create_convolver(&mut self) -> ConvolverNode {
668 let id = self.push_node(NodeDef::fixed_clamped_max(NodeKind::Convolver {
669 buffer: None,
670 normalize: true,
671 buffer_normalize: true,
672 }));
673 ConvolverNode {
674 id,
675 graph: Arc::clone(&self.inner),
676 }
677 }
678
679 pub fn try_create_convolver_with_options(
680 &mut self,
681 options: ConvolverOptions,
682 ) -> Result<ConvolverNode, GraphError> {
683 let convolver = self.create_convolver();
684 convolver.set_normalize(!options.disable_normalization);
685 if let Some(buffer) = options.buffer {
686 convolver.try_buffer(buffer)?;
687 }
688 Ok(convolver)
689 }
690
691 #[must_use]
692 fn analyser(&mut self) -> AnalyserNode {
693 let state = Arc::new(Mutex::new(AnalyserState::new(2048)));
694 let id = self.push_node(NodeDef::new(NodeKind::Analyser {
695 state: Arc::clone(&state),
696 }));
697 AnalyserNode {
698 id,
699 state,
700 graph: Arc::clone(&self.inner),
701 }
702 }
703
704 #[must_use]
705 pub fn create_analyser(&mut self) -> AnalyserNode {
706 self.analyser()
707 }
708
709 pub fn try_create_analyser_with_options(
710 &mut self,
711 options: AnalyserOptions,
712 ) -> Result<AnalyserNode, GraphError> {
713 if !(32..=32768).contains(&options.fft_size)
714 || !options.fft_size.is_power_of_two()
715 || !options.min_decibels.is_finite()
716 || !options.max_decibels.is_finite()
717 || options.min_decibels >= options.max_decibels
718 || !(0.0..=1.0).contains(&options.smoothing_time_constant)
719 {
720 return Err(GraphError::InvalidAnalyserConfig);
721 }
722 let analyser = self.create_analyser();
723 {
724 let mut state = analyser.state.lock().expect("analyser mutex poisoned");
725 state.resize(options.fft_size);
726 state.min_decibels = options.min_decibels;
727 state.max_decibels = options.max_decibels;
728 state.smoothing_time_constant = options.smoothing_time_constant;
729 state.frequency_dirty = true;
730 }
731 Ok(analyser)
732 }
733
734 #[must_use]
735 fn channel_splitter(&mut self, outputs: usize) -> ChannelSplitterNode {
736 let mut inner = self.inner.lock().expect("graph mutex poisoned");
737 let id = NodeId(inner.nodes.len());
738 inner.nodes.push(NodeDef::channel_splitter(outputs));
739 ChannelSplitterNode {
740 id,
741 graph: Arc::clone(&self.inner),
742 context_identity: self.context_identity(),
743 }
744 }
745
746 pub fn try_create_channel_splitter(
747 &mut self,
748 outputs: usize,
749 ) -> Result<ChannelSplitterNode, GraphError> {
750 if !(1..=32).contains(&outputs) {
751 return Err(GraphError::InvalidChannelCount);
752 }
753 Ok(self.channel_splitter(outputs))
754 }
755
756 pub fn try_create_channel_splitter_with_options(
757 &mut self,
758 options: ChannelSplitterOptions,
759 ) -> Result<ChannelSplitterNode, GraphError> {
760 self.try_create_channel_splitter(options.number_of_outputs)
761 }
762
763 #[must_use]
764 pub fn create_channel_splitter(&mut self) -> ChannelSplitterNode {
765 self.channel_splitter(6)
766 }
767
768 #[must_use]
769 fn channel_merger(&mut self, inputs: usize) -> ChannelMergerNode {
770 let mut inner = self.inner.lock().expect("graph mutex poisoned");
771 let id = NodeId(inner.nodes.len());
772 inner.nodes.push(NodeDef::channel_merger(inputs));
773 ChannelMergerNode {
774 id,
775 graph: Arc::clone(&self.inner),
776 context_identity: self.context_identity(),
777 }
778 }
779
780 pub fn try_create_channel_merger(
781 &mut self,
782 inputs: usize,
783 ) -> Result<ChannelMergerNode, GraphError> {
784 if !(1..=32).contains(&inputs) {
785 return Err(GraphError::InvalidChannelCount);
786 }
787 Ok(self.channel_merger(inputs))
788 }
789
790 pub fn try_create_channel_merger_with_options(
791 &mut self,
792 options: ChannelMergerOptions,
793 ) -> Result<ChannelMergerNode, GraphError> {
794 self.try_create_channel_merger(options.number_of_inputs)
795 }
796
797 #[must_use]
798 pub fn create_channel_merger(&mut self) -> ChannelMergerNode {
799 self.channel_merger(6)
800 }
801
802 #[must_use]
803 fn panner(&mut self) -> PannerNode {
804 let id = self.push_node(NodeDef::fixed_clamped_max(NodeKind::Panner {
805 position_x: ParamTimeline::new(0.0),
806 position_y: ParamTimeline::new(0.0),
807 position_z: ParamTimeline::new(0.0),
808 orientation_x: ParamTimeline::new(1.0),
809 orientation_y: ParamTimeline::new(0.0),
810 orientation_z: ParamTimeline::new(0.0),
811 panning_model: PanningModel::EqualPower,
812 distance_model: DistanceModel::Inverse,
813 ref_distance: 1.0,
814 max_distance: 10_000.0,
815 rolloff_factor: 1.0,
816 cone_inner_angle: 360.0,
817 cone_outer_angle: 360.0,
818 cone_outer_gain: 0.0,
819 }));
820 PannerNode {
821 id,
822 graph: Arc::clone(&self.inner),
823 }
824 }
825
826 #[must_use]
827 pub fn create_panner(&mut self) -> PannerNode {
828 self.panner()
829 }
830
831 pub fn try_create_panner_with_options(
832 &mut self,
833 options: PannerOptions,
834 ) -> Result<PannerNode, GraphError> {
835 let panner = self.create_panner();
836 panner.set_panning_model(options.panning_model)?;
837 panner.set_distance_model(options.distance_model);
838 panner.position_x().set_value(options.position_x)?;
839 panner.position_y().set_value(options.position_y)?;
840 panner.position_z().set_value(options.position_z)?;
841 panner.orientation_x().set_value(options.orientation_x)?;
842 panner.orientation_y().set_value(options.orientation_y)?;
843 panner.orientation_z().set_value(options.orientation_z)?;
844 panner.try_ref_distance(options.ref_distance)?;
845 panner.try_max_distance(options.max_distance)?;
846 panner.try_rolloff_factor(options.rolloff_factor)?;
847 panner.try_cone_inner_angle(options.cone_inner_angle)?;
848 panner.try_cone_outer_angle(options.cone_outer_angle)?;
849 panner.try_cone_outer_gain(options.cone_outer_gain)?;
850 Ok(panner)
851 }
852
853 pub fn try_create_audio_worklet_node<P>(
854 &mut self,
855 processor: P,
856 options: AudioWorkletNodeOptions,
857 ) -> Result<AudioWorkletNode, GraphError>
858 where
859 P: AudioWorkletProcessor + 'static,
860 {
861 let explicit_output_channel_count = options.output_channel_count.clone();
862 let output_channel_count = if explicit_output_channel_count.is_none()
863 && options.number_of_inputs == 1
864 && options.number_of_outputs == 1
865 {
866 None
867 } else {
868 Some(
869 explicit_output_channel_count
870 .clone()
871 .unwrap_or_else(|| vec![1; options.number_of_outputs]),
872 )
873 };
874 if options.number_of_inputs > 32
875 || options.number_of_outputs > 32
876 || (options.number_of_inputs == 0 && options.number_of_outputs == 0)
877 || options
878 .output_channel_count
879 .as_ref()
880 .is_some_and(|counts| counts.len() != options.number_of_outputs)
881 || output_channel_count
882 .as_ref()
883 .is_some_and(|counts| counts.iter().any(|count| !(1..=32).contains(count)))
884 || !validate_audio_worklet_parameters(
885 &options.parameter_descriptors,
886 &options.parameter_data,
887 )
888 {
889 return Err(GraphError::InvalidAudioWorkletOptions);
890 }
891 let parameters = options
892 .parameter_descriptors
893 .iter()
894 .map(|descriptor| {
895 let initial_value = options
896 .parameter_data
897 .get(&descriptor.name)
898 .copied()
899 .unwrap_or(descriptor.default_value);
900 (
901 descriptor.name.clone(),
902 ParamTimeline::new(initial_value)
903 .with_nominal_range(descriptor.min_value, descriptor.max_value)
904 .with_automation_rate(descriptor.automation_rate),
905 )
906 })
907 .collect();
908 let id = self.push_node(NodeDef::new(NodeKind::AudioWorklet {
909 inputs: options.number_of_inputs,
910 outputs: options.number_of_outputs,
911 output_channel_count,
912 parameters,
913 processor_options: options.processor_options,
914 processor: AudioWorkletProcessorNode::new(processor),
915 }));
916 Ok(AudioWorkletNode {
917 id,
918 graph: self.inner.clone(),
919 })
920 }
921
922 #[must_use]
923 pub fn create_audio_worklet_node<P>(&mut self, processor: P) -> AudioWorkletNode
924 where
925 P: AudioWorkletProcessor + 'static,
926 {
927 self.try_create_audio_worklet_node(processor, AudioWorkletNodeOptions::default())
928 .expect("default audio worklet options are valid")
929 }
930
931 fn push_node(&mut self, node: NodeDef) -> NodeId {
932 let mut inner = self.inner.lock().expect("graph mutex poisoned");
933 let id = NodeId(inner.nodes.len());
934 inner.nodes.push(node);
935 id
936 }
937
938 fn context_identity(&self) -> usize {
939 Arc::as_ptr(&self.inner) as usize
940 }
941
942 fn validate_handle_context(&self, handle: &impl AudioNodeHandle) -> Result<(), GraphError> {
943 if handle
944 .context_identity()
945 .is_some_and(|identity| identity != self.context_identity())
946 {
947 return Err(GraphError::WrongContext);
948 }
949 Ok(())
950 }
951
952 fn validate_param_target_context(&self, target: &AudioParamHandle) -> Result<(), GraphError> {
953 if target
954 .context_identity()
955 .is_some_and(|identity| identity != self.context_identity())
956 {
957 return Err(GraphError::WrongContext);
958 }
959 Ok(())
960 }
961
962 pub fn label_node(
963 &mut self,
964 node: impl AudioNodeHandle,
965 label: impl Into<String>,
966 ) -> Result<(), GraphError> {
967 self.validate_handle_context(&node)?;
968 let node = node.node_id();
969 let label = label.into();
970 if label.is_empty() || label.contains('.') || label.contains('#') {
971 return Err(GraphError::InvalidNodeLabel);
972 }
973 let mut inner = self.inner.lock().expect("graph mutex poisoned");
974 inner.validate_node(node)?;
975 if inner
976 .nodes
977 .iter()
978 .enumerate()
979 .any(|(index, existing)| index != node.0 && existing.label.as_deref() == Some(&label))
980 {
981 return Err(GraphError::InvalidNodeLabel);
982 }
983 inner.nodes[node.0].label = Some(label);
984 Ok(())
985 }
986
987 pub fn connect(
988 &mut self,
989 source: impl AudioNodeHandle,
990 target: impl AudioNodeHandle,
991 ) -> Result<(), GraphError> {
992 self.validate_handle_context(&source)?;
993 self.validate_handle_context(&target)?;
994 let source = source.node_id();
995 let target = target.node_id();
996 let mut inner = self.inner.lock().expect("graph mutex poisoned");
997 inner.connect_nodes(source, target)
998 }
999
1000 pub fn connect_with_indices(
1001 &mut self,
1002 source: impl AudioNodeHandle,
1003 output: usize,
1004 target: impl AudioNodeHandle,
1005 input: usize,
1006 ) -> Result<(), GraphError> {
1007 self.validate_handle_context(&source)?;
1008 self.validate_handle_context(&target)?;
1009 let source = source.node_id();
1010 let target = target.node_id();
1011 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1012 inner.connect_nodes_with_indices(source, output, target, input)
1013 }
1014
1015 pub fn connect_param(
1016 &mut self,
1017 source: impl AudioNodeHandle,
1018 target: AudioParamHandle,
1019 ) -> Result<(), GraphError> {
1020 self.validate_handle_context(&source)?;
1021 self.validate_param_target_context(&target)?;
1022 let source = source.node_id();
1023 let target = target.id;
1024 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1025 inner.connect_param_node(source, target)
1026 }
1027
1028 pub fn connect_param_from_output(
1029 &mut self,
1030 source: impl AudioNodeHandle,
1031 output: usize,
1032 target: AudioParamHandle,
1033 ) -> Result<(), GraphError> {
1034 self.validate_handle_context(&source)?;
1035 self.validate_param_target_context(&target)?;
1036 let source = source.node_id();
1037 let target = target.id;
1038 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1039 inner.connect_param_node_from_output(source, output, target)
1040 }
1041
1042 pub fn disconnect(
1043 &mut self,
1044 source: impl AudioNodeHandle,
1045 target: impl AudioNodeHandle,
1046 ) -> Result<(), GraphError> {
1047 self.validate_handle_context(&source)?;
1048 self.validate_handle_context(&target)?;
1049 let source = source.node_id();
1050 let target = target.node_id();
1051 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1052 inner.disconnect_nodes(source, target)
1053 }
1054
1055 pub fn disconnect_with_indices(
1056 &mut self,
1057 source: impl AudioNodeHandle,
1058 output: usize,
1059 target: impl AudioNodeHandle,
1060 input: usize,
1061 ) -> Result<(), GraphError> {
1062 self.validate_handle_context(&source)?;
1063 self.validate_handle_context(&target)?;
1064 let source = source.node_id();
1065 let target = target.node_id();
1066 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1067 inner.disconnect_nodes_with_indices(source, output, target, input)
1068 }
1069
1070 pub fn disconnect_param(
1071 &mut self,
1072 source: impl AudioNodeHandle,
1073 target: AudioParamHandle,
1074 ) -> Result<(), GraphError> {
1075 self.validate_handle_context(&source)?;
1076 self.validate_param_target_context(&target)?;
1077 let source = source.node_id();
1078 let target = target.id;
1079 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1080 inner.disconnect_param_node(source, target)
1081 }
1082
1083 pub fn disconnect_param_from_output(
1084 &mut self,
1085 source: impl AudioNodeHandle,
1086 output: usize,
1087 target: AudioParamHandle,
1088 ) -> Result<(), GraphError> {
1089 self.validate_handle_context(&source)?;
1090 self.validate_param_target_context(&target)?;
1091 let source = source.node_id();
1092 let target = target.id;
1093 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1094 inner.disconnect_param_node_from_output(source, output, target)
1095 }
1096
1097 pub fn disconnect_outputs(&mut self, source: impl AudioNodeHandle) -> Result<(), GraphError> {
1098 self.validate_handle_context(&source)?;
1099 let source = source.node_id();
1100 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1101 inner.validate_node(source)?;
1102 inner
1103 .connections
1104 .retain(|connection| connection.source != source);
1105 Ok(())
1106 }
1107
1108 pub fn disconnect_param_outputs(
1109 &mut self,
1110 source: impl AudioNodeHandle,
1111 ) -> Result<(), GraphError> {
1112 self.validate_handle_context(&source)?;
1113 let source = source.node_id();
1114 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1115 inner.validate_node(source)?;
1116 inner
1117 .param_connections
1118 .retain(|connection| connection.source != source);
1119 Ok(())
1120 }
1121
1122 #[must_use]
1123 pub fn sound_data(&self) -> AudioContextSoundData {
1124 AudioContextSoundData {
1125 graph: self.clone(),
1126 sample_rate: self.sample_rate(),
1127 }
1128 }
1129
1130 pub(crate) fn render_offline_channels(
1131 &self,
1132 sample_rate: u32,
1133 frames: usize,
1134 channels: usize,
1135 ) -> Result<AudioBuffer, GraphError> {
1136 if self.state() == OfflineAudioContextState::Closed {
1137 return Err(GraphError::ContextClosed);
1138 }
1139 let sample_rate = sample_rate.max(1);
1140 let channels = channels.max(1);
1141 let mut compiled = self.compiled()?;
1142 compiled.set_destination_channel_count(channels);
1143 let mut runtime = compiled.runtime()?;
1144 let info = MockInfoBuilder::new().build();
1145 let sample_dt = 1.0 / sample_rate as f64;
1146 let mut rendered = vec![vec![0.0; frames]; channels];
1147 let mut frame_index = 0;
1148 while frame_index < frames {
1149 let quantum_frames = (frames - frame_index).min(RENDER_QUANTUM_SIZE_USIZE);
1150 let quantum_start = frame_index as f64 * sample_dt;
1151 let quantum = compiled.render_bus_quantum_with_runtime(
1152 RenderQuantum {
1153 start: quantum_start,
1154 global_start: quantum_start,
1155 sample_dt,
1156 frames: quantum_frames,
1157 commit_source_state: false,
1158 },
1159 None,
1160 &mut runtime,
1161 &info,
1162 );
1163 for (offset, frame) in quantum.iter().enumerate() {
1164 let sample_index = frame_index + offset;
1165 for (channel, samples) in rendered.iter_mut().enumerate() {
1166 samples[sample_index] = frame.channel(channel);
1167 }
1168 }
1169 frame_index += quantum_frames;
1170 }
1171 Ok(AudioBuffer::from_channels(sample_rate, frames, rendered))
1172 }
1173
1174 pub fn node_info(&self, node: impl AudioNodeHandle) -> Result<AudioNodeInfo, GraphError> {
1175 self.validate_handle_context(&node)?;
1176 let node = node.node_id();
1177 let inner = self.inner.lock().expect("graph mutex poisoned");
1178 inner.validate_node(node)?;
1179 Ok(inner.nodes[node.0].info())
1180 }
1181
1182 fn compiled(&self) -> Result<CompiledGraph, GraphError> {
1183 let inner = self.inner.lock().expect("graph mutex poisoned");
1184 inner.compile()
1185 }
1186
1187 fn schedule_named_param_automation(&mut self, automation: &TimedAutomationEvent) {
1188 if validate_automation_time(automation.time_seconds).is_err() {
1189 return;
1190 }
1191 let Some(target) = sequencer_param_target(&automation.target) else {
1192 return;
1193 };
1194 let mut inner = self.inner.lock().expect("graph mutex poisoned");
1195 let mut matched = 0usize;
1196 for node in &mut inner.nodes {
1197 if !target.matches_label(node.label.as_deref()) {
1198 continue;
1199 }
1200 let Some(timeline) = node.kind.param_mut(target.param) else {
1201 continue;
1202 };
1203 let should_apply = target
1204 .index
1205 .is_none_or(|target_index| target_index == matched);
1206 matched += 1;
1207 if !should_apply {
1208 continue;
1209 }
1210 if timeline
1211 .validate_event_time_for_value_curves(automation.time_seconds)
1212 .is_err()
1213 {
1214 continue;
1215 }
1216 match &automation.shape {
1217 AutomationShape::SetValue { value } => {
1218 if validate_automation_value(*value).is_ok() {
1219 *timeline = timeline
1220 .clone()
1221 .with_time_domain(ParamTimeDomain::Global)
1222 .set_value_at_time(*value, automation.time_seconds);
1223 }
1224 }
1225 AutomationShape::LinearRamp { value } => {
1226 if validate_automation_value(*value).is_ok() {
1227 *timeline = timeline
1228 .clone()
1229 .with_time_domain(ParamTimeDomain::Global)
1230 .linear_ramp_to_value_at_time(*value, automation.time_seconds);
1231 }
1232 }
1233 AutomationShape::ValueCurve {
1234 values,
1235 duration_seconds,
1236 } => {
1237 if values
1238 .iter()
1239 .copied()
1240 .all(|value| validate_automation_value(value).is_ok())
1241 {
1242 *timeline = timeline
1243 .clone()
1244 .with_time_domain(ParamTimeDomain::Global)
1245 .set_value_curve_at_time(
1246 values.iter().copied(),
1247 automation.time_seconds,
1248 *duration_seconds,
1249 );
1250 }
1251 }
1252 }
1253 }
1254 }
1255
1256 fn validate_sequencer_automation_target(
1257 &self,
1258 track_id: &TrackId,
1259 automation: &TimedAutomationEvent,
1260 ) -> Result<(), SequencerValidationError> {
1261 validate_automation_time(automation.time_seconds).map_err(|_| {
1262 SequencerValidationError::InvalidAutomationTime {
1263 track_id: track_id.clone(),
1264 target: automation.target.clone(),
1265 time_seconds: automation.time_seconds,
1266 }
1267 })?;
1268 validate_automation_shape(track_id, automation)?;
1269 let Some(target) = sequencer_param_target(&automation.target) else {
1270 return Err(SequencerValidationError::InvalidAutomationTarget {
1271 track_id: track_id.clone(),
1272 target: automation.target.clone(),
1273 });
1274 };
1275 let inner = self.inner.lock().expect("graph mutex poisoned");
1276 let matches = inner
1277 .nodes
1278 .iter()
1279 .filter(|node| {
1280 target.matches_label(node.label.as_deref())
1281 && node.kind.param(target.param).is_some()
1282 })
1283 .count();
1284 let valid = target
1285 .index
1286 .map_or(matches > 0, |target_index| target_index < matches);
1287 if valid {
1288 Ok(())
1289 } else {
1290 Err(SequencerValidationError::InvalidAutomationTarget {
1291 track_id: track_id.clone(),
1292 target: automation.target.clone(),
1293 })
1294 }
1295 }
1296}