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