@waveform-playlist/core 12.5.0 → 12.6.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/README.md +86 -0
- package/dist/index.d.mts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +99 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# @waveform-playlist/core
|
|
2
|
+
|
|
3
|
+
Core types, interfaces and utilities for waveform-playlist — pure functions and TypeScript types with zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
Used by nearly every `@waveform-playlist/*` and `@dawcore/*` package (`engine`, `playout`, `media-element-playout`, `browser`, `ui-components`, `recording`, `annotations`, `spectrogram`, `midi`, `transport`, `dawcore`, `dawcore-spectrogram`, `dawcore-midi`, `webaudio-peaks`, `loaders`, and more). Most consumers get it transitively — install it directly only if you're building on the clip model, timeline math, or fade curves yourself.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Clip & track types** — `AudioClip`, `ClipTrack`, `Timeline`, `Fade`/`FadeType`, `Peaks`/`Bits`/`PeakData`, `WaveformDataObject`
|
|
10
|
+
- **Sample-based timeline math** — `createClip`/`createClipFromSeconds`/`createClipFromTicks`, clip queries (`getClipsInRange`, `clipsOverlap`, `findGaps`), and second-conversion helpers (`clipStartTime`, `clipEndTime`, `clipPixelWidth`)
|
|
11
|
+
- **Fade curves** — linear, exponential, logarithmic, and S-curve generators for native Web Audio `AudioParam` (no Tone.js dependency)
|
|
12
|
+
- **Decibel utilities** — gain ↔ dB ↔ normalized-0-1 conversions for meters and volume controls
|
|
13
|
+
- **Peaks generation** — real-time min/max peak extraction during recording
|
|
14
|
+
- **Musical time** — PPQN tick math, bar/beat conversion, time-signature meter detection, grid snapping
|
|
15
|
+
- **Keyboard shortcuts** — a framework-agnostic shortcut matcher shared by the React and Web Components layers
|
|
16
|
+
- **Spectrogram canvas-ID contract** — canonical build/parse helpers for the `${clipId}-ch${n}-chunk${m}` canvas ID format shared by the worker pool and rendering layers
|
|
17
|
+
- **HTTP range support probing** — detect whether an audio host supports byte-range seeking
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @waveform-playlist/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Zero dependencies — safe to install anywhere without pulling in Web Audio, React, or Tone.js.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createClipFromSeconds, clipStartTime, clipEndTime, gainToDb } from '@waveform-playlist/core';
|
|
31
|
+
|
|
32
|
+
// Build a clip from a decoded AudioBuffer, positioned at 2.5s on the timeline
|
|
33
|
+
const clip = createClipFromSeconds({
|
|
34
|
+
audioBuffer,
|
|
35
|
+
startTime: 2.5,
|
|
36
|
+
offset: 0,
|
|
37
|
+
fadeIn: { duration: 0.5, type: 'linear' },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
clipStartTime(clip); // 2.5
|
|
41
|
+
clipEndTime(clip); // 2.5 + audioBuffer.duration
|
|
42
|
+
|
|
43
|
+
// Convert a linear gain value to dB for a Tone.js Volume node
|
|
44
|
+
gainToDb(0.5); // ≈ -6.02
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { buildSpectrogramCanvasId, parseSpectrogramCanvasId } from '@waveform-playlist/core';
|
|
49
|
+
|
|
50
|
+
const id = buildSpectrogramCanvasId({ clipId: 'clip-1', channelIndex: 0, chunkIndex: 3 });
|
|
51
|
+
// "clip-1-ch0-chunk3"
|
|
52
|
+
|
|
53
|
+
parseSpectrogramCanvasId(id);
|
|
54
|
+
// { clipId: 'clip-1', channelIndex: 0, chunkIndex: 3 }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
| Module | Key exports |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `types/` | `AudioClip`, `ClipTrack`, `Timeline`, `Track`, `Fade`, `FadeType`, `Peaks`, `Bits`, `PeakData`, `WaveformDataObject`, `SpectrogramConfig`, `RenderMode`, `ColorMapValue` |
|
|
62
|
+
| `clipTimeHelpers` | `clipStartTime`, `clipEndTime`, `clipOffsetTime`, `clipDurationTime`, `clipPixelWidth`, `trackChannelCount` |
|
|
63
|
+
| clip constructors (`types/clip`) | `createClip`, `createClipFromSeconds`, `createClipFromTicks`, `createTrack`, `createTimeline`, `getClipsInRange`, `getClipsAtSample`, `clipsOverlap`, `sortClipsByTime`, `findGaps` |
|
|
64
|
+
| `fades` | `applyFadeIn`, `applyFadeOut`, `generateCurve`, `linearCurve`, `exponentialCurve`, `logarithmicCurve`, `sCurveCurve` |
|
|
65
|
+
| `utils/dBUtils` | `gainToDb`, `dBToNormalized`, `normalizedToDb`, `gainToNormalized` |
|
|
66
|
+
| `utils/conversions` | `samplesToSeconds`, `secondsToSamples`, `samplesToPixels`, `pixelsToSamples`, `pixelsToSeconds`, `secondsToPixels` |
|
|
67
|
+
| `utils/beatsAndBars` | `PPQN`, `ticksPerBeat`, `ticksPerBar`, `ticksToSamples`, `samplesToTicks`, `snapToGrid` |
|
|
68
|
+
| `utils/musicalTicks` | `computeMusicalTicks`, `snapToTicks`, `snapTickToGrid`, `SnapTo`, `MusicalTick` |
|
|
69
|
+
| `utils/meterDetection` | `MeterEntry`, `detectMeterChanges` |
|
|
70
|
+
| `utils/peaksGenerator` | `generatePeaks`, `appendPeaks` |
|
|
71
|
+
| `utils/audioBufferUtils` | `concatenateAudioData`, `createAudioBuffer`, `appendToAudioBuffer`, `calculateDuration` |
|
|
72
|
+
| `utils/latency` | `resolveRecordingOffsetSamples` — override-vs-auto recording latency compensation (shared by React + dawcore) |
|
|
73
|
+
| `utils/carveClipRange` | `carveClipRange` — punch-in replace: carve a sample range out of a clip list (trim/remove/split) |
|
|
74
|
+
| `keyboard` | `KeyboardShortcut`, `handleKeyboardEvent`, `getShortcutLabel` |
|
|
75
|
+
| `spectrogramCanvasId` | `buildSpectrogramCanvasId`, `parseSpectrogramCanvasId`, `SpectrogramCanvasIdParts` |
|
|
76
|
+
| `probeRangeSupport` | `probeRangeSupport`, `RangeSupport` |
|
|
77
|
+
| `enumerateMicrophones` | `enumerateMicrophones`, `watchMicrophoneDevices`, `MicrophoneEnumeration` — permission-free device listing with `supported`/`hasLabels` flags (labels are browser-redacted pre-permission) |
|
|
78
|
+
| `constants` | `MAX_CANVAS_WIDTH` |
|
|
79
|
+
|
|
80
|
+
## Examples & Documentation
|
|
81
|
+
|
|
82
|
+
- Guides: [naomiaro.github.io/waveform-playlist](https://naomiaro.github.io/waveform-playlist/)
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -880,6 +880,37 @@ declare const SPECTROGRAM_DEFAULTS: Required<Omit<SpectrogramConfig, 'maxFrequen
|
|
|
880
880
|
/** Default color map when none is specified. */
|
|
881
881
|
declare const DEFAULT_SPECTROGRAM_COLOR_MAP: ColorMapValue;
|
|
882
882
|
|
|
883
|
+
/**
|
|
884
|
+
* Punch-in replace: carve a sample range out of a clip list.
|
|
885
|
+
*
|
|
886
|
+
* Used when a newly recorded clip lands at the playhead and must REPLACE any
|
|
887
|
+
* existing clip content between its start and end (issue #579): partial
|
|
888
|
+
* overlaps are trimmed, fully-covered clips are removed, and a clip that
|
|
889
|
+
* spans the whole range is split into two.
|
|
890
|
+
*/
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Return a new clip list with the sample range [rangeStart, rangeEnd)
|
|
894
|
+
* removed from every clip that overlaps it.
|
|
895
|
+
*
|
|
896
|
+
* - Clips outside the range are returned by reference (untouched).
|
|
897
|
+
* - Clips fully inside the range are dropped.
|
|
898
|
+
* - A clip overlapping only the range's start keeps its head (right-trim).
|
|
899
|
+
* - A clip overlapping only the range's end keeps its tail: its start moves
|
|
900
|
+
* to rangeEnd and `offsetSamples` advances by the carved amount.
|
|
901
|
+
* - A clip containing the whole range splits into head + tail; the tail is a
|
|
902
|
+
* new clip (deterministic id `<original>-carve-<rangeEnd>`) sharing the
|
|
903
|
+
* same audioBuffer.
|
|
904
|
+
*
|
|
905
|
+
* Clips whose `startSample` changes lose their `startTick` — a sample-space
|
|
906
|
+
* carve cannot recompute ticks, and the engine re-enriches clips without
|
|
907
|
+
* `startTick` on ingestion (see AudioClip.startTick).
|
|
908
|
+
*
|
|
909
|
+
* Pure and immutable: input clips are never mutated. An empty or inverted
|
|
910
|
+
* range returns the input list unchanged.
|
|
911
|
+
*/
|
|
912
|
+
declare function carveClipRange(clips: readonly AudioClip[], rangeStart: number, rangeEnd: number): AudioClip[];
|
|
913
|
+
|
|
883
914
|
/** Clip start position in seconds */
|
|
884
915
|
declare function clipStartTime(clip: AudioClip): number;
|
|
885
916
|
/** Clip end position in seconds (start + duration) */
|
|
@@ -1036,4 +1067,51 @@ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
|
1036
1067
|
*/
|
|
1037
1068
|
declare function probeRangeSupport(url: string, fetchImpl?: FetchLike): Promise<RangeSupport>;
|
|
1038
1069
|
|
|
1039
|
-
|
|
1070
|
+
/**
|
|
1071
|
+
* Framework-agnostic microphone enumeration.
|
|
1072
|
+
*
|
|
1073
|
+
* Works without requesting a stream, but browsers redact device information
|
|
1074
|
+
* until microphone permission is granted: labels are empty everywhere, and
|
|
1075
|
+
* Chrome/Safari typically also redact deviceIds (often exposing a single
|
|
1076
|
+
* placeholder entry). The `hasLabels` flag tells consumers whether the list
|
|
1077
|
+
* is a usable picker or just a "microphones exist" signal — re-enumerate
|
|
1078
|
+
* after a successful getUserMedia to get the real list.
|
|
1079
|
+
*
|
|
1080
|
+
* Follows the probeRangeSupport pattern: the browser API is injectable for
|
|
1081
|
+
* tests and non-browser environments degrade gracefully.
|
|
1082
|
+
*/
|
|
1083
|
+
interface MicrophoneDeviceInfo {
|
|
1084
|
+
deviceId: string;
|
|
1085
|
+
/** Real device label, or a generated fallback name when redacted. */
|
|
1086
|
+
label: string;
|
|
1087
|
+
groupId: string;
|
|
1088
|
+
}
|
|
1089
|
+
interface MicrophoneEnumeration {
|
|
1090
|
+
/** False when enumeration is unavailable — SSR/Node, insecure context
|
|
1091
|
+
* (mediaDevices requires HTTPS or localhost), or a very old browser. */
|
|
1092
|
+
supported: boolean;
|
|
1093
|
+
/** True when the browser exposed real device labels. False before
|
|
1094
|
+
* microphone permission is granted (labels — and often deviceIds — are
|
|
1095
|
+
* redacted); the `devices` labels are then generated fallbacks. */
|
|
1096
|
+
hasLabels: boolean;
|
|
1097
|
+
devices: MicrophoneDeviceInfo[];
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* List audio input devices without requesting a stream.
|
|
1101
|
+
*
|
|
1102
|
+
* @param mediaDevices - Injectable for tests; defaults to `navigator.mediaDevices`.
|
|
1103
|
+
*/
|
|
1104
|
+
declare function enumerateMicrophones(mediaDevices?: MediaDevices): Promise<MicrophoneEnumeration>;
|
|
1105
|
+
/**
|
|
1106
|
+
* Subscribe to the microphone device list: the listener fires once with the
|
|
1107
|
+
* initial enumeration and again on every `devicechange` (hot-plug, permission
|
|
1108
|
+
* grant in some browsers). Returns an unsubscribe function.
|
|
1109
|
+
*
|
|
1110
|
+
* Enumeration failures are logged and skipped — the subscription survives a
|
|
1111
|
+
* transient failure and delivers the next successful enumeration.
|
|
1112
|
+
*
|
|
1113
|
+
* @param mediaDevices - Injectable for tests; defaults to `navigator.mediaDevices`.
|
|
1114
|
+
*/
|
|
1115
|
+
declare function watchMicrophoneDevices(listener: (enumeration: MicrophoneEnumeration) => void, mediaDevices?: MediaDevices): () => void;
|
|
1116
|
+
|
|
1117
|
+
export { type AnnotationAction, type AnnotationActionOptions, type AnnotationData, type AnnotationEventMap, type AnnotationFormat, type AnnotationListOptions, type AudioBuffer$1 as AudioBuffer, type AudioClip, type Bits, type ClipTrack, type ColorMapEntry, type ColorMapName, type ColorMapValue, type CreateClipOptions, type CreateClipOptionsSeconds, type CreateClipOptionsTicks, type CreateTrackOptions, DEFAULT_SPECTROGRAM_COLOR_MAP, type FFTSize, type Fade, type FadeConfig, type FadeType, type Gap, InteractionState, type KeyboardShortcut, MAX_CANVAS_WIDTH, MIN_PIXELS_PER_UNIT, type MeterEntry, type MicrophoneDeviceInfo, type MicrophoneEnumeration, type MidiNoteData, type MusicalTick, type MusicalTickData, type MusicalTickParams, PPQN, type PeakData, type Peaks, type PlaylistConfig, type PlayoutState, type RangeSupport, type RenderAnnotationItemProps, type RenderMode, SPECTROGRAM_DEFAULTS, type SnapTo, type SpectrogramCanvasIdParts, type SpectrogramComputeConfig, type SpectrogramConfig, type SpectrogramData, type SpectrogramDisplayConfig, type TickType, type TimeSelection, type Timeline, type Track, type TrackEffectsFunction, type TrackSpectrogramOverrides, type WaveformConfig, type WaveformDataObject, type ZoomLevel, appendPeaks, appendToAudioBuffer, applyFadeIn, applyFadeOut, audibleLatencySamples, buildSpectrogramCanvasId, calculateDuration, carveClipRange, clipDurationTime, clipEndTime, clipOffsetTime, clipPixelWidth, clipStartTime, clipsOverlap, computeMusicalTicks, concatenateAudioData, createAudioBuffer, createClip, createClipFromSeconds, createClipFromTicks, createTimeline, createTrack, dBToNormalized, detectMeterChanges, enumerateMicrophones, exponentialCurve, findGaps, gainToDb, gainToNormalized, generateCurve, generatePeaks, getClipsAtSample, getClipsInRange, getShortcutLabel, handleKeyboardEvent, linearCurve, logarithmicCurve, normalizedToDb, parseSpectrogramCanvasId, pixelsToSamples, pixelsToSeconds, probeRangeSupport, resolveRecordingOffsetSamples, sCurveCurve, samplesToPixels, samplesToSeconds, samplesToTicks, secondsToPixels, secondsToSamples, snapTickToGrid, snapToGrid, snapToTicks, sortClipsByTime, ticksPerBar, ticksPerBeat, ticksToSamples, trackChannelCount, watchMicrophoneDevices };
|
package/dist/index.d.ts
CHANGED
|
@@ -880,6 +880,37 @@ declare const SPECTROGRAM_DEFAULTS: Required<Omit<SpectrogramConfig, 'maxFrequen
|
|
|
880
880
|
/** Default color map when none is specified. */
|
|
881
881
|
declare const DEFAULT_SPECTROGRAM_COLOR_MAP: ColorMapValue;
|
|
882
882
|
|
|
883
|
+
/**
|
|
884
|
+
* Punch-in replace: carve a sample range out of a clip list.
|
|
885
|
+
*
|
|
886
|
+
* Used when a newly recorded clip lands at the playhead and must REPLACE any
|
|
887
|
+
* existing clip content between its start and end (issue #579): partial
|
|
888
|
+
* overlaps are trimmed, fully-covered clips are removed, and a clip that
|
|
889
|
+
* spans the whole range is split into two.
|
|
890
|
+
*/
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Return a new clip list with the sample range [rangeStart, rangeEnd)
|
|
894
|
+
* removed from every clip that overlaps it.
|
|
895
|
+
*
|
|
896
|
+
* - Clips outside the range are returned by reference (untouched).
|
|
897
|
+
* - Clips fully inside the range are dropped.
|
|
898
|
+
* - A clip overlapping only the range's start keeps its head (right-trim).
|
|
899
|
+
* - A clip overlapping only the range's end keeps its tail: its start moves
|
|
900
|
+
* to rangeEnd and `offsetSamples` advances by the carved amount.
|
|
901
|
+
* - A clip containing the whole range splits into head + tail; the tail is a
|
|
902
|
+
* new clip (deterministic id `<original>-carve-<rangeEnd>`) sharing the
|
|
903
|
+
* same audioBuffer.
|
|
904
|
+
*
|
|
905
|
+
* Clips whose `startSample` changes lose their `startTick` — a sample-space
|
|
906
|
+
* carve cannot recompute ticks, and the engine re-enriches clips without
|
|
907
|
+
* `startTick` on ingestion (see AudioClip.startTick).
|
|
908
|
+
*
|
|
909
|
+
* Pure and immutable: input clips are never mutated. An empty or inverted
|
|
910
|
+
* range returns the input list unchanged.
|
|
911
|
+
*/
|
|
912
|
+
declare function carveClipRange(clips: readonly AudioClip[], rangeStart: number, rangeEnd: number): AudioClip[];
|
|
913
|
+
|
|
883
914
|
/** Clip start position in seconds */
|
|
884
915
|
declare function clipStartTime(clip: AudioClip): number;
|
|
885
916
|
/** Clip end position in seconds (start + duration) */
|
|
@@ -1036,4 +1067,51 @@ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
|
1036
1067
|
*/
|
|
1037
1068
|
declare function probeRangeSupport(url: string, fetchImpl?: FetchLike): Promise<RangeSupport>;
|
|
1038
1069
|
|
|
1039
|
-
|
|
1070
|
+
/**
|
|
1071
|
+
* Framework-agnostic microphone enumeration.
|
|
1072
|
+
*
|
|
1073
|
+
* Works without requesting a stream, but browsers redact device information
|
|
1074
|
+
* until microphone permission is granted: labels are empty everywhere, and
|
|
1075
|
+
* Chrome/Safari typically also redact deviceIds (often exposing a single
|
|
1076
|
+
* placeholder entry). The `hasLabels` flag tells consumers whether the list
|
|
1077
|
+
* is a usable picker or just a "microphones exist" signal — re-enumerate
|
|
1078
|
+
* after a successful getUserMedia to get the real list.
|
|
1079
|
+
*
|
|
1080
|
+
* Follows the probeRangeSupport pattern: the browser API is injectable for
|
|
1081
|
+
* tests and non-browser environments degrade gracefully.
|
|
1082
|
+
*/
|
|
1083
|
+
interface MicrophoneDeviceInfo {
|
|
1084
|
+
deviceId: string;
|
|
1085
|
+
/** Real device label, or a generated fallback name when redacted. */
|
|
1086
|
+
label: string;
|
|
1087
|
+
groupId: string;
|
|
1088
|
+
}
|
|
1089
|
+
interface MicrophoneEnumeration {
|
|
1090
|
+
/** False when enumeration is unavailable — SSR/Node, insecure context
|
|
1091
|
+
* (mediaDevices requires HTTPS or localhost), or a very old browser. */
|
|
1092
|
+
supported: boolean;
|
|
1093
|
+
/** True when the browser exposed real device labels. False before
|
|
1094
|
+
* microphone permission is granted (labels — and often deviceIds — are
|
|
1095
|
+
* redacted); the `devices` labels are then generated fallbacks. */
|
|
1096
|
+
hasLabels: boolean;
|
|
1097
|
+
devices: MicrophoneDeviceInfo[];
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* List audio input devices without requesting a stream.
|
|
1101
|
+
*
|
|
1102
|
+
* @param mediaDevices - Injectable for tests; defaults to `navigator.mediaDevices`.
|
|
1103
|
+
*/
|
|
1104
|
+
declare function enumerateMicrophones(mediaDevices?: MediaDevices): Promise<MicrophoneEnumeration>;
|
|
1105
|
+
/**
|
|
1106
|
+
* Subscribe to the microphone device list: the listener fires once with the
|
|
1107
|
+
* initial enumeration and again on every `devicechange` (hot-plug, permission
|
|
1108
|
+
* grant in some browsers). Returns an unsubscribe function.
|
|
1109
|
+
*
|
|
1110
|
+
* Enumeration failures are logged and skipped — the subscription survives a
|
|
1111
|
+
* transient failure and delivers the next successful enumeration.
|
|
1112
|
+
*
|
|
1113
|
+
* @param mediaDevices - Injectable for tests; defaults to `navigator.mediaDevices`.
|
|
1114
|
+
*/
|
|
1115
|
+
declare function watchMicrophoneDevices(listener: (enumeration: MicrophoneEnumeration) => void, mediaDevices?: MediaDevices): () => void;
|
|
1116
|
+
|
|
1117
|
+
export { type AnnotationAction, type AnnotationActionOptions, type AnnotationData, type AnnotationEventMap, type AnnotationFormat, type AnnotationListOptions, type AudioBuffer$1 as AudioBuffer, type AudioClip, type Bits, type ClipTrack, type ColorMapEntry, type ColorMapName, type ColorMapValue, type CreateClipOptions, type CreateClipOptionsSeconds, type CreateClipOptionsTicks, type CreateTrackOptions, DEFAULT_SPECTROGRAM_COLOR_MAP, type FFTSize, type Fade, type FadeConfig, type FadeType, type Gap, InteractionState, type KeyboardShortcut, MAX_CANVAS_WIDTH, MIN_PIXELS_PER_UNIT, type MeterEntry, type MicrophoneDeviceInfo, type MicrophoneEnumeration, type MidiNoteData, type MusicalTick, type MusicalTickData, type MusicalTickParams, PPQN, type PeakData, type Peaks, type PlaylistConfig, type PlayoutState, type RangeSupport, type RenderAnnotationItemProps, type RenderMode, SPECTROGRAM_DEFAULTS, type SnapTo, type SpectrogramCanvasIdParts, type SpectrogramComputeConfig, type SpectrogramConfig, type SpectrogramData, type SpectrogramDisplayConfig, type TickType, type TimeSelection, type Timeline, type Track, type TrackEffectsFunction, type TrackSpectrogramOverrides, type WaveformConfig, type WaveformDataObject, type ZoomLevel, appendPeaks, appendToAudioBuffer, applyFadeIn, applyFadeOut, audibleLatencySamples, buildSpectrogramCanvasId, calculateDuration, carveClipRange, clipDurationTime, clipEndTime, clipOffsetTime, clipPixelWidth, clipStartTime, clipsOverlap, computeMusicalTicks, concatenateAudioData, createAudioBuffer, createClip, createClipFromSeconds, createClipFromTicks, createTimeline, createTrack, dBToNormalized, detectMeterChanges, enumerateMicrophones, exponentialCurve, findGaps, gainToDb, gainToNormalized, generateCurve, generatePeaks, getClipsAtSample, getClipsInRange, getShortcutLabel, handleKeyboardEvent, linearCurve, logarithmicCurve, normalizedToDb, parseSpectrogramCanvasId, pixelsToSamples, pixelsToSeconds, probeRangeSupport, resolveRecordingOffsetSamples, sCurveCurve, samplesToPixels, samplesToSeconds, samplesToTicks, secondsToPixels, secondsToSamples, snapTickToGrid, snapToGrid, snapToTicks, sortClipsByTime, ticksPerBar, ticksPerBeat, ticksToSamples, trackChannelCount, watchMicrophoneDevices };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
audibleLatencySamples: () => audibleLatencySamples,
|
|
34
34
|
buildSpectrogramCanvasId: () => buildSpectrogramCanvasId,
|
|
35
35
|
calculateDuration: () => calculateDuration,
|
|
36
|
+
carveClipRange: () => carveClipRange,
|
|
36
37
|
clipDurationTime: () => clipDurationTime,
|
|
37
38
|
clipEndTime: () => clipEndTime,
|
|
38
39
|
clipOffsetTime: () => clipOffsetTime,
|
|
@@ -49,6 +50,7 @@ __export(index_exports, {
|
|
|
49
50
|
createTrack: () => createTrack,
|
|
50
51
|
dBToNormalized: () => dBToNormalized,
|
|
51
52
|
detectMeterChanges: () => detectMeterChanges,
|
|
53
|
+
enumerateMicrophones: () => enumerateMicrophones,
|
|
52
54
|
exponentialCurve: () => exponentialCurve,
|
|
53
55
|
findGaps: () => findGaps,
|
|
54
56
|
gainToDb: () => gainToDb,
|
|
@@ -80,7 +82,8 @@ __export(index_exports, {
|
|
|
80
82
|
ticksPerBar: () => ticksPerBar,
|
|
81
83
|
ticksPerBeat: () => ticksPerBeat,
|
|
82
84
|
ticksToSamples: () => ticksToSamples,
|
|
83
|
-
trackChannelCount: () => trackChannelCount
|
|
85
|
+
trackChannelCount: () => trackChannelCount,
|
|
86
|
+
watchMicrophoneDevices: () => watchMicrophoneDevices
|
|
84
87
|
});
|
|
85
88
|
module.exports = __toCommonJS(index_exports);
|
|
86
89
|
|
|
@@ -721,6 +724,44 @@ var SPECTROGRAM_DEFAULTS = {
|
|
|
721
724
|
};
|
|
722
725
|
var DEFAULT_SPECTROGRAM_COLOR_MAP = "viridis";
|
|
723
726
|
|
|
727
|
+
// src/utils/carveClipRange.ts
|
|
728
|
+
function carveClipRange(clips, rangeStart, rangeEnd) {
|
|
729
|
+
if (!(rangeEnd > rangeStart)) {
|
|
730
|
+
return [...clips];
|
|
731
|
+
}
|
|
732
|
+
const result = [];
|
|
733
|
+
for (const clip of clips) {
|
|
734
|
+
const clipStart = clip.startSample;
|
|
735
|
+
const clipEnd = clip.startSample + clip.durationSamples;
|
|
736
|
+
if (clipEnd <= rangeStart || clipStart >= rangeEnd) {
|
|
737
|
+
result.push(clip);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
const keepHead = clipStart < rangeStart;
|
|
741
|
+
const keepTail = clipEnd > rangeEnd;
|
|
742
|
+
if (keepHead) {
|
|
743
|
+
result.push({
|
|
744
|
+
...clip,
|
|
745
|
+
durationSamples: rangeStart - clipStart
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
if (keepTail) {
|
|
749
|
+
const carvedFromClipStart = rangeEnd - clipStart;
|
|
750
|
+
const tail = {
|
|
751
|
+
...clip,
|
|
752
|
+
// Head + tail from one clip must not share an id
|
|
753
|
+
id: keepHead ? `${clip.id}-carve-${rangeEnd}` : clip.id,
|
|
754
|
+
startSample: rangeEnd,
|
|
755
|
+
durationSamples: clipEnd - rangeEnd,
|
|
756
|
+
offsetSamples: clip.offsetSamples + carvedFromClipStart
|
|
757
|
+
};
|
|
758
|
+
delete tail.startTick;
|
|
759
|
+
result.push(tail);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
|
|
724
765
|
// src/clipTimeHelpers.ts
|
|
725
766
|
function clipStartTime(clip) {
|
|
726
767
|
return clip.startSample / clip.sampleRate;
|
|
@@ -910,6 +951,59 @@ async function probeRangeSupport(url, fetchImpl = fetch) {
|
|
|
910
951
|
controller.abort();
|
|
911
952
|
}
|
|
912
953
|
}
|
|
954
|
+
|
|
955
|
+
// src/enumerateMicrophones.ts
|
|
956
|
+
var UNSUPPORTED = Object.freeze({
|
|
957
|
+
supported: false,
|
|
958
|
+
hasLabels: false,
|
|
959
|
+
devices: []
|
|
960
|
+
});
|
|
961
|
+
function resolveMediaDevices(mediaDevices) {
|
|
962
|
+
if (mediaDevices) return mediaDevices;
|
|
963
|
+
return typeof navigator !== "undefined" ? navigator.mediaDevices : void 0;
|
|
964
|
+
}
|
|
965
|
+
async function enumerateMicrophones(mediaDevices) {
|
|
966
|
+
const md = resolveMediaDevices(mediaDevices);
|
|
967
|
+
if (!md || typeof md.enumerateDevices !== "function") {
|
|
968
|
+
return UNSUPPORTED;
|
|
969
|
+
}
|
|
970
|
+
const all = await md.enumerateDevices();
|
|
971
|
+
const inputs = all.filter((device) => device.kind === "audioinput");
|
|
972
|
+
const hasLabels = inputs.some((device) => device.label.length > 0);
|
|
973
|
+
return {
|
|
974
|
+
supported: true,
|
|
975
|
+
hasLabels,
|
|
976
|
+
devices: inputs.map((device, index) => ({
|
|
977
|
+
deviceId: device.deviceId,
|
|
978
|
+
label: device.label || // Pre-permission fallbacks: Chrome redacts deviceId to '' as well,
|
|
979
|
+
// so fall through to a positional name when there's nothing to slice.
|
|
980
|
+
(device.deviceId ? `Microphone ${device.deviceId.slice(0, 8)}` : `Microphone ${index + 1}`),
|
|
981
|
+
groupId: device.groupId
|
|
982
|
+
}))
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function watchMicrophoneDevices(listener, mediaDevices) {
|
|
986
|
+
const md = resolveMediaDevices(mediaDevices);
|
|
987
|
+
if (!md || typeof md.enumerateDevices !== "function") {
|
|
988
|
+
queueMicrotask(() => listener(UNSUPPORTED));
|
|
989
|
+
return () => {
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
let active = true;
|
|
993
|
+
const deliver = () => {
|
|
994
|
+
enumerateMicrophones(md).then((result) => {
|
|
995
|
+
if (active) listener(result);
|
|
996
|
+
}).catch((err) => {
|
|
997
|
+
console.warn("[waveform-playlist] Microphone enumeration failed:", String(err));
|
|
998
|
+
});
|
|
999
|
+
};
|
|
1000
|
+
md.addEventListener("devicechange", deliver);
|
|
1001
|
+
deliver();
|
|
1002
|
+
return () => {
|
|
1003
|
+
active = false;
|
|
1004
|
+
md.removeEventListener("devicechange", deliver);
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
913
1007
|
// Annotate the CommonJS export names for ESM import in node:
|
|
914
1008
|
0 && (module.exports = {
|
|
915
1009
|
DEFAULT_SPECTROGRAM_COLOR_MAP,
|
|
@@ -925,6 +1019,7 @@ async function probeRangeSupport(url, fetchImpl = fetch) {
|
|
|
925
1019
|
audibleLatencySamples,
|
|
926
1020
|
buildSpectrogramCanvasId,
|
|
927
1021
|
calculateDuration,
|
|
1022
|
+
carveClipRange,
|
|
928
1023
|
clipDurationTime,
|
|
929
1024
|
clipEndTime,
|
|
930
1025
|
clipOffsetTime,
|
|
@@ -941,6 +1036,7 @@ async function probeRangeSupport(url, fetchImpl = fetch) {
|
|
|
941
1036
|
createTrack,
|
|
942
1037
|
dBToNormalized,
|
|
943
1038
|
detectMeterChanges,
|
|
1039
|
+
enumerateMicrophones,
|
|
944
1040
|
exponentialCurve,
|
|
945
1041
|
findGaps,
|
|
946
1042
|
gainToDb,
|
|
@@ -972,6 +1068,7 @@ async function probeRangeSupport(url, fetchImpl = fetch) {
|
|
|
972
1068
|
ticksPerBar,
|
|
973
1069
|
ticksPerBeat,
|
|
974
1070
|
ticksToSamples,
|
|
975
|
-
trackChannelCount
|
|
1071
|
+
trackChannelCount,
|
|
1072
|
+
watchMicrophoneDevices
|
|
976
1073
|
});
|
|
977
1074
|
//# sourceMappingURL=index.js.map
|