solid-pianoroll 0.0.24 → 0.0.26

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.
Files changed (53) hide show
  1. package/dist/{cjs/index.js → dev.cjs} +408 -430
  2. package/dist/dev.css +212 -0
  3. package/dist/dev.js +1330 -0
  4. package/dist/dev.jsx +1130 -0
  5. package/dist/index.cjs +1330 -0
  6. package/dist/index.css +212 -0
  7. package/dist/{types/usePianoRollState.d.ts → index.d.ts} +38 -6
  8. package/dist/{esm/index.js → index.js} +412 -429
  9. package/dist/index.jsx +1130 -0
  10. package/package.json +46 -34
  11. package/dist/cjs/index.js.map +0 -1
  12. package/dist/esm/index.js.map +0 -1
  13. package/dist/source/PianoRoll.jsx +0 -82
  14. package/dist/source/PianoRollContext.jsx +0 -11
  15. package/dist/source/PianoRollGrid.jsx +0 -64
  16. package/dist/source/PianoRollKeys.jsx +0 -72
  17. package/dist/source/PianoRollNotes.jsx +0 -160
  18. package/dist/source/PianoRollNotesScroller.jsx +0 -16
  19. package/dist/source/PianoRollScale.jsx +0 -43
  20. package/dist/source/PianoRollScrollZoomViewPort.jsx +0 -49
  21. package/dist/source/PianoRollTrackList.jsx +0 -27
  22. package/dist/source/PianoRollTrackListScroller.jsx +0 -16
  23. package/dist/source/index.jsx +0 -5
  24. package/dist/source/types.js +0 -1
  25. package/dist/source/useBoundingClientRect.js +0 -29
  26. package/dist/source/useNotes.js +0 -23
  27. package/dist/source/usePianoRollGrid.js +0 -45
  28. package/dist/source/usePianoRollState.js +0 -126
  29. package/dist/source/viewport/PlayHead.jsx +0 -43
  30. package/dist/source/viewport/ScrollZoomContainer.jsx +0 -104
  31. package/dist/source/viewport/ScrollZoomViewPort.jsx +0 -31
  32. package/dist/source/viewport/ZoomSliderControl.jsx +0 -29
  33. package/dist/source/viewport/createViewPortDimension.js +0 -51
  34. package/dist/types/PianoRoll.d.ts +0 -8
  35. package/dist/types/PianoRollContext.d.ts +0 -45
  36. package/dist/types/PianoRollGrid.d.ts +0 -2
  37. package/dist/types/PianoRollKeys.d.ts +0 -9
  38. package/dist/types/PianoRollNotes.d.ts +0 -5
  39. package/dist/types/PianoRollNotesScroller.d.ts +0 -3
  40. package/dist/types/PianoRollScale.d.ts +0 -2
  41. package/dist/types/PianoRollScrollZoomViewPort.d.ts +0 -3
  42. package/dist/types/PianoRollTrackList.d.ts +0 -2
  43. package/dist/types/PianoRollTrackListScroller.d.ts +0 -3
  44. package/dist/types/index.d.ts +0 -6
  45. package/dist/types/types.d.ts +0 -8
  46. package/dist/types/useBoundingClientRect.d.ts +0 -3
  47. package/dist/types/useNotes.d.ts +0 -9
  48. package/dist/types/usePianoRollGrid.d.ts +0 -13
  49. package/dist/types/viewport/PlayHead.d.ts +0 -8
  50. package/dist/types/viewport/ScrollZoomContainer.d.ts +0 -8
  51. package/dist/types/viewport/ScrollZoomViewPort.d.ts +0 -39
  52. package/dist/types/viewport/ZoomSliderControl.d.ts +0 -6
  53. package/dist/types/viewport/createViewPortDimension.d.ts +0 -40
@@ -1,160 +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 calculateNoteDragValues = (event) => {
18
- const targetTrackIndex = context.mode === "tracks"
19
- ? clamp(Math.floor(verticalViewPort().calculatePosition(event.clientY)), 0, context.tracks.length - 1)
20
- : currentNoteTrackIndex() > -1
21
- ? currentNoteTrackIndex()
22
- : context.selectedTrackIndex;
23
- const midi = context.mode === "keys"
24
- ? Math.floor(128 - verticalViewPort().calculatePosition(event.clientY))
25
- : getInitialNote()?.midi ?? 60;
26
- const horiontalPosition = horizontalViewPort().calculatePosition(event.clientX);
27
- return {
28
- targetTrackIndex,
29
- midi,
30
- horiontalPosition,
31
- };
32
- };
33
- const handleMouseMove = (mouseMoveEvent) => {
34
- mouseMoveEvent.preventDefault();
35
- mouseMoveEvent.stopPropagation();
36
- const note = getInitialNote();
37
- if (!note)
38
- return;
39
- const { targetTrackIndex, midi, horiontalPosition } = calculateNoteDragValues(mouseMoveEvent);
40
- const ticks = context.snapValueToGridIfEnabled(horiontalPosition - diffPosition(), mouseMoveEvent.altKey);
41
- const updatedNote = {
42
- ...note,
43
- midi,
44
- ...(noteDragMode() === "move" && { ticks }),
45
- ...(noteDragMode() === "trimStart" && {
46
- ticks: ticks < note.ticks + note.durationTicks ? ticks : note.ticks + note.durationTicks,
47
- durationTicks: ticks < note.ticks + note.durationTicks
48
- ? note.ticks + note.durationTicks - ticks
49
- : context.snapValueToGridIfEnabled(horiontalPosition - note.ticks, mouseMoveEvent.altKey),
50
- }),
51
- ...(noteDragMode() === "trimEnd" && {
52
- ticks: ticks < note.ticks ? ticks : note.ticks,
53
- durationTicks: ticks < note.ticks
54
- ? note.ticks - ticks
55
- : context.snapValueToGridIfEnabled(horiontalPosition - note.ticks, mouseMoveEvent.altKey),
56
- }),
57
- };
58
- const previousTrackIndex = currentNoteTrackIndex();
59
- const previousNoteIndex = currentNoteIndex();
60
- if (targetTrackIndex === previousTrackIndex) {
61
- context.onNoteChange?.(previousTrackIndex, previousNoteIndex, updatedNote);
62
- }
63
- else {
64
- if (context.onInsertNote) {
65
- context.onRemoveNote?.(previousTrackIndex, previousNoteIndex);
66
- const newNoteIndex = context.onInsertNote(targetTrackIndex, updatedNote);
67
- setCurrentNoteTrackIndex(targetTrackIndex);
68
- setCurrentNoteIndex(newNoteIndex);
69
- }
70
- }
71
- };
72
- const stopDragging = () => {
73
- window.removeEventListener("mousemove", handleMouseMove);
74
- window.removeEventListener("mouseup", stopDragging);
75
- setIsDragging(false);
76
- setCurrentNoteIndex(-1);
77
- setCurrentNoteTrackIndex(-1);
78
- };
79
- const startDragging = () => {
80
- window.addEventListener("mousemove", handleMouseMove);
81
- window.addEventListener("mouseup", stopDragging);
82
- };
83
- const getClasses = (noteDragMode) => {
84
- return noteDragMode ? [styles.Note, styles[noteDragMode]] : [styles.Note];
85
- };
86
- return (<div classList={{ [styles.PianoRollNotes]: true }} ref={props.ref} onMouseDown={(mouseDownEvent) => {
87
- mouseDownEvent.preventDefault();
88
- mouseDownEvent.stopPropagation();
89
- const { targetTrackIndex, midi, horiontalPosition } = calculateNoteDragValues(mouseDownEvent);
90
- const ticks = context.snapValueToGridIfEnabled(horiontalPosition, mouseDownEvent.altKey, context);
91
- const durationTicks = gridDivisionTicks();
92
- const newNote = {
93
- midi,
94
- ticks,
95
- durationTicks,
96
- velocity: 100,
97
- };
98
- const newNoteIndex = context.onInsertNote(targetTrackIndex, newNote);
99
- setIsDragging(true);
100
- setCurrentNoteTrackIndex(targetTrackIndex);
101
- setCurrentNoteIndex(newNoteIndex);
102
- setInitialNote(newNote);
103
- setDiffPosition(0);
104
- setNoteDragMode("trimEnd");
105
- startDragging();
106
- }}>
107
- <For each={context.tracks}>
108
- {(track, trackIndex) => {
109
- return (<Show when={trackIndex() === context.selectedTrackIndex ||
110
- context.showAllTracks ||
111
- context.mode === "tracks"}>
112
- <For each={track.notes}>
113
- {(note, noteIndex) => {
114
- const verticalVirtualDimensions = createMemo(() => verticalViewPort().calculatePixelDimensions(context.mode === "keys" ? 127 - note.midi : trackIndex(), 1));
115
- const horizontalDimensions = createMemo(() => horizontalViewPort().calculatePixelDimensions(note.ticks, note.durationTicks));
116
- return (<Show when={verticalViewPort().isVisible(verticalVirtualDimensions()) &&
117
- horizontalViewPort().isVisible(horizontalDimensions())}>
118
- <div class={getClasses(noteDragMode()).join(" ")} draggable={false} onMouseMove={(event) => {
119
- if (isDragging())
120
- return;
121
- event.stopPropagation();
122
- const relativeX = horizontalViewPort().calculatePixelValue(horizontalViewPort().calculatePosition(event.clientX));
123
- const noteStartX = horizontalViewPort().calculatePixelValue(note.ticks);
124
- const noteEndX = horizontalViewPort().calculatePixelValue(note.ticks + note.durationTicks);
125
- setNoteDragMode(relativeX - noteStartX < 3
126
- ? "trimStart"
127
- : noteEndX - relativeX < 3
128
- ? "trimEnd"
129
- : "move");
130
- }} onDblClick={(event) => {
131
- event.stopPropagation();
132
- context.onRemoveNote?.(trackIndex(), noteIndex());
133
- }} onMouseDown={(event) => {
134
- event.stopPropagation();
135
- const initialPosition = horizontalViewPort().calculatePosition(event.clientX);
136
- setDiffPosition(noteDragMode() === "trimEnd"
137
- ? -(note.ticks + note.durationTicks - initialPosition)
138
- : initialPosition - note.ticks);
139
- setIsDragging(true);
140
- setCurrentNoteIndex(noteIndex());
141
- setCurrentNoteTrackIndex(trackIndex());
142
- setInitialNote(note);
143
- startDragging();
144
- }} style={{
145
- "background-color": `${track.color}`,
146
- opacity: (note.velocity / 128 + 2) / 3,
147
- top: `${verticalVirtualDimensions().offset}px`,
148
- height: `${verticalVirtualDimensions().size}px`,
149
- left: `${horizontalDimensions().offset}px`,
150
- width: `${horizontalDimensions().size}px`,
151
- }}></div>
152
- </Show>);
153
- }}
154
- </For>
155
- </Show>);
156
- }}
157
- </For>
158
- </div>);
159
- };
160
- 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,43 +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
- import { usePianoRollContext } from "./PianoRollContext";
6
- const PianoRollScale = () => {
7
- const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
8
- const grid = usePianoRollGrid();
9
- const context = usePianoRollContext();
10
- const updatePlayheadPosition = (event) => {
11
- const position = horizontalViewPort().calculatePosition(event.clientX);
12
- context.onPlayHeadPositionChange(position, event);
13
- };
14
- return (<div class={styles.PianoRollScale} onMouseDown={(event) => {
15
- updatePlayheadPosition(event);
16
- const handleMouseUp = () => {
17
- window.removeEventListener("mousemove", updatePlayheadPosition);
18
- window.removeEventListener("mouseup", handleMouseUp);
19
- };
20
- window.addEventListener("mousemove", updatePlayheadPosition);
21
- window.addEventListener("mouseup", handleMouseUp);
22
- }}>
23
- <Index each={grid()}>
24
- {(entry) => {
25
- return (<Show when={horizontalViewPort().isVisible(entry().virtualDimensions)}>
26
- <div classList={{
27
- [styles["PianoRollScale-Time"]]: true,
28
- [styles["Highlighted"]]: entry().isHighlighted,
29
- [styles["HighlightedBorder"]]: entry().hasHighlightedBorder,
30
- }} style={{
31
- left: `${entry().virtualDimensions.offset}px`,
32
- width: `${entry().virtualDimensions.size}px`,
33
- }}>
34
- <Show when={entry().showLabel}>
35
- <div class={styles["PianoRollScale-Label"]}>{entry().label}</div>
36
- </Show>
37
- </div>
38
- </Show>);
39
- }}
40
- </Index>
41
- </div>);
42
- };
43
- 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;
@@ -1,5 +0,0 @@
1
- import PianoRoll from "./PianoRoll";
2
- import PlayHead from "./viewport/PlayHead";
3
- import useNotes from "./useNotes";
4
- import createPianoRollstate from "./usePianoRollState";
5
- export { PianoRoll, createPianoRollstate, useNotes, PlayHead };
@@ -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
- }
@@ -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,126 +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
- playHeadPosition: 0,
13
- gridDivision: 4,
14
- snapToGrid: true,
15
- duration: 0,
16
- tracks: [],
17
- selectedTrackIndex: 0,
18
- pressedKeys: {},
19
- notesScrollerClientRect: { left: 0, width: 0, top: 0, height: 0 },
20
- tracksScrollerClientRect: { left: 0, width: 0, top: 0, height: 0 },
21
- };
22
- const propNameToHandlerName = (name) => `on${name[0]?.toUpperCase()}${name.slice(1)}Change`;
23
- export const pianoRollStatePropNames = [
24
- ...Object.keys(defaultState),
25
- ...Object.keys(defaultState).map(propNameToHandlerName),
26
- "onNoteChange",
27
- "onInsertNote",
28
- "onRemoveNote",
29
- "onNoteDown",
30
- "onNoteUp",
31
- "isKeyDown",
32
- "snapValueToGridIfEnabled",
33
- ];
34
- const createPianoRollstate = (initialState) => {
35
- const [state, setState] = createStore({
36
- ...defaultState,
37
- ...initialState,
38
- });
39
- const handlers = Object.fromEntries(Object.entries(state).map((entry) => {
40
- const handlerName = propNameToHandlerName(entry[0]);
41
- return [
42
- handlerName,
43
- (value, originalEvent) => {
44
- const handler = initialState?.[handlerName];
45
- setState(entry[0], value);
46
- if (handler) {
47
- handler(value, originalEvent);
48
- }
49
- },
50
- ];
51
- }));
52
- const onPlayheadPositionChange = (playheadPosition, originalEvent) => {
53
- handlers.onPlayHeadPositionChange(snapValueToGridIfEnabled(playheadPosition, !!originalEvent?.altKey));
54
- };
55
- const updateNotes = async (trackIndex, getNotes) => {
56
- const track = state.tracks[trackIndex];
57
- if (!track)
58
- return;
59
- const notes = track.notes;
60
- handlers.onTracksChange([
61
- ...state.tracks.slice(0, trackIndex),
62
- { ...track, notes: getNotes(notes) },
63
- ...state.tracks.slice(trackIndex + 1),
64
- ]);
65
- };
66
- const onNoteChange = (trackIndex, noteIndex, note) => {
67
- updateNotes(trackIndex, (notes) => [
68
- ...notes.slice(0, noteIndex),
69
- note,
70
- ...notes.slice(noteIndex + 1),
71
- ]);
72
- };
73
- const onInsertNote = (trackIndex, note) => {
74
- const track = state.tracks[trackIndex];
75
- if (!track)
76
- return -1;
77
- const notes = track.notes;
78
- const newNoteIndex = Math.max(notes.findIndex(({ ticks }) => ticks > note.ticks), 0);
79
- updateNotes(trackIndex, (notes) => [
80
- ...notes.slice(0, newNoteIndex),
81
- note,
82
- ...notes.slice(newNoteIndex),
83
- ]);
84
- return newNoteIndex;
85
- };
86
- const onRemoveNote = (trackIndex, noteIndex) => {
87
- updateNotes(trackIndex, (notes) => [
88
- ...notes.slice(0, noteIndex),
89
- ...notes.slice(noteIndex + 1),
90
- ]);
91
- };
92
- const updateKeyPressedState = (trackIndex, keyNumber, value) => {
93
- handlers.onPressedKeysChange?.({
94
- ...state.pressedKeys,
95
- [trackIndex]: {
96
- ...state.pressedKeys[trackIndex],
97
- [keyNumber]: value,
98
- },
99
- });
100
- };
101
- const onNoteDown = (trackIndex, keyNumber) => {
102
- updateKeyPressedState(trackIndex, keyNumber, true);
103
- };
104
- const onNoteUp = (trackIndex, keyNumber) => {
105
- updateKeyPressedState(trackIndex, keyNumber, false);
106
- };
107
- const isKeyDown = (trackIndex, keyNumber) => !!state.pressedKeys[trackIndex]?.[keyNumber];
108
- const snapValueToGridIfEnabled = (value, altKey) => {
109
- const gridDivisionTicks = (state.ppq * 4) / state.gridDivision;
110
- return state.snapToGrid && !altKey
111
- ? Math.round(value / gridDivisionTicks) * gridDivisionTicks
112
- : value;
113
- };
114
- return mergeProps(state, {
115
- ...handlers,
116
- onNoteChange,
117
- onInsertNote,
118
- onRemoveNote,
119
- onNoteDown,
120
- onNoteUp,
121
- isKeyDown,
122
- snapValueToGridIfEnabled,
123
- onPlayheadPositionChange,
124
- });
125
- };
126
- 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 = (event) => {
32
- const newPosition = viewPort.calculatePosition(event.clientX);
33
- props.onPositionChange?.(newPosition, event);
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;