solid-pianoroll 0.0.7 → 0.0.9

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.
@@ -9,22 +9,23 @@ import VerticalZoomControl from "./VerticalZoomControl";
9
9
  import PianoRollGrid from "./PianoRollGrid";
10
10
  const PianoRoll = (allProps) => {
11
11
  const [contextProps, divProps] = splitProps(allProps, [
12
- "ppq",
12
+ "condenseKeys",
13
+ "duration",
14
+ "gridDivision",
13
15
  "notes",
14
16
  "position",
15
- "duration",
16
- "zoom",
17
+ "ppq",
18
+ "snapToGrid",
17
19
  "verticalPosition",
18
20
  "verticalZoom",
19
- "onVerticalZoomChange",
20
- "onVerticalPositionChange",
21
- "onZoomChange",
22
- "onPositionChange",
23
- "onNoteChange",
24
- "gridDivision",
25
- "snapToGrid",
21
+ "zoom",
26
22
  "onInsertNote",
23
+ "onNoteChange",
24
+ "onPositionChange",
27
25
  "onRemoveNote",
26
+ "onVerticalPositionChange",
27
+ "onVerticalZoomChange",
28
+ "onZoomChange",
28
29
  ]);
29
30
  const [scrollContainer, setScrollContainer] = createSignal();
30
31
  const [notesContainer, setNotesContainer] = createSignal();
@@ -13,13 +13,13 @@ export const PianoRollContextProvider = (props) => {
13
13
  viewPortSize: clientRect().width,
14
14
  virtualPosition: props.position,
15
15
  virtualRange: props.duration,
16
- zoom: props.zoom,
16
+ zoom: props.zoom * (500 / clientRect().width),
17
17
  }));
18
18
  const verticalViewPort = useViewPortScaler(() => ({
19
19
  viewPortOffset: clientRect().top,
20
20
  viewPortSize: clientRect().height,
21
21
  virtualPosition: props.verticalPosition,
22
- zoom: props.verticalZoom,
22
+ zoom: props.verticalZoom * (500 / clientRect().height),
23
23
  virtualRange: 128,
24
24
  }));
25
25
  const getContextValue = () => {
@@ -3,10 +3,25 @@ import { usePianoRollContext } from "./PianoRollContext";
3
3
  import { blackKeys, keys } from "./PianoRollKeys";
4
4
  const PianoRollGrid = () => {
5
5
  const context = usePianoRollContext();
6
- const gridDivisorTicks = createMemo(() => (context.ppq * 4) / context.gridDivision);
6
+ const measureTicks = createMemo(() => context.ppq * 4);
7
+ const selectedGridDivisorTicks = createMemo(() => measureTicks() / context.gridDivision);
8
+ function calculateVisibleGridDivisorTicks(value) {
9
+ if (context.horizontalViewPort.getVisibleRange() / value > 100) {
10
+ return calculateVisibleGridDivisorTicks(value * 2);
11
+ }
12
+ if (context.horizontalViewPort.getVisibleRange() / value < 30) {
13
+ return calculateVisibleGridDivisorTicks(value / 2);
14
+ }
15
+ return value;
16
+ }
17
+ const gridDivisorTicks = createMemo(() => calculateVisibleGridDivisorTicks(selectedGridDivisorTicks()));
7
18
  const gridArray = createMemo(() => {
8
- const numberOfLines = context.duration / gridDivisorTicks();
9
- return Array.from({ length: numberOfLines }).map((_, index) => index * gridDivisorTicks());
19
+ const numberOfLines = Math.ceil(context.horizontalViewPort.getVisibleRange() / gridDivisorTicks());
20
+ const startIndex = Math.floor(context.position / gridDivisorTicks());
21
+ return Array.from({ length: numberOfLines }).map((_, index) => ({
22
+ index: index + startIndex,
23
+ ticks: (index + startIndex) * gridDivisorTicks(),
24
+ }));
10
25
  });
11
26
  return (<div class="PianoRoll-Grid-Container" style={{
12
27
  position: "absolute",
@@ -19,19 +34,27 @@ const PianoRollGrid = () => {
19
34
  height: "100%",
20
35
  }}>
21
36
  <Index each={gridArray()}>
22
- {(tick, index) => {
23
- const virtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(tick(), gridDivisorTicks()));
37
+ {(entry) => {
38
+ const virtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(entry().ticks, gridDivisorTicks()));
24
39
  return (<Show when={virtualDimensions().size > 0}>
25
40
  <div class="PianoRoll-Grid-Time" style={{
26
41
  "z-index": 1,
27
42
  position: "absolute",
28
43
  "box-sizing": "border-box",
29
- background: index % 4 === 0 ? "#ccc" : "#ddd",
44
+ background: Math.ceil((entry().index + 1 * selectedGridDivisorTicks()) / measureTicks()) %
45
+ 2 ===
46
+ 0
47
+ ? "#ddd"
48
+ : "#ccc",
30
49
  top: `0px`,
31
50
  height: `100%`,
32
51
  left: `${virtualDimensions().offset}px`,
33
52
  width: `${virtualDimensions().size}px`,
34
- "border-left": "1px gray solid",
53
+ "border-left-style": "solid",
54
+ "border-left-width": "0.5px",
55
+ "border-left-color": (entry().index * gridDivisorTicks()) % selectedGridDivisorTicks() === 0
56
+ ? "gray"
57
+ : "#bbb",
35
58
  }}></div>
36
59
  </Show>);
37
60
  }}
@@ -1,5 +1,4 @@
1
1
  import { createEffect, createMemo, createSignal } from "solid-js";
2
- import { clamp } from "./helpers";
3
2
  import { usePianoRollContext } from "./PianoRollContext";
4
3
  const ScrollContainer = (props) => {
5
4
  let scrollContentRef;
@@ -25,8 +24,8 @@ const ScrollContainer = (props) => {
25
24
  didUpdateScroll = false;
26
25
  return;
27
26
  }
28
- const maxVerticalPosition = 128 - 128 / context.verticalZoom;
29
- const maxPosition = context.duration - context.duration / context.zoom;
27
+ const maxVerticalPosition = context.verticalViewPort.getMaxPosition();
28
+ const maxPosition = context.horizontalViewPort.getMaxPosition();
30
29
  const { width, height } = context.clientRect;
31
30
  const { scrollTop, scrollLeft, scrollWidth, scrollHeight } = event.currentTarget;
32
31
  const scrollTopAmount = scrollTop / (scrollHeight - height);
@@ -36,16 +35,16 @@ const ScrollContainer = (props) => {
36
35
  };
37
36
  let didUpdateScroll = false;
38
37
  createEffect(() => {
39
- const maxVerticalPosition = 128 - 128 / context.verticalZoom;
40
- const maxPosition = context.duration - context.duration / context.zoom;
38
+ const maxVerticalPosition = context.verticalViewPort.getMaxPosition();
39
+ const maxPosition = context.horizontalViewPort.getMaxPosition();
41
40
  const scrollTopAmount = maxVerticalPosition > 0 ? context.verticalPosition / maxVerticalPosition : 0;
42
41
  const scrollLeftAmount = maxPosition > 0 ? context.position / maxPosition : 0;
43
42
  const { height, width } = context.clientRect;
44
43
  if (!scrollContentRef?.parentElement)
45
44
  return;
46
- const scrollDivHeight = clamp(context.verticalZoom * height, height, 10000);
45
+ const scrollDivHeight = context.verticalViewPort.getScrollSize();
47
46
  const scrollTop = scrollTopAmount * (scrollDivHeight - height);
48
- const scrollDivWidth = clamp(context.zoom * width, width, 10000);
47
+ const scrollDivWidth = context.horizontalViewPort.getScrollSize();
49
48
  const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
50
49
  didUpdateScroll = true;
51
50
  scrollContentRef.style.height = `${scrollDivHeight}px`;
@@ -1,31 +1,49 @@
1
1
  import { createEffect, createSignal } from "solid-js";
2
2
  const usePianoRoll = () => {
3
- const [position, setPosition] = createSignal(0);
4
- const [zoom, setZoom] = createSignal(10);
5
- const [verticalZoom, setVerticalZoom] = createSignal(5);
6
- const [verticalPosition, setVerticalPosition] = createSignal(64);
7
- const [gridDivision, setGridDivision] = createSignal(16);
8
- const [snapToGrid, setSnapToGrid] = createSignal(true);
9
- const [notes, setNotes] = createSignal([]);
3
+ const [ppq, onPpqChange] = createSignal(120);
4
+ const [position, onPositionChange] = createSignal(0);
5
+ const [zoom, onZoomChange] = createSignal(10);
6
+ const [verticalZoom, onVerticalZoomChange] = createSignal(5);
7
+ const [verticalPosition, onVerticalPositionChange] = createSignal(64);
8
+ const [gridDivision, onGridDivisionChange] = createSignal(16);
9
+ const [snapToGrid, onSnapToGridChange] = createSignal(true);
10
+ const [notes, onNotesChange] = createSignal([]);
10
11
  const [duration, setDuration] = createSignal(0);
12
+ const onNoteChange = (index, note) => {
13
+ onNotesChange([...notes().slice(0, index), note, ...notes().splice(index + 1)]);
14
+ };
15
+ const onInsertNote = (note) => {
16
+ const index = Math.max(notes().findIndex(({ ticks }) => ticks > note.ticks), 0);
17
+ onNotesChange([...notes().slice(0, index), note, ...notes().splice(index)]);
18
+ return index;
19
+ };
20
+ const onRemoveNote = (index) => {
21
+ onNotesChange([...notes().slice(0, index), ...notes().splice(index + 1)]);
22
+ };
11
23
  createEffect(() => {
12
- setDuration((notes()[notes().length - 1]?.ticks ?? 0) + (notes()[notes().length - 1]?.durationTicks ?? 0));
24
+ setDuration(Math.max((notes()[notes().length - 1]?.ticks ?? 0) +
25
+ (notes()[notes().length - 1]?.durationTicks ?? 0), ppq() * 4 * 4));
13
26
  });
14
27
  return {
28
+ ppq,
29
+ onPpqChange,
15
30
  position,
16
- setPosition,
31
+ onPositionChange,
17
32
  zoom,
18
- setZoom,
33
+ onZoomChange,
19
34
  verticalPosition,
20
- setVerticalPosition,
35
+ onVerticalPositionChange,
21
36
  verticalZoom,
22
- setVerticalZoom,
37
+ onVerticalZoomChange,
23
38
  gridDivision,
24
- setGridDivision,
39
+ onGridDivisionChange,
25
40
  snapToGrid,
26
- setSnapToGrid,
41
+ onSnapToGridChange,
27
42
  notes,
28
- setNotes,
43
+ onNotesChange,
44
+ onNoteChange,
45
+ onInsertNote,
46
+ onRemoveNote,
29
47
  duration,
30
48
  };
31
49
  };
@@ -1,5 +1,4 @@
1
1
  import { createMemo } from "solid-js";
2
- import { clamp } from "./helpers";
3
2
  export default function useViewPortScaler(getState) {
4
3
  const state = createMemo(() => getState());
5
4
  function getScaledValue(position) {
@@ -9,12 +8,20 @@ export default function useViewPortScaler(getState) {
9
8
  function getCoordinates(position) {
10
9
  return getScaledValue(position) - getScaledValue(state().virtualPosition);
11
10
  }
11
+ function getVisibleRange() {
12
+ return state().virtualRange / state().zoom;
13
+ }
12
14
  function getPosition(offset) {
13
- const visibleDuration = state().virtualRange / state().zoom;
14
15
  const percentX = (offset - state().viewPortOffset) / state().viewPortSize;
15
- const position = state().virtualPosition + percentX * visibleDuration;
16
+ const position = state().virtualPosition + percentX * getVisibleRange();
16
17
  return position;
17
18
  }
19
+ function getMaxPosition() {
20
+ return state().virtualRange - state().virtualRange / state().zoom;
21
+ }
22
+ function getScrollSize() {
23
+ return clamp(state().zoom * state().viewPortSize, state().viewPortSize, 100000);
24
+ }
18
25
  function getVirtualDimensions(position, length) {
19
26
  const virtualLeft = getCoordinates(position);
20
27
  const virtualWidth = getScaledValue(length);
@@ -27,5 +34,11 @@ export default function useViewPortScaler(getState) {
27
34
  getCoordinates,
28
35
  getPosition,
29
36
  getVirtualDimensions,
37
+ getVisibleRange,
38
+ getMaxPosition,
39
+ getScrollSize,
30
40
  };
31
41
  }
42
+ export const clamp = (value, min, max) => {
43
+ return Math.max(min, Math.min(max, value));
44
+ };
@@ -11,6 +11,7 @@ export type PianoRollProps = {
11
11
  position: number;
12
12
  duration: number;
13
13
  zoom: number;
14
+ condenseKeys: boolean;
14
15
  onVerticalZoomChange?: (zoom: number) => void;
15
16
  onVerticalPositionChange?: (zoom: number) => void;
16
17
  onZoomChange?: (zoom: number) => void;
@@ -1,20 +1,25 @@
1
1
  import { GridDivision } from "./PianoRoll";
2
2
  import { Note } from "./types";
3
3
  declare const usePianoRoll: () => {
4
+ ppq: import("solid-js").Accessor<number>;
5
+ onPpqChange: import("solid-js").Setter<number>;
4
6
  position: import("solid-js").Accessor<number>;
5
- setPosition: import("solid-js").Setter<number>;
7
+ onPositionChange: import("solid-js").Setter<number>;
6
8
  zoom: import("solid-js").Accessor<number>;
7
- setZoom: import("solid-js").Setter<number>;
9
+ onZoomChange: import("solid-js").Setter<number>;
8
10
  verticalPosition: import("solid-js").Accessor<number>;
9
- setVerticalPosition: import("solid-js").Setter<number>;
11
+ onVerticalPositionChange: import("solid-js").Setter<number>;
10
12
  verticalZoom: import("solid-js").Accessor<number>;
11
- setVerticalZoom: import("solid-js").Setter<number>;
13
+ onVerticalZoomChange: import("solid-js").Setter<number>;
12
14
  gridDivision: import("solid-js").Accessor<GridDivision>;
13
- setGridDivision: import("solid-js").Setter<GridDivision>;
15
+ onGridDivisionChange: import("solid-js").Setter<GridDivision>;
14
16
  snapToGrid: import("solid-js").Accessor<boolean>;
15
- setSnapToGrid: import("solid-js").Setter<boolean>;
17
+ onSnapToGridChange: import("solid-js").Setter<boolean>;
16
18
  notes: import("solid-js").Accessor<Note[]>;
17
- setNotes: import("solid-js").Setter<Note[]>;
19
+ onNotesChange: import("solid-js").Setter<Note[]>;
20
+ onNoteChange: (index: number, note: Note) => void;
21
+ onInsertNote: (note: Note) => number;
22
+ onRemoveNote: (index: number) => void;
18
23
  duration: import("solid-js").Accessor<number>;
19
24
  };
20
25
  export default usePianoRoll;
@@ -13,4 +13,8 @@ export default function useViewPortScaler(getState: () => {
13
13
  offset: number;
14
14
  size: number;
15
15
  };
16
+ getVisibleRange: () => number;
17
+ getMaxPosition: () => number;
18
+ getScrollSize: () => number;
16
19
  };
20
+ export declare const clamp: (value: number, min: number, max: number) => number;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.7",
2
+ "version": "0.0.9",
3
3
  "name": "solid-pianoroll",
4
4
  "description": "Pianoroll UI Control for Solid JS apps",
5
5
  "license": "MIT",
@@ -1,3 +0,0 @@
1
- export const clamp = (value, min, max) => {
2
- return Math.max(min, Math.min(max, value));
3
- };
@@ -1 +0,0 @@
1
- export declare const clamp: (value: number, min: number, max: number) => number;