waa-play 0.1.1 → 0.2.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 +40 -123
- package/dist/adapters.cjs +6 -6
- package/dist/adapters.d.cts +15 -3
- package/dist/adapters.d.ts +15 -3
- package/dist/adapters.js +1 -1
- package/dist/buffer.cjs +5 -5
- package/dist/buffer.d.cts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +1 -1
- package/dist/{chunk-T74FBKTY.js → chunk-2FGUFHZM.js} +2 -2
- package/dist/chunk-2FGUFHZM.js.map +1 -0
- package/dist/{chunk-CPAT75WD.cjs → chunk-3VTU5OX5.cjs} +2 -2
- package/dist/chunk-3VTU5OX5.cjs.map +1 -0
- package/dist/{chunk-2DL7CAEP.js → chunk-7JUVBZ6B.js} +2 -2
- package/dist/chunk-7JUVBZ6B.js.map +1 -0
- package/dist/{chunk-D5CD5KQZ.cjs → chunk-BRS7LZVH.cjs} +2 -2
- package/dist/chunk-BRS7LZVH.cjs.map +1 -0
- package/dist/{chunk-QWNV2BZ5.cjs → chunk-F6WXD3XW.cjs} +2 -2
- package/dist/chunk-F6WXD3XW.cjs.map +1 -0
- package/dist/{chunk-C2ASIYN5.js → chunk-FESPIMZM.js} +3 -7
- package/dist/chunk-FESPIMZM.js.map +1 -0
- package/dist/{chunk-GYH2JSCY.js → chunk-FY273Z3I.js} +2 -2
- package/dist/chunk-FY273Z3I.js.map +1 -0
- package/dist/{chunk-TULV7V5M.cjs → chunk-G37HMZEX.cjs} +1075 -982
- package/dist/chunk-G37HMZEX.cjs.map +1 -0
- package/dist/{chunk-V2QX5K42.js → chunk-GDBOHOGF.js} +1074 -982
- package/dist/chunk-GDBOHOGF.js.map +1 -0
- package/dist/{chunk-5J7S6QV3.cjs → chunk-HIF3UAF3.cjs} +2 -2
- package/dist/chunk-HIF3UAF3.cjs.map +1 -0
- package/dist/{chunk-CRODJ4KS.js → chunk-HTN52U23.js} +13 -6
- package/dist/chunk-HTN52U23.js.map +1 -0
- package/dist/{chunk-RWJ4EWJT.js → chunk-HYRDCTBO.js} +152 -116
- package/dist/chunk-HYRDCTBO.js.map +1 -0
- package/dist/chunk-JIHPQAEA.js +90 -0
- package/dist/chunk-JIHPQAEA.js.map +1 -0
- package/dist/chunk-KVKW7W66.cjs +148 -0
- package/dist/chunk-KVKW7W66.cjs.map +1 -0
- package/dist/{chunk-4LNVRSTM.cjs → chunk-OIY6I4TU.cjs} +3 -7
- package/dist/chunk-OIY6I4TU.cjs.map +1 -0
- package/dist/chunk-OZN5X4N6.cjs +96 -0
- package/dist/chunk-OZN5X4N6.cjs.map +1 -0
- package/dist/{chunk-CJJC6ASU.js → chunk-PL4J3NR7.js} +2 -2
- package/dist/chunk-PL4J3NR7.js.map +1 -0
- package/dist/chunk-QFJQU7TQ.js +146 -0
- package/dist/chunk-QFJQU7TQ.js.map +1 -0
- package/dist/{chunk-M5PDY5EZ.cjs → chunk-QGZGERGK.cjs} +2 -2
- package/dist/chunk-QGZGERGK.cjs.map +1 -0
- package/dist/{chunk-QFFQQMU4.cjs → chunk-VOSIA3GF.cjs} +13 -6
- package/dist/chunk-VOSIA3GF.cjs.map +1 -0
- package/dist/{chunk-PZE6HTZR.cjs → chunk-VY4UMZMJ.cjs} +154 -118
- package/dist/chunk-VY4UMZMJ.cjs.map +1 -0
- package/dist/{chunk-LETS7FKB.js → chunk-YFK7ETCF.js} +2 -2
- package/dist/chunk-YFK7ETCF.js.map +1 -0
- package/dist/context.d.cts +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/emitter.cjs +2 -2
- package/dist/emitter.js +1 -1
- package/dist/engine-7DCOERRN.js +4 -0
- package/dist/{engine-M2U4LE3F.js.map → engine-7DCOERRN.js.map} +1 -1
- package/dist/engine-ALWPAIX6.cjs +17 -0
- package/dist/{engine-5JK2FCNL.cjs.map → engine-ALWPAIX6.cjs.map} +1 -1
- package/dist/fade.cjs +5 -5
- package/dist/fade.d.cts +1 -1
- package/dist/fade.d.ts +1 -1
- package/dist/fade.js +1 -1
- package/dist/index.cjs +47 -42
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +10 -9
- package/dist/nodes.cjs +11 -11
- package/dist/nodes.js +1 -1
- package/dist/play.cjs +3 -3
- package/dist/play.d.cts +1 -1
- package/dist/play.d.ts +1 -1
- package/dist/play.js +2 -2
- package/dist/player.cjs +22 -0
- package/dist/player.cjs.map +1 -0
- package/dist/player.d.cts +64 -0
- package/dist/player.d.ts +64 -0
- package/dist/player.js +13 -0
- package/dist/player.js.map +1 -0
- package/dist/scheduler.cjs +3 -3
- package/dist/scheduler.d.cts +1 -1
- package/dist/scheduler.d.ts +1 -1
- package/dist/scheduler.js +1 -1
- package/dist/stretcher.cjs +3 -3
- package/dist/stretcher.d.cts +5 -3
- package/dist/stretcher.d.ts +5 -3
- package/dist/stretcher.js +2 -2
- package/dist/synth.cjs +4 -4
- package/dist/synth.js +1 -1
- package/dist/{types-DUrbEbPl.d.cts → types-BYC6m7Q0.d.cts} +6 -6
- package/dist/{types-DUrbEbPl.d.ts → types-BYC6m7Q0.d.ts} +6 -6
- package/dist/waveform.cjs +4 -4
- package/dist/waveform.d.cts +1 -1
- package/dist/waveform.d.ts +1 -1
- package/dist/waveform.js +1 -1
- package/package.json +19 -7
- package/dist/chunk-2DL7CAEP.js.map +0 -1
- package/dist/chunk-4LNVRSTM.cjs.map +0 -1
- package/dist/chunk-5J7S6QV3.cjs.map +0 -1
- package/dist/chunk-AGP2IRC6.js +0 -63
- package/dist/chunk-AGP2IRC6.js.map +0 -1
- package/dist/chunk-C2ASIYN5.js.map +0 -1
- package/dist/chunk-CJJC6ASU.js.map +0 -1
- package/dist/chunk-CPAT75WD.cjs.map +0 -1
- package/dist/chunk-CRODJ4KS.js.map +0 -1
- package/dist/chunk-D5CD5KQZ.cjs.map +0 -1
- package/dist/chunk-GYH2JSCY.js.map +0 -1
- package/dist/chunk-HTGOHC73.cjs +0 -69
- package/dist/chunk-HTGOHC73.cjs.map +0 -1
- package/dist/chunk-LETS7FKB.js.map +0 -1
- package/dist/chunk-M5PDY5EZ.cjs.map +0 -1
- package/dist/chunk-PZE6HTZR.cjs.map +0 -1
- package/dist/chunk-QFFQQMU4.cjs.map +0 -1
- package/dist/chunk-QWNV2BZ5.cjs.map +0 -1
- package/dist/chunk-RWJ4EWJT.js.map +0 -1
- package/dist/chunk-T74FBKTY.js.map +0 -1
- package/dist/chunk-TULV7V5M.cjs.map +0 -1
- package/dist/chunk-V2QX5K42.js.map +0 -1
- package/dist/engine-5JK2FCNL.cjs +0 -13
- package/dist/engine-M2U4LE3F.js +0 -4
package/README.md
CHANGED
|
@@ -1,163 +1,80 @@
|
|
|
1
1
|
# waa-play
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/waa-play)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
Web Audio API modules with WSOLA time-stretching, chunk-based streaming, and waveform extraction
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
waa-play は Web Audio API を用いた音声操作のためのモジュール群です。ピッチ保持タイムストレッチ、ストリーミング再生、波形抽出、AudioNode チェーン接続などの機能を提供します。
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Web Audio API の `playbackRate` で再生速度を変更するとピッチも連動して変化します。waa-play は、ピッチを保持したまま再生速度を変えたいユースケースのために作成しました。
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
この実装は完璧なものではなく、すべてのユースケースに対応するわけではないことに注意してください。
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
## Documentation & Demo
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
- **Composable** — モノリシックな Player クラスではなく、小さな関数を組み合わせて利用
|
|
19
|
-
- **Zero Dependencies** — Web Audio API のみに依存、バンドルサイズを最小化
|
|
20
|
-
- **Framework-agnostic** — React / Vue / Svelte / Vanilla JS のどれでも同じ API で動作
|
|
21
|
-
- **TypeScript-first** — 完全な型定義により、エディタ補完で快適に開発
|
|
22
|
-
- **Sample-accurate** — `AudioContext.currentTime` ベースの精密な再生位置追跡
|
|
23
|
-
- **Tree-shakeable** — 使う関数だけがバンドルに含まれる
|
|
24
|
-
- **Pitch-preserving time-stretch** — WSOLA アルゴリズムによるテンポ変更を Web Worker で処理
|
|
25
|
-
- **Chunk-based streaming** — 音声をチャンク分割で逐次処理し、低スペック環境でも安定動作
|
|
15
|
+
https://ivgtr.github.io/waa/
|
|
26
16
|
|
|
27
|
-
##
|
|
17
|
+
## ピッチ保持タイムストレッチ(WSOLA)
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
19
|
+
waa-play は WSOLA (Waveform Similarity Overlap-Add) アルゴリズムを採用しています。
|
|
20
|
+
音声信号を小さなフレームに分割し、類似した波形を重ね合わせて再構築することで、ピッチを変えずに時間的な伸縮を実現します。
|
|
32
21
|
|
|
33
|
-
|
|
22
|
+
HTML5 Audio 要素の `preservePitch` オプションと同様の効果を目指していますが、いくつかの制約があります。
|
|
23
|
+
WSOLA の処理はクライアントサイドで行われるため、CPU リソースを多く消費する可能性があります。
|
|
24
|
+
また、AudioBuffer 全体を事前に読み込む必要があるため、長い音声ファイルでは変換に時間がかかる場合があります。
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
import { createContext, loadBuffer, play, createGain } from "waa-play";
|
|
26
|
+
これらの制約に対して、Worker による並列処理やチャンクベースのストリーミングで対処しています。
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
const buffer = await loadBuffer(ctx, "/audio/track.mp3");
|
|
28
|
+
### Worker ベースの変換
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
WSOLA 変換は Web Worker 内で実行され、メインスレッドのパフォーマンスへの影響を最小限に抑えています。
|
|
31
|
+
Worker は複数生成され、変換タスクがキューイングされて効率的に処理されます。
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
console.log(`${position.toFixed(1)}s / ${duration.toFixed(1)}s`);
|
|
46
|
-
});
|
|
33
|
+
### チャンクベースのストリーミング再生
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
playback.resume();
|
|
51
|
-
```
|
|
35
|
+
変換済みの音声がチャンク単位で順次再生されます。
|
|
36
|
+
これにより、長時間の音声でもすべての変換を待つことなく、スムーズな再生が可能です。
|
|
52
37
|
|
|
53
|
-
|
|
38
|
+
### イベント通知
|
|
54
39
|
|
|
55
|
-
|
|
40
|
+
再生位置の更新や再生終了などのイベントが通知され、UI の更新や他の処理に利用できます。
|
|
56
41
|
|
|
57
|
-
|
|
58
|
-
import { createGain, createFilter, createPanner, chain } from "waa-play";
|
|
59
|
-
|
|
60
|
-
const gain = createGain(ctx, 0.8);
|
|
61
|
-
const lowpass = createFilter(ctx, { type: "lowpass", frequency: 2000 });
|
|
62
|
-
const panner = createPanner(ctx, -0.5);
|
|
63
|
-
|
|
64
|
-
chain(sourceNode, gain, lowpass, panner);
|
|
65
|
-
panner.connect(ctx.destination);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### 波形の描画
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
import { extractPeaks } from "waa-play";
|
|
42
|
+
## Quick Start
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ctx2d.fillRect(i * barWidth, canvas.height * (1 - peak), barWidth - 1, canvas.height * peak);
|
|
76
|
-
});
|
|
44
|
+
```bash
|
|
45
|
+
npm install waa-play
|
|
77
46
|
```
|
|
78
47
|
|
|
79
|
-
###
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
import { useSyncExternalStore, useCallback } from "react";
|
|
83
|
-
import { subscribeSnapshot, getSnapshot, type Playback } from "waa-play";
|
|
84
|
-
|
|
85
|
-
function usePlayback(playback: Playback | null) {
|
|
86
|
-
const subscribe = useCallback(
|
|
87
|
-
(cb: () => void) => {
|
|
88
|
-
if (!playback) return () => {};
|
|
89
|
-
return subscribeSnapshot(playback, cb);
|
|
90
|
-
},
|
|
91
|
-
[playback],
|
|
92
|
-
);
|
|
93
|
-
return useSyncExternalStore(
|
|
94
|
-
subscribe,
|
|
95
|
-
() => (playback ? getSnapshot(playback) : null),
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
```
|
|
48
|
+
### 最も簡単な使い方(WaaPlayer)
|
|
99
49
|
|
|
100
|
-
|
|
50
|
+
`WaaPlayer` が `AudioContext` を内部管理し、全モジュールの機能を統合して提供します。
|
|
101
51
|
|
|
102
52
|
```ts
|
|
103
|
-
import {
|
|
53
|
+
import { WaaPlayer } from "waa-play";
|
|
104
54
|
|
|
105
|
-
const
|
|
106
|
-
const buffer = await
|
|
55
|
+
const player = new WaaPlayer();
|
|
56
|
+
const buffer = await player.load("/audio/track.mp3");
|
|
107
57
|
|
|
108
|
-
|
|
109
|
-
const playback = play(
|
|
110
|
-
playbackRate: 0.75, // テンポ 75%(スロー再生)
|
|
111
|
-
preservePitch: true, // ピッチは変わらない
|
|
112
|
-
});
|
|
58
|
+
const gain = player.createGain(0.8);
|
|
59
|
+
const playback = player.play(buffer, { through: [gain] }); // 再生
|
|
113
60
|
|
|
114
|
-
//
|
|
115
|
-
playback.setPlaybackRate(1.25); // テンポ 125%(高速再生)
|
|
61
|
+
playback.setPlaybackRate(1.5); // 再生速度を 1.5 倍に変更(リアルタイム反映)
|
|
116
62
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (snapshot.stretcher) {
|
|
120
|
-
console.log(`変換進捗: ${(snapshot.stretcher.conversionProgress * 100).toFixed(0)}%`);
|
|
121
|
-
console.log(`バッファ: ${snapshot.stretcher.bufferHealth}`);
|
|
122
|
-
}
|
|
63
|
+
playback.on("timeupdate", ({ position, duration }) => {
|
|
64
|
+
console.log(`${position.toFixed(2)}s / ${duration.toFixed(2)}s`);
|
|
123
65
|
});
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 他のライブラリの AudioContext を使う
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
import { play, loadBuffer } from "waa-play";
|
|
130
|
-
import { Tone } from "tone";
|
|
131
66
|
|
|
132
|
-
|
|
133
|
-
const buffer = await loadBuffer(ctx, "/audio/track.mp3");
|
|
134
|
-
play(ctx, buffer);
|
|
67
|
+
playback.dispose(); // 再生停止・リソース解放
|
|
135
68
|
```
|
|
136
69
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
必要なものだけ import できます。
|
|
70
|
+
既存の `AudioContext` を渡すこともできます。これは例えば、他の AudioNode と接続したい場合に有用です。
|
|
140
71
|
|
|
141
72
|
```ts
|
|
142
|
-
|
|
143
|
-
import { loadBuffer } from "waa-play/buffer";
|
|
144
|
-
import { extractPeaks } from "waa-play/waveform";
|
|
73
|
+
const player = new WaaPlayer(existingAudioContext);
|
|
145
74
|
```
|
|
146
75
|
|
|
147
|
-
| Module | 概要 |
|
|
148
|
-
|--------|------|
|
|
149
|
-
| `context` | AudioContext のライフサイクル管理 |
|
|
150
|
-
| `buffer` | 音声ファイルのロード・デコード |
|
|
151
|
-
| `play` | 再生制御(play / pause / seek / loop / preservePitch) |
|
|
152
|
-
| `emitter` | 型安全なイベントエミッター |
|
|
153
|
-
| `nodes` | AudioNode のファクトリとチェーン接続 |
|
|
154
|
-
| `waveform` | 波形データの抽出(ピーク・RMS) |
|
|
155
|
-
| `fade` | フェード処理(in / out / crossfade) |
|
|
156
|
-
| `scheduler` | スケジューリングと BPM ベースのクロック |
|
|
157
|
-
| `synth` | 波形バッファの生成(sin / noise / click) |
|
|
158
|
-
| `adapters` | フレームワーク統合(React / Vue / Svelte) |
|
|
159
|
-
| `stretcher` | ピッチ保持タイムストレッチ(WSOLA) |
|
|
160
|
-
|
|
161
76
|
## License
|
|
162
77
|
|
|
163
|
-
MIT
|
|
78
|
+
MIT © [ivgtr](https://github.com/ivgtr)
|
|
79
|
+
|
|
80
|
+
[](https://twitter.com/ivgtr) [](LICENSE)
|
package/dist/adapters.cjs
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkOZN5X4N6_cjs = require('./chunk-OZN5X4N6.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "getSnapshot", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkOZN5X4N6_cjs.getSnapshot; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "onFrame", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkOZN5X4N6_cjs.onFrame; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "subscribeSnapshot", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkOZN5X4N6_cjs.subscribeSnapshot; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "whenEnded", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkOZN5X4N6_cjs.whenEnded; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "whenPosition", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkOZN5X4N6_cjs.whenPosition; }
|
|
26
26
|
});
|
|
27
27
|
//# sourceMappingURL=adapters.cjs.map
|
|
28
28
|
//# sourceMappingURL=adapters.cjs.map
|
package/dist/adapters.d.cts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { e as Playback, g as PlaybackSnapshot } from './types-
|
|
1
|
+
import { e as Playback, g as PlaybackSnapshot } from './types-BYC6m7Q0.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get an immutable snapshot of the current playback state.
|
|
5
5
|
* Designed for use with React's `useSyncExternalStore` or similar patterns.
|
|
6
|
+
*
|
|
7
|
+
* Always returns a referentially stable (cached) object. The cache is updated
|
|
8
|
+
* by `subscribeSnapshot` (on playback events) and `onFrame` (every animation
|
|
9
|
+
* frame), so `getSnapshot` itself never computes a fresh snapshot — it only
|
|
10
|
+
* reads or initialises the cache. This guarantees the reference-equality
|
|
11
|
+
* contract required by `useSyncExternalStore`.
|
|
6
12
|
*/
|
|
7
13
|
declare function getSnapshot(playback: Playback): PlaybackSnapshot;
|
|
8
14
|
/**
|
|
@@ -14,10 +20,16 @@ declare function getSnapshot(playback: Playback): PlaybackSnapshot;
|
|
|
14
20
|
*
|
|
15
21
|
* ```ts
|
|
16
22
|
* // React example:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
23
|
+
* import { useCallback } from "react";
|
|
24
|
+
* const subscribe = useCallback(
|
|
25
|
+
* (cb: () => void) => subscribeSnapshot(playback, cb),
|
|
26
|
+
* [playback],
|
|
27
|
+
* );
|
|
28
|
+
* const snap = useCallback(
|
|
19
29
|
* () => getSnapshot(playback),
|
|
30
|
+
* [playback],
|
|
20
31
|
* );
|
|
32
|
+
* const snapshot = useSyncExternalStore(subscribe, snap, snap);
|
|
21
33
|
* ```
|
|
22
34
|
*/
|
|
23
35
|
declare function subscribeSnapshot(playback: Playback, callback: () => void): () => void;
|
package/dist/adapters.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { e as Playback, g as PlaybackSnapshot } from './types-
|
|
1
|
+
import { e as Playback, g as PlaybackSnapshot } from './types-BYC6m7Q0.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get an immutable snapshot of the current playback state.
|
|
5
5
|
* Designed for use with React's `useSyncExternalStore` or similar patterns.
|
|
6
|
+
*
|
|
7
|
+
* Always returns a referentially stable (cached) object. The cache is updated
|
|
8
|
+
* by `subscribeSnapshot` (on playback events) and `onFrame` (every animation
|
|
9
|
+
* frame), so `getSnapshot` itself never computes a fresh snapshot — it only
|
|
10
|
+
* reads or initialises the cache. This guarantees the reference-equality
|
|
11
|
+
* contract required by `useSyncExternalStore`.
|
|
6
12
|
*/
|
|
7
13
|
declare function getSnapshot(playback: Playback): PlaybackSnapshot;
|
|
8
14
|
/**
|
|
@@ -14,10 +20,16 @@ declare function getSnapshot(playback: Playback): PlaybackSnapshot;
|
|
|
14
20
|
*
|
|
15
21
|
* ```ts
|
|
16
22
|
* // React example:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
23
|
+
* import { useCallback } from "react";
|
|
24
|
+
* const subscribe = useCallback(
|
|
25
|
+
* (cb: () => void) => subscribeSnapshot(playback, cb),
|
|
26
|
+
* [playback],
|
|
27
|
+
* );
|
|
28
|
+
* const snap = useCallback(
|
|
19
29
|
* () => getSnapshot(playback),
|
|
30
|
+
* [playback],
|
|
20
31
|
* );
|
|
32
|
+
* const snapshot = useSyncExternalStore(subscribe, snap, snap);
|
|
21
33
|
* ```
|
|
22
34
|
*/
|
|
23
35
|
declare function subscribeSnapshot(playback: Playback, callback: () => void): () => void;
|
package/dist/adapters.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition } from './chunk-
|
|
1
|
+
export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition } from './chunk-JIHPQAEA.js';
|
|
2
2
|
//# sourceMappingURL=adapters.js.map
|
|
3
3
|
//# sourceMappingURL=adapters.js.map
|
package/dist/buffer.cjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk3VTU5OX5_cjs = require('./chunk-3VTU5OX5.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "getBufferInfo", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunk3VTU5OX5_cjs.getBufferInfo; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "loadBuffer", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunk3VTU5OX5_cjs.loadBuffer; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "loadBufferFromBlob", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunk3VTU5OX5_cjs.loadBufferFromBlob; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "loadBuffers", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunk3VTU5OX5_cjs.loadBuffers; }
|
|
22
22
|
});
|
|
23
23
|
//# sourceMappingURL=buffer.cjs.map
|
|
24
24
|
//# sourceMappingURL=buffer.cjs.map
|
package/dist/buffer.d.cts
CHANGED
package/dist/buffer.d.ts
CHANGED
package/dist/buffer.js
CHANGED
|
@@ -51,5 +51,5 @@ function getBufferInfo(buffer) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export { getBufferInfo, loadBuffer, loadBufferFromBlob, loadBuffers };
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-2FGUFHZM.js.map
|
|
55
|
+
//# sourceMappingURL=chunk-2FGUFHZM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/buffer.ts"],"names":[],"mappings":";AAeA,eAAsB,UAAA,CACpB,GAAA,EACA,GAAA,EACA,OAAA,EACsB;AACtB,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,MAAM,CAAA,uBAAA,EAA0B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,EACpF;AAEA,EAAA,IAAI,OAAA,EAAS,cAAc,QAAA,CAAS,IAAA,IAAQ,SAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,EAAG;AAClF,IAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAC,CAAA;AAC3D,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,IAAA,MAAM,SAAuB,EAAC;AAC9B,IAAA,IAAI,QAAA,GAAW,CAAA;AAEf,IAAA,WAAS;AACP,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AACV,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AACjB,MAAA,QAAA,IAAY,KAAA,CAAM,UAAA;AAClB,MAAA,OAAA,CAAQ,UAAA,CAAW,KAAA,GAAQ,CAAA,GAAI,QAAA,GAAW,QAAQ,CAAC,CAAA;AAAA,IACrD;AAEA,IAAA,MAAM,MAAA,GAAS,IAAI,UAAA,CAAW,QAAQ,CAAA;AACtC,IAAA,IAAI,MAAA,GAAS,CAAA;AACb,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,MAAA,CAAO,GAAA,CAAI,OAAO,MAAM,CAAA;AACxB,MAAA,MAAA,IAAU,KAAA,CAAM,UAAA;AAAA,IAClB;AAEA,IAAA,OAAO,GAAA,CAAI,eAAA,CAAgB,MAAA,CAAO,MAAM,CAAA;AAAA,EAC1C;AAEA,EAAA,MAAM,WAAA,GAAc,MAAM,QAAA,CAAS,WAAA,EAAY;AAC/C,EAAA,OAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AACxC;AAKA,eAAsB,kBAAA,CAAmB,KAAmB,IAAA,EAAkC;AAC5F,EAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,WAAA,EAAY;AAC3C,EAAA,OAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AACxC;AAaA,eAAsB,WAAA,CACpB,KACA,GAAA,EACmC;AACnC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AAClC,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,GAAA;AAAA,IAC5B,QAAQ,GAAA,CAAI,OAAO,CAAC,GAAA,EAAK,GAAG,CAAA,KAAM;AAChC,MAAA,MAAM,MAAA,GAAS,MAAM,UAAA,CAAW,GAAA,EAAK,GAAG,CAAA;AACxC,MAAA,OAAO,CAAC,KAAK,MAAM,CAAA;AAAA,IACrB,CAAC;AAAA,GACH;AACA,EAAA,OAAO,IAAI,IAAI,OAAO,CAAA;AACxB;AAKO,SAAS,cAAc,MAAA,EAAiC;AAC7D,EAAA,OAAO;AAAA,IACL,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,kBAAkB,MAAA,CAAO,gBAAA;AAAA,IACzB,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,QAAQ,MAAA,CAAO;AAAA,GACjB;AACF","file":"chunk-2FGUFHZM.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M2: Audio buffer loading utilities\n// ---------------------------------------------------------------------------\n\nimport type { BufferInfo, LoadBufferOptions } from \"./types.js\";\n\n/**\n * Fetch an audio file from a URL and decode it into an `AudioBuffer`.\n *\n * ```ts\n * const buffer = await loadBuffer(ctx, \"/audio/track.mp3\", {\n * onProgress: (p) => console.log(`${(p * 100).toFixed(0)}%`),\n * });\n * ```\n */\nexport async function loadBuffer(\n ctx: AudioContext,\n url: string,\n options?: LoadBufferOptions,\n): Promise<AudioBuffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch audio: ${response.status} ${response.statusText}`);\n }\n\n if (options?.onProgress && response.body && response.headers.get(\"content-length\")) {\n const total = Number(response.headers.get(\"content-length\"));\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let received = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n received += value.byteLength;\n options.onProgress(total > 0 ? received / total : 0);\n }\n\n const merged = new Uint8Array(received);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return ctx.decodeAudioData(merged.buffer);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return ctx.decodeAudioData(arrayBuffer);\n}\n\n/**\n * Decode an `AudioBuffer` from a `Blob` or `File`.\n */\nexport async function loadBufferFromBlob(ctx: AudioContext, blob: Blob): Promise<AudioBuffer> {\n const arrayBuffer = await blob.arrayBuffer();\n return ctx.decodeAudioData(arrayBuffer);\n}\n\n/**\n * Load multiple audio files in parallel.\n *\n * ```ts\n * const buffers = await loadBuffers(ctx, {\n * kick: \"/samples/kick.wav\",\n * snare: \"/samples/snare.wav\",\n * });\n * buffers.get(\"kick\"); // AudioBuffer\n * ```\n */\nexport async function loadBuffers(\n ctx: AudioContext,\n map: Record<string, string>,\n): Promise<Map<string, AudioBuffer>> {\n const entries = Object.entries(map);\n const results = await Promise.all(\n entries.map(async ([key, url]) => {\n const buffer = await loadBuffer(ctx, url);\n return [key, buffer] as const;\n }),\n );\n return new Map(results);\n}\n\n/**\n * Return metadata about an `AudioBuffer`.\n */\nexport function getBufferInfo(buffer: AudioBuffer): BufferInfo {\n return {\n duration: buffer.duration,\n numberOfChannels: buffer.numberOfChannels,\n sampleRate: buffer.sampleRate,\n length: buffer.length,\n };\n}\n"]}
|
|
@@ -56,5 +56,5 @@ exports.getBufferInfo = getBufferInfo;
|
|
|
56
56
|
exports.loadBuffer = loadBuffer;
|
|
57
57
|
exports.loadBufferFromBlob = loadBufferFromBlob;
|
|
58
58
|
exports.loadBuffers = loadBuffers;
|
|
59
|
-
//# sourceMappingURL=chunk-
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
59
|
+
//# sourceMappingURL=chunk-3VTU5OX5.cjs.map
|
|
60
|
+
//# sourceMappingURL=chunk-3VTU5OX5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/buffer.ts"],"names":[],"mappings":";;;AAeA,eAAsB,UAAA,CACpB,GAAA,EACA,GAAA,EACA,OAAA,EACsB;AACtB,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAChC,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,MAAM,CAAA,uBAAA,EAA0B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,EACpF;AAEA,EAAA,IAAI,OAAA,EAAS,cAAc,QAAA,CAAS,IAAA,IAAQ,SAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,EAAG;AAClF,IAAA,MAAM,QAAQ,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAC,CAAA;AAC3D,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,IAAA,MAAM,SAAuB,EAAC;AAC9B,IAAA,IAAI,QAAA,GAAW,CAAA;AAEf,IAAA,WAAS;AACP,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AACV,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AACjB,MAAA,QAAA,IAAY,KAAA,CAAM,UAAA;AAClB,MAAA,OAAA,CAAQ,UAAA,CAAW,KAAA,GAAQ,CAAA,GAAI,QAAA,GAAW,QAAQ,CAAC,CAAA;AAAA,IACrD;AAEA,IAAA,MAAM,MAAA,GAAS,IAAI,UAAA,CAAW,QAAQ,CAAA;AACtC,IAAA,IAAI,MAAA,GAAS,CAAA;AACb,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,MAAA,CAAO,GAAA,CAAI,OAAO,MAAM,CAAA;AACxB,MAAA,MAAA,IAAU,KAAA,CAAM,UAAA;AAAA,IAClB;AAEA,IAAA,OAAO,GAAA,CAAI,eAAA,CAAgB,MAAA,CAAO,MAAM,CAAA;AAAA,EAC1C;AAEA,EAAA,MAAM,WAAA,GAAc,MAAM,QAAA,CAAS,WAAA,EAAY;AAC/C,EAAA,OAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AACxC;AAKA,eAAsB,kBAAA,CAAmB,KAAmB,IAAA,EAAkC;AAC5F,EAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,WAAA,EAAY;AAC3C,EAAA,OAAO,GAAA,CAAI,gBAAgB,WAAW,CAAA;AACxC;AAaA,eAAsB,WAAA,CACpB,KACA,GAAA,EACmC;AACnC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AAClC,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,GAAA;AAAA,IAC5B,QAAQ,GAAA,CAAI,OAAO,CAAC,GAAA,EAAK,GAAG,CAAA,KAAM;AAChC,MAAA,MAAM,MAAA,GAAS,MAAM,UAAA,CAAW,GAAA,EAAK,GAAG,CAAA;AACxC,MAAA,OAAO,CAAC,KAAK,MAAM,CAAA;AAAA,IACrB,CAAC;AAAA,GACH;AACA,EAAA,OAAO,IAAI,IAAI,OAAO,CAAA;AACxB;AAKO,SAAS,cAAc,MAAA,EAAiC;AAC7D,EAAA,OAAO;AAAA,IACL,UAAU,MAAA,CAAO,QAAA;AAAA,IACjB,kBAAkB,MAAA,CAAO,gBAAA;AAAA,IACzB,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,QAAQ,MAAA,CAAO;AAAA,GACjB;AACF","file":"chunk-3VTU5OX5.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M2: Audio buffer loading utilities\n// ---------------------------------------------------------------------------\n\nimport type { BufferInfo, LoadBufferOptions } from \"./types.js\";\n\n/**\n * Fetch an audio file from a URL and decode it into an `AudioBuffer`.\n *\n * ```ts\n * const buffer = await loadBuffer(ctx, \"/audio/track.mp3\", {\n * onProgress: (p) => console.log(`${(p * 100).toFixed(0)}%`),\n * });\n * ```\n */\nexport async function loadBuffer(\n ctx: AudioContext,\n url: string,\n options?: LoadBufferOptions,\n): Promise<AudioBuffer> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch audio: ${response.status} ${response.statusText}`);\n }\n\n if (options?.onProgress && response.body && response.headers.get(\"content-length\")) {\n const total = Number(response.headers.get(\"content-length\"));\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let received = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n received += value.byteLength;\n options.onProgress(total > 0 ? received / total : 0);\n }\n\n const merged = new Uint8Array(received);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return ctx.decodeAudioData(merged.buffer);\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return ctx.decodeAudioData(arrayBuffer);\n}\n\n/**\n * Decode an `AudioBuffer` from a `Blob` or `File`.\n */\nexport async function loadBufferFromBlob(ctx: AudioContext, blob: Blob): Promise<AudioBuffer> {\n const arrayBuffer = await blob.arrayBuffer();\n return ctx.decodeAudioData(arrayBuffer);\n}\n\n/**\n * Load multiple audio files in parallel.\n *\n * ```ts\n * const buffers = await loadBuffers(ctx, {\n * kick: \"/samples/kick.wav\",\n * snare: \"/samples/snare.wav\",\n * });\n * buffers.get(\"kick\"); // AudioBuffer\n * ```\n */\nexport async function loadBuffers(\n ctx: AudioContext,\n map: Record<string, string>,\n): Promise<Map<string, AudioBuffer>> {\n const entries = Object.entries(map);\n const results = await Promise.all(\n entries.map(async ([key, url]) => {\n const buffer = await loadBuffer(ctx, url);\n return [key, buffer] as const;\n }),\n );\n return new Map(results);\n}\n\n/**\n * Return metadata about an `AudioBuffer`.\n */\nexport function getBufferInfo(buffer: AudioBuffer): BufferInfo {\n return {\n duration: buffer.duration,\n numberOfChannels: buffer.numberOfChannels,\n sampleRate: buffer.sampleRate,\n length: buffer.length,\n };\n}\n"]}
|
|
@@ -65,5 +65,5 @@ function createClock(ctx, options) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export { createClock, createScheduler };
|
|
68
|
-
//# sourceMappingURL=chunk-
|
|
69
|
-
//# sourceMappingURL=chunk-
|
|
68
|
+
//# sourceMappingURL=chunk-7JUVBZ6B.js.map
|
|
69
|
+
//# sourceMappingURL=chunk-7JUVBZ6B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scheduler.ts"],"names":[],"mappings":";AA6BO,SAAS,eAAA,CAAgB,KAAmB,OAAA,EAAuC;AACxF,EAAA,MAAM,EAAE,SAAA,GAAY,GAAA,EAAK,WAAW,EAAA,EAAG,GAAI,WAAW,EAAC;AAEvD,EAAA,MAAM,SAA2B,EAAC;AAClC,EAAA,IAAI,OAAA,GAAiD,IAAA;AAErD,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,MAAM,OAAA,GAAU,IAAI,WAAA,GAAc,SAAA;AAClC,IAAA,KAAA,IAAS,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC3C,MAAA,MAAM,GAAA,GAAM,OAAO,CAAC,CAAA;AACpB,MAAA,IAAI,GAAA,CAAI,QAAQ,OAAA,EAAS;AACvB,QAAA,GAAA,CAAI,QAAA,CAAS,IAAI,IAAI,CAAA;AACrB,QAAA,MAAA,CAAO,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA,CAAS,EAAA,EAAI,IAAA,EAAM,QAAA,EAAU;AAC3B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,EAAA,EAAI,IAAA,EAAM,UAAU,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,OAAO,EAAA,EAAI;AACT,MAAA,MAAM,MAAM,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAC/C,MAAA,IAAI,GAAA,KAAQ,EAAA,EAAI,MAAA,CAAO,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,IAAI,YAAY,IAAA,EAAM;AACtB,MAAA,OAAA,GAAU,WAAA,CAAY,MAAM,QAAQ,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,IAAA,GAAO;AACL,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,aAAA,CAAc,OAAO,CAAA;AACrB,QAAA,OAAA,GAAU,IAAA;AAAA,MACZ;AAAA,IACF,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,IAAA,CAAK,IAAA,EAAK;AACV,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAAA,IAClB;AAAA,GACF;AACF;AAsBO,SAAS,WAAA,CAAY,KAAmB,OAAA,EAA+B;AAC5E,EAAA,IAAI,GAAA,GAAM,SAAS,GAAA,IAAO,GAAA;AAC1B,EAAA,MAAM,YAAY,GAAA,CAAI,WAAA;AAEtB,EAAA,SAAS,cAAA,GAAiB;AACxB,IAAA,OAAO,EAAA,GAAK,GAAA;AAAA,EACd;AAEA,EAAA,OAAO;AAAA,IACL,WAAW,IAAA,EAAsB;AAC/B,MAAA,OAAO,SAAA,GAAY,OAAO,cAAA,EAAe;AAAA,IAC3C,CAAA;AAAA,IACA,cAAA,GAAyB;AACvB,MAAA,OAAA,CAAQ,GAAA,CAAI,WAAA,GAAc,SAAA,IAAa,cAAA,EAAe;AAAA,IACxD,CAAA;AAAA,IACA,eAAA,GAA0B;AACxB,MAAA,MAAM,OAAA,GAAU,KAAK,cAAA,EAAe;AACpC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAC9B,MAAA,OAAO,KAAK,UAAA,CAAW,IAAA,KAAS,OAAA,GAAU,IAAA,GAAO,IAAI,IAAI,CAAA;AAAA,IAC3D,CAAA;AAAA,IACA,OAAO,KAAA,EAAe;AACpB,MAAA,GAAA,GAAM,KAAA;AAAA,IACR,CAAA;AAAA,IACA,MAAA,GAAiB;AACf,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACF","file":"chunk-7JUVBZ6B.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M8: Scheduler & Clock\n// ---------------------------------------------------------------------------\n\nimport type { ClockOptions, ScheduledEvent, SchedulerOptions } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Scheduler\n// ---------------------------------------------------------------------------\n\nexport interface Scheduler {\n /** Schedule a callback at a specific AudioContext time. */\n schedule(id: string, time: number, callback: (time: number) => void): void;\n /** Remove a scheduled event by id. */\n cancel(id: string): void;\n /** Start the scheduler loop. */\n start(): void;\n /** Stop the scheduler loop. */\n stop(): void;\n /** Dispose of all resources. */\n dispose(): void;\n}\n\n/**\n * Create a lookahead-based event scheduler.\n *\n * Uses `setInterval` to check upcoming events and fire callbacks\n * slightly before their scheduled time, enabling sample-accurate timing.\n */\nexport function createScheduler(ctx: AudioContext, options?: SchedulerOptions): Scheduler {\n const { lookahead = 0.1, interval = 25 } = options ?? {};\n\n const events: ScheduledEvent[] = [];\n let timerId: ReturnType<typeof setInterval> | null = null;\n\n function tick() {\n const horizon = ctx.currentTime + lookahead;\n for (let i = events.length - 1; i >= 0; i--) {\n const evt = events[i]!;\n if (evt.time <= horizon) {\n evt.callback(evt.time);\n events.splice(i, 1);\n }\n }\n }\n\n return {\n schedule(id, time, callback) {\n events.push({ id, time, callback });\n },\n cancel(id) {\n const idx = events.findIndex((e) => e.id === id);\n if (idx !== -1) events.splice(idx, 1);\n },\n start() {\n if (timerId !== null) return;\n timerId = setInterval(tick, interval);\n },\n stop() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n },\n dispose() {\n this.stop();\n events.length = 0;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Clock\n// ---------------------------------------------------------------------------\n\nexport interface Clock {\n /** Convert a beat number to AudioContext time (seconds). */\n beatToTime(beat: number): number;\n /** Get the current beat based on AudioContext time. */\n getCurrentBeat(): number;\n /** Get the AudioContext time of the next beat boundary. */\n getNextBeatTime(): number;\n /** Update BPM. */\n setBpm(bpm: number): void;\n /** Get current BPM. */\n getBpm(): number;\n}\n\n/**\n * Create a BPM-based clock tied to an `AudioContext`.\n */\nexport function createClock(ctx: AudioContext, options?: ClockOptions): Clock {\n let bpm = options?.bpm ?? 120;\n const startTime = ctx.currentTime;\n\n function secondsPerBeat() {\n return 60 / bpm;\n }\n\n return {\n beatToTime(beat: number): number {\n return startTime + beat * secondsPerBeat();\n },\n getCurrentBeat(): number {\n return (ctx.currentTime - startTime) / secondsPerBeat();\n },\n getNextBeatTime(): number {\n const current = this.getCurrentBeat();\n const next = Math.ceil(current);\n return this.beatToTime(next === current ? next + 1 : next);\n },\n setBpm(value: number) {\n bpm = value;\n },\n getBpm(): number {\n return bpm;\n },\n };\n}\n"]}
|
|
@@ -68,5 +68,5 @@ function createClock(ctx, options) {
|
|
|
68
68
|
|
|
69
69
|
exports.createClock = createClock;
|
|
70
70
|
exports.createScheduler = createScheduler;
|
|
71
|
-
//# sourceMappingURL=chunk-
|
|
72
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-BRS7LZVH.cjs.map
|
|
72
|
+
//# sourceMappingURL=chunk-BRS7LZVH.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scheduler.ts"],"names":[],"mappings":";;;AA6BO,SAAS,eAAA,CAAgB,KAAmB,OAAA,EAAuC;AACxF,EAAA,MAAM,EAAE,SAAA,GAAY,GAAA,EAAK,WAAW,EAAA,EAAG,GAAI,WAAW,EAAC;AAEvD,EAAA,MAAM,SAA2B,EAAC;AAClC,EAAA,IAAI,OAAA,GAAiD,IAAA;AAErD,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,MAAM,OAAA,GAAU,IAAI,WAAA,GAAc,SAAA;AAClC,IAAA,KAAA,IAAS,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC3C,MAAA,MAAM,GAAA,GAAM,OAAO,CAAC,CAAA;AACpB,MAAA,IAAI,GAAA,CAAI,QAAQ,OAAA,EAAS;AACvB,QAAA,GAAA,CAAI,QAAA,CAAS,IAAI,IAAI,CAAA;AACrB,QAAA,MAAA,CAAO,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA,CAAS,EAAA,EAAI,IAAA,EAAM,QAAA,EAAU;AAC3B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,EAAA,EAAI,IAAA,EAAM,UAAU,CAAA;AAAA,IACpC,CAAA;AAAA,IACA,OAAO,EAAA,EAAI;AACT,MAAA,MAAM,MAAM,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAC/C,MAAA,IAAI,GAAA,KAAQ,EAAA,EAAI,MAAA,CAAO,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,IAAI,YAAY,IAAA,EAAM;AACtB,MAAA,OAAA,GAAU,WAAA,CAAY,MAAM,QAAQ,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,IAAA,GAAO;AACL,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,aAAA,CAAc,OAAO,CAAA;AACrB,QAAA,OAAA,GAAU,IAAA;AAAA,MACZ;AAAA,IACF,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,IAAA,CAAK,IAAA,EAAK;AACV,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAAA,IAClB;AAAA,GACF;AACF;AAsBO,SAAS,WAAA,CAAY,KAAmB,OAAA,EAA+B;AAC5E,EAAA,IAAI,GAAA,GAAM,SAAS,GAAA,IAAO,GAAA;AAC1B,EAAA,MAAM,YAAY,GAAA,CAAI,WAAA;AAEtB,EAAA,SAAS,cAAA,GAAiB;AACxB,IAAA,OAAO,EAAA,GAAK,GAAA;AAAA,EACd;AAEA,EAAA,OAAO;AAAA,IACL,WAAW,IAAA,EAAsB;AAC/B,MAAA,OAAO,SAAA,GAAY,OAAO,cAAA,EAAe;AAAA,IAC3C,CAAA;AAAA,IACA,cAAA,GAAyB;AACvB,MAAA,OAAA,CAAQ,GAAA,CAAI,WAAA,GAAc,SAAA,IAAa,cAAA,EAAe;AAAA,IACxD,CAAA;AAAA,IACA,eAAA,GAA0B;AACxB,MAAA,MAAM,OAAA,GAAU,KAAK,cAAA,EAAe;AACpC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAC9B,MAAA,OAAO,KAAK,UAAA,CAAW,IAAA,KAAS,OAAA,GAAU,IAAA,GAAO,IAAI,IAAI,CAAA;AAAA,IAC3D,CAAA;AAAA,IACA,OAAO,KAAA,EAAe;AACpB,MAAA,GAAA,GAAM,KAAA;AAAA,IACR,CAAA;AAAA,IACA,MAAA,GAAiB;AACf,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACF","file":"chunk-BRS7LZVH.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M8: Scheduler & Clock\n// ---------------------------------------------------------------------------\n\nimport type { ClockOptions, ScheduledEvent, SchedulerOptions } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Scheduler\n// ---------------------------------------------------------------------------\n\nexport interface Scheduler {\n /** Schedule a callback at a specific AudioContext time. */\n schedule(id: string, time: number, callback: (time: number) => void): void;\n /** Remove a scheduled event by id. */\n cancel(id: string): void;\n /** Start the scheduler loop. */\n start(): void;\n /** Stop the scheduler loop. */\n stop(): void;\n /** Dispose of all resources. */\n dispose(): void;\n}\n\n/**\n * Create a lookahead-based event scheduler.\n *\n * Uses `setInterval` to check upcoming events and fire callbacks\n * slightly before their scheduled time, enabling sample-accurate timing.\n */\nexport function createScheduler(ctx: AudioContext, options?: SchedulerOptions): Scheduler {\n const { lookahead = 0.1, interval = 25 } = options ?? {};\n\n const events: ScheduledEvent[] = [];\n let timerId: ReturnType<typeof setInterval> | null = null;\n\n function tick() {\n const horizon = ctx.currentTime + lookahead;\n for (let i = events.length - 1; i >= 0; i--) {\n const evt = events[i]!;\n if (evt.time <= horizon) {\n evt.callback(evt.time);\n events.splice(i, 1);\n }\n }\n }\n\n return {\n schedule(id, time, callback) {\n events.push({ id, time, callback });\n },\n cancel(id) {\n const idx = events.findIndex((e) => e.id === id);\n if (idx !== -1) events.splice(idx, 1);\n },\n start() {\n if (timerId !== null) return;\n timerId = setInterval(tick, interval);\n },\n stop() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n },\n dispose() {\n this.stop();\n events.length = 0;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Clock\n// ---------------------------------------------------------------------------\n\nexport interface Clock {\n /** Convert a beat number to AudioContext time (seconds). */\n beatToTime(beat: number): number;\n /** Get the current beat based on AudioContext time. */\n getCurrentBeat(): number;\n /** Get the AudioContext time of the next beat boundary. */\n getNextBeatTime(): number;\n /** Update BPM. */\n setBpm(bpm: number): void;\n /** Get current BPM. */\n getBpm(): number;\n}\n\n/**\n * Create a BPM-based clock tied to an `AudioContext`.\n */\nexport function createClock(ctx: AudioContext, options?: ClockOptions): Clock {\n let bpm = options?.bpm ?? 120;\n const startTime = ctx.currentTime;\n\n function secondsPerBeat() {\n return 60 / bpm;\n }\n\n return {\n beatToTime(beat: number): number {\n return startTime + beat * secondsPerBeat();\n },\n getCurrentBeat(): number {\n return (ctx.currentTime - startTime) / secondsPerBeat();\n },\n getNextBeatTime(): number {\n const current = this.getCurrentBeat();\n const next = Math.ceil(current);\n return this.beatToTime(next === current ? next + 1 : next);\n },\n setBpm(value: number) {\n bpm = value;\n },\n getBpm(): number {\n return bpm;\n },\n };\n}\n"]}
|
|
@@ -33,5 +33,5 @@ function createClickBuffer(ctx, frequency, duration) {
|
|
|
33
33
|
exports.createClickBuffer = createClickBuffer;
|
|
34
34
|
exports.createNoiseBuffer = createNoiseBuffer;
|
|
35
35
|
exports.createSineBuffer = createSineBuffer;
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
37
|
-
//# sourceMappingURL=chunk-
|
|
36
|
+
//# sourceMappingURL=chunk-F6WXD3XW.cjs.map
|
|
37
|
+
//# sourceMappingURL=chunk-F6WXD3XW.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/synth.ts"],"names":[],"mappings":";;;AAQO,SAAS,gBAAA,CACd,GAAA,EACA,SAAA,EACA,QAAA,EACa;AACb,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,aAAa,QAAQ,CAAA;AAClD,EAAA,MAAM,SAAS,GAAA,CAAI,YAAA,CAAa,CAAA,EAAG,MAAA,EAAQ,IAAI,UAAU,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AAEpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAK,CAAA,GAAI,KAAK,EAAA,GAAK,SAAA,GAAY,CAAA,GAAK,GAAA,CAAI,UAAU,CAAA;AAAA,EACnE;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,iBAAA,CAAkB,KAAmB,QAAA,EAA+B;AAClF,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,aAAa,QAAQ,CAAA;AAClD,EAAA,MAAM,SAAS,GAAA,CAAI,YAAA,CAAa,CAAA,EAAG,MAAA,EAAQ,IAAI,UAAU,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AAEpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,IAAA,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,CAAA;AAAA,EAChC;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,iBAAA,CACd,GAAA,EACA,SAAA,EACA,QAAA,EACa;AACb,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,aAAa,QAAQ,CAAA;AAClD,EAAA,MAAM,SAAS,GAAA,CAAI,YAAA,CAAa,CAAA,EAAG,MAAA,EAAQ,IAAI,UAAU,CAAA;AACzD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,cAAA,CAAe,CAAC,CAAA;AAEpC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAE/B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAK,EAAA,GAAK,IAAK,MAAM,CAAA;AAC3C,IAAA,IAAA,CAAK,CAAC,CAAA,GAAI,QAAA,GAAW,IAAA,CAAK,GAAA,CAAK,CAAA,GAAI,IAAA,CAAK,EAAA,GAAK,SAAA,GAAY,CAAA,GAAK,GAAA,CAAI,UAAU,CAAA;AAAA,EAC9E;AAEA,EAAA,OAAO,MAAA;AACT","file":"chunk-F6WXD3XW.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M9: Buffer synthesis\n// ---------------------------------------------------------------------------\n\n/**\n * Create an `AudioBuffer` containing a sine wave.\n * Useful for test tones and debugging.\n */\nexport function createSineBuffer(\n ctx: AudioContext,\n frequency: number,\n duration: number,\n): AudioBuffer {\n const length = Math.ceil(ctx.sampleRate * duration);\n const buffer = ctx.createBuffer(1, length, ctx.sampleRate);\n const data = buffer.getChannelData(0);\n\n for (let i = 0; i < length; i++) {\n data[i] = Math.sin((2 * Math.PI * frequency * i) / ctx.sampleRate);\n }\n\n return buffer;\n}\n\n/**\n * Create an `AudioBuffer` containing white noise.\n */\nexport function createNoiseBuffer(ctx: AudioContext, duration: number): AudioBuffer {\n const length = Math.ceil(ctx.sampleRate * duration);\n const buffer = ctx.createBuffer(1, length, ctx.sampleRate);\n const data = buffer.getChannelData(0);\n\n for (let i = 0; i < length; i++) {\n data[i] = Math.random() * 2 - 1;\n }\n\n return buffer;\n}\n\n/**\n * Create an `AudioBuffer` containing a short click/impulse.\n */\nexport function createClickBuffer(\n ctx: AudioContext,\n frequency: number,\n duration: number,\n): AudioBuffer {\n const length = Math.ceil(ctx.sampleRate * duration);\n const buffer = ctx.createBuffer(1, length, ctx.sampleRate);\n const data = buffer.getChannelData(0);\n\n for (let i = 0; i < length; i++) {\n // Exponential decay envelope.\n const envelope = Math.exp((-5 * i) / length);\n data[i] = envelope * Math.sin((2 * Math.PI * frequency * i) / ctx.sampleRate);\n }\n\n return buffer;\n}\n"]}
|
|
@@ -7,10 +7,7 @@ function applyRamp(node, from, to, duration, curve) {
|
|
|
7
7
|
param.setValueAtTime(from, now);
|
|
8
8
|
switch (curve) {
|
|
9
9
|
case "exponential":
|
|
10
|
-
param.exponentialRampToValueAtTime(
|
|
11
|
-
Math.max(to, EXP_MIN),
|
|
12
|
-
now + duration
|
|
13
|
-
);
|
|
10
|
+
param.exponentialRampToValueAtTime(Math.max(to, EXP_MIN), now + duration);
|
|
14
11
|
break;
|
|
15
12
|
case "equal-power": {
|
|
16
13
|
const steps = Math.max(Math.ceil(duration * 100), 2);
|
|
@@ -23,7 +20,6 @@ function applyRamp(node, from, to, duration, curve) {
|
|
|
23
20
|
param.setValueCurveAtTime(values, now, duration);
|
|
24
21
|
break;
|
|
25
22
|
}
|
|
26
|
-
case "linear":
|
|
27
23
|
default:
|
|
28
24
|
param.linearRampToValueAtTime(to, now + duration);
|
|
29
25
|
break;
|
|
@@ -63,5 +59,5 @@ function autoFade(playback, gain, options) {
|
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
export { autoFade, crossfade, fadeIn, fadeOut };
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
67
|
-
//# sourceMappingURL=chunk-
|
|
62
|
+
//# sourceMappingURL=chunk-FESPIMZM.js.map
|
|
63
|
+
//# sourceMappingURL=chunk-FESPIMZM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fade.ts"],"names":[],"mappings":";AAaA,IAAM,OAAA,GAAU,IAAA;AAEhB,SAAS,SAAA,CACP,IAAA,EACA,IAAA,EACA,EAAA,EACA,UACA,KAAA,EACM;AACN,EAAA,MAAM,QAAQ,IAAA,CAAK,IAAA;AACnB,EAAA,MAAM,GAAA,GAAM,KAAK,OAAA,CAAQ,WAAA;AACzB,EAAA,KAAA,CAAM,sBAAsB,GAAG,CAAA;AAC/B,EAAA,KAAA,CAAM,cAAA,CAAe,MAAM,GAAG,CAAA;AAE9B,EAAA,QAAQ,KAAA;AAAO,IACb,KAAK,aAAA;AACH,MAAA,KAAA,CAAM,6BAA6B,IAAA,CAAK,GAAA,CAAI,IAAI,OAAO,CAAA,EAAG,MAAM,QAAQ,CAAA;AACxE,MAAA;AAAA,IACF,KAAK,aAAA,EAAe;AAElB,MAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,IAAA,CAAK,KAAK,QAAA,GAAW,GAAG,GAAG,CAAC,CAAA;AACnD,MAAA,MAAM,MAAA,GAAS,IAAI,YAAA,CAAa,KAAK,CAAA;AACrC,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,QAAA,MAAM,CAAA,GAAI,KAAK,KAAA,GAAQ,CAAA,CAAA;AACvB,QAAA,MAAM,IAAA,GAAO,QAAQ,EAAA,GAAK,IAAA,IAAQ,KAAK,GAAA,CAAK,CAAA,GAAI,IAAA,CAAK,EAAA,GAAM,CAAC,CAAA;AAC5D,QAAA,MAAA,CAAO,CAAC,CAAA,GAAI,IAAA;AAAA,MACd;AACA,MAAA,KAAA,CAAM,mBAAA,CAAoB,MAAA,EAAQ,GAAA,EAAK,QAAQ,CAAA;AAC/C,MAAA;AAAA,IACF;AAAA,IACA;AACE,MAAA,KAAA,CAAM,uBAAA,CAAwB,EAAA,EAAI,GAAA,GAAM,QAAQ,CAAA;AAChD,MAAA;AAAA;AAEN;AAKO,SAAS,MAAA,CAAO,IAAA,EAAgB,MAAA,EAAgB,OAAA,EAA6B;AAClF,EAAA,MAAM,EAAE,QAAA,GAAW,CAAA,EAAG,QAAQ,QAAA,EAAS,GAAI,WAAW,EAAC;AACvD,EAAA,SAAA,CAAU,IAAA,EAAM,CAAA,EAAG,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAC5C;AAKO,SAAS,OAAA,CAAQ,MAAgB,OAAA,EAA6B;AACnE,EAAA,MAAM,EAAE,QAAA,GAAW,CAAA,EAAG,QAAQ,QAAA,EAAS,GAAI,WAAW,EAAC;AACvD,EAAA,SAAA,CAAU,MAAM,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,CAAA,EAAG,UAAU,KAAK,CAAA;AACrD;AAKO,SAAS,SAAA,CAAU,KAAA,EAAiB,KAAA,EAAiB,OAAA,EAAkC;AAC5F,EAAA,MAAM,EAAE,QAAA,GAAW,CAAA,EAAG,QAAQ,QAAA,EAAS,GAAI,WAAW,EAAC;AACvD,EAAA,SAAA,CAAU,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAA,EAAG,UAAU,KAAK,CAAA;AACrD,EAAA,SAAA,CAAU,OAAO,CAAA,EAAG,KAAA,CAAM,KAAK,KAAA,IAAS,CAAA,EAAG,UAAU,KAAK,CAAA;AAC5D;AAMO,SAAS,QAAA,CACd,QAAA,EACA,IAAA,EACA,OAAA,EACY;AACZ,EAAA,MAAM;AAAA,IACJ,QAAQ,cAAA,GAAiB,CAAA;AAAA,IACzB,SAAS,eAAA,GAAkB,CAAA;AAAA,IAC3B,KAAA,GAAQ;AAAA,GACV,GAAI,WAAW,EAAC;AAEhB,EAAA,MAAM,QAAA,GAAW,SAAS,WAAA,EAAY;AACtC,EAAA,IAAI,gBAAA,GAAmB,KAAA;AAGvB,EAAA,IAAI,iBAAiB,CAAA,EAAG;AACtB,IAAA,SAAA,CAAU,IAAA,EAAM,CAAA,EAAG,CAAA,EAAG,cAAA,EAAgB,KAAK,CAAA;AAAA,EAC7C;AAEA,EAAA,MAAM,QAAQ,QAAA,CAAS,EAAA,CAAG,cAAc,CAAC,EAAE,UAAS,KAAM;AACxD,IAAA,IAAI,kBAAkB,CAAA,IAAK,CAAC,gBAAA,IAAoB,QAAA,IAAY,WAAW,eAAA,EAAiB;AACtF,MAAA,gBAAA,GAAmB,IAAA;AACnB,MAAA,SAAA,CAAU,MAAM,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,CAAA,EAAG,iBAAiB,KAAK,CAAA;AAAA,IAC5D;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAO,KAAA;AACT","file":"chunk-FESPIMZM.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M7: Fade utilities\n// ---------------------------------------------------------------------------\n\nimport type {\n AutoFadeOptions,\n CrossfadeOptions,\n FadeCurve,\n FadeOptions,\n Playback,\n} from \"./types.js\";\n\n/** Minimum value used for exponential ramps (cannot ramp to/from 0). */\nconst EXP_MIN = 0.0001;\n\nfunction applyRamp(\n node: GainNode,\n from: number,\n to: number,\n duration: number,\n curve: FadeCurve,\n): void {\n const param = node.gain;\n const now = node.context.currentTime;\n param.cancelScheduledValues(now);\n param.setValueAtTime(from, now);\n\n switch (curve) {\n case \"exponential\":\n param.exponentialRampToValueAtTime(Math.max(to, EXP_MIN), now + duration);\n break;\n case \"equal-power\": {\n // Approximate equal-power with a setValueCurveAtTime.\n const steps = Math.max(Math.ceil(duration * 100), 2);\n const values = new Float32Array(steps);\n for (let i = 0; i < steps; i++) {\n const t = i / (steps - 1);\n const gain = from + (to - from) * Math.sin((t * Math.PI) / 2);\n values[i] = gain;\n }\n param.setValueCurveAtTime(values, now, duration);\n break;\n }\n default:\n param.linearRampToValueAtTime(to, now + duration);\n break;\n }\n}\n\n/**\n * Fade a `GainNode` in from 0 to `target`.\n */\nexport function fadeIn(gain: GainNode, target: number, options?: FadeOptions): void {\n const { duration = 1, curve = \"linear\" } = options ?? {};\n applyRamp(gain, 0, target, duration, curve);\n}\n\n/**\n * Fade a `GainNode` out to 0.\n */\nexport function fadeOut(gain: GainNode, options?: FadeOptions): void {\n const { duration = 1, curve = \"linear\" } = options ?? {};\n applyRamp(gain, gain.gain.value, 0, duration, curve);\n}\n\n/**\n * Crossfade between two `GainNode`s.\n */\nexport function crossfade(gainA: GainNode, gainB: GainNode, options?: CrossfadeOptions): void {\n const { duration = 1, curve = \"linear\" } = options ?? {};\n applyRamp(gainA, gainA.gain.value, 0, duration, curve);\n applyRamp(gainB, 0, gainA.gain.value || 1, duration, curve);\n}\n\n/**\n * Automatically apply fade-in at start and/or fade-out near end of a Playback.\n * Returns a cleanup function.\n */\nexport function autoFade(\n playback: Playback,\n gain: GainNode,\n options?: AutoFadeOptions,\n): () => void {\n const {\n fadeIn: fadeInDuration = 0,\n fadeOut: fadeOutDuration = 0,\n curve = \"linear\",\n } = options ?? {};\n\n const duration = playback.getDuration();\n let fadeOutScheduled = false;\n\n // Apply fade-in immediately if playing from the start.\n if (fadeInDuration > 0) {\n applyRamp(gain, 0, 1, fadeInDuration, curve);\n }\n\n const unsub = playback.on(\"timeupdate\", ({ position }) => {\n if (fadeOutDuration > 0 && !fadeOutScheduled && position >= duration - fadeOutDuration) {\n fadeOutScheduled = true;\n applyRamp(gain, gain.gain.value, 0, fadeOutDuration, curve);\n }\n });\n\n return unsub;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/emitter.ts"],"names":[],"mappings":";AA6BO,SAAS,aAAA,GAEK;AACnB,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAA8C;AAEpE,EAAA,SAAS,OAA+B,KAAA,EAAU;AAChD,IAAA,IAAI,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,GAAA,uBAAU,GAAA,EAAI;AACd,MAAA,SAAA,CAAU,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,EAAA,CAA2B,OAAU,OAAA,EAAgD;AACnF,MAAA,MAAM,GAAA,GAAM,OAAO,KAAK,CAAA;AACxB,MAAA,GAAA,CAAI,IAAI,OAAgC,CAAA;AACxC,MAAA,OAAO,MAAM;AACX,QAAA,GAAA,CAAI,OAAO,OAAgC,CAAA;AAAA,MAC7C,CAAA;AAAA,IACF,CAAA;AAAA,IAEA,GAAA,CAA4B,OAAU,OAAA,EAA0C;AAC9E,MAAA,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,MAAA,CAAO,OAAgC,CAAA;AAAA,IAC/D,CAAA;AAAA,IAEA,IAAA,CAA6B,OAAU,IAAA,EAAuB;AAC5D,MAAA,MAAM,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC/B,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,KAAA,MAAW,WAAW,GAAA,EAAK;AACzB,QAAA,OAAA,CAAQ,IAAa,CAAA;AAAA,MACvB;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,KAAA,EAA4B;AAChC,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,SAAA,CAAU,OAAO,KAAK,CAAA;AAAA,MACxB,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,KAAA,EAAM;AAAA,MAClB;AAAA,IACF;AAAA,GACF;AACF","file":"chunk-FY273Z3I.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M4: Lightweight type-safe event emitter\n// ---------------------------------------------------------------------------\n\n/** A minimal, type-safe event emitter. */\nexport interface Emitter<Events extends Record<string, any> = Record<string, unknown>> {\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void): () => void;\n\n /** Unsubscribe a handler from an event. */\n off<K extends keyof Events>(event: K, handler: (data: Events[K]) => void): void;\n\n /** Emit an event with data. */\n emit<K extends keyof Events>(event: K, data: Events[K]): void;\n\n /** Remove all listeners (optionally for a specific event). */\n clear(event?: keyof Events): void;\n}\n\n/**\n * Create a lightweight, type-safe event emitter.\n *\n * ```ts\n * const emitter = createEmitter<{ tick: number; done: void }>();\n * const unsub = emitter.on(\"tick\", (n) => console.log(n));\n * emitter.emit(\"tick\", 42);\n * unsub();\n * ```\n */\nexport function createEmitter<\n Events extends Record<string, any> = Record<string, unknown>,\n>(): Emitter<Events> {\n const listeners = new Map<keyof Events, Set<(data: never) => void>>();\n\n function getSet<K extends keyof Events>(event: K) {\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n return set;\n }\n\n return {\n on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void): () => void {\n const set = getSet(event);\n set.add(handler as (data: never) => void);\n return () => {\n set.delete(handler as (data: never) => void);\n };\n },\n\n off<K extends keyof Events>(event: K, handler: (data: Events[K]) => void): void {\n listeners.get(event)?.delete(handler as (data: never) => void);\n },\n\n emit<K extends keyof Events>(event: K, data: Events[K]): void {\n const set = listeners.get(event);\n if (!set) return;\n for (const handler of set) {\n handler(data as never);\n }\n },\n\n clear(event?: keyof Events): void {\n if (event !== undefined) {\n listeners.delete(event);\n } else {\n listeners.clear();\n }\n },\n };\n}\n"]}
|