@jadujoel/web-audio-clip-node 0.1.5 → 0.1.7

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.
Files changed (49) hide show
  1. package/README.md +127 -36
  2. package/dist/audio/ClipNode.d.ts +2 -0
  3. package/dist/audio/ClipNode.js +7 -2
  4. package/dist/audio/processor-code.d.ts +1 -1
  5. package/dist/audio/processor-code.js +1 -1
  6. package/dist/audio/processor-kernel.js +4 -1
  7. package/dist/audio/processor.js +11 -2
  8. package/dist/audio/version.d.ts +1 -1
  9. package/dist/audio/version.js +1 -1
  10. package/dist/audio/workletUrl.js +2 -2
  11. package/dist/components/AudioControl.js +4 -4
  12. package/dist/components/ControlSection.js +1 -1
  13. package/dist/components/DetuneControl.js +2 -2
  14. package/dist/components/FilterControl.js +2 -2
  15. package/dist/components/GainControl.js +2 -2
  16. package/dist/components/PanControl.js +2 -2
  17. package/dist/components/PlaybackRateControl.js +2 -2
  18. package/dist/components/PlayheadSlider.js +2 -2
  19. package/dist/hooks/useClipNode.js +7 -7
  20. package/dist/lib-react.js +14 -14
  21. package/dist/lib.bundle.js +3 -3
  22. package/dist/lib.bundle.js.map +3 -3
  23. package/dist/lib.js +11 -11
  24. package/dist/processor.js +2 -2
  25. package/dist/processor.js.map +4 -4
  26. package/dist/store/clipStore.js +2 -2
  27. package/dist/styles.css.d.ts +3 -0
  28. package/examples/README.md +12 -4
  29. package/examples/cdn-vanilla/README.md +10 -6
  30. package/examples/cdn-vanilla/index.html +1065 -33
  31. package/examples/esm-bundler/package.json +1 -1
  32. package/examples/index.html +17 -0
  33. package/examples/react/README.md +1 -1
  34. package/examples/react/bun.lock +45 -0
  35. package/examples/react/src/App.tsx +56 -6
  36. package/examples/react/src/css.d.ts +1 -0
  37. package/examples/react/tsconfig.json +15 -0
  38. package/examples/self-hosted/package.json +2 -4
  39. package/examples/self-hosted/public/processor.js +4 -0
  40. package/examples/self-hosted/src/main.ts +1 -3
  41. package/examples/streaming/README.md +25 -0
  42. package/examples/streaming/build-worker.ts +21 -0
  43. package/examples/streaming/decode-worker.ts +308 -0
  44. package/examples/streaming/index.html +211 -0
  45. package/examples/streaming/main.ts +276 -0
  46. package/examples/streaming/package.json +12 -0
  47. package/package.json +6 -2
  48. package/examples/esm-bundler/bun.lock +0 -15
  49. package/examples/self-hosted/bun.lock +0 -15
@@ -0,0 +1,211 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ClipNode – Streaming Example</title>
7
+ <style>
8
+ :root { color-scheme: dark; }
9
+ body {
10
+ font-family: system-ui, sans-serif;
11
+ max-width: 640px;
12
+ margin: 2rem auto;
13
+ padding: 0 1rem;
14
+ color: #e2e8f0;
15
+ background: #0f172a;
16
+ }
17
+ h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
18
+ p.desc { color: #94a3b8; margin-top: 0; font-size: 0.9rem; }
19
+ label { display: block; margin-top: 1rem; font-size: 0.85rem; color: #94a3b8; }
20
+ input[type="text"] {
21
+ width: 100%; box-sizing: border-box;
22
+ padding: 0.5rem; margin-top: 0.25rem;
23
+ border: 1px solid #334155; border-radius: 6px;
24
+ background: #1e293b; color: #e2e8f0; font-size: 0.9rem;
25
+ }
26
+ select {
27
+ width: 100%; box-sizing: border-box;
28
+ padding: 0.5rem; margin-top: 0.25rem;
29
+ border: 1px solid #334155; border-radius: 6px;
30
+ background: #1e293b; color: #e2e8f0; font-size: 0.9rem;
31
+ }
32
+ .buttons { display: flex; gap: 0.5rem; margin-top: 1rem; }
33
+ button {
34
+ padding: 0.5rem 1.25rem; border: none; border-radius: 6px;
35
+ font-size: 0.9rem; cursor: pointer; font-weight: 600;
36
+ }
37
+ button:disabled { opacity: 0.4; cursor: default; }
38
+ #stream { background: #38bdf8; color: #0f172a; }
39
+ #pause { background: #facc15; color: #0f172a; }
40
+ #stop { background: #fb7185; color: #0f172a; }
41
+ .progress-wrap {
42
+ margin-top: 1rem; height: 6px; border-radius: 3px;
43
+ background: #1e293b; overflow: hidden;
44
+ }
45
+ .progress-bar {
46
+ height: 100%; width: 0%; border-radius: 3px;
47
+ background: #38bdf8; transition: width 0.15s;
48
+ }
49
+ #status {
50
+ margin-top: 0.75rem; font-size: 0.85rem; color: #94a3b8;
51
+ min-height: 1.2em;
52
+ }
53
+
54
+ /* ── Controls ─────────────────────────────────────── */
55
+ .controls {
56
+ margin-top: 1.5rem;
57
+ border-top: 1px solid #334155;
58
+ padding-top: 1rem;
59
+ }
60
+ .controls h2 {
61
+ font-size: 1rem; margin: 0 0 0.75rem;
62
+ color: #cbd5e1;
63
+ }
64
+ .control-row {
65
+ display: grid;
66
+ grid-template-columns: 110px 1fr 56px;
67
+ align-items: center;
68
+ gap: 0.5rem;
69
+ margin-bottom: 0.5rem;
70
+ }
71
+ .control-row label {
72
+ margin: 0; font-size: 0.82rem; color: #94a3b8;
73
+ text-align: right; padding-right: 0.25rem;
74
+ }
75
+ .control-row input[type="range"] {
76
+ width: 100%; accent-color: #38bdf8;
77
+ }
78
+ .control-row .val {
79
+ font-size: 0.8rem; color: #e2e8f0;
80
+ font-variant-numeric: tabular-nums;
81
+ text-align: left;
82
+ }
83
+ .control-group {
84
+ margin-bottom: 0.75rem;
85
+ }
86
+ .control-group summary {
87
+ cursor: pointer; font-size: 0.9rem;
88
+ color: #cbd5e1; font-weight: 600;
89
+ padding: 0.25rem 0;
90
+ }
91
+ .control-group[open] summary { margin-bottom: 0.5rem; }
92
+ .loop-toggle {
93
+ display: flex; align-items: center; gap: 0.5rem;
94
+ margin-bottom: 0.5rem;
95
+ }
96
+ .loop-toggle label { margin: 0; font-size: 0.85rem; }
97
+ </style>
98
+ </head>
99
+ <body>
100
+ <h1>ClipNode — Streaming</h1>
101
+ <p class="desc">
102
+ Stream &amp; decode an MP3 in a Web Worker, feeding decoded audio
103
+ directly to the AudioWorklet processor via MessagePort.
104
+ </p>
105
+
106
+ <label for="url">Audio URL</label>
107
+ <input type="text" id="url" value="https://jadujoel.github.io/web-audio-clip-node/example.mp3" />
108
+
109
+ <label for="throttle-select">Network Speed</label>
110
+ <select id="throttle-select">
111
+ <option value="0" selected>Normal (unlimited)</option>
112
+ <option value="204800">Slow (~200 KB/s)</option>
113
+ <option value="51200">Turtle (~50 KB/s)</option>
114
+ </select>
115
+
116
+ <div class="buttons">
117
+ <button id="stream">▶ Stream &amp; Play</button>
118
+ <button id="pause" disabled>⏸ Pause</button>
119
+ <button id="stop" disabled>■ Stop</button>
120
+ </div>
121
+
122
+ <div class="progress-wrap"><div class="progress-bar" id="progress"></div></div>
123
+ <p id="status">Idle</p>
124
+
125
+ <!-- ── Audio Controls ───────────────────────────────── -->
126
+ <div class="controls" id="controls" style="display:none">
127
+ <h2>Controls</h2>
128
+
129
+ <details class="control-group" open>
130
+ <summary>Volume &amp; Panning</summary>
131
+ <div class="control-row">
132
+ <label for="ctrl-gain">Gain</label>
133
+ <input type="range" id="ctrl-gain" min="0" max="2" step="0.01" value="1" />
134
+ <span class="val" id="val-gain">1.00</span>
135
+ </div>
136
+ <div class="control-row">
137
+ <label for="ctrl-pan">Pan</label>
138
+ <input type="range" id="ctrl-pan" min="-1" max="1" step="0.01" value="0" />
139
+ <span class="val" id="val-pan">C</span>
140
+ </div>
141
+ </details>
142
+
143
+ <details class="control-group" open>
144
+ <summary>Speed &amp; Pitch</summary>
145
+ <div class="control-row">
146
+ <label for="ctrl-rate">Playback Rate</label>
147
+ <input type="range" id="ctrl-rate" min="0.25" max="4" step="0.01" value="1" />
148
+ <span class="val" id="val-rate">1.00×</span>
149
+ </div>
150
+ <div class="control-row">
151
+ <label for="ctrl-detune">Detune</label>
152
+ <input type="range" id="ctrl-detune" min="-2400" max="2400" step="1" value="0" />
153
+ <span class="val" id="val-detune">0 ct</span>
154
+ </div>
155
+ </details>
156
+
157
+ <details class="control-group">
158
+ <summary>Filters</summary>
159
+ <div class="control-row">
160
+ <label for="ctrl-lowpass">Lowpass</label>
161
+ <input type="range" id="ctrl-lowpass" min="20" max="20000" step="1" value="20000" />
162
+ <span class="val" id="val-lowpass">20000 Hz</span>
163
+ </div>
164
+ <div class="control-row">
165
+ <label for="ctrl-highpass">Highpass</label>
166
+ <input type="range" id="ctrl-highpass" min="20" max="20000" step="1" value="20" />
167
+ <span class="val" id="val-highpass">20 Hz</span>
168
+ </div>
169
+ </details>
170
+
171
+ <details class="control-group">
172
+ <summary>Fades</summary>
173
+ <div class="control-row">
174
+ <label for="ctrl-fadein">Fade In</label>
175
+ <input type="range" id="ctrl-fadein" min="0" max="5" step="0.01" value="0" />
176
+ <span class="val" id="val-fadein">0.00 s</span>
177
+ </div>
178
+ <div class="control-row">
179
+ <label for="ctrl-fadeout">Fade Out</label>
180
+ <input type="range" id="ctrl-fadeout" min="0" max="5" step="0.01" value="0" />
181
+ <span class="val" id="val-fadeout">0.00 s</span>
182
+ </div>
183
+ </details>
184
+
185
+ <details class="control-group">
186
+ <summary>Loop</summary>
187
+ <div class="loop-toggle">
188
+ <input type="checkbox" id="ctrl-loop" checked />
189
+ <label for="ctrl-loop">Loop enabled</label>
190
+ </div>
191
+ <div class="control-row">
192
+ <label for="ctrl-loopstart">Loop Start</label>
193
+ <input type="range" id="ctrl-loopstart" min="0" max="120" step="0.01" value="0" />
194
+ <span class="val" id="val-loopstart">0.00 s</span>
195
+ </div>
196
+ <div class="control-row">
197
+ <label for="ctrl-loopend">Loop End</label>
198
+ <input type="range" id="ctrl-loopend" min="0" max="120" step="0.01" value="0" />
199
+ <span class="val" id="val-loopend">0.00 s</span>
200
+ </div>
201
+ <div class="control-row">
202
+ <label for="ctrl-crossfade">Crossfade</label>
203
+ <input type="range" id="ctrl-crossfade" min="0" max="5" step="0.01" value="0" />
204
+ <span class="val" id="val-crossfade">0.00 s</span>
205
+ </div>
206
+ </details>
207
+ </div>
208
+
209
+ <script type="module" src="./main.ts"></script>
210
+ </body>
211
+ </html>
@@ -0,0 +1,276 @@
1
+ import { ClipNode, getProcessorBlobUrl } from "@jadujoel/web-audio-clip-node";
2
+ import { workerCode } from "./generated/worker-code";
3
+
4
+ function getWorkerBlobUrl(): string {
5
+ const blob = new Blob([workerCode], { type: "application/javascript" });
6
+ return URL.createObjectURL(blob);
7
+ }
8
+
9
+ // ── DOM references ───────────────────────────────────────────────────
10
+ const streamBtn = document.getElementById("stream") as HTMLButtonElement;
11
+ const pauseBtn = document.getElementById("pause") as HTMLButtonElement;
12
+ const stopBtn = document.getElementById("stop") as HTMLButtonElement;
13
+ const urlInput = document.getElementById("url") as HTMLInputElement;
14
+ const throttleSelect = document.getElementById("throttle-select") as HTMLSelectElement;
15
+ const progressBar = document.getElementById("progress") as HTMLDivElement;
16
+ const statusText = document.getElementById("status") as HTMLParagraphElement;
17
+ const controlsPanel = document.getElementById("controls") as HTMLDivElement;
18
+
19
+ // Control sliders
20
+ const gainSlider = document.getElementById("ctrl-gain") as HTMLInputElement;
21
+ const panSlider = document.getElementById("ctrl-pan") as HTMLInputElement;
22
+ const rateSlider = document.getElementById("ctrl-rate") as HTMLInputElement;
23
+ const detuneSlider = document.getElementById("ctrl-detune") as HTMLInputElement;
24
+ const lowpassSlider = document.getElementById("ctrl-lowpass") as HTMLInputElement;
25
+ const highpassSlider = document.getElementById(
26
+ "ctrl-highpass",
27
+ ) as HTMLInputElement;
28
+ const fadeInSlider = document.getElementById("ctrl-fadein") as HTMLInputElement;
29
+ const fadeOutSlider = document.getElementById(
30
+ "ctrl-fadeout",
31
+ ) as HTMLInputElement;
32
+ const loopCheckbox = document.getElementById("ctrl-loop") as HTMLInputElement;
33
+ const loopStartSlider = document.getElementById(
34
+ "ctrl-loopstart",
35
+ ) as HTMLInputElement;
36
+ const loopEndSlider = document.getElementById(
37
+ "ctrl-loopend",
38
+ ) as HTMLInputElement;
39
+ const crossfadeSlider = document.getElementById(
40
+ "ctrl-crossfade",
41
+ ) as HTMLInputElement;
42
+
43
+ // Value displays
44
+ const valGain = document.getElementById("val-gain") as HTMLSpanElement;
45
+ const valPan = document.getElementById("val-pan") as HTMLSpanElement;
46
+ const valRate = document.getElementById("val-rate") as HTMLSpanElement;
47
+ const valDetune = document.getElementById("val-detune") as HTMLSpanElement;
48
+ const valLowpass = document.getElementById("val-lowpass") as HTMLSpanElement;
49
+ const valHighpass = document.getElementById("val-highpass") as HTMLSpanElement;
50
+ const valFadeIn = document.getElementById("val-fadein") as HTMLSpanElement;
51
+ const valFadeOut = document.getElementById("val-fadeout") as HTMLSpanElement;
52
+ const valLoopStart = document.getElementById(
53
+ "val-loopstart",
54
+ ) as HTMLSpanElement;
55
+ const valLoopEnd = document.getElementById("val-loopend") as HTMLSpanElement;
56
+ const valCrossfade = document.getElementById(
57
+ "val-crossfade",
58
+ ) as HTMLSpanElement;
59
+
60
+ // ── State ────────────────────────────────────────────────────────────
61
+ let ctx: AudioContext | null = null;
62
+ let clip: ClipNode | null = null;
63
+ let worker: Worker | null = null;
64
+
65
+ // ── Helpers ──────────────────────────────────────────────────────────
66
+ function setStatus(msg: string) {
67
+ statusText.textContent = msg;
68
+ }
69
+
70
+ function setProgress(ratio: number) {
71
+ progressBar.style.width = `${Math.min(100, ratio * 100).toFixed(1)}%`;
72
+ }
73
+
74
+ // ── Stream & Play ────────────────────────────────────────────────────
75
+ streamBtn.addEventListener("click", async () => {
76
+ const url = urlInput.value.trim();
77
+ if (!url) {
78
+ setStatus("Enter a URL first.");
79
+ return;
80
+ }
81
+
82
+ // Tear down previous run
83
+ if (worker) {
84
+ worker.postMessage({ type: "abort" });
85
+ worker.terminate();
86
+ worker = null;
87
+ }
88
+ if (clip) {
89
+ clip.stop();
90
+ clip.disconnect();
91
+ clip = null;
92
+ }
93
+
94
+ // Create AudioContext
95
+ if (!ctx) {
96
+ ctx = new AudioContext();
97
+ await ctx.audioWorklet.addModule(getProcessorBlobUrl());
98
+ } else {
99
+ await ctx.resume();
100
+ }
101
+
102
+ // Create ClipNode
103
+ clip = new ClipNode(ctx);
104
+ clip.loop = true;
105
+ clip.connect(ctx.destination);
106
+
107
+ // Apply current slider values to the new clip
108
+ applyControls();
109
+
110
+ // Show controls panel
111
+ controlsPanel.style.display = "";
112
+ pauseBtn.disabled = false;
113
+ stopBtn.disabled = false;
114
+
115
+ // Create MessageChannel: port1 → Worker, port2 → Processor
116
+ const channel = new MessageChannel();
117
+
118
+ // Transfer port2 to the processor via the ClipNode API (zero main-thread allocation)
119
+ clip.transferPort(channel.port2);
120
+
121
+ // Create and start decode worker from inline Blob URL
122
+ worker = new Worker(getWorkerBlobUrl());
123
+
124
+ worker.onmessage = (ev: MessageEvent) => {
125
+ const { type } = ev.data;
126
+ switch (type) {
127
+ case "progress": {
128
+ const { bytesReceived, totalBytes } = ev.data;
129
+ if (totalBytes) {
130
+ setProgress(bytesReceived / totalBytes);
131
+ setStatus(
132
+ `Downloading… ${((bytesReceived / 1024) | 0)} / ${((totalBytes / 1024) | 0)} KB`,
133
+ );
134
+ } else {
135
+ setStatus(`Downloading… ${((bytesReceived / 1024) | 0)} KB`);
136
+ }
137
+ break;
138
+ }
139
+ case "decoded": {
140
+ const { samplesDecoded } = ev.data;
141
+ if (samplesDecoded > 0 && clip && clip.state === "initial") {
142
+ clip.start();
143
+ setStatus("Streaming & playing…");
144
+ }
145
+ break;
146
+ }
147
+ case "info": {
148
+ setStatus(`Decoding: ${ev.data.sampleRate} Hz, ${ev.data.channels} ch`);
149
+ break;
150
+ }
151
+ case "done": {
152
+ setStatus(`Done — ${ev.data.samplesDecoded} samples decoded.`);
153
+ setProgress(1);
154
+ break;
155
+ }
156
+ case "error": {
157
+ setStatus(`Error: ${ev.data.message}`);
158
+ break;
159
+ }
160
+ case "aborted": {
161
+ setStatus("Aborted.");
162
+ break;
163
+ }
164
+ }
165
+ };
166
+
167
+ // Init the worker with the port and absolute URL
168
+ const absoluteUrl = new URL(url, location.href).href;
169
+ const throttle = Number(throttleSelect.value);
170
+ worker.postMessage(
171
+ { type: "init", port: channel.port1, url: absoluteUrl, throttle },
172
+ [channel.port1],
173
+ );
174
+
175
+ setStatus(throttle > 0 ? `Starting stream… (${(throttle / 1024).toFixed(0)} KB/s)` : "Starting stream…");
176
+ setProgress(0);
177
+ });
178
+
179
+ // ── Transport controls ───────────────────────────────────────────────
180
+ pauseBtn.addEventListener("click", () => {
181
+ if (!clip || !ctx) return;
182
+ if (clip.state === "playing" || clip.state === "started") {
183
+ clip.pause();
184
+ setStatus("Paused.");
185
+ } else if (clip.state === "paused") {
186
+ clip.start();
187
+ setStatus("Resumed.");
188
+ }
189
+ });
190
+
191
+ stopBtn.addEventListener("click", () => {
192
+ if (!clip) return;
193
+ clip.stop();
194
+ if (worker) {
195
+ worker.postMessage({ type: "abort" });
196
+ worker.terminate();
197
+ worker = null;
198
+ }
199
+ setProgress(0);
200
+ setStatus("Stopped.");
201
+ });
202
+
203
+ // ── Control wiring ───────────────────────────────────────────────────
204
+ function formatPan(v: number): string {
205
+ if (Math.abs(v) < 0.005) return "C";
206
+ return v < 0 ? `L ${(-v * 100).toFixed(0)}` : `R ${(v * 100).toFixed(0)}`;
207
+ }
208
+
209
+ function formatHz(v: number): string {
210
+ return v >= 1000 ? `${(v / 1000).toFixed(1)} kHz` : `${v} Hz`;
211
+ }
212
+
213
+ function applyControls() {
214
+ if (!clip) return;
215
+ clip.gain.value = Number(gainSlider.value);
216
+ clip.pan.value = Number(panSlider.value);
217
+ clip.playbackRate.value = Number(rateSlider.value);
218
+ clip.detune.value = Number(detuneSlider.value);
219
+ clip.lowpass.value = Number(lowpassSlider.value);
220
+ clip.highpass.value = Number(highpassSlider.value);
221
+ clip.fadeIn = Number(fadeInSlider.value);
222
+ clip.fadeOut = Number(fadeOutSlider.value);
223
+ clip.loop = loopCheckbox.checked;
224
+ clip.loopStart = Number(loopStartSlider.value);
225
+ clip.loopEnd = Number(loopEndSlider.value);
226
+ clip.loopCrossfade = Number(crossfadeSlider.value);
227
+ }
228
+
229
+ // Wire each slider to update the value display and the clip
230
+ gainSlider.addEventListener("input", () => {
231
+ valGain.textContent = Number(gainSlider.value).toFixed(2);
232
+ if (clip) clip.gain.value = Number(gainSlider.value);
233
+ });
234
+ panSlider.addEventListener("input", () => {
235
+ valPan.textContent = formatPan(Number(panSlider.value));
236
+ if (clip) clip.pan.value = Number(panSlider.value);
237
+ });
238
+ rateSlider.addEventListener("input", () => {
239
+ valRate.textContent = `${Number(rateSlider.value).toFixed(2)}×`;
240
+ if (clip) clip.playbackRate.value = Number(rateSlider.value);
241
+ });
242
+ detuneSlider.addEventListener("input", () => {
243
+ valDetune.textContent = `${detuneSlider.value} ct`;
244
+ if (clip) clip.detune.value = Number(detuneSlider.value);
245
+ });
246
+ lowpassSlider.addEventListener("input", () => {
247
+ valLowpass.textContent = formatHz(Number(lowpassSlider.value));
248
+ if (clip) clip.lowpass.value = Number(lowpassSlider.value);
249
+ });
250
+ highpassSlider.addEventListener("input", () => {
251
+ valHighpass.textContent = formatHz(Number(highpassSlider.value));
252
+ if (clip) clip.highpass.value = Number(highpassSlider.value);
253
+ });
254
+ fadeInSlider.addEventListener("input", () => {
255
+ valFadeIn.textContent = `${Number(fadeInSlider.value).toFixed(2)} s`;
256
+ if (clip) clip.fadeIn = Number(fadeInSlider.value);
257
+ });
258
+ fadeOutSlider.addEventListener("input", () => {
259
+ valFadeOut.textContent = `${Number(fadeOutSlider.value).toFixed(2)} s`;
260
+ if (clip) clip.fadeOut = Number(fadeOutSlider.value);
261
+ });
262
+ loopCheckbox.addEventListener("change", () => {
263
+ if (clip) clip.loop = loopCheckbox.checked;
264
+ });
265
+ loopStartSlider.addEventListener("input", () => {
266
+ valLoopStart.textContent = `${Number(loopStartSlider.value).toFixed(2)} s`;
267
+ if (clip) clip.loopStart = Number(loopStartSlider.value);
268
+ });
269
+ loopEndSlider.addEventListener("input", () => {
270
+ valLoopEnd.textContent = `${Number(loopEndSlider.value).toFixed(2)} s`;
271
+ if (clip) clip.loopEnd = Number(loopEndSlider.value);
272
+ });
273
+ crossfadeSlider.addEventListener("input", () => {
274
+ valCrossfade.textContent = `${Number(crossfadeSlider.value).toFixed(2)} s`;
275
+ if (clip) clip.loopCrossfade = Number(crossfadeSlider.value);
276
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "streaming-example",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build:worker": "bun run build-worker.ts",
7
+ "dev": "bun run build:worker && bun index.html"
8
+ },
9
+ "dependencies": {
10
+ "@jadujoel/web-audio-clip-node": "file:../.."
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jadujoel/web-audio-clip-node",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Full-featured AudioWorklet clip player with playback rate, detune, gain, pan, filters, looping, fades, crossfade, and streaming buffer support. React components included.",
6
6
  "keywords": [
@@ -30,7 +30,10 @@
30
30
  "import": "./dist/lib-react.js"
31
31
  },
32
32
  "./processor": "./dist/processor.js",
33
- "./styles.css": "./dist/styles.css"
33
+ "./styles.css": {
34
+ "types": "./dist/styles.css.d.ts",
35
+ "default": "./dist/styles.css"
36
+ }
34
37
  },
35
38
  "main": "./dist/lib.js",
36
39
  "types": "./dist/lib.d.ts",
@@ -45,6 +48,7 @@
45
48
  "build": "bun build.ts",
46
49
  "build:lib": "bun build.ts --lib",
47
50
  "dev": "bun serve.ts",
51
+ "examples": "bun examples.ts",
48
52
  "lint": "biome check",
49
53
  "test": "bun test",
50
54
  "typecheck": "tsc --noEmit",
@@ -1,15 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 0,
4
- "workspaces": {
5
- "": {
6
- "name": "esm-bundler-example",
7
- "dependencies": {
8
- "@jadujoel/web-audio-clip-node": "latest",
9
- },
10
- },
11
- },
12
- "packages": {
13
- "@jadujoel/web-audio-clip-node": ["@jadujoel/web-audio-clip-node@0.1.4", "", { "peerDependencies": { "react": ">=18", "react-dom": ">=18", "zustand": ">=4" }, "optionalPeers": ["react", "react-dom", "zustand"] }, "sha512-mQhckPRRz6fOUdqJbBatyWyhEo0zOKTaKHt5KltxF+F0o4iq7GcLhlR/iv/Qu/qn0rH+9eOlNxypDF6gG5su/w=="],
14
- }
15
- }
@@ -1,15 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 1,
4
- "workspaces": {
5
- "": {
6
- "name": "self-hosted-example",
7
- "dependencies": {
8
- "@jadujoel/web-audio-clip-node": "^0.1.1",
9
- },
10
- },
11
- },
12
- "packages": {
13
- "@jadujoel/web-audio-clip-node": ["@jadujoel/web-audio-clip-node@0.1.4", "", { "peerDependencies": { "react": ">=18", "react-dom": ">=18", "zustand": ">=4" }, "optionalPeers": ["react", "react-dom", "zustand"] }, "sha512-mQhckPRRz6fOUdqJbBatyWyhEo0zOKTaKHt5KltxF+F0o4iq7GcLhlR/iv/Qu/qn0rH+9eOlNxypDF6gG5su/w=="],
14
- }
15
- }