@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 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
- 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 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, clipDurationTime, clipEndTime, clipOffsetTime, clipPixelWidth, clipStartTime, clipsOverlap, computeMusicalTicks, concatenateAudioData, createAudioBuffer, createClip, createClipFromSeconds, createClipFromTicks, createTimeline, createTrack, dBToNormalized, detectMeterChanges, 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 };
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
- 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 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, clipDurationTime, clipEndTime, clipOffsetTime, clipPixelWidth, clipStartTime, clipsOverlap, computeMusicalTicks, concatenateAudioData, createAudioBuffer, createClip, createClipFromSeconds, createClipFromTicks, createTimeline, createTrack, dBToNormalized, detectMeterChanges, 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 };
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