@waveform-playlist/recording 12.2.0 → 13.0.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 +153 -139
- package/dist/index.d.mts +35 -10
- package/dist/index.d.ts +35 -10
- package/dist/index.js +104 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -57
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# @waveform-playlist/recording
|
|
2
2
|
|
|
3
|
-
Audio recording support for waveform-playlist using AudioWorklet.
|
|
3
|
+
Audio recording support for waveform-playlist using AudioWorklet — mic capture, live waveform preview, VU metering, and overdub, as a set of React hooks.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- **AudioWorklet-based capture** — direct PCM access on the audio thread, no `ScriptProcessorNode`
|
|
8
|
+
- **Live waveform preview** — incremental per-channel peaks as you record, before the final `AudioBuffer` exists
|
|
9
|
+
- **Sample-accurate VU metering** — a dedicated meter worklet measures peak/RMS on every sample, not just once per animation frame
|
|
10
|
+
- **Multi-channel recording** — auto-detects the microphone's actual channel count from the `MediaStream`
|
|
11
|
+
- **Overdub with latency compensation** — record against existing playback; the finalized clip's timeline position accounts for output latency and Tone.js scheduling lookahead, with an optional manual override
|
|
12
|
+
- **Device selection & hot-plug** — enumerate microphones, switch devices between takes (refused while actively recording — a mid-recording switch would silently record silence), auto-fallback if the active device disconnects
|
|
13
|
+
- **Pause/resume** — pauses the worklet itself, not just the UI
|
|
14
|
+
- **Hooks only** — no bundled UI components; wire the state into your own controls or `@waveform-playlist/ui-components`'s `SegmentedVUMeter`
|
|
12
15
|
|
|
13
16
|
## Installation
|
|
14
17
|
|
|
@@ -16,208 +19,219 @@ Audio recording support for waveform-playlist using AudioWorklet.
|
|
|
16
19
|
npm install @waveform-playlist/recording
|
|
17
20
|
```
|
|
18
21
|
|
|
22
|
+
`@waveform-playlist/recording` requires these peer dependencies:
|
|
23
|
+
|
|
24
|
+
| Package | Purpose |
|
|
25
|
+
|---------|---------|
|
|
26
|
+
| `react` | ^18.0.0 |
|
|
27
|
+
| `styled-components` | ^6.0.0 — required transitively by `@waveform-playlist/ui-components` |
|
|
28
|
+
| `tone` | ^15.0.0 — recording shares the global AudioContext with `@waveform-playlist/playout` |
|
|
29
|
+
|
|
30
|
+
Pairs with `@waveform-playlist/browser` — see the [Recording guide](https://naomiaro.github.io/waveform-playlist/docs/react/guides/recording) for a full `WaveformPlaylistProvider` integration.
|
|
31
|
+
|
|
19
32
|
## Usage
|
|
20
33
|
|
|
21
34
|
### Basic Recording
|
|
22
35
|
|
|
23
|
-
```
|
|
24
|
-
import {
|
|
36
|
+
```tsx
|
|
37
|
+
import { useMicrophoneAccess, useRecording } from '@waveform-playlist/recording';
|
|
25
38
|
|
|
26
|
-
function
|
|
27
|
-
const { stream, requestAccess } = useMicrophoneAccess();
|
|
28
|
-
const { isRecording,
|
|
39
|
+
function RecordButton() {
|
|
40
|
+
const { stream, hasPermission, requestAccess } = useMicrophoneAccess();
|
|
41
|
+
const { isRecording, duration, peaks, startRecording, stopRecording } = useRecording(stream);
|
|
29
42
|
|
|
30
43
|
const handleRecord = async () => {
|
|
31
|
-
if (!
|
|
44
|
+
if (!hasPermission) {
|
|
32
45
|
await requestAccess();
|
|
46
|
+
return;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
if (isRecording) {
|
|
36
50
|
const audioBuffer = await stopRecording();
|
|
37
|
-
//
|
|
51
|
+
// audioBuffer is the finalized AudioBuffer — add it to a track, upload it, etc.
|
|
38
52
|
} else {
|
|
39
53
|
await startRecording();
|
|
40
54
|
}
|
|
41
55
|
};
|
|
42
56
|
|
|
43
57
|
return (
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{/* Display waveform using peaks */}
|
|
48
|
-
</div>
|
|
58
|
+
<button onClick={handleRecord}>
|
|
59
|
+
{isRecording ? `Stop (${duration.toFixed(1)}s)` : 'Record'}
|
|
60
|
+
</button>
|
|
49
61
|
);
|
|
50
62
|
}
|
|
51
63
|
```
|
|
52
64
|
|
|
53
|
-
###
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function RecordingApp() {
|
|
65
|
-
const { stream, devices, requestAccess } = useMicrophoneAccess();
|
|
66
|
-
const { isRecording, duration, startRecording, stopRecording } = useRecording(stream);
|
|
67
|
-
const [selectedDevice, setSelectedDevice] = useState<string>();
|
|
68
|
-
|
|
69
|
-
const handleDeviceChange = async (deviceId: string) => {
|
|
70
|
-
setSelectedDevice(deviceId);
|
|
71
|
-
await requestAccess(deviceId);
|
|
72
|
-
};
|
|
65
|
+
### VU Meter
|
|
66
|
+
|
|
67
|
+
`useMicrophoneLevel` drives level monitoring independently of recording — useful for an input-check screen before the user hits record.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { useMicrophoneAccess, useMicrophoneLevel } from '@waveform-playlist/recording';
|
|
71
|
+
import { SegmentedVUMeter } from '@waveform-playlist/ui-components';
|
|
72
|
+
|
|
73
|
+
function MicMonitor() {
|
|
74
|
+
const { stream, requestAccess } = useMicrophoneAccess();
|
|
75
|
+
const { levels, peakLevels } = useMicrophoneLevel(stream, { channelCount: 2 });
|
|
73
76
|
|
|
74
77
|
return (
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
onDeviceChange={handleDeviceChange}
|
|
80
|
-
/>
|
|
81
|
-
<RecordButton
|
|
82
|
-
isRecording={isRecording}
|
|
83
|
-
onClick={isRecording ? stopRecording : startRecording}
|
|
84
|
-
/>
|
|
85
|
-
<RecordingIndicator isRecording={isRecording} duration={duration} />
|
|
86
|
-
</div>
|
|
78
|
+
<>
|
|
79
|
+
<button onClick={() => requestAccess()}>Enable Microphone</button>
|
|
80
|
+
<SegmentedVUMeter levels={levels} peakLevels={peakLevels} />
|
|
81
|
+
</>
|
|
87
82
|
);
|
|
88
83
|
}
|
|
89
84
|
```
|
|
90
85
|
|
|
91
|
-
###
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
86
|
+
### Integrated Recording (with a track list)
|
|
87
|
+
|
|
88
|
+
`useIntegratedRecording` combines microphone access, metering, and recording into one hook that appends the finalized clip directly to a `ClipTrack[]` — the same array shape used by `@waveform-playlist/browser`'s `WaveformPlaylistProvider`.
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { useIntegratedRecording } from '@waveform-playlist/recording';
|
|
92
|
+
import type { ClipTrack } from '@waveform-playlist/core';
|
|
93
|
+
|
|
94
|
+
function Recorder({
|
|
95
|
+
tracks,
|
|
96
|
+
setTracks,
|
|
97
|
+
selectedTrackId,
|
|
98
|
+
currentTime,
|
|
99
|
+
}: {
|
|
100
|
+
tracks: ClipTrack[];
|
|
101
|
+
setTracks: (tracks: ClipTrack[]) => void;
|
|
102
|
+
selectedTrackId: string | null;
|
|
103
|
+
currentTime: number;
|
|
104
|
+
}) {
|
|
105
|
+
const {
|
|
106
|
+
isRecording,
|
|
107
|
+
duration,
|
|
108
|
+
levels,
|
|
109
|
+
peakLevels,
|
|
110
|
+
devices,
|
|
111
|
+
selectedDevice,
|
|
112
|
+
requestMicAccess,
|
|
113
|
+
changeDevice,
|
|
114
|
+
startRecording,
|
|
115
|
+
stopRecording,
|
|
116
|
+
recordingPeaks, // live per-channel peaks — feed straight into your waveform preview
|
|
117
|
+
error,
|
|
118
|
+
} = useIntegratedRecording(tracks, setTracks, selectedTrackId, {
|
|
119
|
+
currentTime,
|
|
120
|
+
channelCount: 2,
|
|
121
|
+
});
|
|
115
122
|
|
|
116
123
|
return (
|
|
117
|
-
<
|
|
118
|
-
<button onClick={
|
|
119
|
-
<button onClick={startRecording}>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
<div>
|
|
125
|
+
<button onClick={() => requestMicAccess()}>Enable Microphone</button>
|
|
126
|
+
<button onClick={isRecording ? stopRecording : startRecording} disabled={!selectedTrackId}>
|
|
127
|
+
{isRecording ? `Stop (${duration.toFixed(1)}s)` : 'Record'}
|
|
128
|
+
</button>
|
|
129
|
+
{error && <p>{error.message}</p>}
|
|
130
|
+
</div>
|
|
123
131
|
);
|
|
124
132
|
}
|
|
125
133
|
```
|
|
126
134
|
|
|
135
|
+
Stopping adds a new `AudioClip` to `selectedTrackId` at the timeline position captured when recording STARTED (punch-in semantics): the take lands exactly at the playhead and REPLACES any existing clip content it overlaps — partial overlaps are trimmed, fully-covered clips removed, and a clip spanning the take is split in two.
|
|
136
|
+
|
|
127
137
|
## API Reference
|
|
128
138
|
|
|
129
139
|
### Hooks
|
|
130
140
|
|
|
131
141
|
#### `useMicrophoneAccess()`
|
|
132
142
|
|
|
133
|
-
Manages microphone
|
|
143
|
+
Manages microphone permission, device enumeration, and hot-plug detection.
|
|
134
144
|
|
|
135
|
-
**Returns:**
|
|
136
|
-
- `stream: MediaStream | null`
|
|
137
|
-
- `devices: MicrophoneDevice[]`
|
|
138
|
-
- `hasPermission: boolean`
|
|
139
|
-
- `isLoading: boolean`
|
|
140
|
-
- `requestAccess: (deviceId?: string) => Promise<void>`
|
|
141
|
-
- `stopStream: () => void`
|
|
142
|
-
- `error: Error | null`
|
|
145
|
+
**Returns (`UseMicrophoneAccessReturn`):**
|
|
146
|
+
- `stream: MediaStream | null`
|
|
147
|
+
- `devices: MicrophoneDevice[]` — `{ deviceId, label, groupId }`
|
|
148
|
+
- `hasPermission: boolean`
|
|
149
|
+
- `isLoading: boolean`
|
|
150
|
+
- `requestAccess: (deviceId?: string, audioConstraints?: MediaTrackConstraints) => Promise<void>`
|
|
151
|
+
- `stopStream: () => void`
|
|
152
|
+
- `error: Error | null`
|
|
153
|
+
|
|
154
|
+
Requested audio constraints default to `echoCancellation: false`, `noiseSuppression: false`, `autoGainControl: false`, `latency: 0` (raw signal, low latency) — pass `audioConstraints` to override.
|
|
143
155
|
|
|
144
156
|
#### `useRecording(stream, options?)`
|
|
145
157
|
|
|
146
|
-
|
|
158
|
+
The core AudioWorklet-based recording hook.
|
|
147
159
|
|
|
148
160
|
**Parameters:**
|
|
149
|
-
- `stream: MediaStream | null`
|
|
161
|
+
- `stream: MediaStream | null`
|
|
150
162
|
- `options?: RecordingOptions`
|
|
151
|
-
- `
|
|
152
|
-
- `
|
|
153
|
-
- `
|
|
163
|
+
- `channelCount?: number` — fallback used only if the stream doesn't report its own channel count (default: `1`)
|
|
164
|
+
- `samplesPerPixel?: number` — peak resolution for the live preview (default: `1024`)
|
|
165
|
+
- `bits?: 8 | 16` — peak value bit depth (default: `16`)
|
|
166
|
+
|
|
167
|
+
**Returns (`UseRecordingReturn`):**
|
|
168
|
+
- `isRecording: boolean`, `isPaused: boolean`, `duration: number` (seconds)
|
|
169
|
+
- `peaks: (Int8Array | Int16Array)[]` — one entry per channel, growing live during recording
|
|
170
|
+
- `audioBuffer: AudioBuffer | null` — set after `stopRecording()` resolves
|
|
171
|
+
- `level: number`, `peakLevel: number` — **deprecated** (always `0`); use `useMicrophoneLevel` for metering. Removed in the next major
|
|
172
|
+
- `startRecording: () => Promise<boolean>` — resolves `true` when the capture pipeline actually started (`false`: no stream, already recording, worklet failure); check it before starting synchronized playback
|
|
173
|
+
- `stopRecording: () => Promise<AudioBuffer | null>` — awaits the worklet's final flush before resolving, so the last samples are never dropped
|
|
174
|
+
- `pauseRecording: () => void`, `resumeRecording: () => void` — pause/resume the worklet itself, not just the UI
|
|
175
|
+
- `error: Error | null`
|
|
154
176
|
|
|
155
|
-
|
|
156
|
-
- `isRecording: boolean` - Whether recording is active
|
|
157
|
-
- `isPaused: boolean` - Whether recording is paused
|
|
158
|
-
- `duration: number` - Recording duration in seconds
|
|
159
|
-
- `peaks: number[]` - Peak data for waveform visualization
|
|
160
|
-
- `audioBuffer: AudioBuffer | null` - Final recorded audio buffer
|
|
161
|
-
- `startRecording: () => Promise<void>` - Start recording
|
|
162
|
-
- `stopRecording: () => Promise<AudioBuffer | null>` - Stop and finalize recording
|
|
163
|
-
- `pauseRecording: () => void` - Pause recording
|
|
164
|
-
- `resumeRecording: () => void` - Resume recording
|
|
165
|
-
- `error: Error | null` - Error state
|
|
177
|
+
#### `useMicrophoneLevel(stream, options?)`
|
|
166
178
|
|
|
167
|
-
|
|
179
|
+
Sample-accurate VU metering via a separate meter AudioWorklet — independent of `useRecording`, so it works before recording starts.
|
|
168
180
|
|
|
169
|
-
|
|
181
|
+
**Parameters:**
|
|
182
|
+
- `stream: MediaStream | null`
|
|
183
|
+
- `options?: UseMicrophoneLevelOptions`
|
|
184
|
+
- `updateRate?: number` — Hz (default: `60`)
|
|
185
|
+
- `channelCount?: number` (default: `1`)
|
|
170
186
|
|
|
171
|
-
|
|
187
|
+
**Returns (`UseMicrophoneLevelReturn`):**
|
|
188
|
+
- `levels: number[]`, `peakLevels: number[]`, `rmsLevels: number[]` — per channel, normalized 0–1
|
|
189
|
+
- `level: number`, `peakLevel: number` — scalar convenience values (max across channels when `channelCount > 1`)
|
|
190
|
+
- `resetPeak: () => void` — clears held peak indicators, e.g. on device switch
|
|
191
|
+
- `error: Error | null`
|
|
172
192
|
|
|
173
|
-
|
|
174
|
-
- `isRecording: boolean` - Recording state
|
|
175
|
-
- `onClick: () => void` - Click handler
|
|
176
|
-
- `disabled?: boolean` - Disabled state
|
|
177
|
-
- `className?: string` - CSS class name
|
|
193
|
+
#### `useIntegratedRecording(tracks, setTracks, selectedTrackId, options?)`
|
|
178
194
|
|
|
179
|
-
|
|
195
|
+
Batteries-included hook: wires `useMicrophoneAccess` + `useMicrophoneLevel` + `useRecording` together and appends the finalized recording to `tracks` as a new `AudioClip`.
|
|
180
196
|
|
|
181
|
-
|
|
197
|
+
**Parameters:**
|
|
198
|
+
- `tracks: ClipTrack[]`, `setTracks: (tracks: ClipTrack[]) => void`, `selectedTrackId: string | null`
|
|
199
|
+
- `options?: IntegratedRecordingOptions`
|
|
200
|
+
- `currentTime?: number` — playback/cursor position; the clip is captured at this position at record *start* (not stop), so overdubbing while transport is running lands the clip correctly
|
|
201
|
+
- `audioConstraints?: MediaTrackConstraints`
|
|
202
|
+
- `channelCount?: number` (default: `1`)
|
|
203
|
+
- `samplesPerPixel?: number` (default: `1024`)
|
|
204
|
+
- `latencyOffset?: number` — seconds; overrides the auto-computed `outputLatency + lookAhead` compensation applied to the clip's start. `0` disables compensation; omit to auto-compute
|
|
182
205
|
|
|
183
|
-
**
|
|
184
|
-
- `devices: MicrophoneDevice[]` - Available devices
|
|
185
|
-
- `selectedDeviceId?: string` - Currently selected device
|
|
186
|
-
- `onDeviceChange: (deviceId: string) => void` - Change handler
|
|
187
|
-
- `disabled?: boolean` - Disabled state
|
|
188
|
-
- `className?: string` - CSS class name
|
|
206
|
+
**Returns (`UseIntegratedRecordingReturn`):** recording state (`isRecording`, `isPaused`, `duration`, `level`, `peakLevel`, `levels`, `peakLevels`, `rmsLevels`), microphone state (`stream`, `devices`, `hasPermission`, `selectedDevice`), controls (`startRecording` — resolves `true` when capture actually started, `stopRecording`, `pauseRecording`, `resumeRecording`, `requestMicAccess`, `changeDevice`), `recordingPeaks` (live per-channel peaks for preview), and a combined `error`.
|
|
189
207
|
|
|
190
|
-
|
|
208
|
+
**Overdub with `@waveform-playlist/browser`:** call `usePlaylistControls().setRecordingActive(true, trackId)` *before* `play()` — while the session is active the end-of-timeline auto-stop is suppressed (the take can run past existing audio) and the recorded-over track's existing content is transiently muted (punch-in replaces it). Both reset automatically when the recording ends; check `startRecording()`'s boolean and release the session on `false`.
|
|
191
209
|
|
|
192
|
-
|
|
210
|
+
### Types
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
- `isRecording: boolean` - Recording state
|
|
196
|
-
- `isPaused?: boolean` - Paused state
|
|
197
|
-
- `duration: number` - Duration in seconds
|
|
198
|
-
- `formatTime?: (seconds: number) => string` - Custom time formatter
|
|
199
|
-
- `className?: string` - CSS class name
|
|
212
|
+
`RecordingState`, `RecordingData`, `MicrophoneDevice`, `RecordingOptions`, `UseRecordingReturn`, `UseMicrophoneAccessReturn` are all exported from the package root for consumers building their own UI around these hooks.
|
|
200
213
|
|
|
201
214
|
## Architecture
|
|
202
215
|
|
|
203
|
-
The recording implementation uses AudioWorklet for low-latency audio capture:
|
|
204
|
-
|
|
205
216
|
```
|
|
206
217
|
getUserMedia → MediaStream
|
|
207
218
|
↓
|
|
208
|
-
|
|
219
|
+
MediaStreamSource (shared global AudioContext)
|
|
209
220
|
↓
|
|
210
|
-
|
|
211
|
-
|
|
221
|
+
AudioWorklet Processors (from @waveform-playlist/worklets)
|
|
222
|
+
- recording-processor: captures raw PCM per channel
|
|
223
|
+
- meter-processor: sample-accurate peak/RMS
|
|
212
224
|
↓
|
|
213
|
-
Main Thread (React
|
|
214
|
-
- Accumulates audio data
|
|
215
|
-
- Generates peaks
|
|
216
|
-
- Updates
|
|
225
|
+
Main Thread (React Hooks)
|
|
226
|
+
- Accumulates audio data per channel
|
|
227
|
+
- Generates live peaks incrementally
|
|
228
|
+
- Updates VU meter state
|
|
217
229
|
↓
|
|
218
|
-
|
|
230
|
+
Final AudioBuffer (after stopRecording's stop-handshake)
|
|
219
231
|
```
|
|
220
232
|
|
|
233
|
+
Each hook creates its own `MediaStreamSource` from the shared context rather than reusing one across hooks — required for Firefox, which throws if source and destination nodes come from different context instances.
|
|
234
|
+
|
|
221
235
|
## Browser Support
|
|
222
236
|
|
|
223
237
|
- Chrome 66+
|
package/dist/index.d.mts
CHANGED
|
@@ -55,9 +55,22 @@ interface UseRecordingReturn {
|
|
|
55
55
|
duration: number;
|
|
56
56
|
peaks: (Int8Array | Int16Array)[];
|
|
57
57
|
audioBuffer: AudioBuffer | null;
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated Always 0 — VU levels come from `useMicrophoneLevel`
|
|
60
|
+
* (meter-processor worklet), not this hook. Will be removed in the next
|
|
61
|
+
* major version.
|
|
62
|
+
*/
|
|
58
63
|
level: number;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Always 0 — use `useMicrophoneLevel`'s `peakLevel` instead.
|
|
66
|
+
* Will be removed in the next major version.
|
|
67
|
+
*/
|
|
59
68
|
peakLevel: number;
|
|
60
|
-
|
|
69
|
+
/** Start capturing. Resolves `true` when the capture pipeline actually
|
|
70
|
+
* started, `false` when it could not (no stream, already recording,
|
|
71
|
+
* worklet/module failure, unmounted). Callers that synchronize playback
|
|
72
|
+
* with recording should check the result before starting playback. */
|
|
73
|
+
startRecording: () => Promise<boolean>;
|
|
61
74
|
stopRecording: () => Promise<AudioBuffer | null>;
|
|
62
75
|
pauseRecording: () => void;
|
|
63
76
|
resumeRecording: () => void;
|
|
@@ -121,16 +134,19 @@ interface UseMicrophoneLevelReturn {
|
|
|
121
134
|
*/
|
|
122
135
|
resetPeak: () => void;
|
|
123
136
|
/**
|
|
124
|
-
* Per-channel peak levels (0-1). Array length matches
|
|
137
|
+
* Per-channel peak levels (0-1). Array length matches the metered channel
|
|
138
|
+
* count: the mic's auto-detected channels, with mono mirrored up to the
|
|
139
|
+
* requested `channelCount`. A mic with MORE channels than `channelCount`
|
|
140
|
+
* yields one entry per actual channel.
|
|
125
141
|
* True peak: max absolute sample value per analysis frame.
|
|
126
142
|
*/
|
|
127
143
|
levels: number[];
|
|
128
144
|
/**
|
|
129
|
-
* Per-channel held peak levels (0-1).
|
|
145
|
+
* Per-channel held peak levels (0-1). Same length contract as `levels`.
|
|
130
146
|
*/
|
|
131
147
|
peakLevels: number[];
|
|
132
148
|
/**
|
|
133
|
-
* Per-channel RMS levels (0-1).
|
|
149
|
+
* Per-channel RMS levels (0-1). Same length contract as `levels`.
|
|
134
150
|
* RMS: root mean square of samples per analysis frame.
|
|
135
151
|
*/
|
|
136
152
|
rmsLevels: number[];
|
|
@@ -164,8 +180,10 @@ declare function useMicrophoneLevel(stream: MediaStream | null, options?: UseMic
|
|
|
164
180
|
|
|
165
181
|
interface IntegratedRecordingOptions {
|
|
166
182
|
/**
|
|
167
|
-
* Current playback/cursor position in seconds
|
|
168
|
-
*
|
|
183
|
+
* Current playback/cursor position in seconds.
|
|
184
|
+
* Punch-in semantics (#579): the recorded clip lands exactly at this
|
|
185
|
+
* position and REPLACES any existing clip content it overlaps — partial
|
|
186
|
+
* overlaps are trimmed, fully-covered clips removed, spanning clips split.
|
|
169
187
|
*/
|
|
170
188
|
currentTime?: number;
|
|
171
189
|
/**
|
|
@@ -199,18 +217,25 @@ interface UseIntegratedRecordingReturn {
|
|
|
199
217
|
duration: number;
|
|
200
218
|
level: number;
|
|
201
219
|
peakLevel: number;
|
|
202
|
-
/**
|
|
220
|
+
/**
|
|
221
|
+
* Per-channel peak levels (0-1). Array length matches the metered channel
|
|
222
|
+
* count (auto-detected mic channels; mono mirrored up to channelCount —
|
|
223
|
+
* see UseMicrophoneLevelReturn.levels).
|
|
224
|
+
*/
|
|
203
225
|
levels: number[];
|
|
204
|
-
/** Per-channel held peak levels (0-1).
|
|
226
|
+
/** Per-channel held peak levels (0-1). Same length contract as `levels`. */
|
|
205
227
|
peakLevels: number[];
|
|
206
|
-
/** Per-channel RMS levels (0-1).
|
|
228
|
+
/** Per-channel RMS levels (0-1). Same length contract as `levels`. */
|
|
207
229
|
rmsLevels: number[];
|
|
208
230
|
error: Error | null;
|
|
209
231
|
stream: MediaStream | null;
|
|
210
232
|
devices: MicrophoneDevice[];
|
|
211
233
|
hasPermission: boolean;
|
|
212
234
|
selectedDevice: string | null;
|
|
213
|
-
|
|
235
|
+
/** Start capturing on the selected track. Resolves `true` when the capture
|
|
236
|
+
* pipeline actually started — callers that synchronize playback with
|
|
237
|
+
* recording (overdub) should check this before starting playback. */
|
|
238
|
+
startRecording: () => Promise<boolean>;
|
|
214
239
|
stopRecording: () => void;
|
|
215
240
|
pauseRecording: () => void;
|
|
216
241
|
resumeRecording: () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -55,9 +55,22 @@ interface UseRecordingReturn {
|
|
|
55
55
|
duration: number;
|
|
56
56
|
peaks: (Int8Array | Int16Array)[];
|
|
57
57
|
audioBuffer: AudioBuffer | null;
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated Always 0 — VU levels come from `useMicrophoneLevel`
|
|
60
|
+
* (meter-processor worklet), not this hook. Will be removed in the next
|
|
61
|
+
* major version.
|
|
62
|
+
*/
|
|
58
63
|
level: number;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Always 0 — use `useMicrophoneLevel`'s `peakLevel` instead.
|
|
66
|
+
* Will be removed in the next major version.
|
|
67
|
+
*/
|
|
59
68
|
peakLevel: number;
|
|
60
|
-
|
|
69
|
+
/** Start capturing. Resolves `true` when the capture pipeline actually
|
|
70
|
+
* started, `false` when it could not (no stream, already recording,
|
|
71
|
+
* worklet/module failure, unmounted). Callers that synchronize playback
|
|
72
|
+
* with recording should check the result before starting playback. */
|
|
73
|
+
startRecording: () => Promise<boolean>;
|
|
61
74
|
stopRecording: () => Promise<AudioBuffer | null>;
|
|
62
75
|
pauseRecording: () => void;
|
|
63
76
|
resumeRecording: () => void;
|
|
@@ -121,16 +134,19 @@ interface UseMicrophoneLevelReturn {
|
|
|
121
134
|
*/
|
|
122
135
|
resetPeak: () => void;
|
|
123
136
|
/**
|
|
124
|
-
* Per-channel peak levels (0-1). Array length matches
|
|
137
|
+
* Per-channel peak levels (0-1). Array length matches the metered channel
|
|
138
|
+
* count: the mic's auto-detected channels, with mono mirrored up to the
|
|
139
|
+
* requested `channelCount`. A mic with MORE channels than `channelCount`
|
|
140
|
+
* yields one entry per actual channel.
|
|
125
141
|
* True peak: max absolute sample value per analysis frame.
|
|
126
142
|
*/
|
|
127
143
|
levels: number[];
|
|
128
144
|
/**
|
|
129
|
-
* Per-channel held peak levels (0-1).
|
|
145
|
+
* Per-channel held peak levels (0-1). Same length contract as `levels`.
|
|
130
146
|
*/
|
|
131
147
|
peakLevels: number[];
|
|
132
148
|
/**
|
|
133
|
-
* Per-channel RMS levels (0-1).
|
|
149
|
+
* Per-channel RMS levels (0-1). Same length contract as `levels`.
|
|
134
150
|
* RMS: root mean square of samples per analysis frame.
|
|
135
151
|
*/
|
|
136
152
|
rmsLevels: number[];
|
|
@@ -164,8 +180,10 @@ declare function useMicrophoneLevel(stream: MediaStream | null, options?: UseMic
|
|
|
164
180
|
|
|
165
181
|
interface IntegratedRecordingOptions {
|
|
166
182
|
/**
|
|
167
|
-
* Current playback/cursor position in seconds
|
|
168
|
-
*
|
|
183
|
+
* Current playback/cursor position in seconds.
|
|
184
|
+
* Punch-in semantics (#579): the recorded clip lands exactly at this
|
|
185
|
+
* position and REPLACES any existing clip content it overlaps — partial
|
|
186
|
+
* overlaps are trimmed, fully-covered clips removed, spanning clips split.
|
|
169
187
|
*/
|
|
170
188
|
currentTime?: number;
|
|
171
189
|
/**
|
|
@@ -199,18 +217,25 @@ interface UseIntegratedRecordingReturn {
|
|
|
199
217
|
duration: number;
|
|
200
218
|
level: number;
|
|
201
219
|
peakLevel: number;
|
|
202
|
-
/**
|
|
220
|
+
/**
|
|
221
|
+
* Per-channel peak levels (0-1). Array length matches the metered channel
|
|
222
|
+
* count (auto-detected mic channels; mono mirrored up to channelCount —
|
|
223
|
+
* see UseMicrophoneLevelReturn.levels).
|
|
224
|
+
*/
|
|
203
225
|
levels: number[];
|
|
204
|
-
/** Per-channel held peak levels (0-1).
|
|
226
|
+
/** Per-channel held peak levels (0-1). Same length contract as `levels`. */
|
|
205
227
|
peakLevels: number[];
|
|
206
|
-
/** Per-channel RMS levels (0-1).
|
|
228
|
+
/** Per-channel RMS levels (0-1). Same length contract as `levels`. */
|
|
207
229
|
rmsLevels: number[];
|
|
208
230
|
error: Error | null;
|
|
209
231
|
stream: MediaStream | null;
|
|
210
232
|
devices: MicrophoneDevice[];
|
|
211
233
|
hasPermission: boolean;
|
|
212
234
|
selectedDevice: string | null;
|
|
213
|
-
|
|
235
|
+
/** Start capturing on the selected track. Resolves `true` when the capture
|
|
236
|
+
* pipeline actually started — callers that synchronize playback with
|
|
237
|
+
* recording (overdub) should check this before starting playback. */
|
|
238
|
+
startRecording: () => Promise<boolean>;
|
|
214
239
|
stopRecording: () => void;
|
|
215
240
|
pauseRecording: () => void;
|
|
216
241
|
resumeRecording: () => void;
|