waa-play 0.1.0
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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/adapters.cjs +28 -0
- package/dist/adapters.cjs.map +1 -0
- package/dist/adapters.d.cts +42 -0
- package/dist/adapters.d.ts +42 -0
- package/dist/adapters.js +3 -0
- package/dist/adapters.js.map +1 -0
- package/dist/buffer.cjs +24 -0
- package/dist/buffer.cjs.map +1 -0
- package/dist/buffer.d.cts +34 -0
- package/dist/buffer.d.ts +34 -0
- package/dist/buffer.js +3 -0
- package/dist/buffer.js.map +1 -0
- package/dist/chunk-2DL7CAEP.js +69 -0
- package/dist/chunk-2DL7CAEP.js.map +1 -0
- package/dist/chunk-37CPPRLV.js +24 -0
- package/dist/chunk-37CPPRLV.js.map +1 -0
- package/dist/chunk-4LNVRSTM.cjs +72 -0
- package/dist/chunk-4LNVRSTM.cjs.map +1 -0
- package/dist/chunk-5J7S6QV3.cjs +44 -0
- package/dist/chunk-5J7S6QV3.cjs.map +1 -0
- package/dist/chunk-6UTN73HG.cjs +29 -0
- package/dist/chunk-6UTN73HG.cjs.map +1 -0
- package/dist/chunk-AGP2IRC6.js +63 -0
- package/dist/chunk-AGP2IRC6.js.map +1 -0
- package/dist/chunk-C2ASIYN5.js +67 -0
- package/dist/chunk-C2ASIYN5.js.map +1 -0
- package/dist/chunk-CJJC6ASU.js +73 -0
- package/dist/chunk-CJJC6ASU.js.map +1 -0
- package/dist/chunk-CPAT75WD.cjs +60 -0
- package/dist/chunk-CPAT75WD.cjs.map +1 -0
- package/dist/chunk-CRODJ4KS.js +71 -0
- package/dist/chunk-CRODJ4KS.js.map +1 -0
- package/dist/chunk-D5CD5KQZ.cjs +72 -0
- package/dist/chunk-D5CD5KQZ.cjs.map +1 -0
- package/dist/chunk-GYH2JSCY.js +42 -0
- package/dist/chunk-GYH2JSCY.js.map +1 -0
- package/dist/chunk-HTGOHC73.cjs +69 -0
- package/dist/chunk-HTGOHC73.cjs.map +1 -0
- package/dist/chunk-LETS7FKB.js +33 -0
- package/dist/chunk-LETS7FKB.js.map +1 -0
- package/dist/chunk-M5PDY5EZ.cjs +84 -0
- package/dist/chunk-M5PDY5EZ.cjs.map +1 -0
- package/dist/chunk-PZE6HTZR.cjs +358 -0
- package/dist/chunk-PZE6HTZR.cjs.map +1 -0
- package/dist/chunk-QFFQQMU4.cjs +75 -0
- package/dist/chunk-QFFQQMU4.cjs.map +1 -0
- package/dist/chunk-QWNV2BZ5.cjs +37 -0
- package/dist/chunk-QWNV2BZ5.cjs.map +1 -0
- package/dist/chunk-RWJ4EWJT.js +356 -0
- package/dist/chunk-RWJ4EWJT.js.map +1 -0
- package/dist/chunk-T74FBKTY.js +55 -0
- package/dist/chunk-T74FBKTY.js.map +1 -0
- package/dist/chunk-TULV7V5M.cjs +1710 -0
- package/dist/chunk-TULV7V5M.cjs.map +1 -0
- package/dist/chunk-V2QX5K42.js +1708 -0
- package/dist/chunk-V2QX5K42.js.map +1 -0
- package/dist/context.cjs +24 -0
- package/dist/context.cjs.map +1 -0
- package/dist/context.d.cts +27 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -0
- package/dist/emitter.cjs +12 -0
- package/dist/emitter.cjs.map +1 -0
- package/dist/emitter.d.cts +24 -0
- package/dist/emitter.d.ts +24 -0
- package/dist/emitter.js +3 -0
- package/dist/emitter.js.map +1 -0
- package/dist/engine-5JK2FCNL.cjs +13 -0
- package/dist/engine-5JK2FCNL.cjs.map +1 -0
- package/dist/engine-M2U4LE3F.js +4 -0
- package/dist/engine-M2U4LE3F.js.map +1 -0
- package/dist/fade.cjs +24 -0
- package/dist/fade.cjs.map +1 -0
- package/dist/fade.d.cts +21 -0
- package/dist/fade.d.ts +21 -0
- package/dist/fade.js +3 -0
- package/dist/fade.js.map +1 -0
- package/dist/index.cjs +165 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes.cjs +48 -0
- package/dist/nodes.cjs.map +1 -0
- package/dist/nodes.d.cts +61 -0
- package/dist/nodes.d.ts +61 -0
- package/dist/nodes.js +3 -0
- package/dist/nodes.js.map +1 -0
- package/dist/play.cjs +13 -0
- package/dist/play.cjs.map +1 -0
- package/dist/play.d.cts +19 -0
- package/dist/play.d.ts +19 -0
- package/dist/play.js +4 -0
- package/dist/play.js.map +1 -0
- package/dist/scheduler.cjs +16 -0
- package/dist/scheduler.cjs.map +1 -0
- package/dist/scheduler.d.cts +39 -0
- package/dist/scheduler.d.ts +39 -0
- package/dist/scheduler.js +3 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/stretcher.cjs +13 -0
- package/dist/stretcher.cjs.map +1 -0
- package/dist/stretcher.d.cts +171 -0
- package/dist/stretcher.d.ts +171 -0
- package/dist/stretcher.js +4 -0
- package/dist/stretcher.js.map +1 -0
- package/dist/synth.cjs +20 -0
- package/dist/synth.cjs.map +1 -0
- package/dist/synth.d.cts +15 -0
- package/dist/synth.d.ts +15 -0
- package/dist/synth.js +3 -0
- package/dist/synth.js.map +1 -0
- package/dist/types-DUrbEbPl.d.cts +177 -0
- package/dist/types-DUrbEbPl.d.ts +177 -0
- package/dist/waveform.cjs +20 -0
- package/dist/waveform.cjs.map +1 -0
- package/dist/waveform.d.cts +22 -0
- package/dist/waveform.d.ts +22 -0
- package/dist/waveform.js +3 -0
- package/dist/waveform.js.map +1 -0
- package/package.json +123 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ivgtr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# waa-play
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/waa-play)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
Composable Web Audio API utilities.
|
|
8
|
+
|
|
9
|
+
BYO AudioContext / Zero Dependencies / Framework-agnostic / Sample-accurate / Pitch-preserving time-stretch / Chunk-based streaming
|
|
10
|
+
|
|
11
|
+
**[Demo](https://ivgtr.github.io/waa/)**
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
再生からピッチ保持タイムストレッチまで、必要な機能を関数単位で組み合わせられる Web Audio ライブラリです。
|
|
16
|
+
|
|
17
|
+
- **BYO AudioContext** — AudioContext を外から渡す設計で、他のライブラリとの共存が容易
|
|
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** — 音声をチャンク分割で逐次処理し、低スペック環境でも安定動作
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install waa-play
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createContext, loadBuffer, play, createGain } from "waa-play";
|
|
37
|
+
|
|
38
|
+
const ctx = createContext();
|
|
39
|
+
const buffer = await loadBuffer(ctx, "/audio/track.mp3");
|
|
40
|
+
|
|
41
|
+
const gain = createGain(ctx, 0.8);
|
|
42
|
+
const playback = play(ctx, buffer, { through: [gain] });
|
|
43
|
+
|
|
44
|
+
playback.on("timeupdate", ({ position, duration }) => {
|
|
45
|
+
console.log(`${position.toFixed(1)}s / ${duration.toFixed(1)}s`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
playback.pause();
|
|
49
|
+
playback.seek(30);
|
|
50
|
+
playback.resume();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage Examples
|
|
54
|
+
|
|
55
|
+
### オーディオグラフの構築
|
|
56
|
+
|
|
57
|
+
```ts
|
|
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";
|
|
72
|
+
|
|
73
|
+
const peaks = extractPeaks(buffer, { resolution: 500 });
|
|
74
|
+
peaks.forEach((peak, i) => {
|
|
75
|
+
ctx2d.fillRect(i * barWidth, canvas.height * (1 - peak), barWidth - 1, canvas.height * peak);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### React で使う
|
|
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
|
+
```
|
|
99
|
+
|
|
100
|
+
### ピッチを保持したままテンポを変更
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { createContext, loadBuffer, play, onFrame } from "waa-play";
|
|
104
|
+
|
|
105
|
+
const ctx = createContext();
|
|
106
|
+
const buffer = await loadBuffer(ctx, "/audio/track.mp3");
|
|
107
|
+
|
|
108
|
+
// preservePitch: true を渡すだけでピッチ保持タイムストレッチが有効に
|
|
109
|
+
const playback = play(ctx, buffer, {
|
|
110
|
+
playbackRate: 0.75, // テンポ 75%(スロー再生)
|
|
111
|
+
preservePitch: true, // ピッチは変わらない
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 再生中にテンポを変更
|
|
115
|
+
playback.setPlaybackRate(1.25); // テンポ 125%(高速再生)
|
|
116
|
+
|
|
117
|
+
// stretcher の状態を監視
|
|
118
|
+
const stopLoop = onFrame(playback, (snapshot) => {
|
|
119
|
+
if (snapshot.stretcher) {
|
|
120
|
+
console.log(`変換進捗: ${(snapshot.stretcher.conversionProgress * 100).toFixed(0)}%`);
|
|
121
|
+
console.log(`バッファ: ${snapshot.stretcher.bufferHealth}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 他のライブラリの AudioContext を使う
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { play, loadBuffer } from "waa-play";
|
|
130
|
+
import { Tone } from "tone";
|
|
131
|
+
|
|
132
|
+
const ctx = Tone.context.rawContext;
|
|
133
|
+
const buffer = await loadBuffer(ctx, "/audio/track.mp3");
|
|
134
|
+
play(ctx, buffer);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Modules
|
|
138
|
+
|
|
139
|
+
必要なものだけ import できます。
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { play } from "waa-play/play";
|
|
143
|
+
import { loadBuffer } from "waa-play/buffer";
|
|
144
|
+
import { extractPeaks } from "waa-play/waveform";
|
|
145
|
+
```
|
|
146
|
+
|
|
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
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkHTGOHC73_cjs = require('./chunk-HTGOHC73.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "getSnapshot", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return chunkHTGOHC73_cjs.getSnapshot; }
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(exports, "onFrame", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return chunkHTGOHC73_cjs.onFrame; }
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(exports, "subscribeSnapshot", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return chunkHTGOHC73_cjs.subscribeSnapshot; }
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "whenEnded", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return chunkHTGOHC73_cjs.whenEnded; }
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports, "whenPosition", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return chunkHTGOHC73_cjs.whenPosition; }
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=adapters.cjs.map
|
|
28
|
+
//# sourceMappingURL=adapters.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"adapters.cjs"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { e as Playback, g as PlaybackSnapshot } from './types-DUrbEbPl.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get an immutable snapshot of the current playback state.
|
|
5
|
+
* Designed for use with React's `useSyncExternalStore` or similar patterns.
|
|
6
|
+
*/
|
|
7
|
+
declare function getSnapshot(playback: Playback): PlaybackSnapshot;
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe to playback state changes, calling `callback` with a fresh
|
|
10
|
+
* snapshot whenever the state updates.
|
|
11
|
+
*
|
|
12
|
+
* Returns an unsubscribe function. Works as the `subscribe` parameter for
|
|
13
|
+
* React's `useSyncExternalStore`.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // React example:
|
|
17
|
+
* const snap = useSyncExternalStore(
|
|
18
|
+
* (cb) => subscribeSnapshot(playback, cb),
|
|
19
|
+
* () => getSnapshot(playback),
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function subscribeSnapshot(playback: Playback, callback: () => void): () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Call `callback` on every animation frame with the current playback snapshot.
|
|
26
|
+
* Useful for smooth UI animations (waveform cursors, progress bars, etc.).
|
|
27
|
+
*
|
|
28
|
+
* Returns a `stop` function that cancels the loop.
|
|
29
|
+
*/
|
|
30
|
+
declare function onFrame(playback: Playback, callback: (snapshot: PlaybackSnapshot) => void): () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Return a `Promise` that resolves when the playback reaches the `"stopped"`
|
|
33
|
+
* state via the `ended` event (natural end, not manual stop).
|
|
34
|
+
*/
|
|
35
|
+
declare function whenEnded(playback: Playback): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Return a `Promise` that resolves when the playback position reaches or
|
|
38
|
+
* exceeds `position` seconds.
|
|
39
|
+
*/
|
|
40
|
+
declare function whenPosition(playback: Playback, position: number): Promise<void>;
|
|
41
|
+
|
|
42
|
+
export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { e as Playback, g as PlaybackSnapshot } from './types-DUrbEbPl.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get an immutable snapshot of the current playback state.
|
|
5
|
+
* Designed for use with React's `useSyncExternalStore` or similar patterns.
|
|
6
|
+
*/
|
|
7
|
+
declare function getSnapshot(playback: Playback): PlaybackSnapshot;
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe to playback state changes, calling `callback` with a fresh
|
|
10
|
+
* snapshot whenever the state updates.
|
|
11
|
+
*
|
|
12
|
+
* Returns an unsubscribe function. Works as the `subscribe` parameter for
|
|
13
|
+
* React's `useSyncExternalStore`.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // React example:
|
|
17
|
+
* const snap = useSyncExternalStore(
|
|
18
|
+
* (cb) => subscribeSnapshot(playback, cb),
|
|
19
|
+
* () => getSnapshot(playback),
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function subscribeSnapshot(playback: Playback, callback: () => void): () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Call `callback` on every animation frame with the current playback snapshot.
|
|
26
|
+
* Useful for smooth UI animations (waveform cursors, progress bars, etc.).
|
|
27
|
+
*
|
|
28
|
+
* Returns a `stop` function that cancels the loop.
|
|
29
|
+
*/
|
|
30
|
+
declare function onFrame(playback: Playback, callback: (snapshot: PlaybackSnapshot) => void): () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Return a `Promise` that resolves when the playback reaches the `"stopped"`
|
|
33
|
+
* state via the `ended` event (natural end, not manual stop).
|
|
34
|
+
*/
|
|
35
|
+
declare function whenEnded(playback: Playback): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Return a `Promise` that resolves when the playback position reaches or
|
|
38
|
+
* exceeds `position` seconds.
|
|
39
|
+
*/
|
|
40
|
+
declare function whenPosition(playback: Playback, position: number): Promise<void>;
|
|
41
|
+
|
|
42
|
+
export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition };
|
package/dist/adapters.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"adapters.js"}
|
package/dist/buffer.cjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkCPAT75WD_cjs = require('./chunk-CPAT75WD.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "getBufferInfo", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return chunkCPAT75WD_cjs.getBufferInfo; }
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(exports, "loadBuffer", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return chunkCPAT75WD_cjs.loadBuffer; }
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(exports, "loadBufferFromBlob", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return chunkCPAT75WD_cjs.loadBufferFromBlob; }
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "loadBuffers", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return chunkCPAT75WD_cjs.loadBuffers; }
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=buffer.cjs.map
|
|
24
|
+
//# sourceMappingURL=buffer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"buffer.cjs"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { B as BufferInfo, L as LoadBufferOptions } from './types-DUrbEbPl.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetch an audio file from a URL and decode it into an `AudioBuffer`.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const buffer = await loadBuffer(ctx, "/audio/track.mp3", {
|
|
8
|
+
* onProgress: (p) => console.log(`${(p * 100).toFixed(0)}%`),
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
declare function loadBuffer(ctx: AudioContext, url: string, options?: LoadBufferOptions): Promise<AudioBuffer>;
|
|
13
|
+
/**
|
|
14
|
+
* Decode an `AudioBuffer` from a `Blob` or `File`.
|
|
15
|
+
*/
|
|
16
|
+
declare function loadBufferFromBlob(ctx: AudioContext, blob: Blob): Promise<AudioBuffer>;
|
|
17
|
+
/**
|
|
18
|
+
* Load multiple audio files in parallel.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const buffers = await loadBuffers(ctx, {
|
|
22
|
+
* kick: "/samples/kick.wav",
|
|
23
|
+
* snare: "/samples/snare.wav",
|
|
24
|
+
* });
|
|
25
|
+
* buffers.get("kick"); // AudioBuffer
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function loadBuffers(ctx: AudioContext, map: Record<string, string>): Promise<Map<string, AudioBuffer>>;
|
|
29
|
+
/**
|
|
30
|
+
* Return metadata about an `AudioBuffer`.
|
|
31
|
+
*/
|
|
32
|
+
declare function getBufferInfo(buffer: AudioBuffer): BufferInfo;
|
|
33
|
+
|
|
34
|
+
export { getBufferInfo, loadBuffer, loadBufferFromBlob, loadBuffers };
|
package/dist/buffer.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { B as BufferInfo, L as LoadBufferOptions } from './types-DUrbEbPl.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetch an audio file from a URL and decode it into an `AudioBuffer`.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const buffer = await loadBuffer(ctx, "/audio/track.mp3", {
|
|
8
|
+
* onProgress: (p) => console.log(`${(p * 100).toFixed(0)}%`),
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
declare function loadBuffer(ctx: AudioContext, url: string, options?: LoadBufferOptions): Promise<AudioBuffer>;
|
|
13
|
+
/**
|
|
14
|
+
* Decode an `AudioBuffer` from a `Blob` or `File`.
|
|
15
|
+
*/
|
|
16
|
+
declare function loadBufferFromBlob(ctx: AudioContext, blob: Blob): Promise<AudioBuffer>;
|
|
17
|
+
/**
|
|
18
|
+
* Load multiple audio files in parallel.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const buffers = await loadBuffers(ctx, {
|
|
22
|
+
* kick: "/samples/kick.wav",
|
|
23
|
+
* snare: "/samples/snare.wav",
|
|
24
|
+
* });
|
|
25
|
+
* buffers.get("kick"); // AudioBuffer
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function loadBuffers(ctx: AudioContext, map: Record<string, string>): Promise<Map<string, AudioBuffer>>;
|
|
29
|
+
/**
|
|
30
|
+
* Return metadata about an `AudioBuffer`.
|
|
31
|
+
*/
|
|
32
|
+
declare function getBufferInfo(buffer: AudioBuffer): BufferInfo;
|
|
33
|
+
|
|
34
|
+
export { getBufferInfo, loadBuffer, loadBufferFromBlob, loadBuffers };
|
package/dist/buffer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"buffer.js"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/scheduler.ts
|
|
2
|
+
function createScheduler(ctx, options) {
|
|
3
|
+
const { lookahead = 0.1, interval = 25 } = options ?? {};
|
|
4
|
+
const events = [];
|
|
5
|
+
let timerId = null;
|
|
6
|
+
function tick() {
|
|
7
|
+
const horizon = ctx.currentTime + lookahead;
|
|
8
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
9
|
+
const evt = events[i];
|
|
10
|
+
if (evt.time <= horizon) {
|
|
11
|
+
evt.callback(evt.time);
|
|
12
|
+
events.splice(i, 1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
schedule(id, time, callback) {
|
|
18
|
+
events.push({ id, time, callback });
|
|
19
|
+
},
|
|
20
|
+
cancel(id) {
|
|
21
|
+
const idx = events.findIndex((e) => e.id === id);
|
|
22
|
+
if (idx !== -1) events.splice(idx, 1);
|
|
23
|
+
},
|
|
24
|
+
start() {
|
|
25
|
+
if (timerId !== null) return;
|
|
26
|
+
timerId = setInterval(tick, interval);
|
|
27
|
+
},
|
|
28
|
+
stop() {
|
|
29
|
+
if (timerId !== null) {
|
|
30
|
+
clearInterval(timerId);
|
|
31
|
+
timerId = null;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
dispose() {
|
|
35
|
+
this.stop();
|
|
36
|
+
events.length = 0;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function createClock(ctx, options) {
|
|
41
|
+
let bpm = options?.bpm ?? 120;
|
|
42
|
+
const startTime = ctx.currentTime;
|
|
43
|
+
function secondsPerBeat() {
|
|
44
|
+
return 60 / bpm;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
beatToTime(beat) {
|
|
48
|
+
return startTime + beat * secondsPerBeat();
|
|
49
|
+
},
|
|
50
|
+
getCurrentBeat() {
|
|
51
|
+
return (ctx.currentTime - startTime) / secondsPerBeat();
|
|
52
|
+
},
|
|
53
|
+
getNextBeatTime() {
|
|
54
|
+
const current = this.getCurrentBeat();
|
|
55
|
+
const next = Math.ceil(current);
|
|
56
|
+
return this.beatToTime(next === current ? next + 1 : next);
|
|
57
|
+
},
|
|
58
|
+
setBpm(value) {
|
|
59
|
+
bpm = value;
|
|
60
|
+
},
|
|
61
|
+
getBpm() {
|
|
62
|
+
return bpm;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { createClock, createScheduler };
|
|
68
|
+
//# sourceMappingURL=chunk-2DL7CAEP.js.map
|
|
69
|
+
//# sourceMappingURL=chunk-2DL7CAEP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scheduler.ts"],"names":[],"mappings":";AA6BO,SAAS,eAAA,CACd,KACA,OAAA,EACW;AACX,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,CACd,KACA,OAAA,EACO;AACP,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-2DL7CAEP.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(\n ctx: AudioContext,\n options?: SchedulerOptions,\n): 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(\n ctx: AudioContext,\n options?: ClockOptions,\n): 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"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
function createContext(options) {
|
|
3
|
+
return new AudioContext({
|
|
4
|
+
sampleRate: options?.sampleRate,
|
|
5
|
+
latencyHint: options?.latencyHint
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
async function resumeContext(ctx) {
|
|
9
|
+
if (ctx.state === "suspended") {
|
|
10
|
+
await ctx.resume();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function ensureRunning(ctx) {
|
|
14
|
+
if (ctx.state !== "running") {
|
|
15
|
+
await ctx.resume();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function now(ctx) {
|
|
19
|
+
return ctx.currentTime;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { createContext, ensureRunning, now, resumeContext };
|
|
23
|
+
//# sourceMappingURL=chunk-37CPPRLV.js.map
|
|
24
|
+
//# sourceMappingURL=chunk-37CPPRLV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts"],"names":[],"mappings":";AAaO,SAAS,cAAc,OAAA,EAA8C;AAC1E,EAAA,OAAO,IAAI,YAAA,CAAa;AAAA,IACtB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,aAAa,OAAA,EAAS;AAAA,GACvB,CAAA;AACH;AAQA,eAAsB,cAAc,GAAA,EAAkC;AACpE,EAAA,IAAI,GAAA,CAAI,UAAU,WAAA,EAAa;AAC7B,IAAA,MAAM,IAAI,MAAA,EAAO;AAAA,EACnB;AACF;AAKA,eAAsB,cAAc,GAAA,EAAkC;AACpE,EAAA,IAAI,GAAA,CAAI,UAAU,SAAA,EAAW;AAC3B,IAAA,MAAM,IAAI,MAAA,EAAO;AAAA,EACnB;AACF;AAKO,SAAS,IAAI,GAAA,EAA2B;AAC7C,EAAA,OAAO,GAAA,CAAI,WAAA;AACb","file":"chunk-37CPPRLV.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M1: AudioContext utilities\n// ---------------------------------------------------------------------------\n\nimport type { CreateContextOptions } from \"./types.js\";\n\n/**\n * Create an `AudioContext` with optional configuration.\n *\n * This is the only place in the library where `new AudioContext()` is called.\n * Users are encouraged to create their own context and pass it to other\n * functions — this helper exists purely for convenience.\n */\nexport function createContext(options?: CreateContextOptions): AudioContext {\n return new AudioContext({\n sampleRate: options?.sampleRate,\n latencyHint: options?.latencyHint,\n });\n}\n\n/**\n * Resume a suspended AudioContext (e.g. blocked by autoplay policy).\n *\n * Should be called inside a user-interaction event handler (click, keydown…)\n * if the context is in the `\"suspended\"` state.\n */\nexport async function resumeContext(ctx: AudioContext): Promise<void> {\n if (ctx.state === \"suspended\") {\n await ctx.resume();\n }\n}\n\n/**\n * Ensure the context is in the `\"running\"` state, resuming if necessary.\n */\nexport async function ensureRunning(ctx: AudioContext): Promise<void> {\n if (ctx.state !== \"running\") {\n await ctx.resume();\n }\n}\n\n/**\n * Shorthand for `ctx.currentTime`.\n */\nexport function now(ctx: AudioContext): number {\n return ctx.currentTime;\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/fade.ts
|
|
4
|
+
var EXP_MIN = 1e-4;
|
|
5
|
+
function applyRamp(node, from, to, duration, curve) {
|
|
6
|
+
const param = node.gain;
|
|
7
|
+
const now = node.context.currentTime;
|
|
8
|
+
param.cancelScheduledValues(now);
|
|
9
|
+
param.setValueAtTime(from, now);
|
|
10
|
+
switch (curve) {
|
|
11
|
+
case "exponential":
|
|
12
|
+
param.exponentialRampToValueAtTime(
|
|
13
|
+
Math.max(to, EXP_MIN),
|
|
14
|
+
now + duration
|
|
15
|
+
);
|
|
16
|
+
break;
|
|
17
|
+
case "equal-power": {
|
|
18
|
+
const steps = Math.max(Math.ceil(duration * 100), 2);
|
|
19
|
+
const values = new Float32Array(steps);
|
|
20
|
+
for (let i = 0; i < steps; i++) {
|
|
21
|
+
const t = i / (steps - 1);
|
|
22
|
+
const gain = from + (to - from) * Math.sin(t * Math.PI / 2);
|
|
23
|
+
values[i] = gain;
|
|
24
|
+
}
|
|
25
|
+
param.setValueCurveAtTime(values, now, duration);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "linear":
|
|
29
|
+
default:
|
|
30
|
+
param.linearRampToValueAtTime(to, now + duration);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function fadeIn(gain, target, options) {
|
|
35
|
+
const { duration = 1, curve = "linear" } = options ?? {};
|
|
36
|
+
applyRamp(gain, 0, target, duration, curve);
|
|
37
|
+
}
|
|
38
|
+
function fadeOut(gain, options) {
|
|
39
|
+
const { duration = 1, curve = "linear" } = options ?? {};
|
|
40
|
+
applyRamp(gain, gain.gain.value, 0, duration, curve);
|
|
41
|
+
}
|
|
42
|
+
function crossfade(gainA, gainB, options) {
|
|
43
|
+
const { duration = 1, curve = "linear" } = options ?? {};
|
|
44
|
+
applyRamp(gainA, gainA.gain.value, 0, duration, curve);
|
|
45
|
+
applyRamp(gainB, 0, gainA.gain.value || 1, duration, curve);
|
|
46
|
+
}
|
|
47
|
+
function autoFade(playback, gain, options) {
|
|
48
|
+
const {
|
|
49
|
+
fadeIn: fadeInDuration = 0,
|
|
50
|
+
fadeOut: fadeOutDuration = 0,
|
|
51
|
+
curve = "linear"
|
|
52
|
+
} = options ?? {};
|
|
53
|
+
const duration = playback.getDuration();
|
|
54
|
+
let fadeOutScheduled = false;
|
|
55
|
+
if (fadeInDuration > 0) {
|
|
56
|
+
applyRamp(gain, 0, 1, fadeInDuration, curve);
|
|
57
|
+
}
|
|
58
|
+
const unsub = playback.on("timeupdate", ({ position }) => {
|
|
59
|
+
if (fadeOutDuration > 0 && !fadeOutScheduled && position >= duration - fadeOutDuration) {
|
|
60
|
+
fadeOutScheduled = true;
|
|
61
|
+
applyRamp(gain, gain.gain.value, 0, fadeOutDuration, curve);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return unsub;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
exports.autoFade = autoFade;
|
|
68
|
+
exports.crossfade = crossfade;
|
|
69
|
+
exports.fadeIn = fadeIn;
|
|
70
|
+
exports.fadeOut = fadeOut;
|
|
71
|
+
//# sourceMappingURL=chunk-4LNVRSTM.cjs.map
|
|
72
|
+
//# sourceMappingURL=chunk-4LNVRSTM.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fade.ts"],"names":[],"mappings":"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","file":"chunk-4LNVRSTM.cjs","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(\n Math.max(to, EXP_MIN),\n now + duration,\n );\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 case \"linear\":\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(\n gain: GainNode,\n target: number,\n options?: FadeOptions,\n): 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(\n gainA: GainNode,\n gainB: GainNode,\n options?: CrossfadeOptions,\n): 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 (\n fadeOutDuration > 0 &&\n !fadeOutScheduled &&\n position >= duration - fadeOutDuration\n ) {\n fadeOutScheduled = true;\n applyRamp(gain, gain.gain.value, 0, fadeOutDuration, curve);\n }\n });\n\n return unsub;\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/emitter.ts
|
|
4
|
+
function createEmitter() {
|
|
5
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
6
|
+
function getSet(event) {
|
|
7
|
+
let set = listeners.get(event);
|
|
8
|
+
if (!set) {
|
|
9
|
+
set = /* @__PURE__ */ new Set();
|
|
10
|
+
listeners.set(event, set);
|
|
11
|
+
}
|
|
12
|
+
return set;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
on(event, handler) {
|
|
16
|
+
const set = getSet(event);
|
|
17
|
+
set.add(handler);
|
|
18
|
+
return () => {
|
|
19
|
+
set.delete(handler);
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
off(event, handler) {
|
|
23
|
+
listeners.get(event)?.delete(handler);
|
|
24
|
+
},
|
|
25
|
+
emit(event, data) {
|
|
26
|
+
const set = listeners.get(event);
|
|
27
|
+
if (!set) return;
|
|
28
|
+
for (const handler of set) {
|
|
29
|
+
handler(data);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
clear(event) {
|
|
33
|
+
if (event !== void 0) {
|
|
34
|
+
listeners.delete(event);
|
|
35
|
+
} else {
|
|
36
|
+
listeners.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.createEmitter = createEmitter;
|
|
43
|
+
//# sourceMappingURL=chunk-5J7S6QV3.cjs.map
|
|
44
|
+
//# sourceMappingURL=chunk-5J7S6QV3.cjs.map
|