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/README.md +1 -1
- package/dist/cjs/index.js +172 -57
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +172 -58
- package/dist/esm/index.js.map +1 -1
- package/dist/source/PianoRollContext.jsx +2 -2
- package/dist/source/PianoRollGrid.jsx +25 -6
- package/dist/source/ScrollContainer.jsx +70 -22
- package/dist/source/index.jsx +2 -1
- package/dist/source/types.js +1 -0
- package/dist/source/usePianoRoll.js +50 -0
- package/dist/source/useViewPortScaler.js +16 -3
- package/dist/types/PianoRoll.d.ts +2 -3
- package/dist/types/index.d.ts +2 -1
- package/dist/types/types.d.ts +2 -0
- package/dist/types/usePianoRoll.d.ts +25 -0
- package/dist/types/useViewPortScaler.d.ts +4 -0
- package/package.json +1 -1
- package/dist/source/helpers.js +0 -3
- package/dist/types/helpers.d.ts +0 -1
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 *
|
|
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 =
|
|
363
|
-
const maxPosition = context.
|
|
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 =
|
|
382
|
-
const maxPosition = context.
|
|
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 =
|
|
400
|
+
const scrollDivHeight = context.verticalViewPort.getScrollSize();
|
|
391
401
|
const scrollTop = scrollTopAmount * (scrollDivHeight - height);
|
|
392
|
-
const scrollDivWidth =
|
|
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 =
|
|
413
|
-
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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", "
|
|
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
|
|
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.
|
|
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) =>
|
|
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:
|
|
506
|
-
const virtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(
|
|
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 =
|
|
522
|
-
_v$5 = `${virtualDimensions().
|
|
523
|
-
|
|
524
|
-
_v$
|
|
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$
|
|
552
|
-
_v$
|
|
553
|
-
_v$
|
|
554
|
-
_v$
|
|
555
|
-
_v$
|
|
556
|
-
_v$
|
|
557
|
-
_v$
|
|
558
|
-
_v$
|
|
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
|
-
|
|
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
|