waa-play 0.1.1 → 0.2.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.
Files changed (52) hide show
  1. package/README.md +60 -95
  2. package/dist/adapters.cjs +6 -6
  3. package/dist/adapters.d.cts +14 -2
  4. package/dist/adapters.d.ts +14 -2
  5. package/dist/adapters.js +1 -1
  6. package/dist/{chunk-V2QX5K42.js → chunk-2FFORBOP.js} +24 -4
  7. package/dist/chunk-2FFORBOP.js.map +1 -0
  8. package/dist/chunk-7S5KWTZ6.cjs +93 -0
  9. package/dist/chunk-7S5KWTZ6.cjs.map +1 -0
  10. package/dist/{chunk-PZE6HTZR.cjs → chunk-CTUCTTIE.cjs} +13 -4
  11. package/dist/chunk-CTUCTTIE.cjs.map +1 -0
  12. package/dist/chunk-IMNRPYBM.js +146 -0
  13. package/dist/chunk-IMNRPYBM.js.map +1 -0
  14. package/dist/{chunk-TULV7V5M.cjs → chunk-SIMLANWE.cjs} +24 -4
  15. package/dist/chunk-SIMLANWE.cjs.map +1 -0
  16. package/dist/chunk-VKT7YCWK.js +87 -0
  17. package/dist/chunk-VKT7YCWK.js.map +1 -0
  18. package/dist/{chunk-RWJ4EWJT.js → chunk-X4IFO7U7.js} +13 -4
  19. package/dist/chunk-X4IFO7U7.js.map +1 -0
  20. package/dist/chunk-XZBMBZA3.cjs +148 -0
  21. package/dist/chunk-XZBMBZA3.cjs.map +1 -0
  22. package/dist/engine-QUMYW73L.cjs +13 -0
  23. package/dist/{engine-5JK2FCNL.cjs.map → engine-QUMYW73L.cjs.map} +1 -1
  24. package/dist/engine-TYI7OX7O.js +4 -0
  25. package/dist/{engine-M2U4LE3F.js.map → engine-TYI7OX7O.js.map} +1 -1
  26. package/dist/index.cjs +13 -8
  27. package/dist/index.d.cts +1 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +3 -2
  30. package/dist/play.cjs +2 -2
  31. package/dist/play.js +1 -1
  32. package/dist/player.cjs +22 -0
  33. package/dist/player.cjs.map +1 -0
  34. package/dist/player.d.cts +64 -0
  35. package/dist/player.d.ts +64 -0
  36. package/dist/player.js +13 -0
  37. package/dist/player.js.map +1 -0
  38. package/dist/stretcher.cjs +2 -2
  39. package/dist/stretcher.d.cts +2 -0
  40. package/dist/stretcher.d.ts +2 -0
  41. package/dist/stretcher.js +1 -1
  42. package/package.json +9 -4
  43. package/dist/chunk-AGP2IRC6.js +0 -63
  44. package/dist/chunk-AGP2IRC6.js.map +0 -1
  45. package/dist/chunk-HTGOHC73.cjs +0 -69
  46. package/dist/chunk-HTGOHC73.cjs.map +0 -1
  47. package/dist/chunk-PZE6HTZR.cjs.map +0 -1
  48. package/dist/chunk-RWJ4EWJT.js.map +0 -1
  49. package/dist/chunk-TULV7V5M.cjs.map +0 -1
  50. package/dist/chunk-V2QX5K42.js.map +0 -1
  51. package/dist/engine-5JK2FCNL.cjs +0 -13
  52. package/dist/engine-M2U4LE3F.js +0 -4
package/README.md CHANGED
@@ -4,25 +4,11 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
6
 
7
- Composable Web Audio API utilities.
7
+ Convenient composable building blocks for Web Audio API.
8
8
 
9
- BYO AudioContext / Zero Dependencies / Framework-agnostic / Sample-accurate / Pitch-preserving time-stretch / Chunk-based streaming
9
+ Time-stretch / Streaming / Waveform / BYO AudioContext / Zero Dependencies
10
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** — 音声をチャンク分割で逐次処理し、低スペック環境でも安定動作
11
+ [Documentation & Demo](https://ivgtr.github.io/waa/)
26
12
 
27
13
  ## Install
28
14
 
@@ -30,122 +16,81 @@ BYO AudioContext / Zero Dependencies / Framework-agnostic / Sample-accurate / Pi
30
16
  npm install waa-play
31
17
  ```
32
18
 
33
- ## Quick Start
19
+ ## Features
34
20
 
35
- ```ts
36
- import { createContext, loadBuffer, play, createGain } from "waa-play";
21
+ ### Pitch-preserving time-stretch
22
+ 再生速度を変えてもピッチが変わりません。処理は別スレッドで実行されるため、UI をブロックしません。
37
23
 
38
- const ctx = createContext();
39
- const buffer = await loadBuffer(ctx, "/audio/track.mp3");
24
+ ### Streaming playback
25
+ 音声を段階的に処理し、バッファリング状態をイベントで通知します。ローディング UI を簡単に実装できます。
40
26
 
41
- const gain = createGain(ctx, 0.8);
42
- const playback = play(ctx, buffer, { through: [gain] });
27
+ ### BYO AudioContext
28
+ 既存の AudioContext やオーディオグラフをそのまま使えます。他のライブラリとの統合も容易です。
43
29
 
44
- playback.on("timeupdate", ({ position, duration }) => {
45
- console.log(`${position.toFixed(1)}s / ${duration.toFixed(1)}s`);
46
- });
30
+ ### Framework integration
31
+ React・Vue・Svelte など、お好みのフレームワークで再生状態をリアクティブに扱えます。
47
32
 
48
- playback.pause();
49
- playback.seek(30);
50
- playback.resume();
51
- ```
33
+ ### Waveform extraction
34
+ 波形データを取得し、プログレスバーや波形ビジュアライザーを構築できます。
35
+
36
+ ### Background-tab safe
37
+ バックグラウンドタブでも再生位置の追跡が継続します。
38
+
39
+ ## Quick Start
52
40
 
53
- ## Usage Examples
41
+ ### Class API (WaaPlayer)
54
42
 
55
- ### オーディオグラフの構築
43
+ 最もシンプルな使い方です。`WaaPlayer` が `AudioContext` を内部管理し、全モジュールの機能を統合して提供します。
56
44
 
57
45
  ```ts
58
- import { createGain, createFilter, createPanner, chain } from "waa-play";
46
+ import { WaaPlayer } from "waa-play";
59
47
 
60
- const gain = createGain(ctx, 0.8);
61
- const lowpass = createFilter(ctx, { type: "lowpass", frequency: 2000 });
62
- const panner = createPanner(ctx, -0.5);
48
+ const player = new WaaPlayer();
49
+ const buffer = await player.load("/audio/track.mp3");
50
+
51
+ const gain = player.createGain(0.8);
52
+ const playback = player.play(buffer, { through: [gain] });
63
53
 
64
- chain(sourceNode, gain, lowpass, panner);
65
- panner.connect(ctx.destination);
54
+ player.dispose();
66
55
  ```
67
56
 
68
- ### 波形の描画
57
+ 既存の `AudioContext` を渡すこともできます。
69
58
 
70
59
  ```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
- });
60
+ const player = new WaaPlayer(existingAudioContext);
77
61
  ```
78
62
 
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
- ```
63
+ ### Function API
99
64
 
100
- ### ピッチを保持したままテンポを変更
65
+ 個別の関数だけ import したい場合はこちら。全関数が `AudioContext` を第一引数に取ります。
101
66
 
102
67
  ```ts
103
- import { createContext, loadBuffer, play, onFrame } from "waa-play";
68
+ import { createContext, loadBuffer, play, createGain } from "waa-play";
104
69
 
105
70
  const ctx = createContext();
106
71
  const buffer = await loadBuffer(ctx, "/audio/track.mp3");
107
72
 
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%(高速再生)
73
+ const gain = createGain(ctx, 0.8);
74
+ const playback = play(ctx, buffer, { through: [gain] });
116
75
 
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
- }
76
+ playback.on("timeupdate", ({ position, duration }) => {
77
+ console.log(`${position.toFixed(1)}s / ${duration.toFixed(1)}s`);
123
78
  });
124
79
  ```
125
80
 
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
81
  ## Modules
138
82
 
139
- 必要なものだけ import できます。
83
+ 必要なものだけ個別に import できます。
140
84
 
141
85
  ```ts
142
86
  import { play } from "waa-play/play";
143
87
  import { loadBuffer } from "waa-play/buffer";
144
- import { extractPeaks } from "waa-play/waveform";
88
+ import { WaaPlayer } from "waa-play/player";
145
89
  ```
146
90
 
147
91
  | Module | 概要 |
148
92
  |--------|------|
93
+ | `player` | 全モジュールを統合するクラスベース API |
149
94
  | `context` | AudioContext のライフサイクル管理 |
150
95
  | `buffer` | 音声ファイルのロード・デコード |
151
96
  | `play` | 再生制御(play / pause / seek / loop / preservePitch) |
@@ -158,6 +103,26 @@ import { extractPeaks } from "waa-play/waveform";
158
103
  | `adapters` | フレームワーク統合(React / Vue / Svelte) |
159
104
  | `stretcher` | ピッチ保持タイムストレッチ(WSOLA) |
160
105
 
106
+ ## API
107
+
108
+ 詳細な使い方・API リファレンスは [Documentation & Demo](https://ivgtr.github.io/waa/) をご覧ください。
109
+
110
+ ### WaaPlayer
111
+
112
+ | メソッド | 概要 |
113
+ |----------|------|
114
+ | `resume()` / `ensureRunning()` / `now()` | AudioContext 制御 |
115
+ | `load(url)` / `loadFromBlob(blob)` / `loadAll(map)` | 音声ロード |
116
+ | `play(buffer, options?)` | 再生(`Playback` を返す) |
117
+ | `createGain()` / `createFilter()` / `createPanner()` / `createCompressor()` / `createAnalyser()` | ノード生成 |
118
+ | `chain(...nodes)` / `disconnectChain(...nodes)` | グラフ接続 |
119
+ | `fadeIn()` / `fadeOut()` / `crossfade()` / `autoFade()` | フェード |
120
+ | `extractPeaks()` / `extractPeakPairs()` / `extractRMS()` | 波形抽出 |
121
+ | `createScheduler()` / `createClock()` | スケジューリング |
122
+ | `createSineBuffer()` / `createNoiseBuffer()` / `createClickBuffer()` | バッファ合成 |
123
+ | `getSnapshot()` / `subscribeSnapshot()` / `onFrame()` / `whenEnded()` / `whenPosition()` | アダプター |
124
+ | `dispose()` | リソース解放 |
125
+
161
126
  ## License
162
127
 
163
128
  MIT
package/dist/adapters.cjs CHANGED
@@ -1,28 +1,28 @@
1
1
  'use strict';
2
2
 
3
- var chunkHTGOHC73_cjs = require('./chunk-HTGOHC73.cjs');
3
+ var chunk7S5KWTZ6_cjs = require('./chunk-7S5KWTZ6.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "getSnapshot", {
8
8
  enumerable: true,
9
- get: function () { return chunkHTGOHC73_cjs.getSnapshot; }
9
+ get: function () { return chunk7S5KWTZ6_cjs.getSnapshot; }
10
10
  });
11
11
  Object.defineProperty(exports, "onFrame", {
12
12
  enumerable: true,
13
- get: function () { return chunkHTGOHC73_cjs.onFrame; }
13
+ get: function () { return chunk7S5KWTZ6_cjs.onFrame; }
14
14
  });
15
15
  Object.defineProperty(exports, "subscribeSnapshot", {
16
16
  enumerable: true,
17
- get: function () { return chunkHTGOHC73_cjs.subscribeSnapshot; }
17
+ get: function () { return chunk7S5KWTZ6_cjs.subscribeSnapshot; }
18
18
  });
19
19
  Object.defineProperty(exports, "whenEnded", {
20
20
  enumerable: true,
21
- get: function () { return chunkHTGOHC73_cjs.whenEnded; }
21
+ get: function () { return chunk7S5KWTZ6_cjs.whenEnded; }
22
22
  });
23
23
  Object.defineProperty(exports, "whenPosition", {
24
24
  enumerable: true,
25
- get: function () { return chunkHTGOHC73_cjs.whenPosition; }
25
+ get: function () { return chunk7S5KWTZ6_cjs.whenPosition; }
26
26
  });
27
27
  //# sourceMappingURL=adapters.cjs.map
28
28
  //# sourceMappingURL=adapters.cjs.map
@@ -3,6 +3,12 @@ import { e as Playback, g as PlaybackSnapshot } from './types-DUrbEbPl.cjs';
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
- * const snap = useSyncExternalStore(
18
- * (cb) => subscribeSnapshot(playback, cb),
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;
@@ -3,6 +3,12 @@ import { e as Playback, g as PlaybackSnapshot } from './types-DUrbEbPl.js';
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
- * const snap = useSyncExternalStore(
18
- * (cb) => subscribeSnapshot(playback, cb),
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-AGP2IRC6.js';
1
+ export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition } from './chunk-VKT7YCWK.js';
2
2
  //# sourceMappingURL=adapters.js.map
3
3
  //# sourceMappingURL=adapters.js.map
@@ -1148,7 +1148,6 @@ function createChunkPlayer(ctx, options) {
1148
1148
  }
1149
1149
  function resume() {
1150
1150
  if (!paused || disposed) return;
1151
- paused = false;
1152
1151
  }
1153
1152
  function stop() {
1154
1153
  if (stopped || disposed) return;
@@ -1277,6 +1276,7 @@ function createStretcherEngine(ctx, buffer, options) {
1277
1276
  const {
1278
1277
  tempo: initialTempo,
1279
1278
  offset = 0,
1279
+ loop: initialLoop = false,
1280
1280
  through = [],
1281
1281
  destination = ctx.destination
1282
1282
  } = options;
@@ -1285,6 +1285,7 @@ function createStretcherEngine(ctx, buffer, options) {
1285
1285
  const totalDuration = buffer.duration;
1286
1286
  let phase = "waiting";
1287
1287
  let currentTempo = initialTempo;
1288
+ let isLooping = initialLoop;
1288
1289
  let disposed = false;
1289
1290
  let bufferingStartTime = 0;
1290
1291
  let currentChunkIndex = 0;
@@ -1461,6 +1462,20 @@ function createStretcherEngine(ctx, buffer, options) {
1461
1462
  function advanceToNextChunk() {
1462
1463
  const nextIdx = currentChunkIndex + 1;
1463
1464
  if (nextIdx >= chunks.length) {
1465
+ if (isLooping) {
1466
+ currentChunkIndex = 0;
1467
+ scheduler.handleSeek(0);
1468
+ const chunk2 = chunks[0];
1469
+ if (chunk2 && chunk2.state === "ready") {
1470
+ playCurrentChunk();
1471
+ } else {
1472
+ bufferingResumePosition = 0;
1473
+ enterBuffering("seek");
1474
+ }
1475
+ emitter.emit("loop", void 0);
1476
+ evictDistantChunks();
1477
+ return;
1478
+ }
1464
1479
  phase = "ended";
1465
1480
  chunkPlayer.stop();
1466
1481
  emitter.emit("ended", void 0);
@@ -1631,9 +1646,10 @@ function createStretcherEngine(ctx, buffer, options) {
1631
1646
  if (disposed || phase !== "paused") return;
1632
1647
  const chunk = chunks[currentChunkIndex];
1633
1648
  if (chunk && chunk.state === "ready") {
1649
+ const resumePosition = chunkPlayer.getCurrentPosition();
1634
1650
  phase = "playing";
1635
1651
  chunkPlayer.resume();
1636
- playCurrentChunk(chunkPlayer.getCurrentPosition());
1652
+ playCurrentChunk(resumePosition);
1637
1653
  } else {
1638
1654
  enterBuffering("underrun");
1639
1655
  }
@@ -1670,6 +1686,9 @@ function createStretcherEngine(ctx, buffer, options) {
1670
1686
  phase = "ended";
1671
1687
  chunkPlayer.stop();
1672
1688
  }
1689
+ function setLoop(value) {
1690
+ isLooping = value;
1691
+ }
1673
1692
  function setTempo(newTempo) {
1674
1693
  if (disposed || phase === "ended" || newTempo === currentTempo) return;
1675
1694
  bufferingResumePosition = getPositionInOriginalBuffer();
@@ -1694,6 +1713,7 @@ function createStretcherEngine(ctx, buffer, options) {
1694
1713
  seek,
1695
1714
  stop,
1696
1715
  setTempo,
1716
+ setLoop,
1697
1717
  getCurrentPosition: getPositionInOriginalBuffer,
1698
1718
  getStatus,
1699
1719
  getSnapshot,
@@ -1704,5 +1724,5 @@ function createStretcherEngine(ctx, buffer, options) {
1704
1724
  }
1705
1725
 
1706
1726
  export { createStretcherEngine };
1707
- //# sourceMappingURL=chunk-V2QX5K42.js.map
1708
- //# sourceMappingURL=chunk-V2QX5K42.js.map
1727
+ //# sourceMappingURL=chunk-2FFORBOP.js.map
1728
+ //# sourceMappingURL=chunk-2FFORBOP.js.map