Skip to main content

melody_bay/webaudio/
context.rs

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}