@waveform-playlist/browser 15.2.0 → 15.3.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 +104 -0
- package/dist/index.d.mts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +47 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -21
- package/dist/index.mjs.map +1 -1
- package/dist/tone.js +5 -4
- package/dist/tone.js.map +1 -1
- package/dist/tone.mjs +5 -4
- package/dist/tone.mjs.map +1 -1
- package/package.json +8 -9
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @waveform-playlist/browser
|
|
2
|
+
|
|
3
|
+
React components, hooks, and context providers for building a multitrack Web Audio editor and player — canvas waveform rendering, clip-based editing, and playback controls on top of Tone.js or a native `HTMLAudioElement`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multitrack editor** — `<Waveform />` renders canvas waveforms, per-track controls (mute/solo/volume/pan), a time ruler, and a playhead for any number of tracks
|
|
8
|
+
- **Clip-based editing** — drag-to-move, boundary trimming with collision detection, splitting at the playhead, snap-to-grid (beats/bars or timescale), undo/redo
|
|
9
|
+
- **Split context providers** — `WaveformPlaylistProvider` (multitrack, Tone.js-backed) and `MediaElementPlaylistProvider` (single-track, `<audio>`-backed with pitch-preserving playback rate)
|
|
10
|
+
- **Four focused context hooks** — `usePlaybackAnimation`, `usePlaylistState`, `usePlaylistControls`, `usePlaylistData` isolate 60fps playhead updates from low-frequency state so unrelated components don't re-render on every animation frame
|
|
11
|
+
- **Audio effects** — 20 Tone.js effects (reverb, delay, modulation, filter, distortion, dynamics, spatial) with runtime parameter control, master and per-track chains, plus WAM 2.0 plugin hosting
|
|
12
|
+
- **Recording** — pairs with `@waveform-playlist/recording` for mic input, overdubbing, and live waveform preview; punch-in takes replace overlapped content, and `setRecordingActive(active, armedTrackId)` suppresses the end-of-timeline auto-stop and transiently mutes the recorded-over track for the session
|
|
13
|
+
- **Annotations & spectrogram** — optional integration contexts for `@waveform-playlist/annotations` and `@waveform-playlist/spectrogram`
|
|
14
|
+
- **WAV export** — render the full mix (including effects) offline to a downloadable WAV file
|
|
15
|
+
- **MediaElement player mode** — a lightweight single-track player with pitch-preserving speed control, for podcast/audiobook-style use cases that don't need multitrack mixing
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @waveform-playlist/browser tone @dnd-kit/react
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`@waveform-playlist/browser` requires these peer dependencies:
|
|
24
|
+
|
|
25
|
+
| Package | Purpose |
|
|
26
|
+
|---------|---------|
|
|
27
|
+
| `react` / `react-dom` | ^18.0.0 or ^19.0.0 |
|
|
28
|
+
| `styled-components` | ^6.0.0 — CSS-in-JS styling |
|
|
29
|
+
| `tone` | ^15.0.0 — Web Audio engine (optional if you supply your own `PlayoutAdapter`) |
|
|
30
|
+
| `@dnd-kit/react` | ^0.3.0 — drag-and-drop (includes `@dnd-kit/dom` and `@dnd-kit/abstract`) |
|
|
31
|
+
| `@waveform-playlist/playout` | Tone.js playout engine (optional peer, dynamically imported by default) |
|
|
32
|
+
| `@waveform-playlist/media-element-playout` | engine for `MediaElementPlaylistProvider` (optional peer) |
|
|
33
|
+
| `@waveform-playlist/annotations` / `@waveform-playlist/recording` / `@dawcore/wam` | optional, install only if you use those features |
|
|
34
|
+
|
|
35
|
+
**Engine-free core:** the package's main entry has no static dependency on `tone` or `@waveform-playlist/playout` — a `MediaElementPlaylistProvider`-only consumer, or one supplying a custom `createAdapter`, never resolves either. Tone-coupled exports (`useAudioTracks`, `useDynamicTracks`, effects hooks, `useExportWav`, `useOutputMeter`, `useMasterAnalyser`) live at the **`@waveform-playlist/browser/tone`** subpath:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { useAudioTracks, useDynamicEffects, useExportWav } from '@waveform-playlist/browser/tone';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import {
|
|
45
|
+
WaveformPlaylistProvider,
|
|
46
|
+
Waveform,
|
|
47
|
+
PlayButton,
|
|
48
|
+
PauseButton,
|
|
49
|
+
StopButton,
|
|
50
|
+
} from '@waveform-playlist/browser';
|
|
51
|
+
import { useAudioTracks } from '@waveform-playlist/browser/tone';
|
|
52
|
+
|
|
53
|
+
function MyPlaylist() {
|
|
54
|
+
const { tracks, loading, error } = useAudioTracks([
|
|
55
|
+
{ src: '/audio/vocals.mp3', name: 'Vocals' },
|
|
56
|
+
{ src: '/audio/guitar.mp3', name: 'Guitar' },
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
if (loading) return <div>Loading audio...</div>;
|
|
60
|
+
if (error) return <div>Error: {error}</div>;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<WaveformPlaylistProvider
|
|
64
|
+
tracks={tracks}
|
|
65
|
+
samplesPerPixel={1024}
|
|
66
|
+
waveHeight={128}
|
|
67
|
+
timescale
|
|
68
|
+
controls={{ show: true, width: 200 }}
|
|
69
|
+
>
|
|
70
|
+
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
|
|
71
|
+
<PlayButton />
|
|
72
|
+
<PauseButton />
|
|
73
|
+
<StopButton />
|
|
74
|
+
</div>
|
|
75
|
+
<Waveform />
|
|
76
|
+
</WaveformPlaylistProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Prefer a lightweight single-track player instead? Use `MediaElementPlaylistProvider` with `@waveform-playlist/media-element-playout` — no Tone.js, no AudioBuffer decoding, and pitch-preserving playback rate out of the box.
|
|
82
|
+
|
|
83
|
+
## Context Hooks
|
|
84
|
+
|
|
85
|
+
`WaveformPlaylistProvider` splits its context into four hooks so components only re-render for the data they actually read:
|
|
86
|
+
|
|
87
|
+
| Hook | Exposes |
|
|
88
|
+
|------|---------|
|
|
89
|
+
| `usePlaybackAnimation` | `isPlaying`, `currentTime`, animation refs, `getPlaybackTime()`, `registerFrameCallback()` for 60fps-driven UI (playhead, progress bars) |
|
|
90
|
+
| `usePlaylistState` | Low-frequency state — selection, loop region, annotations, `selectedTrackId`, `canUndo`/`canRedo` |
|
|
91
|
+
| `usePlaylistControls` | Actions — `play`, `pause`, `stop`, `seekTo`, track mute/solo/volume/pan, zoom, undo/redo, `setRecordingActive` (recording-session suppression + armed-track mute) |
|
|
92
|
+
| `usePlaylistData` | Derived/config data — `tracks`, `duration`, `sampleRate`, `peaksDataArray`, `trackStates`, `isReady` |
|
|
93
|
+
|
|
94
|
+
`usePlaylistDataOptional` is the same as `usePlaylistData` but returns `null` outside a provider (for components that render both inside and outside one). `MediaElementPlaylistProvider` has the equivalent `useMediaElementAnimation`, `useMediaElementState`, `useMediaElementControls`, `useMediaElementData`.
|
|
95
|
+
|
|
96
|
+
## Examples & Documentation
|
|
97
|
+
|
|
98
|
+
- [naomiaro.github.io/waveform-playlist](https://naomiaro.github.io/waveform-playlist/) — guides, API reference, and live examples
|
|
99
|
+
- [Basic Usage guide](https://naomiaro.github.io/waveform-playlist/docs/react/getting-started/basic-usage) — walkthrough of the provider pattern
|
|
100
|
+
- [`examples/media-element-player`](https://github.com/naomiaro/waveform-playlist/tree/main/examples/media-element-player) — `MediaElementPlaylistProvider` starter (`pnpm example:media-element`)
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -94,8 +94,12 @@ interface PlaylistStateContextValue {
|
|
|
94
94
|
selectedTrackId: string | null;
|
|
95
95
|
loopStart: number;
|
|
96
96
|
loopEnd: number;
|
|
97
|
-
/** Whether playback continues past the end of loaded audio
|
|
97
|
+
/** Whether playback continues past the end of loaded audio instead of
|
|
98
|
+
* auto-stopping (implies the fillViewport layout) */
|
|
98
99
|
indefinitePlayback: boolean;
|
|
100
|
+
/** Whether the timeline visually fills the scroll container even when the
|
|
101
|
+
* audio is shorter (layout only — no effect on playback) */
|
|
102
|
+
fillViewport: boolean;
|
|
99
103
|
/** Whether undo is available */
|
|
100
104
|
canUndo: boolean;
|
|
101
105
|
/** Whether redo is available */
|
|
@@ -132,6 +136,16 @@ interface PlaylistControlsContextValue {
|
|
|
132
136
|
clearLoopRegion: () => void;
|
|
133
137
|
undo: () => void;
|
|
134
138
|
redo: () => void;
|
|
139
|
+
/** Mark a recording session active/inactive. While active: (1) the
|
|
140
|
+
* end-of-audio auto-stop is suppressed so overdub playback runs past the
|
|
141
|
+
* end of existing material, and (2) with an `armedTrackId`, that track's
|
|
142
|
+
* existing content is transiently muted — punch-in recording replaces
|
|
143
|
+
* whatever the take overlaps (#579), so the doomed material must not play
|
|
144
|
+
* under the overdub; its previous mute state is restored when the session
|
|
145
|
+
* ends (audio-only, the UI mute control is untouched). Wired automatically
|
|
146
|
+
* from the Waveform / PlaylistVisualization `recordingState` prop; overdub
|
|
147
|
+
* flows should also call it eagerly (before `play()`). */
|
|
148
|
+
setRecordingActive: (active: boolean, armedTrackId?: string | null) => void;
|
|
135
149
|
}
|
|
136
150
|
interface PlaylistDataContextValue {
|
|
137
151
|
duration: number;
|
|
@@ -222,9 +236,17 @@ interface WaveformPlaylistProviderProps {
|
|
|
222
236
|
* Use this during progressive loading to avoid rebuilding the engine for
|
|
223
237
|
* each track — flip to false when all tracks are ready for a single build. */
|
|
224
238
|
deferEngineRebuild?: boolean;
|
|
225
|
-
/** Disable automatic stop when the cursor reaches the end of the
|
|
226
|
-
* track
|
|
239
|
+
/** Disable the automatic stop when the cursor reaches the end of the
|
|
240
|
+
* longest track — the transport rolls until an explicit stop/pause
|
|
241
|
+
* (DAW-style). Implies the fillViewport layout. Recording sessions
|
|
242
|
+
* suppress the auto-stop automatically, so most recording UIs don't
|
|
243
|
+
* need this. */
|
|
227
244
|
indefinitePlayback?: boolean;
|
|
245
|
+
/** Extend the timeline (background + timescale) to fill the visible scroll
|
|
246
|
+
* container even when the audio is shorter. Layout only. Recording UIs
|
|
247
|
+
* typically want this so the empty timeline doesn't collapse to the audio
|
|
248
|
+
* width. */
|
|
249
|
+
fillViewport?: boolean;
|
|
228
250
|
/** Desired AudioContext sample rate. Creates a cross-browser AudioContext at
|
|
229
251
|
* this rate via standardized-audio-context. Pre-computed peaks (.dat files)
|
|
230
252
|
* render instantly when they match. On mismatch, falls back to worker. */
|
package/dist/index.d.ts
CHANGED
|
@@ -94,8 +94,12 @@ interface PlaylistStateContextValue {
|
|
|
94
94
|
selectedTrackId: string | null;
|
|
95
95
|
loopStart: number;
|
|
96
96
|
loopEnd: number;
|
|
97
|
-
/** Whether playback continues past the end of loaded audio
|
|
97
|
+
/** Whether playback continues past the end of loaded audio instead of
|
|
98
|
+
* auto-stopping (implies the fillViewport layout) */
|
|
98
99
|
indefinitePlayback: boolean;
|
|
100
|
+
/** Whether the timeline visually fills the scroll container even when the
|
|
101
|
+
* audio is shorter (layout only — no effect on playback) */
|
|
102
|
+
fillViewport: boolean;
|
|
99
103
|
/** Whether undo is available */
|
|
100
104
|
canUndo: boolean;
|
|
101
105
|
/** Whether redo is available */
|
|
@@ -132,6 +136,16 @@ interface PlaylistControlsContextValue {
|
|
|
132
136
|
clearLoopRegion: () => void;
|
|
133
137
|
undo: () => void;
|
|
134
138
|
redo: () => void;
|
|
139
|
+
/** Mark a recording session active/inactive. While active: (1) the
|
|
140
|
+
* end-of-audio auto-stop is suppressed so overdub playback runs past the
|
|
141
|
+
* end of existing material, and (2) with an `armedTrackId`, that track's
|
|
142
|
+
* existing content is transiently muted — punch-in recording replaces
|
|
143
|
+
* whatever the take overlaps (#579), so the doomed material must not play
|
|
144
|
+
* under the overdub; its previous mute state is restored when the session
|
|
145
|
+
* ends (audio-only, the UI mute control is untouched). Wired automatically
|
|
146
|
+
* from the Waveform / PlaylistVisualization `recordingState` prop; overdub
|
|
147
|
+
* flows should also call it eagerly (before `play()`). */
|
|
148
|
+
setRecordingActive: (active: boolean, armedTrackId?: string | null) => void;
|
|
135
149
|
}
|
|
136
150
|
interface PlaylistDataContextValue {
|
|
137
151
|
duration: number;
|
|
@@ -222,9 +236,17 @@ interface WaveformPlaylistProviderProps {
|
|
|
222
236
|
* Use this during progressive loading to avoid rebuilding the engine for
|
|
223
237
|
* each track — flip to false when all tracks are ready for a single build. */
|
|
224
238
|
deferEngineRebuild?: boolean;
|
|
225
|
-
/** Disable automatic stop when the cursor reaches the end of the
|
|
226
|
-
* track
|
|
239
|
+
/** Disable the automatic stop when the cursor reaches the end of the
|
|
240
|
+
* longest track — the transport rolls until an explicit stop/pause
|
|
241
|
+
* (DAW-style). Implies the fillViewport layout. Recording sessions
|
|
242
|
+
* suppress the auto-stop automatically, so most recording UIs don't
|
|
243
|
+
* need this. */
|
|
227
244
|
indefinitePlayback?: boolean;
|
|
245
|
+
/** Extend the timeline (background + timescale) to fill the visible scroll
|
|
246
|
+
* container even when the audio is shorter. Layout only. Recording UIs
|
|
247
|
+
* typically want this so the empty timeline doesn't collapse to the audio
|
|
248
|
+
* width. */
|
|
249
|
+
fillViewport?: boolean;
|
|
228
250
|
/** Desired AudioContext sample rate. Creates a cross-browser AudioContext at
|
|
229
251
|
* this rate via standardized-audio-context. Pre-computed peaks (.dat files)
|
|
230
252
|
* render instantly when they match. On mismatch, falls back to worker. */
|
package/dist/index.js
CHANGED
|
@@ -1881,14 +1881,13 @@ var WaveformPlaylistProvider = ({
|
|
|
1881
1881
|
soundFontCache,
|
|
1882
1882
|
deferEngineRebuild = false,
|
|
1883
1883
|
indefinitePlayback = false,
|
|
1884
|
+
fillViewport = false,
|
|
1884
1885
|
sampleRate: sampleRateProp,
|
|
1885
1886
|
createAdapter,
|
|
1886
1887
|
children
|
|
1887
1888
|
}) => {
|
|
1888
1889
|
var _a, _b, _c, _d;
|
|
1889
1890
|
const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
|
|
1890
|
-
const indefinitePlaybackRef = (0, import_react17.useRef)(indefinitePlayback);
|
|
1891
|
-
indefinitePlaybackRef.current = indefinitePlayback;
|
|
1892
1891
|
const stableZoomLevels = (0, import_react17.useMemo)(
|
|
1893
1892
|
() => zoomLevels,
|
|
1894
1893
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -1926,6 +1925,28 @@ var WaveformPlaylistProvider = ({
|
|
|
1926
1925
|
const engineRef = (0, import_react17.useRef)(null);
|
|
1927
1926
|
const adapterRef = (0, import_react17.useRef)(null);
|
|
1928
1927
|
const audioInitializedRef = (0, import_react17.useRef)(false);
|
|
1928
|
+
const indefinitePlaybackRef = (0, import_react17.useRef)(indefinitePlayback);
|
|
1929
|
+
indefinitePlaybackRef.current = indefinitePlayback;
|
|
1930
|
+
const recordingActiveRef = (0, import_react17.useRef)(false);
|
|
1931
|
+
const armedTrackMuteRef = (0, import_react17.useRef)(null);
|
|
1932
|
+
const setRecordingActive = (0, import_react17.useCallback)((active, armedTrackId) => {
|
|
1933
|
+
var _a2, _b2, _c2;
|
|
1934
|
+
recordingActiveRef.current = active;
|
|
1935
|
+
const engine = engineRef.current;
|
|
1936
|
+
if (!engine) return;
|
|
1937
|
+
if (active && armedTrackId) {
|
|
1938
|
+
if (((_a2 = armedTrackMuteRef.current) == null ? void 0 : _a2.trackId) === armedTrackId) return;
|
|
1939
|
+
if (armedTrackMuteRef.current) {
|
|
1940
|
+
engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);
|
|
1941
|
+
}
|
|
1942
|
+
const prevMuted = (_c2 = (_b2 = engine.getState().tracks.find((t) => t.id === armedTrackId)) == null ? void 0 : _b2.muted) != null ? _c2 : false;
|
|
1943
|
+
armedTrackMuteRef.current = { trackId: armedTrackId, prevMuted };
|
|
1944
|
+
engine.setTrackMute(armedTrackId, true);
|
|
1945
|
+
} else if (!active && armedTrackMuteRef.current) {
|
|
1946
|
+
engine.setTrackMute(armedTrackMuteRef.current.trackId, armedTrackMuteRef.current.prevMuted);
|
|
1947
|
+
armedTrackMuteRef.current = null;
|
|
1948
|
+
}
|
|
1949
|
+
}, []);
|
|
1929
1950
|
const isPlayingRef = (0, import_react17.useRef)(false);
|
|
1930
1951
|
isPlayingRef.current = isPlaying;
|
|
1931
1952
|
const playStartPositionRef = (0, import_react17.useRef)(0);
|
|
@@ -2517,7 +2538,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2517
2538
|
playbackEndTimeRef.current = null;
|
|
2518
2539
|
return;
|
|
2519
2540
|
}
|
|
2520
|
-
if (time >= duration && !indefinitePlaybackRef.current) {
|
|
2541
|
+
if (time >= duration && !indefinitePlaybackRef.current && !recordingActiveRef.current) {
|
|
2521
2542
|
if (engineRef.current) {
|
|
2522
2543
|
engineRef.current.stop();
|
|
2523
2544
|
}
|
|
@@ -2788,6 +2809,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2788
2809
|
loopStart,
|
|
2789
2810
|
loopEnd,
|
|
2790
2811
|
indefinitePlayback,
|
|
2812
|
+
fillViewport,
|
|
2791
2813
|
canUndo,
|
|
2792
2814
|
canRedo
|
|
2793
2815
|
}),
|
|
@@ -2805,6 +2827,7 @@ var WaveformPlaylistProvider = ({
|
|
|
2805
2827
|
loopStart,
|
|
2806
2828
|
loopEnd,
|
|
2807
2829
|
indefinitePlayback,
|
|
2830
|
+
fillViewport,
|
|
2808
2831
|
canUndo,
|
|
2809
2832
|
canRedo
|
|
2810
2833
|
]
|
|
@@ -2860,7 +2883,9 @@ var WaveformPlaylistProvider = ({
|
|
|
2860
2883
|
clearLoopRegion,
|
|
2861
2884
|
// Undo/redo
|
|
2862
2885
|
undo,
|
|
2863
|
-
redo
|
|
2886
|
+
redo,
|
|
2887
|
+
// Recording
|
|
2888
|
+
setRecordingActive
|
|
2864
2889
|
}),
|
|
2865
2890
|
[
|
|
2866
2891
|
play,
|
|
@@ -2892,7 +2917,8 @@ var WaveformPlaylistProvider = ({
|
|
|
2892
2917
|
setLoopRegionFromSelection,
|
|
2893
2918
|
clearLoopRegion,
|
|
2894
2919
|
undo,
|
|
2895
|
-
redo
|
|
2920
|
+
redo,
|
|
2921
|
+
setRecordingActive
|
|
2896
2922
|
]
|
|
2897
2923
|
);
|
|
2898
2924
|
const dataValue = (0, import_react17.useMemo)(
|
|
@@ -4021,7 +4047,7 @@ var PlaylistVisualization = ({
|
|
|
4021
4047
|
onRemoveTrack,
|
|
4022
4048
|
recordingState
|
|
4023
4049
|
}) => {
|
|
4024
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
4050
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
4025
4051
|
const theme = (0, import_ui_components8.useTheme)();
|
|
4026
4052
|
const { isPlaying, getLookAhead, getOutputLatency } = usePlaybackAnimation();
|
|
4027
4053
|
const {
|
|
@@ -4036,7 +4062,8 @@ var PlaylistVisualization = ({
|
|
|
4036
4062
|
loopStart,
|
|
4037
4063
|
loopEnd,
|
|
4038
4064
|
isLoopEnabled,
|
|
4039
|
-
indefinitePlayback
|
|
4065
|
+
indefinitePlayback,
|
|
4066
|
+
fillViewport
|
|
4040
4067
|
} = usePlaylistState();
|
|
4041
4068
|
const annotationIntegration = (0, import_react26.useContext)(AnnotationIntegrationContext);
|
|
4042
4069
|
const {
|
|
@@ -4051,7 +4078,8 @@ var PlaylistVisualization = ({
|
|
|
4051
4078
|
setScrollContainer,
|
|
4052
4079
|
setSelectedTrackId,
|
|
4053
4080
|
setCurrentTime,
|
|
4054
|
-
setLoopRegion
|
|
4081
|
+
setLoopRegion,
|
|
4082
|
+
setRecordingActive
|
|
4055
4083
|
} = usePlaylistControls();
|
|
4056
4084
|
const {
|
|
4057
4085
|
peaksDataArray,
|
|
@@ -4070,6 +4098,13 @@ var PlaylistVisualization = ({
|
|
|
4070
4098
|
mono
|
|
4071
4099
|
} = usePlaylistData();
|
|
4072
4100
|
const spectrogram = (0, import_react26.useContext)(SpectrogramIntegrationContext);
|
|
4101
|
+
const recordingActive = !!(recordingState == null ? void 0 : recordingState.isRecording);
|
|
4102
|
+
const armedTrackId = (_a = recordingState == null ? void 0 : recordingState.trackId) != null ? _a : null;
|
|
4103
|
+
(0, import_react26.useEffect)(() => {
|
|
4104
|
+
if (!recordingActive) return void 0;
|
|
4105
|
+
setRecordingActive(true, armedTrackId);
|
|
4106
|
+
return () => setRecordingActive(false);
|
|
4107
|
+
}, [recordingActive, armedTrackId, setRecordingActive]);
|
|
4073
4108
|
const perTrackSpectrogramHelpers = (0, import_react26.useMemo)(() => {
|
|
4074
4109
|
if (!spectrogram)
|
|
4075
4110
|
return /* @__PURE__ */ new Map();
|
|
@@ -4107,8 +4142,8 @@ var PlaylistVisualization = ({
|
|
|
4107
4142
|
}, max);
|
|
4108
4143
|
}, 0);
|
|
4109
4144
|
let displayDuration = tracksMaxDuration > 0 ? tracksMaxDuration : duration > 0 ? duration : DEFAULT_EMPTY_TRACK_DURATION;
|
|
4110
|
-
if (indefinitePlayback) {
|
|
4111
|
-
const containerWidth = (
|
|
4145
|
+
if (indefinitePlayback || fillViewport) {
|
|
4146
|
+
const containerWidth = (_c = (_b = scrollContainerRef.current) == null ? void 0 : _b.clientWidth) != null ? _c : 0;
|
|
4112
4147
|
const minContainerDuration = containerWidth * samplesPerPixel / sampleRate;
|
|
4113
4148
|
displayDuration = Math.max(displayDuration, minContainerDuration);
|
|
4114
4149
|
}
|
|
@@ -4566,8 +4601,8 @@ var PlaylistVisualization = ({
|
|
|
4566
4601
|
{
|
|
4567
4602
|
open: settingsModalTrackId !== null,
|
|
4568
4603
|
onClose: () => setSettingsModalTrackId(null),
|
|
4569
|
-
config: settingsModalTrackId !== null ? (
|
|
4570
|
-
colorMap: settingsModalTrackId !== null ? (
|
|
4604
|
+
config: settingsModalTrackId !== null ? (_h = (_g = (_f = (_d = spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)) == null ? void 0 : _d.config) != null ? _f : (_e = tracks.find((t) => t.id === settingsModalTrackId)) == null ? void 0 : _e.spectrogramConfig) != null ? _g : spectrogram.spectrogramConfig) != null ? _h : {} : {},
|
|
4605
|
+
colorMap: settingsModalTrackId !== null ? (_m = (_l = (_k = (_i = spectrogram.trackSpectrogramOverrides.get(settingsModalTrackId)) == null ? void 0 : _i.colorMap) != null ? _k : (_j = tracks.find((t) => t.id === settingsModalTrackId)) == null ? void 0 : _j.spectrogramColorMap) != null ? _l : spectrogram.spectrogramColorMap) != null ? _m : "viridis" : "viridis",
|
|
4571
4606
|
onApply: (newConfig, newColorMap) => {
|
|
4572
4607
|
if (settingsModalTrackId !== null) {
|
|
4573
4608
|
spectrogram.setTrackSpectrogramConfig(settingsModalTrackId, newConfig, newColorMap);
|