@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.mjs CHANGED
@@ -456,6 +456,37 @@ function computeMusicalTicks(params) {
456
456
  };
457
457
  return result;
458
458
  }
459
+ function ticksToBarBeat(tick, meterEntries, ppqn) {
460
+ if (ppqn <= 0) return { bar: 1, beat: 1 };
461
+ const entries = meterEntries.length > 0 ? meterEntries : [{ tick: 0, numerator: 4, denominator: 4 }];
462
+ let barOffset = 0;
463
+ for (let i = 0; i < entries.length; i++) {
464
+ const meter = entries[i];
465
+ const segmentStart = meter.tick;
466
+ const segmentEnd = i + 1 < entries.length ? entries[i + 1].tick : Number.MAX_SAFE_INTEGER;
467
+ const ts2 = [meter.numerator, meter.denominator];
468
+ const tpBar2 = ticksPerBar(ts2, ppqn);
469
+ const tpBeat2 = ticksPerBeat(ts2, ppqn);
470
+ if (tick >= segmentStart && tick < segmentEnd) {
471
+ const offset2 = tick - segmentStart;
472
+ const barIndexInSegment2 = Math.floor(offset2 / tpBar2);
473
+ const beatInBar2 = Math.floor(offset2 % tpBar2 / tpBeat2);
474
+ return { bar: barOffset + barIndexInSegment2 + 1, beat: beatInBar2 + 1 };
475
+ }
476
+ if (segmentEnd !== Number.MAX_SAFE_INTEGER) {
477
+ const segmentLen = segmentEnd - segmentStart;
478
+ barOffset += Math.floor(segmentLen / tpBar2);
479
+ }
480
+ }
481
+ const first = entries[0];
482
+ const ts = [first.numerator, first.denominator];
483
+ const tpBar = ticksPerBar(ts, ppqn);
484
+ const tpBeat = ticksPerBeat(ts, ppqn);
485
+ const offset = tick - first.tick;
486
+ const barIndexInSegment = Math.floor(offset / tpBar);
487
+ const beatInBar = Math.floor(offset % tpBar / tpBeat);
488
+ return { bar: barIndexInSegment + 1, beat: beatInBar + 1 };
489
+ }
459
490
  function snapTickToGrid(tick, snapTo, meterEntries, ppqn = 960) {
460
491
  if (snapTo === "off") return tick;
461
492
  let meter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };
@@ -800,6 +831,14 @@ function applyFadeOut(param, startTime, duration, type = "linear", startValue =
800
831
  }
801
832
 
802
833
  // src/keyboard.ts
834
+ function matchesKeyBinding(event, binding) {
835
+ const keyMatch = event.key.toLowerCase() === binding.key.toLowerCase() || event.key === binding.key;
836
+ const ctrlMatch = binding.ctrlKey === void 0 || event.ctrlKey === binding.ctrlKey;
837
+ const shiftMatch = binding.shiftKey === void 0 || event.shiftKey === binding.shiftKey;
838
+ const metaMatch = binding.metaKey === void 0 || event.metaKey === binding.metaKey;
839
+ const altMatch = binding.altKey === void 0 || event.altKey === binding.altKey;
840
+ return keyMatch && ctrlMatch && shiftMatch && metaMatch && altMatch;
841
+ }
803
842
  function handleKeyboardEvent(event, shortcuts, enabled) {
804
843
  if (!enabled) return;
805
844
  if (event.repeat) return;
@@ -807,14 +846,7 @@ function handleKeyboardEvent(event, shortcuts, enabled) {
807
846
  if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
808
847
  return;
809
848
  }
810
- const matchingShortcut = shortcuts.find((shortcut) => {
811
- const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase() || event.key === shortcut.key;
812
- const ctrlMatch = shortcut.ctrlKey === void 0 || event.ctrlKey === shortcut.ctrlKey;
813
- const shiftMatch = shortcut.shiftKey === void 0 || event.shiftKey === shortcut.shiftKey;
814
- const metaMatch = shortcut.metaKey === void 0 || event.metaKey === shortcut.metaKey;
815
- const altMatch = shortcut.altKey === void 0 || event.altKey === shortcut.altKey;
816
- return keyMatch && ctrlMatch && shiftMatch && metaMatch && altMatch;
817
- });
849
+ const matchingShortcut = shortcuts.find((shortcut) => matchesKeyBinding(event, shortcut));
818
850
  if (matchingShortcut) {
819
851
  if (matchingShortcut.preventDefault !== false) {
820
852
  event.preventDefault();
@@ -915,13 +947,137 @@ function watchMicrophoneDevices(listener, mediaDevices) {
915
947
  md.removeEventListener("devicechange", deliver);
916
948
  };
917
949
  }
950
+
951
+ // src/annotations/boundaries.ts
952
+ var LINK_THRESHOLD = 0.01;
953
+ var MIN_ANNOTATION_DURATION = 0.1;
954
+ var ANNOTATION_LINK_THRESHOLD_TICKS = 0.5;
955
+ function annotationMinDurationTicks(ppqn) {
956
+ return Math.max(1, Math.round(ppqn / 32));
957
+ }
958
+ function updateAnnotationBoundaries(params, options = {}) {
959
+ const { annotationIndex, newTime, isDraggingStart, annotations, duration, linkEndpoints } = params;
960
+ const linkThreshold = options.linkThreshold ?? LINK_THRESHOLD;
961
+ const minDuration = options.minDuration ?? MIN_ANNOTATION_DURATION;
962
+ const updatedAnnotations = [...annotations];
963
+ const annotation = annotations[annotationIndex];
964
+ if (isDraggingStart) {
965
+ const constrainedStart = Math.min(annotation.end - minDuration, Math.max(0, newTime));
966
+ const delta = constrainedStart - annotation.start;
967
+ updatedAnnotations[annotationIndex] = { ...annotation, start: constrainedStart };
968
+ if (linkEndpoints && annotationIndex > 0) {
969
+ const prevAnnotation = updatedAnnotations[annotationIndex - 1];
970
+ if (Math.abs(prevAnnotation.end - annotation.start) < linkThreshold) {
971
+ updatedAnnotations[annotationIndex - 1] = {
972
+ ...prevAnnotation,
973
+ end: Math.max(prevAnnotation.start + minDuration, prevAnnotation.end + delta)
974
+ };
975
+ } else if (constrainedStart <= prevAnnotation.end) {
976
+ updatedAnnotations[annotationIndex] = {
977
+ ...updatedAnnotations[annotationIndex],
978
+ start: prevAnnotation.end
979
+ };
980
+ }
981
+ } else if (!linkEndpoints && annotationIndex > 0 && constrainedStart < updatedAnnotations[annotationIndex - 1].end) {
982
+ updatedAnnotations[annotationIndex - 1] = {
983
+ ...updatedAnnotations[annotationIndex - 1],
984
+ end: constrainedStart
985
+ };
986
+ }
987
+ } else {
988
+ const constrainedEnd = Math.max(annotation.start + minDuration, Math.min(newTime, duration));
989
+ const delta = constrainedEnd - annotation.end;
990
+ updatedAnnotations[annotationIndex] = { ...annotation, end: constrainedEnd };
991
+ if (linkEndpoints && annotationIndex < updatedAnnotations.length - 1) {
992
+ const nextAnnotation = updatedAnnotations[annotationIndex + 1];
993
+ if (Math.abs(nextAnnotation.start - annotation.end) < linkThreshold) {
994
+ const newStart = nextAnnotation.start + delta;
995
+ updatedAnnotations[annotationIndex + 1] = {
996
+ ...nextAnnotation,
997
+ start: Math.min(nextAnnotation.end - minDuration, newStart)
998
+ };
999
+ let currentIndex = annotationIndex + 1;
1000
+ while (currentIndex < updatedAnnotations.length - 1) {
1001
+ const current = updatedAnnotations[currentIndex];
1002
+ const next = updatedAnnotations[currentIndex + 1];
1003
+ if (Math.abs(next.start - current.end) < linkThreshold) {
1004
+ const nextDelta = current.end - annotations[currentIndex].end;
1005
+ updatedAnnotations[currentIndex + 1] = {
1006
+ ...next,
1007
+ start: Math.min(next.end - minDuration, next.start + nextDelta)
1008
+ };
1009
+ currentIndex++;
1010
+ } else {
1011
+ break;
1012
+ }
1013
+ }
1014
+ } else if (constrainedEnd >= nextAnnotation.start) {
1015
+ updatedAnnotations[annotationIndex] = {
1016
+ ...updatedAnnotations[annotationIndex],
1017
+ end: nextAnnotation.start
1018
+ };
1019
+ }
1020
+ } else if (!linkEndpoints && annotationIndex < updatedAnnotations.length - 1 && constrainedEnd > updatedAnnotations[annotationIndex + 1].start) {
1021
+ const nextAnnotation = updatedAnnotations[annotationIndex + 1];
1022
+ updatedAnnotations[annotationIndex + 1] = { ...nextAnnotation, start: constrainedEnd };
1023
+ let currentIndex = annotationIndex + 1;
1024
+ while (currentIndex < updatedAnnotations.length - 1) {
1025
+ const current = updatedAnnotations[currentIndex];
1026
+ const next = updatedAnnotations[currentIndex + 1];
1027
+ if (current.end > next.start) {
1028
+ updatedAnnotations[currentIndex + 1] = { ...next, start: current.end };
1029
+ currentIndex++;
1030
+ } else {
1031
+ break;
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ return updatedAnnotations;
1037
+ }
1038
+
1039
+ // src/annotations/shortcuts.ts
1040
+ var noMods = { ctrlKey: false, metaKey: false };
1041
+ var DEFAULT_ANNOTATION_SHORTCUTS = {
1042
+ selectPrevious: [
1043
+ { key: "ArrowUp", ...noMods },
1044
+ { key: "ArrowLeft", ...noMods }
1045
+ ],
1046
+ selectNext: [
1047
+ { key: "ArrowDown", ...noMods },
1048
+ { key: "ArrowRight", ...noMods }
1049
+ ],
1050
+ selectFirst: [{ key: "Home", ...noMods }],
1051
+ selectLast: [{ key: "End", ...noMods }],
1052
+ clearSelection: [{ key: "Escape", ...noMods }],
1053
+ moveStartEarlier: [{ key: "[", ...noMods }],
1054
+ moveStartLater: [{ key: "]", ...noMods }],
1055
+ // '{' / '}' are what event.key reports for Shift+[ / Shift+] — no explicit
1056
+ // shiftKey needed; the key value itself encodes it.
1057
+ moveEndEarlier: [{ key: "{", ...noMods }],
1058
+ moveEndLater: [{ key: "}", ...noMods }],
1059
+ playActive: [{ key: "Enter", ...noMods }]
1060
+ };
1061
+ var ALL_ACTIONS = Object.keys(DEFAULT_ANNOTATION_SHORTCUTS);
1062
+ function resolveAnnotationShortcuts(remap) {
1063
+ return ALL_ACTIONS.flatMap((action) => {
1064
+ const override = remap?.[action];
1065
+ const bindings = override ? [override] : DEFAULT_ANNOTATION_SHORTCUTS[action];
1066
+ return bindings.map((binding) => ({ action, binding }));
1067
+ });
1068
+ }
918
1069
  export {
1070
+ ANNOTATION_LINK_THRESHOLD_TICKS,
1071
+ DEFAULT_ANNOTATION_SHORTCUTS,
919
1072
  DEFAULT_SPECTROGRAM_COLOR_MAP,
920
1073
  InteractionState,
1074
+ LINK_THRESHOLD,
921
1075
  MAX_CANVAS_WIDTH,
1076
+ MIN_ANNOTATION_DURATION,
922
1077
  MIN_PIXELS_PER_UNIT,
923
1078
  PPQN,
924
1079
  SPECTROGRAM_DEFAULTS,
1080
+ annotationMinDurationTicks,
925
1081
  appendPeaks,
926
1082
  appendToAudioBuffer,
927
1083
  applyFadeIn,
@@ -959,11 +1115,13 @@ export {
959
1115
  handleKeyboardEvent,
960
1116
  linearCurve,
961
1117
  logarithmicCurve,
1118
+ matchesKeyBinding,
962
1119
  normalizedToDb,
963
1120
  parseSpectrogramCanvasId,
964
1121
  pixelsToSamples,
965
1122
  pixelsToSeconds,
966
1123
  probeRangeSupport,
1124
+ resolveAnnotationShortcuts,
967
1125
  resolveRecordingOffsetSamples,
968
1126
  sCurveCurve,
969
1127
  samplesToPixels,
@@ -977,8 +1135,10 @@ export {
977
1135
  sortClipsByTime,
978
1136
  ticksPerBar,
979
1137
  ticksPerBeat,
1138
+ ticksToBarBeat,
980
1139
  ticksToSamples,
981
1140
  trackChannelCount,
1141
+ updateAnnotationBoundaries,
982
1142
  watchMicrophoneDevices
983
1143
  };
984
1144
  //# sourceMappingURL=index.mjs.map