solid-pianoroll 0.0.14 → 0.0.16
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 +401 -418
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +404 -420
- package/dist/esm/index.js.map +1 -1
- package/dist/source/PianoRoll.jsx +18 -3
- package/dist/source/PianoRollContext.jsx +5 -2
- package/dist/source/PianoRollGrid.jsx +20 -3
- package/dist/source/PianoRollKeys.jsx +24 -5
- package/dist/source/PianoRollNotes.jsx +117 -81
- package/dist/source/PianoRollTrackList.jsx +28 -0
- package/dist/source/index.jsx +1 -2
- package/dist/source/usePianoRoll.js +4 -4
- package/dist/types/PianoRoll.d.ts +10 -6
- package/dist/types/PianoRollContext.d.ts +1 -1
- package/dist/types/PianoRollTrackList.d.ts +2 -0
- package/dist/types/index.d.ts +1 -2
- package/dist/types/usePianoRoll.d.ts +2 -2
- package/package.json +1 -1
- package/dist/source/MultiTrackPianoRoll.jsx +0 -156
- package/dist/types/MultiTrackPianoRoll.d.ts +0 -13
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
import styles from "./PianoRollKeys.module.css";
|
|
2
|
-
import { createMemo, Index, Show } from "solid-js";
|
|
2
|
+
import { createMemo, createSignal, Index, Show } from "solid-js";
|
|
3
3
|
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
4
4
|
const PianoRollKeys = () => {
|
|
5
5
|
const viewPort = createMemo(() => useViewPortDimension("vertical"));
|
|
6
6
|
return (<div class={styles.PianoRollKeys}>
|
|
7
7
|
<Index each={keys}>
|
|
8
8
|
{(key) => {
|
|
9
|
+
const [isDown, setIsDown] = createSignal(false);
|
|
9
10
|
const virtualDimensions = createMemo(() => viewPort().calculatePixelDimensions(127 - key().number, 1));
|
|
11
|
+
const previousIsBlack = blackKeys.includes((key().number - 1) % 12);
|
|
12
|
+
const nextIsBlack = blackKeys.includes((key().number + 1) % 12);
|
|
10
13
|
return (<Show when={virtualDimensions().size > 0}>
|
|
11
14
|
<div classList={{
|
|
12
15
|
[styles["Key"]]: true,
|
|
13
16
|
[styles["black"]]: key().isBlack,
|
|
14
17
|
[styles["white"]]: !key().isBlack,
|
|
15
|
-
[styles["
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
[styles["down"]]: isDown(),
|
|
19
|
+
}} onMouseDown={() => setIsDown(true)} onMouseUp={() => setIsDown(false)} title={key().name} data-index={key().number % 12} style={{
|
|
20
|
+
top: `${virtualDimensions().offset -
|
|
21
|
+
(!key().isBlack && nextIsBlack ? virtualDimensions().size / 2 : 0)}px`,
|
|
22
|
+
height: `${virtualDimensions().size +
|
|
23
|
+
(!key().isBlack && nextIsBlack ? virtualDimensions().size / 2 : 0) +
|
|
24
|
+
(!key().isBlack && previousIsBlack ? virtualDimensions().size / 2 : 0)}px`,
|
|
25
|
+
"box-shadow": [
|
|
26
|
+
`0px 0px ${Math.min(virtualDimensions().size / 20, 1)}px ${Math.min(virtualDimensions().size / 50, 1)}px rgba(0, 0, 0, 0.5) ${key().isBlack ? "" : "inset"}`,
|
|
27
|
+
isDown() &&
|
|
28
|
+
`0px 0px ${Math.min(virtualDimensions().size / 8, 2)}px ${Math.min(virtualDimensions().size / 20, 2)}px rgba(0, 0, 0, 0.5) inset`,
|
|
29
|
+
]
|
|
30
|
+
.filter((item) => !!item)
|
|
31
|
+
.join(", "),
|
|
32
|
+
}}>
|
|
33
|
+
<Show when={key().isBlack && false}>
|
|
34
|
+
<div class={styles["black-separator"]} style={{
|
|
18
35
|
top: `${virtualDimensions().offset}px`,
|
|
19
|
-
height: `${virtualDimensions().size}px`,
|
|
36
|
+
height: `${virtualDimensions().size / 2 - 0.25}px`,
|
|
20
37
|
}}></div>
|
|
38
|
+
</Show>
|
|
39
|
+
</div>
|
|
21
40
|
</Show>);
|
|
22
41
|
}}
|
|
23
42
|
</Index>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMemo, createSignal,
|
|
1
|
+
import { batch, createMemo, createSignal, For, Show } from "solid-js";
|
|
2
2
|
import { usePianoRollContext } from "./PianoRollContext";
|
|
3
3
|
import { useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
4
4
|
import styles from "./PianoRollNotes.module.scss";
|
|
@@ -12,38 +12,44 @@ const PianoRollNotes = (props) => {
|
|
|
12
12
|
: value;
|
|
13
13
|
const insertOrUpdateNote = (event) => {
|
|
14
14
|
const position = horizontalViewPort().calculatePosition(event.clientX);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const existingNote = currentNote();
|
|
16
|
+
const midi = context.mode === "keys"
|
|
17
|
+
? 127 - Math.floor(verticalViewPort().calculatePosition(event.clientY))
|
|
18
|
+
: existingNote?.midi ?? 60;
|
|
19
|
+
const targetTrackIndex = context.mode === "tracks"
|
|
20
|
+
? Math.floor(verticalViewPort().calculatePosition(event.clientY))
|
|
21
|
+
: context.selectedTrackIndex ?? 0;
|
|
18
22
|
const eventPositionTicks = Math.floor(position / gridDivisionTicks()) * gridDivisionTicks();
|
|
19
23
|
const velocity = 127;
|
|
20
|
-
const existingNote = newNote();
|
|
21
24
|
const ticks = existingNote?.ticks ?? eventPositionTicks;
|
|
22
25
|
const durationTicks = existingNote?.ticks
|
|
23
26
|
? eventPositionTicks - existingNote?.ticks
|
|
24
27
|
: gridDivisionTicks();
|
|
25
28
|
const note = { midi, ticks, durationTicks, velocity };
|
|
26
29
|
if (existingNote) {
|
|
27
|
-
context.onNoteChange?.(
|
|
28
|
-
return
|
|
30
|
+
context.onNoteChange?.(currentNoteTrackIndex(), currentNoteIndex(), note);
|
|
31
|
+
return currentNoteIndex();
|
|
29
32
|
}
|
|
30
33
|
else {
|
|
31
|
-
return context.onInsertNote?.(note) ?? -1;
|
|
34
|
+
return context.onInsertNote?.(targetTrackIndex, note) ?? -1;
|
|
32
35
|
}
|
|
33
36
|
};
|
|
34
37
|
const [isDragging, setIsDragging] = createSignal(false);
|
|
35
38
|
const [noteDragMode, setNoteDragMode] = createSignal();
|
|
36
|
-
const [
|
|
39
|
+
const [currentNoteIndex, setCurrentNoteIndex] = createSignal(-1);
|
|
40
|
+
const [currentNoteTrackIndex, setCurrentNoteTrackIndex] = createSignal(-1);
|
|
37
41
|
const [isMouseDown, setIsMouseDown] = createSignal(false);
|
|
38
|
-
const
|
|
42
|
+
const currentNote = createMemo(() => context.tracks[currentNoteTrackIndex()]?.notes[currentNoteIndex()]);
|
|
39
43
|
const getClasses = (noteDragMode) => {
|
|
40
44
|
return noteDragMode ? [styles.Note, styles[noteDragMode]] : [styles.Note];
|
|
41
45
|
};
|
|
42
46
|
return (<div class={styles.PianoRollNotes} ref={props.ref} onMouseDown={(event) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
batch(() => {
|
|
48
|
+
setIsMouseDown(true);
|
|
49
|
+
const index = insertOrUpdateNote(event);
|
|
50
|
+
setCurrentNoteTrackIndex(context.selectedTrackIndex ?? 0);
|
|
51
|
+
setCurrentNoteIndex(index);
|
|
52
|
+
});
|
|
47
53
|
}} onMouseMove={(event) => {
|
|
48
54
|
if (isDragging())
|
|
49
55
|
return;
|
|
@@ -52,7 +58,7 @@ const PianoRollNotes = (props) => {
|
|
|
52
58
|
return;
|
|
53
59
|
}
|
|
54
60
|
const index = insertOrUpdateNote(event);
|
|
55
|
-
|
|
61
|
+
setCurrentNoteIndex(index);
|
|
56
62
|
}} onMouseUp={(event) => {
|
|
57
63
|
setIsMouseDown(false);
|
|
58
64
|
if (!event.altKey)
|
|
@@ -61,82 +67,112 @@ const PianoRollNotes = (props) => {
|
|
|
61
67
|
}} onDblClick={(event) => {
|
|
62
68
|
insertOrUpdateNote(event);
|
|
63
69
|
}} onClick={(event) => {
|
|
64
|
-
if (
|
|
65
|
-
|
|
70
|
+
if (currentNote()) {
|
|
71
|
+
setCurrentNoteIndex(-1);
|
|
66
72
|
return;
|
|
67
73
|
}
|
|
68
74
|
if (!event.altKey)
|
|
69
75
|
return;
|
|
70
76
|
insertOrUpdateNote(event);
|
|
71
77
|
}}>
|
|
72
|
-
<
|
|
73
|
-
{(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
78
|
+
<For each={context.tracks}>
|
|
79
|
+
{(track, trackIndex) => {
|
|
80
|
+
return (<Show when={trackIndex() === context.selectedTrackIndex ||
|
|
81
|
+
context.showAllTracks ||
|
|
82
|
+
context.mode === "tracks"}>
|
|
83
|
+
<For each={track.notes}>
|
|
84
|
+
{(note, noteIndex) => {
|
|
85
|
+
const verticalVirtualDimensions = createMemo(() => verticalViewPort().calculatePixelDimensions(context.mode === "keys" ? 127 - note.midi : trackIndex(), 1));
|
|
86
|
+
const horizontalDimensions = createMemo(() => horizontalViewPort().calculatePixelDimensions(note.ticks, note.durationTicks));
|
|
87
|
+
return (<Show when={verticalViewPort().isVisible(verticalVirtualDimensions()) &&
|
|
88
|
+
horizontalViewPort().isVisible(horizontalDimensions())}>
|
|
89
|
+
<div class={getClasses(noteDragMode()).join(" ")} draggable={false} onMouseMove={(event) => {
|
|
90
|
+
if (isDragging())
|
|
91
|
+
return;
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
const relativeX = horizontalViewPort().calculatePixelValue(horizontalViewPort().calculatePosition(event.clientX));
|
|
94
|
+
const noteStartX = horizontalViewPort().calculatePixelValue(note.ticks);
|
|
95
|
+
const noteEndX = horizontalViewPort().calculatePixelValue(note.ticks + note.durationTicks);
|
|
96
|
+
setNoteDragMode(relativeX - noteStartX < 3
|
|
97
|
+
? "trimStart"
|
|
98
|
+
: noteEndX - relativeX < 3
|
|
99
|
+
? "trimEnd"
|
|
100
|
+
: "move");
|
|
101
|
+
}} onDblClick={(event) => {
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
context.onRemoveNote?.(trackIndex(), noteIndex());
|
|
104
|
+
}} onMouseDown={(event) => {
|
|
105
|
+
event.stopPropagation();
|
|
106
|
+
setIsDragging(true);
|
|
107
|
+
setCurrentNoteIndex(noteIndex());
|
|
108
|
+
setCurrentNoteTrackIndex(trackIndex());
|
|
109
|
+
const initialPosition = horizontalViewPort().calculatePosition(event.clientX);
|
|
110
|
+
const diffPosition = initialPosition - note.ticks;
|
|
111
|
+
const handleMouseMove = (mouseMoveEvent) => {
|
|
112
|
+
const note = currentNote();
|
|
113
|
+
if (!note)
|
|
114
|
+
return;
|
|
115
|
+
const ticks = snapValueToGridIfEnabled(Math.max(horizontalViewPort().calculatePosition(mouseMoveEvent.clientX) -
|
|
116
|
+
diffPosition, 0), mouseMoveEvent.altKey);
|
|
117
|
+
const updatedNote = {
|
|
118
|
+
...note,
|
|
119
|
+
...(noteDragMode() === "move" &&
|
|
120
|
+
context.mode === "keys" && {
|
|
121
|
+
midi: Math.round(127 -
|
|
122
|
+
verticalViewPort().calculatePosition(mouseMoveEvent.clientY)),
|
|
123
|
+
}),
|
|
124
|
+
...((noteDragMode() === "move" || noteDragMode() === "trimStart") && {
|
|
125
|
+
ticks,
|
|
126
|
+
}),
|
|
127
|
+
...(noteDragMode() === "trimStart" && {
|
|
128
|
+
durationTicks: note.durationTicks + note.ticks - ticks,
|
|
129
|
+
}),
|
|
130
|
+
...(noteDragMode() === "trimEnd" && {
|
|
131
|
+
durationTicks: snapValueToGridIfEnabled(horizontalViewPort().calculatePosition(mouseMoveEvent.clientX) -
|
|
132
|
+
note.ticks, mouseMoveEvent.altKey),
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
135
|
+
const previousTrackIndex = currentNoteTrackIndex();
|
|
136
|
+
const previousNoteIndex = currentNoteIndex();
|
|
137
|
+
const targetTrackIndex = context.mode === "tracks"
|
|
138
|
+
? Math.floor(verticalViewPort().calculatePosition(mouseMoveEvent.clientY))
|
|
139
|
+
: previousTrackIndex;
|
|
140
|
+
if (targetTrackIndex === previousTrackIndex) {
|
|
141
|
+
context.onNoteChange?.(previousTrackIndex, previousNoteIndex, updatedNote);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
batch(() => {
|
|
145
|
+
if (context.onInsertNote) {
|
|
146
|
+
context.onRemoveNote?.(previousTrackIndex, previousNoteIndex);
|
|
147
|
+
const newNoteIndex = context.onInsertNote(targetTrackIndex, updatedNote);
|
|
148
|
+
setCurrentNoteTrackIndex(targetTrackIndex);
|
|
149
|
+
setCurrentNoteIndex(newNoteIndex);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const handleMouseUp = () => {
|
|
155
|
+
setIsDragging(false);
|
|
156
|
+
setCurrentNoteIndex(-1);
|
|
157
|
+
setCurrentNoteTrackIndex(-1);
|
|
158
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
159
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
160
|
+
};
|
|
161
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
162
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
163
|
+
}} style={{
|
|
164
|
+
"background-color": `rgba(255,0,0, ${(128 + note.velocity) / 256})`,
|
|
131
165
|
top: `${verticalVirtualDimensions().offset}px`,
|
|
132
166
|
height: `${verticalVirtualDimensions().size}px`,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
left: `${horizontalDimensions().offset}px`,
|
|
168
|
+
width: `${horizontalDimensions().size}px`,
|
|
169
|
+
}}></div>
|
|
170
|
+
</Show>);
|
|
171
|
+
}}
|
|
172
|
+
</For>
|
|
137
173
|
</Show>);
|
|
138
174
|
}}
|
|
139
|
-
</
|
|
175
|
+
</For>
|
|
140
176
|
</div>);
|
|
141
177
|
};
|
|
142
178
|
export default PianoRollNotes;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import styles from "./PianoRollTrackList.module.css";
|
|
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("vertical"));
|
|
7
|
+
const context = usePianoRollContext();
|
|
8
|
+
return (<div class={styles.PianoRollTrackList}>
|
|
9
|
+
<Index each={context.tracks}>
|
|
10
|
+
{(track, index) => {
|
|
11
|
+
const virtualDimensions = createMemo(() => viewPort().calculatePixelDimensions(context.mode === "tracks" ? index : index * 8, context.mode === "tracks" ? 1 : 8));
|
|
12
|
+
return (<Show when={virtualDimensions().size > 0}>
|
|
13
|
+
<div classList={{
|
|
14
|
+
[styles["Track"]]: true,
|
|
15
|
+
}} title={track().name} style={{
|
|
16
|
+
top: `${virtualDimensions().offset}px`,
|
|
17
|
+
height: `${virtualDimensions().size}px`,
|
|
18
|
+
background: index === context.selectedTrackIndex ? "gray" : "lightgrey",
|
|
19
|
+
cursor: "pointer",
|
|
20
|
+
}} onClick={() => context.onSelectedTrackIndexChange?.(index)}>
|
|
21
|
+
{index} {track().name || "[unnamed track]"}
|
|
22
|
+
</div>
|
|
23
|
+
</Show>);
|
|
24
|
+
}}
|
|
25
|
+
</Index>
|
|
26
|
+
</div>);
|
|
27
|
+
};
|
|
28
|
+
export default PianoRollTrackList;
|
package/dist/source/index.jsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import MultiTrackPianoRoll from "./MultiTrackPianoRoll";
|
|
2
1
|
import PianoRoll from "./PianoRoll";
|
|
3
2
|
import PlayHead from "./PlayHead";
|
|
4
3
|
import useNotes from "./useNotes";
|
|
5
4
|
import usePianoRoll from "./usePianoRoll";
|
|
6
|
-
export { PianoRoll,
|
|
5
|
+
export { PianoRoll, usePianoRoll, useNotes, PlayHead };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createSignal } from "solid-js";
|
|
2
2
|
const usePianoRoll = () => {
|
|
3
3
|
const [ppq, onPpqChange] = createSignal(120);
|
|
4
|
-
const [
|
|
4
|
+
const [mode, onModeChange] = createSignal("tracks");
|
|
5
5
|
const [position, onPositionChange] = createSignal(0);
|
|
6
6
|
const [zoom, onZoomChange] = createSignal(10);
|
|
7
7
|
const [verticalZoom, onVerticalZoomChange] = createSignal(5);
|
|
8
|
-
const [verticalPosition, onVerticalPositionChange] = createSignal(
|
|
8
|
+
const [verticalPosition, onVerticalPositionChange] = createSignal(44);
|
|
9
9
|
const [gridDivision, onGridDivisionChange] = createSignal(4);
|
|
10
10
|
const [snapToGrid, onSnapToGridChange] = createSignal(true);
|
|
11
11
|
const [duration, onDurationChange] = createSignal(0);
|
|
@@ -24,8 +24,8 @@ const usePianoRoll = () => {
|
|
|
24
24
|
onGridDivisionChange,
|
|
25
25
|
snapToGrid,
|
|
26
26
|
onSnapToGridChange,
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
mode,
|
|
28
|
+
onModeChange,
|
|
29
29
|
duration,
|
|
30
30
|
onDurationChange,
|
|
31
31
|
};
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { JSX, ParentProps } from "solid-js";
|
|
2
|
-
import { Note } from "./types";
|
|
2
|
+
import { Note, Track } from "./types";
|
|
3
3
|
export type GridDivision = 1 | 2 | 4 | 8 | 16 | 32 | 64;
|
|
4
4
|
export type PianoRollProps = {
|
|
5
5
|
ppq: number;
|
|
6
|
-
|
|
6
|
+
tracks: Track[];
|
|
7
7
|
gridDivision: GridDivision;
|
|
8
8
|
snapToGrid: boolean;
|
|
9
|
-
|
|
9
|
+
mode: "keys" | "tracks";
|
|
10
|
+
showAllTracks?: boolean;
|
|
11
|
+
showTrackList?: boolean;
|
|
12
|
+
selectedTrackIndex: number;
|
|
10
13
|
verticalPosition: number;
|
|
11
14
|
verticalZoom: number;
|
|
12
15
|
position: number;
|
|
@@ -16,9 +19,10 @@ export type PianoRollProps = {
|
|
|
16
19
|
onVerticalPositionChange?: (zoom: number) => void;
|
|
17
20
|
onZoomChange?: (zoom: number) => void;
|
|
18
21
|
onPositionChange?: (zoom: number) => void;
|
|
19
|
-
onNoteChange?: (
|
|
20
|
-
onInsertNote?: (note: Note) => number;
|
|
21
|
-
onRemoveNote?: (
|
|
22
|
+
onNoteChange?: (trackIndex: number, noteIndex: number, note: Note) => void;
|
|
23
|
+
onInsertNote?: (trackIndex: number, note: Note) => number;
|
|
24
|
+
onRemoveNote?: (trackIndex: number, noteIndex: number) => void;
|
|
25
|
+
onSelectedTrackIndexChange?: (trackIndex: number) => void;
|
|
22
26
|
};
|
|
23
27
|
declare const PianoRoll: (allProps: ParentProps<PianoRollProps & JSX.IntrinsicElements["div"]>) => JSX.Element;
|
|
24
28
|
export default PianoRoll;
|
|
@@ -2,4 +2,4 @@ import { JSX } from "solid-js";
|
|
|
2
2
|
import { PianoRollProps } from "./PianoRoll";
|
|
3
3
|
export declare const PianoRollContextProvider: import("solid-js/types/reactive/signal").ContextProviderComponent<PianoRollProps>;
|
|
4
4
|
export declare const usePianoRollContext: () => PianoRollProps;
|
|
5
|
-
export declare const splitContextProps: (allProps: PianoRollProps & JSX.IntrinsicElements["div"]) => [Pick<PianoRollProps & JSX.HTMLAttributes<HTMLDivElement>, "position" | "
|
|
5
|
+
export declare const splitContextProps: (allProps: PianoRollProps & JSX.IntrinsicElements["div"]) => [Pick<PianoRollProps & JSX.HTMLAttributes<HTMLDivElement>, "position" | "zoom" | "mode" | "duration" | "gridDivision" | "tracks" | "ppq" | "snapToGrid" | "verticalPosition" | "verticalZoom" | "onInsertNote" | "onNoteChange" | "onPositionChange" | "onRemoveNote" | "onVerticalPositionChange" | "onVerticalZoomChange" | "onZoomChange" | "showAllTracks" | "selectedTrackIndex" | "onSelectedTrackIndexChange">, Omit<PianoRollProps & JSX.HTMLAttributes<HTMLDivElement>, "position" | "zoom" | "mode" | "duration" | "gridDivision" | "tracks" | "ppq" | "snapToGrid" | "verticalPosition" | "verticalZoom" | "onInsertNote" | "onNoteChange" | "onPositionChange" | "onRemoveNote" | "onVerticalPositionChange" | "onVerticalZoomChange" | "onZoomChange" | "showAllTracks" | "selectedTrackIndex" | "onSelectedTrackIndexChange">];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import MultiTrackPianoRoll from "./MultiTrackPianoRoll";
|
|
2
1
|
import PianoRoll from "./PianoRoll";
|
|
3
2
|
import PlayHead from "./PlayHead";
|
|
4
3
|
import useNotes from "./useNotes";
|
|
5
4
|
import usePianoRoll from "./usePianoRoll";
|
|
6
|
-
export { PianoRoll,
|
|
5
|
+
export { PianoRoll, usePianoRoll, useNotes, PlayHead };
|
|
@@ -14,8 +14,8 @@ declare const usePianoRoll: () => {
|
|
|
14
14
|
onGridDivisionChange: import("solid-js").Setter<GridDivision>;
|
|
15
15
|
snapToGrid: import("solid-js").Accessor<boolean>;
|
|
16
16
|
onSnapToGridChange: import("solid-js").Setter<boolean>;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
mode: import("solid-js").Accessor<"keys" | "tracks">;
|
|
18
|
+
onModeChange: import("solid-js").Setter<"keys" | "tracks">;
|
|
19
19
|
duration: import("solid-js").Accessor<number>;
|
|
20
20
|
onDurationChange: import("solid-js").Setter<number>;
|
|
21
21
|
};
|
package/package.json
CHANGED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { createSignal, mergeProps, createMemo, Index, createEffect, splitProps, Show, } from "solid-js";
|
|
2
|
-
import styles from "./PianoRoll.module.css";
|
|
3
|
-
import HorizontalZoomControl from "./viewport/HorizontalZoomControl";
|
|
4
|
-
import { PianoRollContextProvider, splitContextProps } from "./PianoRollContext";
|
|
5
|
-
import PianoRollNotes from "./PianoRollNotes";
|
|
6
|
-
import ScrollContainer from "./viewport/ScrollContainer";
|
|
7
|
-
import PianoRollGrid from "./PianoRollGrid";
|
|
8
|
-
import useNotes from "./useNotes";
|
|
9
|
-
import useBoundingClientRect from "./useBoundingClientRect";
|
|
10
|
-
import { ScrollZoomViewPort, useViewPortDimension } from "./viewport/ScrollZoomViewPort";
|
|
11
|
-
import { clamp } from "./viewport/createViewPortDimension";
|
|
12
|
-
import { createStore } from "solid-js/store";
|
|
13
|
-
import PianoRollKeys from "./PianoRollKeys";
|
|
14
|
-
import VerticalZoomControl from "./viewport/VerticalZoomControl";
|
|
15
|
-
const MultiTrackPianoRoll = (allProps) => {
|
|
16
|
-
const selectedTrack = createMemo(() => allProps.tracks[allProps.selectedTrackIndex]);
|
|
17
|
-
const condensed = createMemo(() => !selectedTrack());
|
|
18
|
-
const [ownProps, restProps] = splitProps(allProps, ["tracks", "selectedTrackIndex"]);
|
|
19
|
-
const [contextProps, divProps] = splitContextProps(mergeProps(restProps, {
|
|
20
|
-
notes: [],
|
|
21
|
-
condensed: false,
|
|
22
|
-
onNoteChange: () => undefined,
|
|
23
|
-
onInsertNote: () => -1,
|
|
24
|
-
onRemoveNote: () => undefined,
|
|
25
|
-
}));
|
|
26
|
-
const [scrollerRef, setScrollerRef] = createSignal();
|
|
27
|
-
const clientRect = useBoundingClientRect(scrollerRef);
|
|
28
|
-
const zoomFactor = 500;
|
|
29
|
-
const minZoom = createMemo(() => 1 / (zoomFactor / clientRect().width));
|
|
30
|
-
const maxZoom = createMemo(() => 500 * (zoomFactor / clientRect().width));
|
|
31
|
-
const minVerticalZoom = createMemo(() => 1 / (zoomFactor / clientRect().height));
|
|
32
|
-
const maxVerticalZoom = createMemo(() => 10 * (zoomFactor / clientRect().height));
|
|
33
|
-
return (<div {...divProps} style={{
|
|
34
|
-
display: "flex",
|
|
35
|
-
flex: 1,
|
|
36
|
-
height: "100%",
|
|
37
|
-
"flex-direction": "row",
|
|
38
|
-
overflow: "hidden",
|
|
39
|
-
...(typeof divProps.style === "object" && divProps.style),
|
|
40
|
-
}}>
|
|
41
|
-
<PianoRollContextProvider value={{ ...contextProps }}>
|
|
42
|
-
<ScrollZoomViewPort dimensions={{
|
|
43
|
-
horizontal: () => ({
|
|
44
|
-
pixelOffset: clientRect().left,
|
|
45
|
-
pixelSize: clientRect().width,
|
|
46
|
-
position: contextProps.position,
|
|
47
|
-
range: contextProps.duration,
|
|
48
|
-
zoom: contextProps.zoom * (zoomFactor / clientRect().width),
|
|
49
|
-
onPositionChange: contextProps.onPositionChange,
|
|
50
|
-
onZoomChange: (zoom) => contextProps.onZoomChange?.(clamp(zoom / (zoomFactor / clientRect().width), minZoom(), maxZoom())),
|
|
51
|
-
}),
|
|
52
|
-
vertical: () => ({
|
|
53
|
-
pixelOffset: clientRect().top,
|
|
54
|
-
pixelSize: clientRect().height,
|
|
55
|
-
position: contextProps.verticalPosition,
|
|
56
|
-
range: 128,
|
|
57
|
-
zoom: contextProps.verticalZoom * (zoomFactor / clientRect().height),
|
|
58
|
-
onPositionChange: contextProps.onVerticalPositionChange,
|
|
59
|
-
onZoomChange: (verticalZoom) => contextProps.onVerticalZoomChange?.(clamp(verticalZoom / (zoomFactor / clientRect().height), minVerticalZoom(), maxVerticalZoom())),
|
|
60
|
-
}),
|
|
61
|
-
}}>
|
|
62
|
-
<div style={{
|
|
63
|
-
width: "250px",
|
|
64
|
-
display: "flex",
|
|
65
|
-
"flex-direction": "row",
|
|
66
|
-
}}>
|
|
67
|
-
<ul style={{
|
|
68
|
-
"margin-block-start": 0,
|
|
69
|
-
"padding-inline-start": 0,
|
|
70
|
-
flex: 1,
|
|
71
|
-
position: "relative",
|
|
72
|
-
}}>
|
|
73
|
-
<Index each={allProps.tracks}>
|
|
74
|
-
{(track, noteIndex) => (<li style={{
|
|
75
|
-
width: "100%",
|
|
76
|
-
height: "30px",
|
|
77
|
-
"list-style": "none",
|
|
78
|
-
display: "flex",
|
|
79
|
-
"align-items": "center",
|
|
80
|
-
"border-top": "1px black solid",
|
|
81
|
-
background: noteIndex === allProps.selectedTrackIndex ? "lightgray" : "none",
|
|
82
|
-
cursor: "pointer",
|
|
83
|
-
}} onClick={() => {
|
|
84
|
-
if (noteIndex === allProps.selectedTrackIndex) {
|
|
85
|
-
allProps.onSelectedTrackIndexChange(-1);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
allProps.onSelectedTrackIndexChange(noteIndex);
|
|
89
|
-
}
|
|
90
|
-
}}>
|
|
91
|
-
{track().name}
|
|
92
|
-
</li>)}
|
|
93
|
-
</Index>
|
|
94
|
-
</ul>
|
|
95
|
-
<Show when={!condensed()}>
|
|
96
|
-
<PianoRollKeys />
|
|
97
|
-
</Show>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<div class={styles.PianoRoll} style={{ flex: 1 }}>
|
|
101
|
-
<div class={styles.PianoRollContainer}>
|
|
102
|
-
<ScrollContainer ref={setScrollerRef}>
|
|
103
|
-
{allProps.children}
|
|
104
|
-
<ul style={{
|
|
105
|
-
margin: 0,
|
|
106
|
-
"margin-block-start": 0,
|
|
107
|
-
"padding-inline-start": 0,
|
|
108
|
-
}}>
|
|
109
|
-
<Index each={allProps.tracks}>
|
|
110
|
-
{(track, trackIndex) => {
|
|
111
|
-
const notes = useNotes();
|
|
112
|
-
createEffect(() => {
|
|
113
|
-
notes.onNotesChange(track().notes);
|
|
114
|
-
});
|
|
115
|
-
const [context, setContext] = createStore({ ...contextProps });
|
|
116
|
-
createEffect(() => {
|
|
117
|
-
setContext({
|
|
118
|
-
...contextProps,
|
|
119
|
-
condensed: condensed(),
|
|
120
|
-
notes: track()?.notes,
|
|
121
|
-
onNoteChange: (noteIndex, note) => allProps?.onNoteChange?.(trackIndex, noteIndex, note),
|
|
122
|
-
onInsertNote: (note) => allProps.onInsertNote?.(trackIndex, note) ?? -1,
|
|
123
|
-
onRemoveNote: (noteIndex) => allProps.onRemoveNote?.(trackIndex, noteIndex),
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
const verticalViewPort = useViewPortDimension("vertical");
|
|
127
|
-
return (<Show when={condensed() || allProps.selectedTrackIndex === trackIndex}>
|
|
128
|
-
<PianoRollContextProvider value={context}>
|
|
129
|
-
<li style={{
|
|
130
|
-
display: "flex",
|
|
131
|
-
position: "relative",
|
|
132
|
-
"list-style": "none",
|
|
133
|
-
"flex-direction": "row",
|
|
134
|
-
height: condensed() ? "30px" : `${verticalViewPort.pixelSize}px`,
|
|
135
|
-
"border-top": "1px black solid",
|
|
136
|
-
}}>
|
|
137
|
-
<PianoRollNotes />
|
|
138
|
-
<PianoRollGrid />
|
|
139
|
-
</li>
|
|
140
|
-
</PianoRollContextProvider>
|
|
141
|
-
</Show>);
|
|
142
|
-
}}
|
|
143
|
-
</Index>
|
|
144
|
-
</ul>
|
|
145
|
-
</ScrollContainer>
|
|
146
|
-
<Show when={!condensed()}>
|
|
147
|
-
<VerticalZoomControl value={contextProps.verticalZoom} onInput={(event) => contextProps.onVerticalZoomChange?.(event.currentTarget.valueAsNumber)}/>
|
|
148
|
-
</Show>
|
|
149
|
-
</div>
|
|
150
|
-
<HorizontalZoomControl style={{ width: "100%", "margin-left": 0 }} value={contextProps.zoom} onInput={(event) => contextProps.onZoomChange?.(event.currentTarget.valueAsNumber)}/>
|
|
151
|
-
</div>
|
|
152
|
-
</ScrollZoomViewPort>
|
|
153
|
-
</PianoRollContextProvider>
|
|
154
|
-
</div>);
|
|
155
|
-
};
|
|
156
|
-
export default MultiTrackPianoRoll;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { JSX, ParentProps } from "solid-js";
|
|
2
|
-
import { PianoRollProps } from "./PianoRoll";
|
|
3
|
-
import { Note, Track } from "./types";
|
|
4
|
-
export type MultiTrackPianoRollProps = Omit<PianoRollProps, "condensed" | "notes" | "onNoteChange" | "onInsertNote" | "onRemoveNote"> & {
|
|
5
|
-
tracks: Track[];
|
|
6
|
-
selectedTrackIndex: number;
|
|
7
|
-
onSelectedTrackIndexChange: (trackIndex: number) => void;
|
|
8
|
-
onNoteChange?: (trackIndex: number, index: number, note: Note) => void;
|
|
9
|
-
onInsertNote?: (trackIndex: number, note: Note) => number;
|
|
10
|
-
onRemoveNote?: (trackIndex: number, index: number) => void;
|
|
11
|
-
} & JSX.IntrinsicElements["div"];
|
|
12
|
-
declare const MultiTrackPianoRoll: (allProps: ParentProps<MultiTrackPianoRollProps>) => JSX.Element;
|
|
13
|
-
export default MultiTrackPianoRoll;
|