solid-pianoroll 0.0.6 → 0.0.8

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/esm/index.js CHANGED
@@ -59,10 +59,6 @@ function useBoundingClientRect(containerRef) {
59
59
  return boundingClientRect;
60
60
  }
61
61
 
62
- const clamp = (value, min, max) => {
63
- return Math.max(min, Math.min(max, value));
64
- };
65
-
66
62
  function useViewPortScaler(getState) {
67
63
  const state = createMemo(() => getState());
68
64
  function getScaledValue(position) {
@@ -72,12 +68,20 @@ function useViewPortScaler(getState) {
72
68
  function getCoordinates(position) {
73
69
  return getScaledValue(position) - getScaledValue(state().virtualPosition);
74
70
  }
71
+ function getVisibleRange() {
72
+ return state().virtualRange / state().zoom;
73
+ }
75
74
  function getPosition(offset) {
76
- const visibleDuration = state().virtualRange / state().zoom;
77
75
  const percentX = (offset - state().viewPortOffset) / state().viewPortSize;
78
- const position = state().virtualPosition + percentX * visibleDuration;
76
+ const position = state().virtualPosition + percentX * getVisibleRange();
79
77
  return position;
80
78
  }
79
+ function getMaxPosition() {
80
+ return state().virtualRange - state().virtualRange / state().zoom;
81
+ }
82
+ function getScrollSize() {
83
+ return clamp(state().zoom * state().viewPortSize, state().viewPortSize, 100000);
84
+ }
81
85
  function getVirtualDimensions(position, length) {
82
86
  const virtualLeft = getCoordinates(position);
83
87
  const virtualWidth = getScaledValue(length);
@@ -92,9 +96,15 @@ function useViewPortScaler(getState) {
92
96
  getScaledValue,
93
97
  getCoordinates,
94
98
  getPosition,
95
- getVirtualDimensions
99
+ getVirtualDimensions,
100
+ getVisibleRange,
101
+ getMaxPosition,
102
+ getScrollSize
96
103
  };
97
104
  }
105
+ const clamp = (value, min, max) => {
106
+ return Math.max(min, Math.min(max, value));
107
+ };
98
108
 
99
109
  const PianoRollContext = createContext(undefined);
100
110
  const PianoRollContextProvider = props => {
@@ -107,13 +117,13 @@ const PianoRollContextProvider = props => {
107
117
  viewPortSize: clientRect().width,
108
118
  virtualPosition: props.position,
109
119
  virtualRange: props.duration,
110
- zoom: props.zoom
120
+ zoom: props.zoom * (500 / clientRect().width)
111
121
  }));
112
122
  const verticalViewPort = useViewPortScaler(() => ({
113
123
  viewPortOffset: clientRect().top,
114
124
  viewPortSize: clientRect().height,
115
125
  virtualPosition: props.verticalPosition,
116
- zoom: props.verticalZoom,
126
+ zoom: props.verticalZoom * (500 / clientRect().height),
117
127
  virtualRange: 128
118
128
  }));
119
129
  const getContextValue = () => {
@@ -343,8 +353,8 @@ const ScrollContainer = props => {
343
353
  const context = usePianoRollContext();
344
354
  const gridDivisorTicks = createMemo(() => context.ppq * 4 / context.gridDivision);
345
355
  const forwardEventToNote = event => {
346
- const x = "clientX" in event ? event.clientX : event.touches[0]?.clientX;
347
- const y = "clientY" in event ? event.clientY : event.touches[0]?.clientY;
356
+ const x = "clientX" in event ? event.clientX : event.touches[0]?.clientX ?? 0;
357
+ const y = "clientY" in event ? event.clientY : event.touches[0]?.clientY ?? 0;
348
358
  const elementUnderMouse = [...(context.notesContainer?.querySelectorAll?.("div") ?? [])].find(element => {
349
359
  const rect = element.getBoundingClientRect();
350
360
  return x >= rect.left && rect.left + rect.width > x && y >= rect.top && rect.top + rect.height > y;
@@ -359,8 +369,8 @@ const ScrollContainer = props => {
359
369
  didUpdateScroll = false;
360
370
  return;
361
371
  }
362
- const maxVerticalPosition = 128 - 128 / context.verticalZoom;
363
- const maxPosition = context.duration - context.duration / context.zoom;
372
+ const maxVerticalPosition = context.verticalViewPort.getMaxPosition();
373
+ const maxPosition = context.horizontalViewPort.getMaxPosition();
364
374
  const {
365
375
  width,
366
376
  height
@@ -378,8 +388,8 @@ const ScrollContainer = props => {
378
388
  };
379
389
  let didUpdateScroll = false;
380
390
  createEffect(() => {
381
- const maxVerticalPosition = 128 - 128 / context.verticalZoom;
382
- const maxPosition = context.duration - context.duration / context.zoom;
391
+ const maxVerticalPosition = context.verticalViewPort.getMaxPosition();
392
+ const maxPosition = context.horizontalViewPort.getMaxPosition();
383
393
  const scrollTopAmount = maxVerticalPosition > 0 ? context.verticalPosition / maxVerticalPosition : 0;
384
394
  const scrollLeftAmount = maxPosition > 0 ? context.position / maxPosition : 0;
385
395
  const {
@@ -387,9 +397,9 @@ const ScrollContainer = props => {
387
397
  width
388
398
  } = context.clientRect;
389
399
  if (!scrollContentRef?.parentElement) return;
390
- const scrollDivHeight = clamp(context.verticalZoom * height, height, 10000);
400
+ const scrollDivHeight = context.verticalViewPort.getScrollSize();
391
401
  const scrollTop = scrollTopAmount * (scrollDivHeight - height);
392
- const scrollDivWidth = clamp(context.zoom * width, width, 10000);
402
+ const scrollDivWidth = context.horizontalViewPort.getScrollSize();
393
403
  const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
394
404
  didUpdateScroll = true;
395
405
  scrollContentRef.style.height = `${scrollDivHeight}px`;
@@ -399,6 +409,30 @@ const ScrollContainer = props => {
399
409
  top: scrollTop
400
410
  });
401
411
  });
412
+ const insertOrUpdateNote = event => {
413
+ const position = context.horizontalViewPort.getPosition(event.clientX);
414
+ const midi = 127 - Math.floor(context.verticalViewPort.getPosition(event.clientY));
415
+ const eventPositionTicks = Math.floor(position / gridDivisorTicks()) * gridDivisorTicks();
416
+ const velocity = 127;
417
+ const existingNote = newNote();
418
+ const ticks = existingNote?.ticks ?? eventPositionTicks;
419
+ const durationTicks = existingNote?.ticks ? eventPositionTicks - existingNote?.ticks : gridDivisorTicks();
420
+ const note = {
421
+ midi,
422
+ ticks,
423
+ durationTicks,
424
+ velocity
425
+ };
426
+ if (existingNote) {
427
+ context.onNoteChange?.(newNoteIndex(), note);
428
+ return newNoteIndex();
429
+ } else {
430
+ return context.onInsertNote?.(note) ?? -1;
431
+ }
432
+ };
433
+ const [newNoteIndex, setNewNoteIndex] = createSignal(-1);
434
+ const newNote = createMemo(() => context.notes[newNoteIndex()]);
435
+ const [isMouseDown, setIsMouseDown] = createSignal(false);
402
436
  return (() => {
403
437
  const _el$ = _tmpl$$3.cloneNode(true),
404
438
  _el$2 = _el$.firstChild;
@@ -409,30 +443,43 @@ const ScrollContainer = props => {
409
443
  _el$.$$touchend = forwardEventToNote;
410
444
  _el$.$$touchmove = forwardEventToNote;
411
445
  _el$.$$touchstart = forwardEventToNote;
412
- _el$.$$mouseup = forwardEventToNote;
413
- _el$.$$dblclick = event => {
414
- if (!forwardEventToNote(event)) {
415
- const position = context.horizontalViewPort.getPosition(event.clientX);
416
- const midi = 127 - Math.floor(context.verticalViewPort.getPosition(event.clientY));
417
- const ticks = Math.floor(position / gridDivisorTicks()) * gridDivisorTicks();
418
- const durationTicks = gridDivisorTicks();
419
- const velocity = 127;
420
- const note = {
421
- midi,
422
- ticks,
423
- durationTicks,
424
- velocity
425
- };
426
- context.onInsertNote?.(note);
446
+ _el$.$$mouseup = event => {
447
+ setIsMouseDown(false);
448
+ if (newNote()) return;
449
+ if (forwardEventToNote(event)) return;
450
+ if (!event.altKey) return;
451
+ insertOrUpdateNote(event);
452
+ };
453
+ _el$.$$click = event => {
454
+ if (newNote()) {
455
+ setNewNoteIndex(-1);
456
+ return;
427
457
  }
458
+ if (forwardEventToNote(event)) return;
459
+ if (!event.altKey) return;
460
+ insertOrUpdateNote(event);
461
+ };
462
+ _el$.$$dblclick = event => {
463
+ if (newNote()) return;
464
+ if (context.isDragging) return;
465
+ if (forwardEventToNote(event)) return;
466
+ insertOrUpdateNote(event);
428
467
  };
429
- _el$.$$click = forwardEventToNote;
430
468
  _el$.$$mousemove = event => {
469
+ event.preventDefault();
431
470
  if (forwardEventToNote(event)) return;
432
471
  if (context.isDragging) return;
433
- context.onNoteDragModeChange(undefined);
472
+ if (!isMouseDown()) {
473
+ context.onNoteDragModeChange(undefined);
474
+ return;
475
+ }
476
+ const index = insertOrUpdateNote(event);
477
+ setNewNoteIndex(index);
478
+ };
479
+ _el$.$$mousedown = event => {
480
+ if (forwardEventToNote(event)) return;
481
+ setIsMouseDown(true);
434
482
  };
435
- _el$.$$mousedown = forwardEventToNote;
436
483
  _el$.addEventListener("scroll", handleScroll);
437
484
  const _ref$ = props.ref;
438
485
  typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$;
@@ -450,7 +497,7 @@ const ScrollContainer = props => {
450
497
  return _el$;
451
498
  })();
452
499
  };
453
- delegateEvents(["mousedown", "mousemove", "click", "dblclick", "mouseup", "touchstart", "touchmove", "touchend"]);
500
+ delegateEvents(["mousedown", "mousemove", "dblclick", "click", "mouseup", "touchstart", "touchmove", "touchend"]);
454
501
 
455
502
  const _tmpl$$2 = /*#__PURE__*/template(`<input type="range" min="1" max="11" step="0.01">`, 1);
456
503
  const VerticalZoomControl = () => {
@@ -485,12 +532,27 @@ const _tmpl$$1 = /*#__PURE__*/template(`<div class="PianoRoll-Grid-Container"><d
485
532
  _tmpl$3 = /*#__PURE__*/template(`<div class="PianoRoll-Grid-Key"></div>`, 2);
486
533
  const PianoRollGrid = () => {
487
534
  const context = usePianoRollContext();
488
- const gridDivisorTicks = createMemo(() => context.ppq * 4 / context.gridDivision);
535
+ const measureTicks = createMemo(() => context.ppq * 4);
536
+ const selectedGridDivisorTicks = createMemo(() => measureTicks() / context.gridDivision);
537
+ function calculateVisibleGridDivisorTicks(value) {
538
+ if (context.horizontalViewPort.getVisibleRange() / value > 100) {
539
+ return calculateVisibleGridDivisorTicks(value * 2);
540
+ }
541
+ if (context.horizontalViewPort.getVisibleRange() / value < 30) {
542
+ return calculateVisibleGridDivisorTicks(value / 2);
543
+ }
544
+ return value;
545
+ }
546
+ const gridDivisorTicks = createMemo(() => calculateVisibleGridDivisorTicks(selectedGridDivisorTicks()));
489
547
  const gridArray = createMemo(() => {
490
- const numberOfLines = context.duration / gridDivisorTicks();
548
+ const numberOfLines = Math.ceil(context.horizontalViewPort.getVisibleRange() / gridDivisorTicks()) + 1;
549
+ const startIndex = Math.floor(context.position / gridDivisorTicks());
491
550
  return Array.from({
492
551
  length: numberOfLines
493
- }).map((_, index) => index * gridDivisorTicks());
552
+ }).map((_, index) => ({
553
+ index: index + startIndex,
554
+ ticks: (index + startIndex) * gridDivisorTicks()
555
+ }));
494
556
  });
495
557
  return (() => {
496
558
  const _el$ = _tmpl$$1.cloneNode(true),
@@ -502,8 +564,8 @@ const PianoRollGrid = () => {
502
564
  get each() {
503
565
  return gridArray();
504
566
  },
505
- children: (tick, index) => {
506
- const virtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(tick(), gridDivisorTicks()));
567
+ children: entry => {
568
+ const virtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(entry().ticks, gridDivisorTicks()));
507
569
  return createComponent(Show, {
508
570
  get when() {
509
571
  return virtualDimensions().size > 0;
@@ -513,19 +575,21 @@ const PianoRollGrid = () => {
513
575
  _el$3.style.setProperty("z-index", "1");
514
576
  _el$3.style.setProperty("position", "absolute");
515
577
  _el$3.style.setProperty("box-sizing", "border-box");
516
- _el$3.style.setProperty("background", index % 4 === 0 ? "#ccc" : "#ddd");
517
578
  _el$3.style.setProperty("top", "0px");
518
579
  _el$3.style.setProperty("height", "100%");
519
580
  _el$3.style.setProperty("border-left", "1px gray solid");
520
581
  effect(_p$ => {
521
- const _v$4 = `${virtualDimensions().offset}px`,
522
- _v$5 = `${virtualDimensions().size}px`;
523
- _v$4 !== _p$._v$4 && _el$3.style.setProperty("left", _p$._v$4 = _v$4);
524
- _v$5 !== _p$._v$5 && _el$3.style.setProperty("width", _p$._v$5 = _v$5);
582
+ const _v$4 = Math.ceil((entry().index + 1) * selectedGridDivisorTicks() / measureTicks()) % 2 === 0 ? "#ddd" : "#ccc",
583
+ _v$5 = `${virtualDimensions().offset}px`,
584
+ _v$6 = `${virtualDimensions().size}px`;
585
+ _v$4 !== _p$._v$4 && _el$3.style.setProperty("background", _p$._v$4 = _v$4);
586
+ _v$5 !== _p$._v$5 && _el$3.style.setProperty("left", _p$._v$5 = _v$5);
587
+ _v$6 !== _p$._v$6 && _el$3.style.setProperty("width", _p$._v$6 = _v$6);
525
588
  return _p$;
526
589
  }, {
527
590
  _v$4: undefined,
528
- _v$5: undefined
591
+ _v$5: undefined,
592
+ _v$6: undefined
529
593
  });
530
594
  return _el$3;
531
595
  }
@@ -548,20 +612,20 @@ const PianoRollGrid = () => {
548
612
  _el$4.style.setProperty("border-color", "gray");
549
613
  _el$4.style.setProperty("z-index", "1");
550
614
  effect(_p$ => {
551
- const _v$6 = `${virtualDimensions().offset}px`,
552
- _v$7 = `${virtualDimensions().size}px`,
553
- _v$8 = key().isBlack ? "rgba(0,0,0,0.2)" : "none",
554
- _v$9 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
555
- _v$6 !== _p$._v$6 && _el$4.style.setProperty("top", _p$._v$6 = _v$6);
556
- _v$7 !== _p$._v$7 && _el$4.style.setProperty("height", _p$._v$7 = _v$7);
557
- _v$8 !== _p$._v$8 && _el$4.style.setProperty("background-color", _p$._v$8 = _v$8);
558
- _v$9 !== _p$._v$9 && _el$4.style.setProperty("border-width", _p$._v$9 = _v$9);
615
+ const _v$7 = `${virtualDimensions().offset}px`,
616
+ _v$8 = `${virtualDimensions().size}px`,
617
+ _v$9 = key().isBlack ? "rgba(0,0,0,0.2)" : "none",
618
+ _v$10 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
619
+ _v$7 !== _p$._v$7 && _el$4.style.setProperty("top", _p$._v$7 = _v$7);
620
+ _v$8 !== _p$._v$8 && _el$4.style.setProperty("height", _p$._v$8 = _v$8);
621
+ _v$9 !== _p$._v$9 && _el$4.style.setProperty("background-color", _p$._v$9 = _v$9);
622
+ _v$10 !== _p$._v$10 && _el$4.style.setProperty("border-width", _p$._v$10 = _v$10);
559
623
  return _p$;
560
624
  }, {
561
- _v$6: undefined,
562
625
  _v$7: undefined,
563
626
  _v$8: undefined,
564
- _v$9: undefined
627
+ _v$9: undefined,
628
+ _v$10: undefined
565
629
  });
566
630
  return _el$4;
567
631
  }
@@ -631,5 +695,55 @@ const PianoRoll = allProps => {
631
695
  }));
632
696
  };
633
697
 
634
- export { PianoRoll };
698
+ const usePianoRoll = () => {
699
+ const [ppq, onPpqChange] = createSignal(120);
700
+ const [position, onPositionChange] = createSignal(0);
701
+ const [zoom, onZoomChange] = createSignal(10);
702
+ const [verticalZoom, onVerticalZoomChange] = createSignal(5);
703
+ const [verticalPosition, onVerticalPositionChange] = createSignal(64);
704
+ const [gridDivision, onGridDivisionChange] = createSignal(16);
705
+ const [snapToGrid, onSnapToGridChange] = createSignal(true);
706
+ const [notes, onNotesChange] = createSignal([]);
707
+ const [duration, setDuration] = createSignal(0);
708
+ const onNoteChange = (index, note) => {
709
+ onNotesChange([...notes().slice(0, index), note, ...notes().splice(index + 1)]);
710
+ };
711
+ const onInsertNote = note => {
712
+ const index = Math.max(notes().findIndex(({
713
+ ticks
714
+ }) => ticks > note.ticks), 0);
715
+ onNotesChange([...notes().slice(0, index), note, ...notes().splice(index)]);
716
+ return index;
717
+ };
718
+ const onRemoveNote = index => {
719
+ onNotesChange([...notes().slice(0, index), ...notes().splice(index + 1)]);
720
+ };
721
+ createEffect(() => {
722
+ setDuration(Math.max((notes()[notes().length - 1]?.ticks ?? 0) + (notes()[notes().length - 1]?.durationTicks ?? 0), ppq() * 4 * 4));
723
+ });
724
+ return {
725
+ ppq,
726
+ onPpqChange,
727
+ position,
728
+ onPositionChange,
729
+ zoom,
730
+ onZoomChange,
731
+ verticalPosition,
732
+ onVerticalPositionChange,
733
+ verticalZoom,
734
+ onVerticalZoomChange,
735
+ gridDivision,
736
+ onGridDivisionChange,
737
+ snapToGrid,
738
+ onSnapToGridChange,
739
+ notes,
740
+ onNotesChange,
741
+ onNoteChange,
742
+ onInsertNote,
743
+ onRemoveNote,
744
+ duration
745
+ };
746
+ };
747
+
748
+ export { PianoRoll, usePianoRoll };
635
749
  //# sourceMappingURL=index.js.map