solid-pianoroll 0.0.23 → 0.0.25
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/{cjs/index.js → dev.cjs} +446 -426
- package/dist/dev.css +212 -0
- package/dist/dev.js +1330 -0
- package/dist/dev.jsx +1130 -0
- package/dist/index.cjs +1330 -0
- package/dist/index.css +212 -0
- package/dist/index.d.ts +105 -0
- package/dist/{esm/index.js → index.js} +450 -425
- package/dist/index.jsx +1130 -0
- package/package.json +43 -34
- package/dist/cjs/index.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/source/PianoRoll.jsx +0 -78
- package/dist/source/PianoRollContext.jsx +0 -11
- package/dist/source/PianoRollGrid.jsx +0 -64
- package/dist/source/PianoRollKeys.jsx +0 -72
- package/dist/source/PianoRollNotes.jsx +0 -162
- package/dist/source/PianoRollNotesScroller.jsx +0 -16
- package/dist/source/PianoRollScale.jsx +0 -29
- package/dist/source/PianoRollScrollZoomViewPort.jsx +0 -49
- package/dist/source/PianoRollTrackList.jsx +0 -27
- package/dist/source/PianoRollTrackListScroller.jsx +0 -16
- package/dist/source/index.jsx +0 -5
- package/dist/source/types.js +0 -1
- package/dist/source/useBoundingClientRect.js +0 -29
- package/dist/source/useNotes.js +0 -23
- package/dist/source/usePianoRollGrid.js +0 -45
- package/dist/source/usePianoRollState.js +0 -106
- package/dist/source/viewport/PlayHead.jsx +0 -43
- package/dist/source/viewport/ScrollZoomContainer.jsx +0 -104
- package/dist/source/viewport/ScrollZoomViewPort.jsx +0 -31
- package/dist/source/viewport/ZoomSliderControl.jsx +0 -29
- package/dist/source/viewport/createViewPortDimension.js +0 -51
- package/dist/types/PianoRoll.d.ts +0 -9
- package/dist/types/PianoRollContext.d.ts +0 -122
- package/dist/types/PianoRollGrid.d.ts +0 -2
- package/dist/types/PianoRollKeys.d.ts +0 -9
- package/dist/types/PianoRollNotes.d.ts +0 -5
- package/dist/types/PianoRollNotesScroller.d.ts +0 -3
- package/dist/types/PianoRollScale.d.ts +0 -2
- package/dist/types/PianoRollScrollZoomViewPort.d.ts +0 -3
- package/dist/types/PianoRollTrackList.d.ts +0 -2
- package/dist/types/PianoRollTrackListScroller.d.ts +0 -3
- package/dist/types/index.d.ts +0 -5
- package/dist/types/types.d.ts +0 -6
- package/dist/types/useBoundingClientRect.d.ts +0 -3
- package/dist/types/useNotes.d.ts +0 -9
- package/dist/types/usePianoRollGrid.d.ts +0 -13
- package/dist/types/usePianoRollState.d.ts +0 -63
- package/dist/types/viewport/PlayHead.d.ts +0 -8
- package/dist/types/viewport/ScrollZoomContainer.d.ts +0 -8
- package/dist/types/viewport/ScrollZoomViewPort.d.ts +0 -39
- package/dist/types/viewport/ZoomSliderControl.d.ts +0 -6
- package/dist/types/viewport/createViewPortDimension.d.ts +0 -40
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { createMemo, createSignal, For, Show } from "solid-js";
|
|
2
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
3
|
-
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
4
|
-
import styles from "./PianoRollNotes.module.scss";
|
|
5
|
-
import { clamp } from "./viewport/createViewPortDimension";
|
|
6
|
-
const PianoRollNotes = (props) => {
|
|
7
|
-
const context = usePianoRollContext();
|
|
8
|
-
const verticalViewPort = createMemo(() => useViewPortDimension(context.mode === "keys" ? "vertical" : "verticalTracks"));
|
|
9
|
-
const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
|
|
10
|
-
const gridDivisionTicks = createMemo(() => (context.ppq * 4) / context.gridDivision);
|
|
11
|
-
const [isDragging, setIsDragging] = createSignal(false);
|
|
12
|
-
const [noteDragMode, setNoteDragMode] = createSignal();
|
|
13
|
-
const [currentNoteIndex, setCurrentNoteIndex] = createSignal(-1);
|
|
14
|
-
const [currentNoteTrackIndex, setCurrentNoteTrackIndex] = createSignal(-1);
|
|
15
|
-
const [diffPosition, setDiffPosition] = createSignal(0);
|
|
16
|
-
const [getInitialNote, setInitialNote] = createSignal();
|
|
17
|
-
const snapValueToGridIfEnabled = (value, altKey) => context.snapToGrid && !altKey
|
|
18
|
-
? Math.round(value / gridDivisionTicks()) * gridDivisionTicks()
|
|
19
|
-
: value;
|
|
20
|
-
const calculateNoteDragValues = (event) => {
|
|
21
|
-
const targetTrackIndex = context.mode === "tracks"
|
|
22
|
-
? clamp(Math.floor(verticalViewPort().calculatePosition(event.clientY)), 0, context.tracks.length - 1)
|
|
23
|
-
: currentNoteTrackIndex() > -1
|
|
24
|
-
? currentNoteTrackIndex()
|
|
25
|
-
: context.selectedTrackIndex;
|
|
26
|
-
const midi = context.mode === "keys"
|
|
27
|
-
? Math.floor(128 - verticalViewPort().calculatePosition(event.clientY))
|
|
28
|
-
: getInitialNote()?.midi ?? 60;
|
|
29
|
-
const horiontalPosition = horizontalViewPort().calculatePosition(event.clientX);
|
|
30
|
-
return {
|
|
31
|
-
targetTrackIndex,
|
|
32
|
-
midi,
|
|
33
|
-
horiontalPosition,
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
const handleMouseMove = (mouseMoveEvent) => {
|
|
37
|
-
mouseMoveEvent.preventDefault();
|
|
38
|
-
mouseMoveEvent.stopPropagation();
|
|
39
|
-
const note = getInitialNote();
|
|
40
|
-
if (!note)
|
|
41
|
-
return;
|
|
42
|
-
const { targetTrackIndex, midi, horiontalPosition } = calculateNoteDragValues(mouseMoveEvent);
|
|
43
|
-
const ticks = snapValueToGridIfEnabled(horiontalPosition - diffPosition(), mouseMoveEvent.altKey);
|
|
44
|
-
const updatedNote = {
|
|
45
|
-
...note,
|
|
46
|
-
midi,
|
|
47
|
-
...(noteDragMode() === "move" && { ticks }),
|
|
48
|
-
...(noteDragMode() === "trimStart" && {
|
|
49
|
-
ticks: ticks < note.ticks + note.durationTicks ? ticks : note.ticks + note.durationTicks,
|
|
50
|
-
durationTicks: ticks < note.ticks + note.durationTicks
|
|
51
|
-
? note.ticks + note.durationTicks - ticks
|
|
52
|
-
: snapValueToGridIfEnabled(horiontalPosition - note.ticks, mouseMoveEvent.altKey),
|
|
53
|
-
}),
|
|
54
|
-
...(noteDragMode() === "trimEnd" && {
|
|
55
|
-
ticks: ticks < note.ticks ? ticks : note.ticks,
|
|
56
|
-
durationTicks: ticks < note.ticks
|
|
57
|
-
? note.ticks - ticks
|
|
58
|
-
: snapValueToGridIfEnabled(horiontalPosition - note.ticks, mouseMoveEvent.altKey),
|
|
59
|
-
}),
|
|
60
|
-
};
|
|
61
|
-
const previousTrackIndex = currentNoteTrackIndex();
|
|
62
|
-
const previousNoteIndex = currentNoteIndex();
|
|
63
|
-
if (targetTrackIndex === previousTrackIndex) {
|
|
64
|
-
context.onNoteChange?.(previousTrackIndex, previousNoteIndex, updatedNote);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
if (context.onInsertNote) {
|
|
68
|
-
context.onRemoveNote?.(previousTrackIndex, previousNoteIndex);
|
|
69
|
-
const newNoteIndex = context.onInsertNote(targetTrackIndex, updatedNote);
|
|
70
|
-
setCurrentNoteTrackIndex(targetTrackIndex);
|
|
71
|
-
setCurrentNoteIndex(newNoteIndex);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const stopDragging = () => {
|
|
76
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
77
|
-
window.removeEventListener("mouseup", stopDragging);
|
|
78
|
-
setIsDragging(false);
|
|
79
|
-
setCurrentNoteIndex(-1);
|
|
80
|
-
setCurrentNoteTrackIndex(-1);
|
|
81
|
-
};
|
|
82
|
-
const startDragging = () => {
|
|
83
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
84
|
-
window.addEventListener("mouseup", stopDragging);
|
|
85
|
-
};
|
|
86
|
-
const getClasses = (noteDragMode) => {
|
|
87
|
-
return noteDragMode ? [styles.Note, styles[noteDragMode]] : [styles.Note];
|
|
88
|
-
};
|
|
89
|
-
return (<div classList={{ [styles.PianoRollNotes]: true }} ref={props.ref} onMouseDown={(mouseDownEvent) => {
|
|
90
|
-
mouseDownEvent.preventDefault();
|
|
91
|
-
mouseDownEvent.stopPropagation();
|
|
92
|
-
const { targetTrackIndex, midi, horiontalPosition } = calculateNoteDragValues(mouseDownEvent);
|
|
93
|
-
const ticks = snapValueToGridIfEnabled(horiontalPosition, mouseDownEvent.altKey);
|
|
94
|
-
const durationTicks = gridDivisionTicks();
|
|
95
|
-
const newNote = {
|
|
96
|
-
midi,
|
|
97
|
-
ticks,
|
|
98
|
-
durationTicks,
|
|
99
|
-
velocity: 100,
|
|
100
|
-
};
|
|
101
|
-
const newNoteIndex = context.onInsertNote(targetTrackIndex, newNote);
|
|
102
|
-
setIsDragging(true);
|
|
103
|
-
setCurrentNoteTrackIndex(targetTrackIndex);
|
|
104
|
-
setCurrentNoteIndex(newNoteIndex);
|
|
105
|
-
setInitialNote(newNote);
|
|
106
|
-
setDiffPosition(0);
|
|
107
|
-
setNoteDragMode("trimEnd");
|
|
108
|
-
startDragging();
|
|
109
|
-
}}>
|
|
110
|
-
<For each={context.tracks}>
|
|
111
|
-
{(track, trackIndex) => {
|
|
112
|
-
return (<Show when={trackIndex() === context.selectedTrackIndex ||
|
|
113
|
-
context.showAllTracks ||
|
|
114
|
-
context.mode === "tracks"}>
|
|
115
|
-
<For each={track.notes}>
|
|
116
|
-
{(note, noteIndex) => {
|
|
117
|
-
const verticalVirtualDimensions = createMemo(() => verticalViewPort().calculatePixelDimensions(context.mode === "keys" ? 127 - note.midi : trackIndex(), 1));
|
|
118
|
-
const horizontalDimensions = createMemo(() => horizontalViewPort().calculatePixelDimensions(note.ticks, note.durationTicks));
|
|
119
|
-
return (<Show when={verticalViewPort().isVisible(verticalVirtualDimensions()) &&
|
|
120
|
-
horizontalViewPort().isVisible(horizontalDimensions())}>
|
|
121
|
-
<div class={getClasses(noteDragMode()).join(" ")} draggable={false} onMouseMove={(event) => {
|
|
122
|
-
if (isDragging())
|
|
123
|
-
return;
|
|
124
|
-
event.stopPropagation();
|
|
125
|
-
const relativeX = horizontalViewPort().calculatePixelValue(horizontalViewPort().calculatePosition(event.clientX));
|
|
126
|
-
const noteStartX = horizontalViewPort().calculatePixelValue(note.ticks);
|
|
127
|
-
const noteEndX = horizontalViewPort().calculatePixelValue(note.ticks + note.durationTicks);
|
|
128
|
-
setNoteDragMode(relativeX - noteStartX < 3
|
|
129
|
-
? "trimStart"
|
|
130
|
-
: noteEndX - relativeX < 3
|
|
131
|
-
? "trimEnd"
|
|
132
|
-
: "move");
|
|
133
|
-
}} onDblClick={(event) => {
|
|
134
|
-
event.stopPropagation();
|
|
135
|
-
context.onRemoveNote?.(trackIndex(), noteIndex());
|
|
136
|
-
}} onMouseDown={(event) => {
|
|
137
|
-
event.stopPropagation();
|
|
138
|
-
const initialPosition = horizontalViewPort().calculatePosition(event.clientX);
|
|
139
|
-
setDiffPosition(noteDragMode() === "trimEnd"
|
|
140
|
-
? -(note.ticks + note.durationTicks - initialPosition)
|
|
141
|
-
: initialPosition - note.ticks);
|
|
142
|
-
setIsDragging(true);
|
|
143
|
-
setCurrentNoteIndex(noteIndex());
|
|
144
|
-
setCurrentNoteTrackIndex(trackIndex());
|
|
145
|
-
setInitialNote(note);
|
|
146
|
-
startDragging();
|
|
147
|
-
}} style={{
|
|
148
|
-
"background-color": `rgba(255,0,0, ${(128 + note.velocity) / 256})`,
|
|
149
|
-
top: `${verticalVirtualDimensions().offset}px`,
|
|
150
|
-
height: `${verticalVirtualDimensions().size}px`,
|
|
151
|
-
left: `${horizontalDimensions().offset}px`,
|
|
152
|
-
width: `${horizontalDimensions().size}px`,
|
|
153
|
-
}}></div>
|
|
154
|
-
</Show>);
|
|
155
|
-
}}
|
|
156
|
-
</For>
|
|
157
|
-
</Show>);
|
|
158
|
-
}}
|
|
159
|
-
</For>
|
|
160
|
-
</div>);
|
|
161
|
-
};
|
|
162
|
-
export default PianoRollNotes;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createEffect, createSignal } from "solid-js";
|
|
2
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
3
|
-
import useBoundingClientRect from "./useBoundingClientRect";
|
|
4
|
-
import ScrollZoomContainer from "./viewport/ScrollZoomContainer";
|
|
5
|
-
const PianoRollNotesScroller = (props) => {
|
|
6
|
-
const context = usePianoRollContext();
|
|
7
|
-
const [ref, setRef] = createSignal();
|
|
8
|
-
const clientRect = useBoundingClientRect(ref);
|
|
9
|
-
createEffect(() => {
|
|
10
|
-
context.onNotesScrollerClientRectChange(clientRect());
|
|
11
|
-
});
|
|
12
|
-
return (<ScrollZoomContainer ref={setRef} verticalDimensionName={context.mode === "keys" ? "vertical" : "verticalTracks"}>
|
|
13
|
-
{props.children}
|
|
14
|
-
</ScrollZoomContainer>);
|
|
15
|
-
};
|
|
16
|
-
export default PianoRollNotesScroller;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { createMemo, Index, Show } from "solid-js";
|
|
2
|
-
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
3
|
-
import styles from "./PianoRollScale.module.css";
|
|
4
|
-
import usePianoRollGrid from "./usePianoRollGrid";
|
|
5
|
-
const PianoRollScale = () => {
|
|
6
|
-
const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
|
|
7
|
-
const grid = usePianoRollGrid();
|
|
8
|
-
return (<div class={styles.PianoRollScale}>
|
|
9
|
-
<Index each={grid()}>
|
|
10
|
-
{(entry) => {
|
|
11
|
-
return (<Show when={horizontalViewPort().isVisible(entry().virtualDimensions)}>
|
|
12
|
-
<div classList={{
|
|
13
|
-
[styles["PianoRollScale-Time"]]: true,
|
|
14
|
-
[styles["Highlighted"]]: entry().isHighlighted,
|
|
15
|
-
[styles["HighlightedBorder"]]: entry().hasHighlightedBorder,
|
|
16
|
-
}} style={{
|
|
17
|
-
left: `${entry().virtualDimensions.offset}px`,
|
|
18
|
-
width: `${entry().virtualDimensions.size}px`,
|
|
19
|
-
}}>
|
|
20
|
-
<Show when={entry().showLabel}>
|
|
21
|
-
<div class={styles["PianoRollScale-Label"]}>{entry().label}</div>
|
|
22
|
-
</Show>
|
|
23
|
-
</div>
|
|
24
|
-
</Show>);
|
|
25
|
-
}}
|
|
26
|
-
</Index>
|
|
27
|
-
</div>);
|
|
28
|
-
};
|
|
29
|
-
export default PianoRollScale;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
2
|
-
import { ScrollZoomViewPort } from "./viewport/ScrollZoomViewPort";
|
|
3
|
-
const PianoRollScrollZoomViewPort = (props) => {
|
|
4
|
-
const zoomFactor = 500;
|
|
5
|
-
const context = usePianoRollContext();
|
|
6
|
-
return (<ScrollZoomViewPort dimensions={{
|
|
7
|
-
horizontal: () => ({
|
|
8
|
-
pixelOffset: context.notesScrollerClientRect.left,
|
|
9
|
-
pixelSize: context.notesScrollerClientRect.width,
|
|
10
|
-
position: context.position,
|
|
11
|
-
range: context.duration,
|
|
12
|
-
zoom: context.zoom * (zoomFactor / context.notesScrollerClientRect.width),
|
|
13
|
-
onPositionChange: context.onPositionChange,
|
|
14
|
-
onZoomChange: (zoom) => context.onZoomChange?.(zoom / (zoomFactor / context.notesScrollerClientRect.width)),
|
|
15
|
-
minZoom: 1,
|
|
16
|
-
maxZoom: 500,
|
|
17
|
-
}),
|
|
18
|
-
vertical: () => ({
|
|
19
|
-
pixelOffset: context.notesScrollerClientRect.top,
|
|
20
|
-
pixelSize: context.notesScrollerClientRect.height,
|
|
21
|
-
position: context.verticalPosition,
|
|
22
|
-
range: 128,
|
|
23
|
-
zoom: context.verticalZoom * (zoomFactor / context.notesScrollerClientRect.height),
|
|
24
|
-
onPositionChange: context.onVerticalPositionChange,
|
|
25
|
-
onZoomChange: (verticalZoom) => context.onVerticalZoomChange?.(verticalZoom / (zoomFactor / context.notesScrollerClientRect.height)),
|
|
26
|
-
minZoom: 1,
|
|
27
|
-
maxZoom: 10,
|
|
28
|
-
}),
|
|
29
|
-
horizontalTracks: () => ({
|
|
30
|
-
pixelOffset: 0,
|
|
31
|
-
pixelSize: context.tracksScrollerClientRect.width,
|
|
32
|
-
}),
|
|
33
|
-
verticalTracks: () => ({
|
|
34
|
-
pixelOffset: context.tracksScrollerClientRect.top,
|
|
35
|
-
pixelSize: context.tracksScrollerClientRect.height,
|
|
36
|
-
position: context.verticalTrackPosition,
|
|
37
|
-
range: context.tracks.length,
|
|
38
|
-
zoom: context.verticalTrackZoom * (zoomFactor / context.tracksScrollerClientRect.height),
|
|
39
|
-
onPositionChange: context.onVerticalTrackPositionChange,
|
|
40
|
-
onZoomChange: (verticalTrackZoom) => context.onVerticalTrackZoomChange?.(verticalTrackZoom / (zoomFactor / context.tracksScrollerClientRect.height)),
|
|
41
|
-
minZoom: 0.8,
|
|
42
|
-
maxZoom: 3,
|
|
43
|
-
}),
|
|
44
|
-
horizontalKeys: () => ({ pixelSize: 60 }),
|
|
45
|
-
}}>
|
|
46
|
-
{props.children}
|
|
47
|
-
</ScrollZoomViewPort>);
|
|
48
|
-
};
|
|
49
|
-
export default PianoRollScrollZoomViewPort;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import styles from "./PianoRollTrackList.module.scss";
|
|
2
|
-
import { createMemo, Index, Show } from "solid-js";
|
|
3
|
-
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
4
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
5
|
-
const PianoRollTrackList = () => {
|
|
6
|
-
const viewPort = createMemo(() => useViewPortDimension("verticalTracks"));
|
|
7
|
-
const context = usePianoRollContext();
|
|
8
|
-
return (<div class={styles.PianoRollTrackList}>
|
|
9
|
-
<Index each={context.tracks}>
|
|
10
|
-
{(track, index) => {
|
|
11
|
-
const virtualDimensions = createMemo(() => viewPort().calculatePixelDimensions(index, 1));
|
|
12
|
-
return (<Show when={virtualDimensions().size > 0}>
|
|
13
|
-
<div classList={{
|
|
14
|
-
[styles["Track"]]: true,
|
|
15
|
-
[styles["selected"]]: index === context.selectedTrackIndex,
|
|
16
|
-
}} title={track().name} style={{
|
|
17
|
-
top: `${virtualDimensions().offset}px`,
|
|
18
|
-
height: `${virtualDimensions().size}px`,
|
|
19
|
-
}} onClick={() => context.onSelectedTrackIndexChange?.(index)}>
|
|
20
|
-
{index} {track().name || "[unnamed track]"}
|
|
21
|
-
</div>
|
|
22
|
-
</Show>);
|
|
23
|
-
}}
|
|
24
|
-
</Index>
|
|
25
|
-
</div>);
|
|
26
|
-
};
|
|
27
|
-
export default PianoRollTrackList;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createEffect, createSignal } from "solid-js";
|
|
2
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
3
|
-
import useBoundingClientRect from "./useBoundingClientRect";
|
|
4
|
-
import ScrollZoomContainer from "./viewport/ScrollZoomContainer";
|
|
5
|
-
const PianoRollTrackListScroller = (props) => {
|
|
6
|
-
const context = usePianoRollContext();
|
|
7
|
-
const [ref, setRef] = createSignal();
|
|
8
|
-
const clientRect = useBoundingClientRect(ref);
|
|
9
|
-
createEffect(() => {
|
|
10
|
-
context.onTracksScrollerClientRectChange(clientRect());
|
|
11
|
-
});
|
|
12
|
-
return (<ScrollZoomContainer ref={setRef} horizontalDimensionName="horizontalTracks" verticalDimensionName="verticalTracks" showScrollbar={context.mode === "keys"}>
|
|
13
|
-
{props.children}
|
|
14
|
-
</ScrollZoomContainer>);
|
|
15
|
-
};
|
|
16
|
-
export default PianoRollTrackListScroller;
|
package/dist/source/index.jsx
DELETED
package/dist/source/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { createEffect, createSignal } from "solid-js";
|
|
2
|
-
const defaultRect = {
|
|
3
|
-
left: 0,
|
|
4
|
-
width: 0,
|
|
5
|
-
top: 0,
|
|
6
|
-
height: 0,
|
|
7
|
-
bottom: 0,
|
|
8
|
-
right: 0,
|
|
9
|
-
x: 0,
|
|
10
|
-
y: 0,
|
|
11
|
-
};
|
|
12
|
-
export default function useBoundingClientRect(containerRef) {
|
|
13
|
-
const [boundingClientRect, setBoundingClientRect] = createSignal(defaultRect);
|
|
14
|
-
createEffect(() => {
|
|
15
|
-
const container = containerRef();
|
|
16
|
-
if (!container)
|
|
17
|
-
return;
|
|
18
|
-
const updateBoundingClientRect = () => {
|
|
19
|
-
setBoundingClientRect(container.getBoundingClientRect() ?? defaultRect);
|
|
20
|
-
};
|
|
21
|
-
const resizeObserver = new ResizeObserver(updateBoundingClientRect);
|
|
22
|
-
resizeObserver.observe(container);
|
|
23
|
-
updateBoundingClientRect();
|
|
24
|
-
return () => {
|
|
25
|
-
resizeObserver.disconnect();
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
return boundingClientRect;
|
|
29
|
-
}
|
package/dist/source/useNotes.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { createSignal } from "solid-js";
|
|
2
|
-
const useNotes = () => {
|
|
3
|
-
const [notes, onNotesChange] = createSignal([]);
|
|
4
|
-
const onNoteChange = (index, note) => {
|
|
5
|
-
onNotesChange([...notes().slice(0, index), note, ...notes().splice(index + 1)]);
|
|
6
|
-
};
|
|
7
|
-
const onInsertNote = (note) => {
|
|
8
|
-
const index = Math.max(notes().findIndex(({ ticks }) => ticks > note.ticks), 0);
|
|
9
|
-
onNotesChange([...notes().slice(0, index), note, ...notes().splice(index)]);
|
|
10
|
-
return index;
|
|
11
|
-
};
|
|
12
|
-
const onRemoveNote = (index) => {
|
|
13
|
-
onNotesChange([...notes().slice(0, index), ...notes().splice(index + 1)]);
|
|
14
|
-
};
|
|
15
|
-
return {
|
|
16
|
-
notes,
|
|
17
|
-
onNotesChange,
|
|
18
|
-
onNoteChange,
|
|
19
|
-
onInsertNote,
|
|
20
|
-
onRemoveNote,
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
export default useNotes;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { createMemo } from "solid-js";
|
|
2
|
-
import { usePianoRollContext } from "./PianoRollContext";
|
|
3
|
-
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
4
|
-
const usePianoRollGrid = () => {
|
|
5
|
-
const context = usePianoRollContext();
|
|
6
|
-
const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
|
|
7
|
-
const measureTicks = createMemo(() => context.ppq * 4);
|
|
8
|
-
const selectedGridDivisorTicks = createMemo(() => measureTicks() / context.gridDivision);
|
|
9
|
-
function calculateVisibleGridDivisorTicks(value) {
|
|
10
|
-
const visibleRange = horizontalViewPort().calculateVisibleRange();
|
|
11
|
-
if (visibleRange <= 0)
|
|
12
|
-
return 0;
|
|
13
|
-
if (visibleRange / value > 100) {
|
|
14
|
-
return calculateVisibleGridDivisorTicks(value * 2);
|
|
15
|
-
}
|
|
16
|
-
if (visibleRange / value < 30) {
|
|
17
|
-
return calculateVisibleGridDivisorTicks(value / 2);
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
const gridDivisorTicks = createMemo(() => calculateVisibleGridDivisorTicks(selectedGridDivisorTicks()));
|
|
22
|
-
const gridArray = createMemo(() => {
|
|
23
|
-
const numberOfLines = Math.ceil(horizontalViewPort().calculateVisibleRange() / gridDivisorTicks() + 1);
|
|
24
|
-
const startIndex = Math.floor(context.position / gridDivisorTicks());
|
|
25
|
-
return Array.from({ length: numberOfLines }).map((_, i) => {
|
|
26
|
-
const index = i + startIndex;
|
|
27
|
-
const ticks = index * gridDivisorTicks();
|
|
28
|
-
const measurePosition = ticks / context.ppq / 4 + 1;
|
|
29
|
-
const bars = Math.floor(measurePosition);
|
|
30
|
-
const beats = Math.floor((measurePosition - bars) * 4);
|
|
31
|
-
const label = `${bars}${beats ? `.${beats}` : ""}`;
|
|
32
|
-
return {
|
|
33
|
-
index,
|
|
34
|
-
ticks,
|
|
35
|
-
isHighlighted: Math.ceil(((index + 1) * selectedGridDivisorTicks()) / measureTicks()) % 2 === 0,
|
|
36
|
-
hasHighlightedBorder: (index * gridDivisorTicks()) % selectedGridDivisorTicks() === 0,
|
|
37
|
-
showLabel: ((index * selectedGridDivisorTicks()) / measureTicks()) % 2 === 0,
|
|
38
|
-
virtualDimensions: horizontalViewPort().calculatePixelDimensions(ticks, gridDivisorTicks()),
|
|
39
|
-
label,
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
return gridArray;
|
|
44
|
-
};
|
|
45
|
-
export default usePianoRollGrid;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { mergeProps } from "solid-js";
|
|
2
|
-
import { createStore } from "solid-js/store";
|
|
3
|
-
const defaultState = {
|
|
4
|
-
ppq: 0,
|
|
5
|
-
mode: "keys",
|
|
6
|
-
position: 0,
|
|
7
|
-
zoom: 10,
|
|
8
|
-
verticalZoom: 5,
|
|
9
|
-
verticalPosition: 44,
|
|
10
|
-
verticalTrackZoom: 0.5,
|
|
11
|
-
verticalTrackPosition: 0,
|
|
12
|
-
gridDivision: 4,
|
|
13
|
-
snapToGrid: true,
|
|
14
|
-
duration: 0,
|
|
15
|
-
tracks: [],
|
|
16
|
-
selectedTrackIndex: 0,
|
|
17
|
-
pressedKeys: {},
|
|
18
|
-
notesScrollerClientRect: { left: 0, width: 0, top: 0, height: 0 },
|
|
19
|
-
tracksScrollerClientRect: { left: 0, width: 0, top: 0, height: 0 },
|
|
20
|
-
};
|
|
21
|
-
const propNameToHandlerName = (name) => `on${name[0]?.toUpperCase()}${name.slice(1)}Change`;
|
|
22
|
-
export const pianoRollStatePropNames = [
|
|
23
|
-
...Object.keys(defaultState),
|
|
24
|
-
...Object.keys(defaultState).map(propNameToHandlerName),
|
|
25
|
-
"onNoteChange",
|
|
26
|
-
"onInsertNote",
|
|
27
|
-
"onRemoveNote",
|
|
28
|
-
"onNoteDown",
|
|
29
|
-
"onNoteUp",
|
|
30
|
-
"isKeyDown",
|
|
31
|
-
];
|
|
32
|
-
const createPianoRollstate = (initialState) => {
|
|
33
|
-
const [state, setState] = createStore({
|
|
34
|
-
...defaultState,
|
|
35
|
-
...initialState,
|
|
36
|
-
});
|
|
37
|
-
const handlers = Object.fromEntries(Object.entries(state).map((entry) => {
|
|
38
|
-
return [
|
|
39
|
-
propNameToHandlerName(entry[0]),
|
|
40
|
-
(value) => setState(entry[0], value),
|
|
41
|
-
];
|
|
42
|
-
}));
|
|
43
|
-
const updateNotes = async (trackIndex, getNotes) => {
|
|
44
|
-
const track = state.tracks[trackIndex];
|
|
45
|
-
if (!track)
|
|
46
|
-
return;
|
|
47
|
-
const notes = track.notes;
|
|
48
|
-
handlers.onTracksChange([
|
|
49
|
-
...state.tracks.slice(0, trackIndex),
|
|
50
|
-
{ ...track, notes: getNotes(notes) },
|
|
51
|
-
...state.tracks.slice(trackIndex + 1),
|
|
52
|
-
]);
|
|
53
|
-
};
|
|
54
|
-
const onNoteChange = (trackIndex, noteIndex, note) => {
|
|
55
|
-
updateNotes(trackIndex, (notes) => [
|
|
56
|
-
...notes.slice(0, noteIndex),
|
|
57
|
-
note,
|
|
58
|
-
...notes.slice(noteIndex + 1),
|
|
59
|
-
]);
|
|
60
|
-
};
|
|
61
|
-
const onInsertNote = (trackIndex, note) => {
|
|
62
|
-
const track = state.tracks[trackIndex];
|
|
63
|
-
if (!track)
|
|
64
|
-
return -1;
|
|
65
|
-
const notes = track.notes;
|
|
66
|
-
const newNoteIndex = Math.max(notes.findIndex(({ ticks }) => ticks > note.ticks), 0);
|
|
67
|
-
updateNotes(trackIndex, (notes) => [
|
|
68
|
-
...notes.slice(0, newNoteIndex),
|
|
69
|
-
note,
|
|
70
|
-
...notes.slice(newNoteIndex),
|
|
71
|
-
]);
|
|
72
|
-
return newNoteIndex;
|
|
73
|
-
};
|
|
74
|
-
const onRemoveNote = (trackIndex, noteIndex) => {
|
|
75
|
-
updateNotes(trackIndex, (notes) => [
|
|
76
|
-
...notes.slice(0, noteIndex),
|
|
77
|
-
...notes.slice(noteIndex + 1),
|
|
78
|
-
]);
|
|
79
|
-
};
|
|
80
|
-
const updateKeyPressedState = (trackIndex, keyNumber, value) => {
|
|
81
|
-
handlers.onPressedKeysChange?.({
|
|
82
|
-
...state.pressedKeys,
|
|
83
|
-
[trackIndex]: {
|
|
84
|
-
...state.pressedKeys[trackIndex],
|
|
85
|
-
[keyNumber]: value,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
const onNoteDown = (trackIndex, keyNumber) => {
|
|
90
|
-
updateKeyPressedState(trackIndex, keyNumber, true);
|
|
91
|
-
};
|
|
92
|
-
const onNoteUp = (trackIndex, keyNumber) => {
|
|
93
|
-
updateKeyPressedState(trackIndex, keyNumber, false);
|
|
94
|
-
};
|
|
95
|
-
const isKeyDown = (trackIndex, keyNumber) => !!state.pressedKeys[trackIndex]?.[keyNumber];
|
|
96
|
-
return mergeProps(state, {
|
|
97
|
-
...handlers,
|
|
98
|
-
onNoteChange,
|
|
99
|
-
onInsertNote,
|
|
100
|
-
onRemoveNote,
|
|
101
|
-
onNoteDown,
|
|
102
|
-
onNoteUp,
|
|
103
|
-
isKeyDown,
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
export default createPianoRollstate;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { createEffect, createMemo, splitProps } from "solid-js";
|
|
2
|
-
import { clamp } from "./createViewPortDimension";
|
|
3
|
-
import { useViewPortDimension } from "./ScrollZoomViewPort";
|
|
4
|
-
const PlayHead = (allProps) => {
|
|
5
|
-
const [props, divProps] = splitProps(allProps, [
|
|
6
|
-
"position",
|
|
7
|
-
"sync",
|
|
8
|
-
"onPositionChange",
|
|
9
|
-
"dimensionName",
|
|
10
|
-
]);
|
|
11
|
-
const viewPort = useViewPortDimension(props.dimensionName ?? "horizontal");
|
|
12
|
-
createEffect(() => {
|
|
13
|
-
if (!props.sync)
|
|
14
|
-
return;
|
|
15
|
-
const maxPosition = viewPort.range;
|
|
16
|
-
const newPosition = clamp(props.position - viewPort.range / viewPort.zoom / 2, 0, maxPosition);
|
|
17
|
-
viewPort.onPositionChange?.(newPosition);
|
|
18
|
-
});
|
|
19
|
-
const leftPosition = createMemo(() => viewPort.calculatePixelOffset(props.position));
|
|
20
|
-
return (<div class="PlayHead" {...divProps} style={{
|
|
21
|
-
position: "absolute",
|
|
22
|
-
height: "100%",
|
|
23
|
-
width: "2px",
|
|
24
|
-
left: `${leftPosition()}px`,
|
|
25
|
-
"background-color": "green",
|
|
26
|
-
cursor: "pointer",
|
|
27
|
-
...(typeof divProps.style === "object" && divProps.style),
|
|
28
|
-
}} onMouseDown={(event) => {
|
|
29
|
-
event.preventDefault();
|
|
30
|
-
event.stopPropagation();
|
|
31
|
-
const handleMouseMove = ({ clientX }) => {
|
|
32
|
-
const newPosition = viewPort.calculatePosition(clientX);
|
|
33
|
-
props.onPositionChange?.(newPosition);
|
|
34
|
-
};
|
|
35
|
-
const handleMouseUp = () => {
|
|
36
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
37
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
38
|
-
};
|
|
39
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
40
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
41
|
-
}}/>);
|
|
42
|
-
};
|
|
43
|
-
export default PlayHead;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { createEffect, createMemo, mergeProps, splitProps } from "solid-js";
|
|
2
|
-
import { useViewPortDimension } from "./ScrollZoomViewPort";
|
|
3
|
-
import styles from "./ScrollZoomContainer.module.css";
|
|
4
|
-
const ScrollZoomContainer = (props) => {
|
|
5
|
-
let scrollContentRef;
|
|
6
|
-
const [ownProps, divProps] = splitProps(props, [
|
|
7
|
-
"ref",
|
|
8
|
-
"verticalDimensionName",
|
|
9
|
-
"horizontalDimensionName",
|
|
10
|
-
"showScrollbar",
|
|
11
|
-
]);
|
|
12
|
-
const propsWithDefaults = mergeProps({
|
|
13
|
-
verticalDimensionName: "vertical",
|
|
14
|
-
horizontalDimensionName: "horizontal",
|
|
15
|
-
showScrollbar: true,
|
|
16
|
-
}, ownProps);
|
|
17
|
-
const verticalViewPort = createMemo(() => useViewPortDimension(propsWithDefaults.verticalDimensionName));
|
|
18
|
-
const horizontalViewPort = createMemo(() => useViewPortDimension(propsWithDefaults.horizontalDimensionName));
|
|
19
|
-
const handleScroll = (event) => {
|
|
20
|
-
event.preventDefault();
|
|
21
|
-
if (didUpdateScroll) {
|
|
22
|
-
didUpdateScroll = false;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
|
|
26
|
-
const maxPosition = horizontalViewPort().calculateMaxPosition();
|
|
27
|
-
const height = verticalViewPort().pixelSize;
|
|
28
|
-
const width = horizontalViewPort().pixelSize;
|
|
29
|
-
const { scrollTop, scrollLeft, scrollWidth, scrollHeight } = event.currentTarget;
|
|
30
|
-
const scrollTopAmount = scrollTop / (scrollHeight - height);
|
|
31
|
-
const scrollLeftAmount = scrollLeft / (scrollWidth - width);
|
|
32
|
-
verticalViewPort().onPositionChange?.(maxVerticalPosition * scrollTopAmount);
|
|
33
|
-
horizontalViewPort().onPositionChange?.(maxPosition * scrollLeftAmount);
|
|
34
|
-
};
|
|
35
|
-
const handleWheel = (event) => {
|
|
36
|
-
if (event.altKey) {
|
|
37
|
-
event.preventDefault();
|
|
38
|
-
if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
|
|
39
|
-
horizontalViewPort()?.onZoomChange?.(horizontalViewPort().zoom / (1 + event.deltaX / horizontalViewPort().pixelSize));
|
|
40
|
-
const maxPosition = horizontalViewPort().calculateMaxPosition();
|
|
41
|
-
horizontalViewPort()?.onPositionChange?.(Math.min(maxPosition, horizontalViewPort()?.position));
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
verticalViewPort()?.onZoomChange?.(verticalViewPort().zoom / (1 + event.deltaY / verticalViewPort().pixelSize));
|
|
45
|
-
const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
|
|
46
|
-
verticalViewPort()?.onPositionChange?.(Math.min(maxVerticalPosition, verticalViewPort()?.position));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else if (!props.showScrollbar) {
|
|
50
|
-
event.preventDefault();
|
|
51
|
-
event.currentTarget.scrollLeft += event.deltaX;
|
|
52
|
-
event.currentTarget.scrollTop += event.deltaY;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
let didUpdateScroll = false;
|
|
56
|
-
createEffect(() => {
|
|
57
|
-
const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
|
|
58
|
-
const maxPosition = horizontalViewPort().calculateMaxPosition();
|
|
59
|
-
const scrollTopAmount = maxVerticalPosition > 0 ? verticalViewPort().position / maxVerticalPosition : 0;
|
|
60
|
-
const scrollLeftAmount = maxPosition > 0 ? horizontalViewPort().position / maxPosition : 0;
|
|
61
|
-
const height = verticalViewPort().pixelSize;
|
|
62
|
-
const width = horizontalViewPort().pixelSize;
|
|
63
|
-
if (!scrollContentRef?.parentElement)
|
|
64
|
-
return;
|
|
65
|
-
const scrollDivHeight = verticalViewPort().zoom * verticalViewPort().pixelSize;
|
|
66
|
-
const scrollTop = scrollTopAmount * (scrollDivHeight - height);
|
|
67
|
-
const scrollDivWidth = horizontalViewPort().zoom * horizontalViewPort().pixelSize;
|
|
68
|
-
const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
|
|
69
|
-
didUpdateScroll = true;
|
|
70
|
-
scrollContentRef.style.height = `${scrollDivHeight}px`;
|
|
71
|
-
scrollContentRef.style.width = `${scrollDivWidth}px`;
|
|
72
|
-
scrollContentRef.parentElement.scrollTo({
|
|
73
|
-
left: scrollLeft,
|
|
74
|
-
top: scrollTop,
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
return (<div ref={props.ref} onScroll={handleScroll} onWheel={handleWheel} {...divProps} style={{
|
|
78
|
-
overflow: propsWithDefaults.showScrollbar ? "scroll" : "hidden",
|
|
79
|
-
...(typeof divProps.style === "object" && divProps.style),
|
|
80
|
-
}} classList={{
|
|
81
|
-
[styles.ScrollZoomContainer]: true,
|
|
82
|
-
...divProps.classList,
|
|
83
|
-
}}>
|
|
84
|
-
<div ref={scrollContentRef}>
|
|
85
|
-
<div style={{
|
|
86
|
-
top: 0,
|
|
87
|
-
left: 0,
|
|
88
|
-
height: `${verticalViewPort().pixelSize}px`,
|
|
89
|
-
width: `${horizontalViewPort().pixelSize}px`,
|
|
90
|
-
position: "sticky",
|
|
91
|
-
display: "flex",
|
|
92
|
-
}}>
|
|
93
|
-
<div style={{
|
|
94
|
-
position: "relative",
|
|
95
|
-
height: `${verticalViewPort().pixelSize}px`,
|
|
96
|
-
width: `${horizontalViewPort().pixelSize}px`,
|
|
97
|
-
}}>
|
|
98
|
-
{props.children}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</div>);
|
|
103
|
-
};
|
|
104
|
-
export default ScrollZoomContainer;
|