Skip to main content

melody_bay/
lib.rs

1//! Kira-compatible generated synthesis sound sources.
2//!
3//! This crate follows the WebAudio graph shape where it maps cleanly to Rust
4//! and Kira. Browser-only integration points are intentionally unsupported:
5//! DOM media elements, MediaStream, sink/device selection,
6//! `decodeAudioData`, deprecated `ScriptProcessorNode`, `AudioRenderCapacity`
7//! events, JavaScript module loading, MessagePort, promises, and EventTarget. Use
8//! [`AudioContext::create_sound_data_source`] for Kira [`SoundData`] media/source
9//! integration, and use Rust [`Result`] values plus source state handles for
10//! errors and ended status. HRTF panning is also unsupported; panner nodes use
11//! equal-power spatialization.
12//!
13//! Use [`AudioContext::create_audio_worklet_node`] with an
14//! [`AudioWorkletProcessor`] implementation for custom render-quantum
15//! processing.
16//!
17//! ```
18//! # use melody_bay::{AudioContext, AudioWorkletProcessContext, AudioWorkletProcessor};
19//! struct Passthrough;
20//!
21//! impl AudioWorkletProcessor for Passthrough {
22//!     fn process(
23//!         &mut self,
24//!         inputs: &[Vec<Vec<f32>>],
25//!         outputs: &mut [Vec<Vec<f32>>],
26//!         _context: AudioWorkletProcessContext,
27//!     ) -> bool {
28//!         if let (Some(input), Some(output)) = (inputs.first(), outputs.first_mut()) {
29//!             for (output_channel, input_channel) in output.iter_mut().zip(input) {
30//!                 output_channel.copy_from_slice(input_channel);
31//!             }
32//!         }
33//!         true
34//!     }
35//! }
36//!
37//! let mut context = AudioContext::new();
38//! let _node = context.create_audio_worklet_node(Passthrough);
39//! ```
40//!
41//! ```compile_fail
42//! let _channel = melody_bay::Channel::main(melody_bay::AudioContext::new());
43//! ```
44//!
45//! Author new musical material with [`IndexedSequence`] when events should sit
46//! on a tempo-mapped grid, or [`TimedSequence`] when events already have exact
47//! timestamps. Indexed sequences resolve into timed sequences before playback.
48//! Automation targets use WebAudio-style parameter names such as `"gain"` or
49//! `"playback_rate"`. Use [`AudioContext::label_node`] and `"label.param"` to
50//! target a specific graph node, for example `"output.gain"`. Add `#n` to
51//! target the nth matching parameter in graph node order, for example
52//! `"gain#1"`. Infallible render/playback helpers follow Kira's ergonomic
53//! `SoundData` style and ignore invalid sequencer automation. Use
54//! [`TimedSequence::validate`] or [`TimedSequence::try_sound_data`] when
55//! automation should be checked before rendering.
56//!
57//! ```
58//! # use kira::sound::SoundData;
59//! # use melody_bay::{
60//! #     AudioContext, IndexedSequence, IndexedTrack, Instrument, Note, TrackId, Velocity,
61//! # };
62//! let mut graph = AudioContext::new();
63//! let source = graph.create_constant_source();
64//! let gain = graph.create_gain();
65//! graph.connect(source, &gain).unwrap();
66//! graph.connect(&gain, graph.destination()).unwrap();
67//!
68//! let mut sequence = IndexedSequence::new(4);
69//! sequence.tempo_at(0, 120.0);
70//! sequence.add_track(
71//!     TrackId::named("lead"),
72//!     IndexedTrack::new(Instrument::graph(graph))
73//!         .note_with_velocity(0, Note::from_midi(60), 4, Velocity::new(0.8))
74//!         .linear_ramp_to_value_at_index(4, "gain", 0.25),
75//! );
76//!
77//! let timed = sequence.resolve();
78//! let _sound_data = timed.try_sound_data().unwrap().sample_rate(44_100);
79//! ```
80//!
81//! ```compile_fail
82//! let _id = melody_bay::EventId(1);
83//! ```
84//!
85//! ```compile_fail
86//! let _time = melody_bay::SequenceTime::seconds(0.0);
87//! ```
88//!
89//! ```compile_fail
90//! let _stem = melody_bay::StemId::named("lead");
91//! ```
92//!
93//! ```compile_fail
94//! let _envelope = melody_bay::Envelope::Adsr(melody_bay::Adsr::default());
95//! ```
96//!
97//! ```compile_fail
98//! let _curve = melody_bay::AutomationCurve::constant(1.0);
99//! ```
100//!
101//! ```compile_fail
102//! let _param = melody_bay::AudioParam::new(0.5);
103//! ```
104//!
105//! ```compile_fail
106//! let _filter = melody_bay::BiquadFilterNode::new(melody_bay::BiquadFilterType::Lowpass);
107//! ```
108//!
109//! ```compile_fail
110//! let _delay = melody_bay::DelayNode::new(0.25);
111//! ```
112//!
113//! ```compile_fail
114//! let _instrument = melody_bay::InstrumentGraph::constant(1.0);
115//! ```
116//!
117//! ```compile_fail
118//! let _oscillator = melody_bay::Oscillator::new(melody_bay::Waveform::Sine, 440.0);
119//! ```
120//!
121//! ```compile_fail
122//! let _gain = melody_bay::Gain { amount: 1.0 };
123//! ```
124//!
125//! ```compile_fail
126//! let _pan = melody_bay::Pan { amount: 0.0 };
127//! ```
128//!
129//! ```compile_fail
130//! let mut context = melody_bay::AudioContext::new();
131//! let _compressor = context.dynamics_compressor();
132//! ```
133//!
134//! ```compile_fail
135//! let _graph = melody_bay::Graph::new();
136//! ```
137//!
138//! ```compile_fail
139//! let _data: Option<melody_bay::GraphSoundData> = None;
140//! ```
141//!
142//! ```compile_fail
143//! let _handle: Option<melody_bay::GraphSoundHandle> = None;
144//! ```
145//!
146//! ```compile_fail
147//! let live = melody_bay::AudioContext::new();
148//! let mut offline = melody_bay::OfflineAudioContext::try_new(2, 128, 48_000).unwrap();
149//! let _buffer = offline.render_graph(&live);
150//! ```
151//!
152//! ```compile_fail
153//! let mut context = melody_bay::AudioContext::new();
154//! let _gain = context.create_gain().set_gain(0.5);
155//! ```
156//!
157//! ```compile_fail
158//! let context = melody_bay::AudioContext::new();
159//! let _listener = context.listener().position(1.0, 2.0, 3.0);
160//! ```
161//!
162//! ```compile_fail
163//! let context = melody_bay::AudioContext::new();
164//! let _listener = context.listener().forward(0.0, 0.0, -1.0);
165//! ```
166//!
167//! ```compile_fail
168//! let context = melody_bay::AudioContext::new();
169//! let _listener = context.listener().up(0.0, 1.0, 0.0);
170//! ```
171//!
172//! ```compile_fail
173//! let mut context = melody_bay::AudioContext::new();
174//! let _gain = context
175//!     .create_gain()
176//!     .gain_param(melody_bay::AudioParam::new(0.5));
177//! ```
178//!
179//! ```compile_fail
180//! let mut context = melody_bay::AudioContext::new();
181//! let _param = context.create_gain().gain_param_id();
182//! ```
183//!
184//! Use [`GainNode::gain`] to access the live node-owned
185//! [`AudioParamHandle`] instead of raw parameter ids.
186//!
187//! ```compile_fail
188//! let mut context = melody_bay::AudioContext::new();
189//! let _id = context.create_gain().gain().id();
190//! ```
191//!
192//! ```compile_fail
193//! let _id: Option<melody_bay::ParamId> = None;
194//! ```
195//!
196//! ```compile_fail
197//! let _id: Option<melody_bay::NodeId> = None;
198//! ```
199//!
200//! ```compile_fail
201//! let mut context = melody_bay::AudioContext::new();
202//! let _id = melody_bay::NodeId::from(context.create_gain());
203//! ```
204//!
205//! ```compile_fail
206//! let mut context = melody_bay::AudioContext::new();
207//! context.set_sample_rate(48_000);
208//! ```
209//!
210//! ```compile_fail
211//! let _context = melody_bay::AudioContext::new_with_sample_rate(48_000);
212//! ```
213//!
214//! ```compile_fail
215//! let mut context = melody_bay::AudioContext::new();
216//! let _source = context.create_constant_source().set_offset(1.0);
217//! ```
218//!
219//! ```compile_fail
220//! let mut context = melody_bay::AudioContext::new();
221//! let _pan = context.create_stereo_panner().set_pan(0.5);
222//! ```
223//!
224//! ```compile_fail
225//! let mut context = melody_bay::AudioContext::new();
226//! let _source = context
227//!     .create_buffer_source()
228//!     .set_playback_rate(2.0);
229//! ```
230//!
231//! ```compile_fail
232//! let mut context = melody_bay::AudioContext::new();
233//! let _delay = context.create_delay().set_delay_time(0.25);
234//! ```
235//!
236//! ```compile_fail
237//! let mut context = melody_bay::AudioContext::new();
238//! let _osc = context.create_oscillator().set_detune(1200.0);
239//! ```
240//!
241//! ```compile_fail
242//! let mut context = melody_bay::AudioContext::new();
243//! let _source = context.create_buffer_source().set_detune(1200.0);
244//! ```
245//!
246//! ```compile_fail
247//! let mut context = melody_bay::AudioContext::new();
248//! let _filter = context.create_biquad_filter().set_detune(1200.0);
249//! ```
250//!
251//! ```compile_fail
252//! let mut context = melody_bay::AudioContext::new();
253//! let _compressor = context.create_dynamics_compressor().set_threshold(-24.0);
254//! ```
255//!
256//! ```compile_fail
257//! let mut context = melody_bay::AudioContext::new();
258//! let _compressor = context.create_dynamics_compressor().set_knee(0.0);
259//! ```
260//!
261//! ```compile_fail
262//! let mut context = melody_bay::AudioContext::new();
263//! let _compressor = context.create_dynamics_compressor().set_ratio(12.0);
264//! ```
265//!
266//! ```compile_fail
267//! let mut context = melody_bay::AudioContext::new();
268//! let _compressor = context.create_dynamics_compressor().set_attack(0.003);
269//! ```
270//!
271//! ```compile_fail
272//! let mut context = melody_bay::AudioContext::new();
273//! let _compressor = context.create_dynamics_compressor().set_release(0.25);
274//! ```
275//!
276//! ```compile_fail
277//! let mut context = melody_bay::AudioContext::new();
278//! let _filter = context.create_biquad_filter().set_frequency(1_000.0);
279//! ```
280//!
281//! ```compile_fail
282//! let mut context = melody_bay::AudioContext::new();
283//! let _filter = context.create_biquad_filter().set_q(0.707);
284//! ```
285//!
286//! ```compile_fail
287//! let mut context = melody_bay::AudioContext::new();
288//! let _filter = context.create_biquad_filter().set_gain(3.0);
289//! ```
290//!
291//! ```compile_fail
292//! let mut context = melody_bay::AudioContext::new();
293//! let _osc = context.create_oscillator().set_frequency(440.0);
294//! ```
295//!
296//! ```compile_fail
297//! let mut context = melody_bay::AudioContext::new();
298//! let _panner = context.create_panner().position(1.0, 2.0, 3.0);
299//! ```
300//!
301//! ```compile_fail
302//! let mut context = melody_bay::AudioContext::new();
303//! let _panner = context.create_panner().orientation(0.0, 0.0, -1.0);
304//! ```
305//!
306//! ```compile_fail
307//! let mut context = melody_bay::AudioContext::new();
308//! let _analyser = context.create_analyser().fft_size(2048);
309//! ```
310//!
311//! ```compile_fail
312//! let mut context = melody_bay::AudioContext::new();
313//! let _analyser = context.create_analyser().min_decibels(-100.0);
314//! ```
315//!
316//! ```compile_fail
317//! let mut context = melody_bay::AudioContext::new();
318//! let _analyser = context.create_analyser().max_decibels(-30.0);
319//! ```
320//!
321//! ```compile_fail
322//! let mut context = melody_bay::AudioContext::new();
323//! let _analyser = context.create_analyser().smoothing_time_constant(0.5);
324//! ```
325//!
326//! ```
327//! let context = melody_bay::AudioContext::new();
328//! let _buffer = context.create_buffer(2, 128, 44_100).unwrap();
329//! ```
330//!
331//! ```
332//! let context = melody_bay::OfflineAudioContext::try_new(2, 128, 44_100).unwrap();
333//! let _buffer = context.create_buffer(2, 128, 44_100).unwrap();
334//! ```
335//!
336//! ```
337//! let context = melody_bay::AudioContext::new();
338//! let _wave = context
339//!     .create_periodic_wave([0.0, 1.0], [0.0, 0.5])
340//!     .unwrap();
341//! ```
342//!
343//! ```
344//! let context = melody_bay::OfflineAudioContext::try_new(2, 128, 44_100).unwrap();
345//! let _wave = context
346//!     .create_periodic_wave([0.0, 1.0], [0.0, 0.5])
347//!     .unwrap();
348//! ```
349//!
350//! ```
351//! let mut context = melody_bay::AudioContext::new();
352//! let _filter = context.create_iir_filter([1.0], [1.0]).unwrap();
353//! ```
354//!
355//! ```compile_fail
356//! let _wave = melody_bay::PeriodicWave::new([0.0, 1.0], [0.0, 0.5]);
357//! ```
358//!
359//! ```
360//! let mut context = melody_bay::OfflineAudioContext::try_new(2, 128, 44_100).unwrap();
361//! let _filter = context.create_iir_filter([1.0], [1.0]).unwrap();
362//! ```
363//!
364//! ```compile_fail
365//! let _context = melody_bay::OfflineAudioContext::new(2, 128, 44_100);
366//! ```
367//!
368//! ```compile_fail
369//! let context = melody_bay::AudioContext::new();
370//! let _rendered = context.render_offline(44_100, 128);
371//! ```
372//!
373//! ```compile_fail
374//! let context = melody_bay::AudioContext::new();
375//! let _rendered = context.render_offline_channels(44_100, 128, 2);
376//! ```
377//!
378//! ```compile_fail
379//! let context = melody_bay::AudioContext::new();
380//! let _rendered = context.render_offline_seconds(44_100, 1.0);
381//! ```
382//!
383//! ```compile_fail
384//! let mut context = melody_bay::AudioContext::new();
385//! let _splitter = context.create_channel_splitter(2);
386//! ```
387//!
388//! ```compile_fail
389//! let mut context = melody_bay::AudioContext::new();
390//! let _merger = context.create_channel_merger(2);
391//! ```
392//!
393//! ```compile_fail
394//! let mut context = melody_bay::OfflineAudioContext::try_new(2, 128, 44_100).unwrap();
395//! let _splitter = context.create_channel_splitter(2);
396//! ```
397//!
398//! ```compile_fail
399//! let mut context = melody_bay::OfflineAudioContext::try_new(2, 128, 44_100).unwrap();
400//! let _merger = context.create_channel_merger(2);
401//! ```
402//!
403//! ```compile_fail
404//! let mut context = melody_bay::AudioContext::new();
405//! let _shaper = context.create_wave_shaper().curve([-1.0, 0.0, 1.0]);
406//! ```
407//!
408//! ```compile_fail
409//! let mut context = melody_bay::AudioContext::new();
410//! let buffer = melody_bay::AudioBuffer::from_mono(44_100, 1, [1.0]);
411//! let _source = context.create_buffer_source().buffer(buffer);
412//! ```
413//!
414//! ```compile_fail
415//! let _buffer = melody_bay::AudioBuffer::from_frames(44_100, &[]);
416//! ```
417//!
418//! ```compile_fail
419//! let _buffer = melody_bay::AudioBuffer::from_mono(44_100, 1, [1.0]);
420//! ```
421//!
422//! ```compile_fail
423//! let _buffer = melody_bay::AudioBuffer::from_stereo(44_100, 1, [1.0], [1.0]);
424//! ```
425//!
426//! ```compile_fail
427//! let _buffer = melody_bay::AudioBuffer::from_channels(44_100, 1, [[1.0]]);
428//! ```
429//!
430//! ```compile_fail
431//! let mut context = melody_bay::AudioContext::new();
432//! let _delay = context.create_delay().max_delay_time(0.5);
433//! ```
434//!
435//! ```compile_fail
436//! let mut context = melody_bay::AudioContext::new();
437//! let _panner = context.create_panner().cone_inner_angle(30.0);
438//! ```
439//!
440//! ```compile_fail
441//! let mut context = melody_bay::AudioContext::new();
442//! let _panner = context.create_panner().cone_outer_angle(60.0);
443//! ```
444//!
445//! ```compile_fail
446//! let mut context = melody_bay::AudioContext::new();
447//! let _panner = context.create_panner().cone_outer_gain(0.25);
448//! ```
449//!
450//! ```compile_fail
451//! let mut context = melody_bay::AudioContext::new();
452//! let _panner = context.create_panner().ref_distance(1.0);
453//! ```
454//!
455//! ```compile_fail
456//! let mut context = melody_bay::AudioContext::new();
457//! let _panner = context.create_panner().max_distance(10.0);
458//! ```
459//!
460//! ```compile_fail
461//! let mut context = melody_bay::AudioContext::new();
462//! let _panner = context.create_panner().rolloff_factor(1.0);
463//! ```
464//!
465//! ```compile_fail
466//! let mut context = melody_bay::AudioContext::new();
467//! let _source = context.create_buffer_source().loop_range(0.0, 1.0);
468//! ```
469//!
470//! ```compile_fail
471//! let mut context = melody_bay::AudioContext::new();
472//! let _osc = context.create_oscillator().start(0.0);
473//! ```
474//!
475//! ```compile_fail
476//! let mut context = melody_bay::AudioContext::new();
477//! let _osc = context.create_oscillator().stop(1.0);
478//! ```
479//!
480//! ```compile_fail
481//! let mut context = melody_bay::AudioContext::new();
482//! let _source = context.create_constant_source().start(0.0);
483//! ```
484//!
485//! ```compile_fail
486//! let mut context = melody_bay::AudioContext::new();
487//! let _source = context.create_constant_source().stop(1.0);
488//! ```
489//!
490//! ```compile_fail
491//! let mut context = melody_bay::AudioContext::new();
492//! let _source = context.create_buffer_source().start(0.0);
493//! ```
494//!
495//! ```compile_fail
496//! let mut context = melody_bay::AudioContext::new();
497//! let _source = context.create_buffer_source().start_with_offset(0.0, 0.25);
498//! ```
499//!
500//! ```compile_fail
501//! let mut context = melody_bay::AudioContext::new();
502//! let _source = context
503//!     .create_buffer_source()
504//!     .start_with_offset_and_duration(0.0, 0.25, 1.0);
505//! ```
506//!
507//! ```compile_fail
508//! let mut context = melody_bay::AudioContext::new();
509//! let _source = context.create_buffer_source().stop(1.0);
510//! ```
511
512#![forbid(unsafe_op_in_unsafe_fn)]
513
514use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
515use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, TAU};
516use std::fmt;
517use std::sync::{
518    Arc, Mutex,
519    atomic::{AtomicBool, AtomicU32, Ordering},
520};
521
522use kira::sound::{Sound, SoundData};
523use kira::{
524    Frame,
525    info::{Info, MockInfoBuilder},
526};
527
528const RENDER_QUANTUM_SIZE_USIZE: usize = 128;
529const RENDER_QUANTUM_SIZE: f64 = RENDER_QUANTUM_SIZE_USIZE as f64;
530const DETUNE_NOMINAL_LIMIT: f32 = 153_600.0;
531const BIQUAD_GAIN_MAX: f32 = 1541.0;
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
534pub(crate) struct NodeId(usize);
535
536include!("core/private.rs");
537include!("import/module_aliases.rs");
538include!("webaudio/node.rs");
539include!("webaudio/buffer.rs");
540include!("webaudio/param/kind.rs");
541include!("webaudio/context.rs");
542include!("sequencer/automation_targets.rs");
543include!("webaudio/channel_config.rs");
544include!("render/graph_state.rs");
545include!("webaudio/nodes/sources.rs");
546include!("webaudio/nodes/gain_param.rs");
547include!("webaudio/nodes/processing.rs");
548include!("webaudio/nodes/panner.rs");
549include!("webaudio/nodes/channel_private.rs");
550include!("render/compiled.rs");
551include!("sequencer/models.rs");
552include!("import/api.rs");
553include!("import/midi.rs");
554include!("import/tracker/types.rs");
555include!("import/tracker/mod_tracker.rs");
556include!("import/tracker/xm.rs");
557include!("import/tracker/effects.rs");
558include!("import/bytes.rs");
559include!("core/waveform.rs");
560include!("webaudio/param/timeline.rs");
561include!("webaudio/types.rs");
562include!("render/sample_voice.rs");
563include!("render/dsp.rs");