@waveform-playlist/core 12.6.0 → 12.6.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/dist/index.d.mts CHANGED
@@ -476,6 +476,10 @@ interface AnnotationData {
476
476
  end: number;
477
477
  lines: string[];
478
478
  language?: string;
479
+ /** Musical position (ticks). Authoritative when BOTH tick fields are set —
480
+ * start/end seconds are then a derived cache (clip startTick pattern). */
481
+ startTick?: number;
482
+ endTick?: number;
479
483
  }
480
484
  /**
481
485
  * Annotation format definition for parsing/serializing
@@ -775,6 +779,21 @@ declare const MIN_PIXELS_PER_UNIT = 8;
775
779
  * correct across meter changes.
776
780
  */
777
781
  declare function computeMusicalTicks(params: MusicalTickParams): MusicalTickData;
782
+ /**
783
+ * Convert an absolute tick to a 1-based bar.beat position, honoring meter
784
+ * changes. Bars/beats are counted per meter segment (the same walk
785
+ * `computeMusicalTicks` uses to build its `barOffset` accumulator). Beat is
786
+ * the integer beat the tick falls WITHIN (floor, 1-based) — a tick exactly on
787
+ * a bar line is beat 1.
788
+ *
789
+ * Guards: empty `meterEntries` → treat as 4/4 from tick 0. `ppqn <= 0` →
790
+ * returns `{ bar: 1, beat: 1 }` (division-by-zero guard, matches
791
+ * `computeMusicalTicks`'s ppqn guard).
792
+ */
793
+ declare function ticksToBarBeat(tick: number, meterEntries: MeterEntry[], ppqn: number): {
794
+ bar: number;
795
+ beat: number;
796
+ };
778
797
  /**
779
798
  * Snaps a tick position to the nearest grid boundary defined by `snapTo`.
780
799
  *
@@ -1025,6 +1044,13 @@ interface KeyboardShortcut {
1025
1044
  description?: string;
1026
1045
  preventDefault?: boolean;
1027
1046
  }
1047
+ /** A key + modifier combination, without an action. Used for remapping maps. */
1048
+ type KeyBinding = Pick<KeyboardShortcut, 'key' | 'ctrlKey' | 'shiftKey' | 'metaKey' | 'altKey'>;
1049
+ /**
1050
+ * Does a keyboard event match a key binding?
1051
+ * `undefined` modifier = match any state; `false` = must NOT be pressed.
1052
+ */
1053
+ declare function matchesKeyBinding(event: KeyboardEvent, binding: KeyBinding): boolean;
1028
1054
  /**
1029
1055
  * Handle a keyboard event against a list of shortcuts.
1030
1056
  * Pure function, no framework dependency.
@@ -1114,4 +1140,57 @@ declare function enumerateMicrophones(mediaDevices?: MediaDevices): Promise<Micr
1114
1140
  */
1115
1141
  declare function watchMicrophoneDevices(listener: (enumeration: MicrophoneEnumeration) => void, mediaDevices?: MediaDevices): () => void;
1116
1142
 
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 };
1143
+ /** Edges within this distance (seconds) are considered "linked". */
1144
+ declare const LINK_THRESHOLD = 0.01;
1145
+ /** Minimum annotation duration (seconds) enforced by boundary edits. */
1146
+ declare const MIN_ANNOTATION_DURATION = 0.1;
1147
+ /** Link threshold for INTEGER TICK positions — closer than half a tick means equal. */
1148
+ declare const ANNOTATION_LINK_THRESHOLD_TICKS = 0.5;
1149
+ /** Minimum tick-annotation duration: a 128th note, floored at 1 tick. */
1150
+ declare function annotationMinDurationTicks(ppqn: number): number;
1151
+ /** Unit constants for a boundary-math run. Defaults are the seconds-domain values. */
1152
+ interface AnnotationBoundaryOptions {
1153
+ linkThreshold?: number;
1154
+ minDuration?: number;
1155
+ }
1156
+ interface AnnotationBoundaryUpdate {
1157
+ annotationIndex: number;
1158
+ newTime: number;
1159
+ isDraggingStart: boolean;
1160
+ annotations: AnnotationData[];
1161
+ duration: number;
1162
+ linkEndpoints: boolean;
1163
+ }
1164
+ /**
1165
+ * Pure boundary-update logic shared by the React annotations package and the
1166
+ * dawcore annotation web components. Handles linked endpoints (moving one edge
1167
+ * drags the linked neighbor edge, with cascade) and collision push-back.
1168
+ * Returns a NEW array; never mutates inputs.
1169
+ */
1170
+ declare function updateAnnotationBoundaries(params: AnnotationBoundaryUpdate, options?: AnnotationBoundaryOptions): AnnotationData[];
1171
+
1172
+ type AnnotationShortcutAction = 'selectPrevious' | 'selectNext' | 'selectFirst' | 'selectLast' | 'clearSelection' | 'moveStartEarlier' | 'moveStartLater' | 'moveEndEarlier' | 'moveEndLater' | 'playActive';
1173
+ /** Key remapping map — null on the consumer means "use defaults". */
1174
+ interface AnnotationShortcutMap {
1175
+ selectPrevious?: KeyBinding;
1176
+ selectNext?: KeyBinding;
1177
+ selectFirst?: KeyBinding;
1178
+ selectLast?: KeyBinding;
1179
+ clearSelection?: KeyBinding;
1180
+ moveStartEarlier?: KeyBinding;
1181
+ moveStartLater?: KeyBinding;
1182
+ moveEndEarlier?: KeyBinding;
1183
+ moveEndLater?: KeyBinding;
1184
+ playActive?: KeyBinding;
1185
+ }
1186
+ declare const DEFAULT_ANNOTATION_SHORTCUTS: Record<AnnotationShortcutAction, KeyBinding[]>;
1187
+ /**
1188
+ * Flatten defaults + remap into a matchable list. A remapped action replaces
1189
+ * ALL of its default bindings with the single provided binding.
1190
+ */
1191
+ declare function resolveAnnotationShortcuts(remap: AnnotationShortcutMap | null): Array<{
1192
+ action: AnnotationShortcutAction;
1193
+ binding: KeyBinding;
1194
+ }>;
1195
+
1196
+ export { ANNOTATION_LINK_THRESHOLD_TICKS, type AnnotationAction, type AnnotationActionOptions, type AnnotationBoundaryOptions, type AnnotationBoundaryUpdate, type AnnotationData, type AnnotationEventMap, type AnnotationFormat, type AnnotationListOptions, type AnnotationShortcutAction, type AnnotationShortcutMap, 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_ANNOTATION_SHORTCUTS, DEFAULT_SPECTROGRAM_COLOR_MAP, type FFTSize, type Fade, type FadeConfig, type FadeType, type Gap, InteractionState, type KeyBinding, type KeyboardShortcut, LINK_THRESHOLD, MAX_CANVAS_WIDTH, MIN_ANNOTATION_DURATION, 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, annotationMinDurationTicks, 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, matchesKeyBinding, normalizedToDb, parseSpectrogramCanvasId, pixelsToSamples, pixelsToSeconds, probeRangeSupport, resolveAnnotationShortcuts, resolveRecordingOffsetSamples, sCurveCurve, samplesToPixels, samplesToSeconds, samplesToTicks, secondsToPixels, secondsToSamples, snapTickToGrid, snapToGrid, snapToTicks, sortClipsByTime, ticksPerBar, ticksPerBeat, ticksToBarBeat, ticksToSamples, trackChannelCount, updateAnnotationBoundaries, watchMicrophoneDevices };
package/dist/index.d.ts CHANGED
@@ -476,6 +476,10 @@ interface AnnotationData {
476
476
  end: number;
477
477
  lines: string[];
478
478
  language?: string;
479
+ /** Musical position (ticks). Authoritative when BOTH tick fields are set —
480
+ * start/end seconds are then a derived cache (clip startTick pattern). */
481
+ startTick?: number;
482
+ endTick?: number;
479
483
  }
480
484
  /**
481
485
  * Annotation format definition for parsing/serializing
@@ -775,6 +779,21 @@ declare const MIN_PIXELS_PER_UNIT = 8;
775
779
  * correct across meter changes.
776
780
  */
777
781
  declare function computeMusicalTicks(params: MusicalTickParams): MusicalTickData;
782
+ /**
783
+ * Convert an absolute tick to a 1-based bar.beat position, honoring meter
784
+ * changes. Bars/beats are counted per meter segment (the same walk
785
+ * `computeMusicalTicks` uses to build its `barOffset` accumulator). Beat is
786
+ * the integer beat the tick falls WITHIN (floor, 1-based) — a tick exactly on
787
+ * a bar line is beat 1.
788
+ *
789
+ * Guards: empty `meterEntries` → treat as 4/4 from tick 0. `ppqn <= 0` →
790
+ * returns `{ bar: 1, beat: 1 }` (division-by-zero guard, matches
791
+ * `computeMusicalTicks`'s ppqn guard).
792
+ */
793
+ declare function ticksToBarBeat(tick: number, meterEntries: MeterEntry[], ppqn: number): {
794
+ bar: number;
795
+ beat: number;
796
+ };
778
797
  /**
779
798
  * Snaps a tick position to the nearest grid boundary defined by `snapTo`.
780
799
  *
@@ -1025,6 +1044,13 @@ interface KeyboardShortcut {
1025
1044
  description?: string;
1026
1045
  preventDefault?: boolean;
1027
1046
  }
1047
+ /** A key + modifier combination, without an action. Used for remapping maps. */
1048
+ type KeyBinding = Pick<KeyboardShortcut, 'key' | 'ctrlKey' | 'shiftKey' | 'metaKey' | 'altKey'>;
1049
+ /**
1050
+ * Does a keyboard event match a key binding?
1051
+ * `undefined` modifier = match any state; `false` = must NOT be pressed.
1052
+ */
1053
+ declare function matchesKeyBinding(event: KeyboardEvent, binding: KeyBinding): boolean;
1028
1054
  /**
1029
1055
  * Handle a keyboard event against a list of shortcuts.
1030
1056
  * Pure function, no framework dependency.
@@ -1114,4 +1140,57 @@ declare function enumerateMicrophones(mediaDevices?: MediaDevices): Promise<Micr
1114
1140
  */
1115
1141
  declare function watchMicrophoneDevices(listener: (enumeration: MicrophoneEnumeration) => void, mediaDevices?: MediaDevices): () => void;
1116
1142
 
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 };
1143
+ /** Edges within this distance (seconds) are considered "linked". */
1144
+ declare const LINK_THRESHOLD = 0.01;
1145
+ /** Minimum annotation duration (seconds) enforced by boundary edits. */
1146
+ declare const MIN_ANNOTATION_DURATION = 0.1;
1147
+ /** Link threshold for INTEGER TICK positions — closer than half a tick means equal. */
1148
+ declare const ANNOTATION_LINK_THRESHOLD_TICKS = 0.5;
1149
+ /** Minimum tick-annotation duration: a 128th note, floored at 1 tick. */
1150
+ declare function annotationMinDurationTicks(ppqn: number): number;
1151
+ /** Unit constants for a boundary-math run. Defaults are the seconds-domain values. */
1152
+ interface AnnotationBoundaryOptions {
1153
+ linkThreshold?: number;
1154
+ minDuration?: number;
1155
+ }
1156
+ interface AnnotationBoundaryUpdate {
1157
+ annotationIndex: number;
1158
+ newTime: number;
1159
+ isDraggingStart: boolean;
1160
+ annotations: AnnotationData[];
1161
+ duration: number;
1162
+ linkEndpoints: boolean;
1163
+ }
1164
+ /**
1165
+ * Pure boundary-update logic shared by the React annotations package and the
1166
+ * dawcore annotation web components. Handles linked endpoints (moving one edge
1167
+ * drags the linked neighbor edge, with cascade) and collision push-back.
1168
+ * Returns a NEW array; never mutates inputs.
1169
+ */
1170
+ declare function updateAnnotationBoundaries(params: AnnotationBoundaryUpdate, options?: AnnotationBoundaryOptions): AnnotationData[];
1171
+
1172
+ type AnnotationShortcutAction = 'selectPrevious' | 'selectNext' | 'selectFirst' | 'selectLast' | 'clearSelection' | 'moveStartEarlier' | 'moveStartLater' | 'moveEndEarlier' | 'moveEndLater' | 'playActive';
1173
+ /** Key remapping map — null on the consumer means "use defaults". */
1174
+ interface AnnotationShortcutMap {
1175
+ selectPrevious?: KeyBinding;
1176
+ selectNext?: KeyBinding;
1177
+ selectFirst?: KeyBinding;
1178
+ selectLast?: KeyBinding;
1179
+ clearSelection?: KeyBinding;
1180
+ moveStartEarlier?: KeyBinding;
1181
+ moveStartLater?: KeyBinding;
1182
+ moveEndEarlier?: KeyBinding;
1183
+ moveEndLater?: KeyBinding;
1184
+ playActive?: KeyBinding;
1185
+ }
1186
+ declare const DEFAULT_ANNOTATION_SHORTCUTS: Record<AnnotationShortcutAction, KeyBinding[]>;
1187
+ /**
1188
+ * Flatten defaults + remap into a matchable list. A remapped action replaces
1189
+ * ALL of its default bindings with the single provided binding.
1190
+ */
1191
+ declare function resolveAnnotationShortcuts(remap: AnnotationShortcutMap | null): Array<{
1192
+ action: AnnotationShortcutAction;
1193
+ binding: KeyBinding;
1194
+ }>;
1195
+
1196
+ export { ANNOTATION_LINK_THRESHOLD_TICKS, type AnnotationAction, type AnnotationActionOptions, type AnnotationBoundaryOptions, type AnnotationBoundaryUpdate, type AnnotationData, type AnnotationEventMap, type AnnotationFormat, type AnnotationListOptions, type AnnotationShortcutAction, type AnnotationShortcutMap, 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_ANNOTATION_SHORTCUTS, DEFAULT_SPECTROGRAM_COLOR_MAP, type FFTSize, type Fade, type FadeConfig, type FadeType, type Gap, InteractionState, type KeyBinding, type KeyboardShortcut, LINK_THRESHOLD, MAX_CANVAS_WIDTH, MIN_ANNOTATION_DURATION, 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, annotationMinDurationTicks, 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, matchesKeyBinding, normalizedToDb, parseSpectrogramCanvasId, pixelsToSamples, pixelsToSeconds, probeRangeSupport, resolveAnnotationShortcuts, resolveRecordingOffsetSamples, sCurveCurve, samplesToPixels, samplesToSeconds, samplesToTicks, secondsToPixels, secondsToSamples, snapTickToGrid, snapToGrid, snapToTicks, sortClipsByTime, ticksPerBar, ticksPerBeat, ticksToBarBeat, ticksToSamples, trackChannelCount, updateAnnotationBoundaries, watchMicrophoneDevices };
package/dist/index.js CHANGED
@@ -20,12 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ANNOTATION_LINK_THRESHOLD_TICKS: () => ANNOTATION_LINK_THRESHOLD_TICKS,
24
+ DEFAULT_ANNOTATION_SHORTCUTS: () => DEFAULT_ANNOTATION_SHORTCUTS,
23
25
  DEFAULT_SPECTROGRAM_COLOR_MAP: () => DEFAULT_SPECTROGRAM_COLOR_MAP,
24
26
  InteractionState: () => InteractionState,
27
+ LINK_THRESHOLD: () => LINK_THRESHOLD,
25
28
  MAX_CANVAS_WIDTH: () => MAX_CANVAS_WIDTH,
29
+ MIN_ANNOTATION_DURATION: () => MIN_ANNOTATION_DURATION,
26
30
  MIN_PIXELS_PER_UNIT: () => MIN_PIXELS_PER_UNIT,
27
31
  PPQN: () => PPQN,
28
32
  SPECTROGRAM_DEFAULTS: () => SPECTROGRAM_DEFAULTS,
33
+ annotationMinDurationTicks: () => annotationMinDurationTicks,
29
34
  appendPeaks: () => appendPeaks,
30
35
  appendToAudioBuffer: () => appendToAudioBuffer,
31
36
  applyFadeIn: () => applyFadeIn,
@@ -63,11 +68,13 @@ __export(index_exports, {
63
68
  handleKeyboardEvent: () => handleKeyboardEvent,
64
69
  linearCurve: () => linearCurve,
65
70
  logarithmicCurve: () => logarithmicCurve,
71
+ matchesKeyBinding: () => matchesKeyBinding,
66
72
  normalizedToDb: () => normalizedToDb,
67
73
  parseSpectrogramCanvasId: () => parseSpectrogramCanvasId,
68
74
  pixelsToSamples: () => pixelsToSamples,
69
75
  pixelsToSeconds: () => pixelsToSeconds,
70
76
  probeRangeSupport: () => probeRangeSupport,
77
+ resolveAnnotationShortcuts: () => resolveAnnotationShortcuts,
71
78
  resolveRecordingOffsetSamples: () => resolveRecordingOffsetSamples,
72
79
  sCurveCurve: () => sCurveCurve,
73
80
  samplesToPixels: () => samplesToPixels,
@@ -81,8 +88,10 @@ __export(index_exports, {
81
88
  sortClipsByTime: () => sortClipsByTime,
82
89
  ticksPerBar: () => ticksPerBar,
83
90
  ticksPerBeat: () => ticksPerBeat,
91
+ ticksToBarBeat: () => ticksToBarBeat,
84
92
  ticksToSamples: () => ticksToSamples,
85
93
  trackChannelCount: () => trackChannelCount,
94
+ updateAnnotationBoundaries: () => updateAnnotationBoundaries,
86
95
  watchMicrophoneDevices: () => watchMicrophoneDevices
87
96
  });
88
97
  module.exports = __toCommonJS(index_exports);
@@ -545,6 +554,37 @@ function computeMusicalTicks(params) {
545
554
  };
546
555
  return result;
547
556
  }
557
+ function ticksToBarBeat(tick, meterEntries, ppqn) {
558
+ if (ppqn <= 0) return { bar: 1, beat: 1 };
559
+ const entries = meterEntries.length > 0 ? meterEntries : [{ tick: 0, numerator: 4, denominator: 4 }];
560
+ let barOffset = 0;
561
+ for (let i = 0; i < entries.length; i++) {
562
+ const meter = entries[i];
563
+ const segmentStart = meter.tick;
564
+ const segmentEnd = i + 1 < entries.length ? entries[i + 1].tick : Number.MAX_SAFE_INTEGER;
565
+ const ts2 = [meter.numerator, meter.denominator];
566
+ const tpBar2 = ticksPerBar(ts2, ppqn);
567
+ const tpBeat2 = ticksPerBeat(ts2, ppqn);
568
+ if (tick >= segmentStart && tick < segmentEnd) {
569
+ const offset2 = tick - segmentStart;
570
+ const barIndexInSegment2 = Math.floor(offset2 / tpBar2);
571
+ const beatInBar2 = Math.floor(offset2 % tpBar2 / tpBeat2);
572
+ return { bar: barOffset + barIndexInSegment2 + 1, beat: beatInBar2 + 1 };
573
+ }
574
+ if (segmentEnd !== Number.MAX_SAFE_INTEGER) {
575
+ const segmentLen = segmentEnd - segmentStart;
576
+ barOffset += Math.floor(segmentLen / tpBar2);
577
+ }
578
+ }
579
+ const first = entries[0];
580
+ const ts = [first.numerator, first.denominator];
581
+ const tpBar = ticksPerBar(ts, ppqn);
582
+ const tpBeat = ticksPerBeat(ts, ppqn);
583
+ const offset = tick - first.tick;
584
+ const barIndexInSegment = Math.floor(offset / tpBar);
585
+ const beatInBar = Math.floor(offset % tpBar / tpBeat);
586
+ return { bar: barIndexInSegment + 1, beat: beatInBar + 1 };
587
+ }
548
588
  function snapTickToGrid(tick, snapTo, meterEntries, ppqn = 960) {
549
589
  if (snapTo === "off") return tick;
550
590
  let meter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };
@@ -889,6 +929,14 @@ function applyFadeOut(param, startTime, duration, type = "linear", startValue =
889
929
  }
890
930
 
891
931
  // src/keyboard.ts
932
+ function matchesKeyBinding(event, binding) {
933
+ const keyMatch = event.key.toLowerCase() === binding.key.toLowerCase() || event.key === binding.key;
934
+ const ctrlMatch = binding.ctrlKey === void 0 || event.ctrlKey === binding.ctrlKey;
935
+ const shiftMatch = binding.shiftKey === void 0 || event.shiftKey === binding.shiftKey;
936
+ const metaMatch = binding.metaKey === void 0 || event.metaKey === binding.metaKey;
937
+ const altMatch = binding.altKey === void 0 || event.altKey === binding.altKey;
938
+ return keyMatch && ctrlMatch && shiftMatch && metaMatch && altMatch;
939
+ }
892
940
  function handleKeyboardEvent(event, shortcuts, enabled) {
893
941
  if (!enabled) return;
894
942
  if (event.repeat) return;
@@ -896,14 +944,7 @@ function handleKeyboardEvent(event, shortcuts, enabled) {
896
944
  if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
897
945
  return;
898
946
  }
899
- const matchingShortcut = shortcuts.find((shortcut) => {
900
- const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase() || event.key === shortcut.key;
901
- const ctrlMatch = shortcut.ctrlKey === void 0 || event.ctrlKey === shortcut.ctrlKey;
902
- const shiftMatch = shortcut.shiftKey === void 0 || event.shiftKey === shortcut.shiftKey;
903
- const metaMatch = shortcut.metaKey === void 0 || event.metaKey === shortcut.metaKey;
904
- const altMatch = shortcut.altKey === void 0 || event.altKey === shortcut.altKey;
905
- return keyMatch && ctrlMatch && shiftMatch && metaMatch && altMatch;
906
- });
947
+ const matchingShortcut = shortcuts.find((shortcut) => matchesKeyBinding(event, shortcut));
907
948
  if (matchingShortcut) {
908
949
  if (matchingShortcut.preventDefault !== false) {
909
950
  event.preventDefault();
@@ -1004,14 +1045,138 @@ function watchMicrophoneDevices(listener, mediaDevices) {
1004
1045
  md.removeEventListener("devicechange", deliver);
1005
1046
  };
1006
1047
  }
1048
+
1049
+ // src/annotations/boundaries.ts
1050
+ var LINK_THRESHOLD = 0.01;
1051
+ var MIN_ANNOTATION_DURATION = 0.1;
1052
+ var ANNOTATION_LINK_THRESHOLD_TICKS = 0.5;
1053
+ function annotationMinDurationTicks(ppqn) {
1054
+ return Math.max(1, Math.round(ppqn / 32));
1055
+ }
1056
+ function updateAnnotationBoundaries(params, options = {}) {
1057
+ const { annotationIndex, newTime, isDraggingStart, annotations, duration, linkEndpoints } = params;
1058
+ const linkThreshold = options.linkThreshold ?? LINK_THRESHOLD;
1059
+ const minDuration = options.minDuration ?? MIN_ANNOTATION_DURATION;
1060
+ const updatedAnnotations = [...annotations];
1061
+ const annotation = annotations[annotationIndex];
1062
+ if (isDraggingStart) {
1063
+ const constrainedStart = Math.min(annotation.end - minDuration, Math.max(0, newTime));
1064
+ const delta = constrainedStart - annotation.start;
1065
+ updatedAnnotations[annotationIndex] = { ...annotation, start: constrainedStart };
1066
+ if (linkEndpoints && annotationIndex > 0) {
1067
+ const prevAnnotation = updatedAnnotations[annotationIndex - 1];
1068
+ if (Math.abs(prevAnnotation.end - annotation.start) < linkThreshold) {
1069
+ updatedAnnotations[annotationIndex - 1] = {
1070
+ ...prevAnnotation,
1071
+ end: Math.max(prevAnnotation.start + minDuration, prevAnnotation.end + delta)
1072
+ };
1073
+ } else if (constrainedStart <= prevAnnotation.end) {
1074
+ updatedAnnotations[annotationIndex] = {
1075
+ ...updatedAnnotations[annotationIndex],
1076
+ start: prevAnnotation.end
1077
+ };
1078
+ }
1079
+ } else if (!linkEndpoints && annotationIndex > 0 && constrainedStart < updatedAnnotations[annotationIndex - 1].end) {
1080
+ updatedAnnotations[annotationIndex - 1] = {
1081
+ ...updatedAnnotations[annotationIndex - 1],
1082
+ end: constrainedStart
1083
+ };
1084
+ }
1085
+ } else {
1086
+ const constrainedEnd = Math.max(annotation.start + minDuration, Math.min(newTime, duration));
1087
+ const delta = constrainedEnd - annotation.end;
1088
+ updatedAnnotations[annotationIndex] = { ...annotation, end: constrainedEnd };
1089
+ if (linkEndpoints && annotationIndex < updatedAnnotations.length - 1) {
1090
+ const nextAnnotation = updatedAnnotations[annotationIndex + 1];
1091
+ if (Math.abs(nextAnnotation.start - annotation.end) < linkThreshold) {
1092
+ const newStart = nextAnnotation.start + delta;
1093
+ updatedAnnotations[annotationIndex + 1] = {
1094
+ ...nextAnnotation,
1095
+ start: Math.min(nextAnnotation.end - minDuration, newStart)
1096
+ };
1097
+ let currentIndex = annotationIndex + 1;
1098
+ while (currentIndex < updatedAnnotations.length - 1) {
1099
+ const current = updatedAnnotations[currentIndex];
1100
+ const next = updatedAnnotations[currentIndex + 1];
1101
+ if (Math.abs(next.start - current.end) < linkThreshold) {
1102
+ const nextDelta = current.end - annotations[currentIndex].end;
1103
+ updatedAnnotations[currentIndex + 1] = {
1104
+ ...next,
1105
+ start: Math.min(next.end - minDuration, next.start + nextDelta)
1106
+ };
1107
+ currentIndex++;
1108
+ } else {
1109
+ break;
1110
+ }
1111
+ }
1112
+ } else if (constrainedEnd >= nextAnnotation.start) {
1113
+ updatedAnnotations[annotationIndex] = {
1114
+ ...updatedAnnotations[annotationIndex],
1115
+ end: nextAnnotation.start
1116
+ };
1117
+ }
1118
+ } else if (!linkEndpoints && annotationIndex < updatedAnnotations.length - 1 && constrainedEnd > updatedAnnotations[annotationIndex + 1].start) {
1119
+ const nextAnnotation = updatedAnnotations[annotationIndex + 1];
1120
+ updatedAnnotations[annotationIndex + 1] = { ...nextAnnotation, start: constrainedEnd };
1121
+ let currentIndex = annotationIndex + 1;
1122
+ while (currentIndex < updatedAnnotations.length - 1) {
1123
+ const current = updatedAnnotations[currentIndex];
1124
+ const next = updatedAnnotations[currentIndex + 1];
1125
+ if (current.end > next.start) {
1126
+ updatedAnnotations[currentIndex + 1] = { ...next, start: current.end };
1127
+ currentIndex++;
1128
+ } else {
1129
+ break;
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ return updatedAnnotations;
1135
+ }
1136
+
1137
+ // src/annotations/shortcuts.ts
1138
+ var noMods = { ctrlKey: false, metaKey: false };
1139
+ var DEFAULT_ANNOTATION_SHORTCUTS = {
1140
+ selectPrevious: [
1141
+ { key: "ArrowUp", ...noMods },
1142
+ { key: "ArrowLeft", ...noMods }
1143
+ ],
1144
+ selectNext: [
1145
+ { key: "ArrowDown", ...noMods },
1146
+ { key: "ArrowRight", ...noMods }
1147
+ ],
1148
+ selectFirst: [{ key: "Home", ...noMods }],
1149
+ selectLast: [{ key: "End", ...noMods }],
1150
+ clearSelection: [{ key: "Escape", ...noMods }],
1151
+ moveStartEarlier: [{ key: "[", ...noMods }],
1152
+ moveStartLater: [{ key: "]", ...noMods }],
1153
+ // '{' / '}' are what event.key reports for Shift+[ / Shift+] — no explicit
1154
+ // shiftKey needed; the key value itself encodes it.
1155
+ moveEndEarlier: [{ key: "{", ...noMods }],
1156
+ moveEndLater: [{ key: "}", ...noMods }],
1157
+ playActive: [{ key: "Enter", ...noMods }]
1158
+ };
1159
+ var ALL_ACTIONS = Object.keys(DEFAULT_ANNOTATION_SHORTCUTS);
1160
+ function resolveAnnotationShortcuts(remap) {
1161
+ return ALL_ACTIONS.flatMap((action) => {
1162
+ const override = remap?.[action];
1163
+ const bindings = override ? [override] : DEFAULT_ANNOTATION_SHORTCUTS[action];
1164
+ return bindings.map((binding) => ({ action, binding }));
1165
+ });
1166
+ }
1007
1167
  // Annotate the CommonJS export names for ESM import in node:
1008
1168
  0 && (module.exports = {
1169
+ ANNOTATION_LINK_THRESHOLD_TICKS,
1170
+ DEFAULT_ANNOTATION_SHORTCUTS,
1009
1171
  DEFAULT_SPECTROGRAM_COLOR_MAP,
1010
1172
  InteractionState,
1173
+ LINK_THRESHOLD,
1011
1174
  MAX_CANVAS_WIDTH,
1175
+ MIN_ANNOTATION_DURATION,
1012
1176
  MIN_PIXELS_PER_UNIT,
1013
1177
  PPQN,
1014
1178
  SPECTROGRAM_DEFAULTS,
1179
+ annotationMinDurationTicks,
1015
1180
  appendPeaks,
1016
1181
  appendToAudioBuffer,
1017
1182
  applyFadeIn,
@@ -1049,11 +1214,13 @@ function watchMicrophoneDevices(listener, mediaDevices) {
1049
1214
  handleKeyboardEvent,
1050
1215
  linearCurve,
1051
1216
  logarithmicCurve,
1217
+ matchesKeyBinding,
1052
1218
  normalizedToDb,
1053
1219
  parseSpectrogramCanvasId,
1054
1220
  pixelsToSamples,
1055
1221
  pixelsToSeconds,
1056
1222
  probeRangeSupport,
1223
+ resolveAnnotationShortcuts,
1057
1224
  resolveRecordingOffsetSamples,
1058
1225
  sCurveCurve,
1059
1226
  samplesToPixels,
@@ -1067,8 +1234,10 @@ function watchMicrophoneDevices(listener, mediaDevices) {
1067
1234
  sortClipsByTime,
1068
1235
  ticksPerBar,
1069
1236
  ticksPerBeat,
1237
+ ticksToBarBeat,
1070
1238
  ticksToSamples,
1071
1239
  trackChannelCount,
1240
+ updateAnnotationBoundaries,
1072
1241
  watchMicrophoneDevices
1073
1242
  });
1074
1243
  //# sourceMappingURL=index.js.map