@weasel-js/audio 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,3 +29,112 @@ engine.play(jump, { bus: 'sfx', position: { x: 40, y: 0 } });
29
29
  Browsers start an `AudioContext` suspended until a user gesture. The engine
30
30
  resumes on the first gesture automatically; `play()` before that drops the voice
31
31
  with a dev warning rather than queueing it.
32
+
33
+ ## Notes and patterns
34
+
35
+ `playNote` plays a synthesized voice — an `OscillatorNode` under an ADSR
36
+ envelope — with no buffer at all. It is a voice like a buffer voice: the same
37
+ handle, bus pool, stealing and `cancelKey`. Pitch is hertz, a name (`'C#4'`) or
38
+ `{ midi: 61 }`. `wave` is a built-in shape or the amplitudes of harmonic
39
+ partials, built once into a `PeriodicWave` and reused. A note with a `duration`
40
+ releases on its own; one without holds until `release()`, which runs the
41
+ envelope's release from wherever it has got to.
42
+
43
+ ```ts
44
+ const pluck = { wave: [1, 0.5, 0.33], envelope: { attack: 4, decay: 120, sustain: 0.3, release: 80 } };
45
+ engine.playNote({ ...pluck, pitch: 'E4', duration: 150, bus: 'music' });
46
+ engine.playNote({ pitch: 260, duration: 80, glide: { to: 660 } });
47
+ ```
48
+
49
+ A `wave` of `{ partials }` sums sine oscillators at any ratios of the pitch,
50
+ each with its own level and an optional exponential `decay` (a time constant in
51
+ ms) — the partials of a bell, a bar or struck metal, which sit off the harmonic
52
+ series where a `PeriodicWave` cannot reach. Levels are scaled to sum to 1.
53
+ `partialPresets` holds a `bell`, `marimba` and `metal` to start from.
54
+
55
+ `playNoise` is the same kind of voice with white, pink or brown noise as its
56
+ source, looped from a buffer made once per color per context. Either voice takes
57
+ a `filter`: a biquad ahead of the envelope, whose cutoff can follow an envelope
58
+ of its own, at `frequency + amount × level`.
59
+
60
+ ```ts
61
+ engine.playNote({ pitch: 'C5', wave: partialPresets.bell, duration: 1500, envelope: { release: 800 } });
62
+ engine.playNote({ pitch: 310, wave: { partials: [{ ratio: 1, decay: 110 }, { ratio: 1.71, gain: 0.5, decay: 80 }] } });
63
+ // A thump: lowpassed noise whose cutoff falls from 4 kHz to 80 Hz.
64
+ engine.playNoise({
65
+ noise: 'white', duration: 20, envelope: { release: 120 },
66
+ filter: { frequency: 80, amount: 4000, envelope: { attack: 0, decay: 60, sustain: 0 } },
67
+ });
68
+ ```
69
+
70
+ `createPatternPlayer` is a step sequencer on top. Events sit on steps — a note
71
+ or a noise with a `length` in steps, or a buffer `sound` — and each step is
72
+ booked through `engine.schedule` only when the lookahead window reaches it, so `setTempo` and
73
+ `setEvents` take effect from the next step. A step that fires more than a step
74
+ late skips ahead in phase instead of playing what it missed in a burst.
75
+
76
+ ```ts
77
+ const player = createPatternPlayer(engine, {
78
+ tempo: 120, // four steps a beat by default
79
+ events: [
80
+ { step: 0, pitch: 'C3', length: 8, wave: 'triangle' },
81
+ { step: 4, sound: snare },
82
+ ],
83
+ });
84
+ player.start();
85
+ player.setTempo(140);
86
+ ```
87
+
88
+ ## Insert effects
89
+
90
+ Each bus has an ordered insert chain, `engine.bus(name).inserts`, between where
91
+ its voices connect and its fader, so mute, solo and the bus gain act on the
92
+ processed signal. An effect is anything with an `input` and an `output` node.
93
+ The built-ins are `createFilterEffect` (a biquad), `createReverbEffect`
94
+ (convolution from an impulse buffer, with a wet/dry mix), `createDelayEffect`
95
+ (a feedback delay with a mix) and `createCompressorEffect` (with a makeup gain).
96
+ Your own nodes go in the same way: `{ input: shaper, output: shaper }`.
97
+
98
+ ```ts
99
+ const music = engine.bus('music').inserts;
100
+ const lowpass = music.add(createFilterEffect(engine.context, { frequency: 800 }));
101
+ const room = music.add(createReverbEffect(engine.context, { impulse, mix: 0.3 }), { bypassed: true });
102
+ room.bypass(false);
103
+ room.moveTo(0);
104
+ lowpass.remove();
105
+ ```
106
+
107
+ Edits never click. `bypass` crossfades the slot's wet and dry routes. `add`,
108
+ `remove` and `moveTo` build the new route beside the old one and crossfade
109
+ between them, one splice at a time, so `slots()` shows an edit at once while the
110
+ audio follows over a few fades; `settled()` resolves when it has caught up. The
111
+ fade is `insertFadeMs`, default 15. A move is a splice out and a splice back in,
112
+ so the moved effect drops out for one fade. A removed effect's `dispose` runs
113
+ once it has left the graph.
114
+
115
+ ## Streaming
116
+
117
+ `load` and `decode` hold a whole sound in memory, which is wrong for a long
118
+ music track. `engine.stream` plays one from an `HTMLMediaElement`, or a URL it
119
+ makes one for, through a `MediaElementAudioSourceNode`:
120
+
121
+ ```ts
122
+ const bed = engine.stream('/music/theme.ogg', { bus: 'music', loop: true, gain: 0.6 });
123
+ bed.stop(800);
124
+ ```
125
+
126
+ It is a voice like the others: a `VoiceHandle`, the bus's inserts and voice
127
+ pool, `cancelKey`, `onDone`, position and pan. What does not carry over:
128
+
129
+ - **No `when`.** The element starts once it has buffered enough; the start is
130
+ not sample-accurate and cannot be booked ahead on the audio clock.
131
+ - **No `detune`.** `rate` is the element's `playbackRate`, which keeps pitch
132
+ unless you set the element's `preservesPitch` to false.
133
+ - **One stream per element.** An element can be routed into a graph once, and
134
+ has one playhead, so streaming an element that is already playing stops the
135
+ earlier voice.
136
+ - **Cross-origin media needs CORS.** Pass `crossOrigin: 'anonymous'` for a URL
137
+ on another origin, or the graph receives silence.
138
+
139
+ A stream counts toward its bus's voice limit and can be stolen, so give music a
140
+ bus of its own.