solid-pianoroll 0.0.8 → 0.0.10

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 (33) hide show
  1. package/dist/cjs/index.js +457 -451
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/esm/index.js +459 -453
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/source/PianoRoll.jsx +43 -16
  6. package/dist/source/PianoRollContext.jsx +3 -42
  7. package/dist/source/PianoRollGrid.jsx +22 -33
  8. package/dist/source/PianoRollKeys.jsx +10 -6
  9. package/dist/source/PianoRollNotes.jsx +91 -46
  10. package/dist/source/usePianoRoll.js +6 -3
  11. package/dist/source/viewport/HorizontalZoomControl.jsx +9 -0
  12. package/dist/source/viewport/ScrollContainer.jsx +87 -0
  13. package/dist/source/viewport/ScrollZoomViewPort.jsx +19 -0
  14. package/dist/source/viewport/VerticalZoomControl.jsx +12 -0
  15. package/dist/source/viewport/createViewPortDimension.js +39 -0
  16. package/dist/types/PianoRoll.d.ts +1 -0
  17. package/dist/types/PianoRollContext.d.ts +2 -22
  18. package/dist/types/PianoRollNotes.d.ts +1 -1
  19. package/dist/types/usePianoRoll.d.ts +1 -0
  20. package/dist/types/viewport/HorizontalZoomControl.d.ts +3 -0
  21. package/dist/types/viewport/ScrollContainer.d.ts +5 -0
  22. package/dist/types/viewport/ScrollZoomViewPort.d.ts +23 -0
  23. package/dist/types/viewport/VerticalZoomControl.d.ts +3 -0
  24. package/dist/types/viewport/createViewPortDimension.d.ts +31 -0
  25. package/package.json +7 -1
  26. package/dist/source/HorizontalZoomControl.jsx +0 -9
  27. package/dist/source/ScrollContainer.jsx +0 -138
  28. package/dist/source/VerticalZoomControl.jsx +0 -12
  29. package/dist/source/useViewPortScaler.js +0 -44
  30. package/dist/types/HorizontalZoomControl.d.ts +0 -2
  31. package/dist/types/ScrollContainer.d.ts +0 -5
  32. package/dist/types/VerticalZoomControl.d.ts +0 -2
  33. package/dist/types/useViewPortScaler.d.ts +0 -20
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createComponent, delegateEvents, effect, template, insert, className, setAttribute, use, memo, style, spread, mergeProps } from 'solid-js/web';
2
- import { createSignal, createEffect, createMemo, createContext, splitProps, useContext, Index, Show } from 'solid-js';
1
+ import { createComponent, insert, effect, classList, setAttribute, className, template, delegateEvents, use, memo, spread, mergeProps } from 'solid-js/web';
2
+ import { createContext, useContext, createEffect, createMemo, Index, Show, createSignal, splitProps } from 'solid-js';
3
3
  import { createStore } from 'solid-js/store';
4
4
 
5
5
  function styleInject(css, ref) {
@@ -27,198 +27,132 @@ function styleInject(css, ref) {
27
27
  }
28
28
  }
29
29
 
30
- var css_248z$1 = ".PianoRoll-module_PianoRoll__rkRYx {\n box-sizing: border-box;\n display: flex;\n overflow: hidden;\n flex-direction: column;\n padding: 16px;\n}\n\n.PianoRoll-module_PianoRollContainer__Oyxr7 {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n\n.PianoRoll-module_PianoRollInnerContainer__9wdIQ {\n position: relative;\n display: flex;\n overflow: hidden;\n flex: 1;\n}\n";
31
- var styles$1 = {"PianoRoll":"PianoRoll-module_PianoRoll__rkRYx","PianoRollContainer":"PianoRoll-module_PianoRollContainer__Oyxr7","PianoRollInnerContainer":"PianoRoll-module_PianoRollInnerContainer__9wdIQ"};
32
- styleInject(css_248z$1);
30
+ var css_248z$3 = ".PianoRoll-module_PianoRoll__rkRYx {\n box-sizing: border-box;\n display: flex;\n overflow: hidden;\n flex-direction: column;\n padding-top: 16px;\n padding-left: 16px;\n}\n\n.PianoRoll-module_PianoRollContainer__Oyxr7 {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: row;\n}\n";
31
+ var styles$3 = {"PianoRoll":"PianoRoll-module_PianoRoll__rkRYx","PianoRollContainer":"PianoRoll-module_PianoRollContainer__Oyxr7"};
32
+ styleInject(css_248z$3);
33
33
 
34
- const defaultRect = {
35
- left: 0,
36
- width: 0,
37
- top: 0,
38
- height: 0,
39
- bottom: 0,
40
- right: 0,
41
- x: 0,
42
- y: 0
34
+ const PianoRollContext = createContext();
35
+ const PianoRollContextProvider = PianoRollContext.Provider;
36
+ const usePianoRollContext = () => {
37
+ const context = useContext(PianoRollContext);
38
+ if (!context) throw new Error("No PianoRollContext found");
39
+ return context;
43
40
  };
44
- function useBoundingClientRect(containerRef) {
45
- const [boundingClientRect, setBoundingClientRect] = createSignal(defaultRect);
46
- createEffect(() => {
47
- const container = containerRef();
48
- if (!container) return;
49
- const updateBoundingClientRect = () => {
50
- setBoundingClientRect(container.getBoundingClientRect() ?? defaultRect);
51
- };
52
- const resizeObserver = new ResizeObserver(updateBoundingClientRect);
53
- resizeObserver.observe(container);
54
- updateBoundingClientRect();
55
- return () => {
56
- resizeObserver.disconnect();
57
- };
58
- });
59
- return boundingClientRect;
60
- }
61
41
 
62
- function useViewPortScaler(getState) {
63
- const state = createMemo(() => getState());
64
- function getScaledValue(position) {
65
- const virtualSize = state().viewPortSize * state().zoom;
66
- return position / state().virtualRange * virtualSize;
67
- }
68
- function getCoordinates(position) {
69
- return getScaledValue(position) - getScaledValue(state().virtualPosition);
42
+ var css_248z$2 = ".PianoRollKeys-module_PianoRollKeys__5vnQ0 {\n position: relative;\n height: 100%;\n width: 50px;\n}\n\n.PianoRollKeys-module_Key__jybNC {\n border-width: 0px;\n position: absolute;\n box-sizing: border-box;\n width: 100%;\n border-color: #000;\n border-style: solid;\n border-right-width: 1px;\n}\n\n.PianoRollKeys-module_black__7rncB {\n background: black;\n}\n\n.PianoRollKeys-module_white__fR2Lm {\n background: white;\n}\n\n.PianoRollKeys-module_whiteAndNextIsWhite__PN1O8 {\n border-top-width: 0.1px;\n}\n\n.PianoRollKeys-module_whiteAndPreviousIsWhite__-fP3q {\n border-bottom-width: 0.1px;\n}\n";
43
+ var styles$2 = {"PianoRollKeys":"PianoRollKeys-module_PianoRollKeys__5vnQ0","Key":"PianoRollKeys-module_Key__jybNC","black":"PianoRollKeys-module_black__7rncB","white":"PianoRollKeys-module_white__fR2Lm","whiteAndNextIsWhite":"PianoRollKeys-module_whiteAndNextIsWhite__PN1O8","whiteAndPreviousIsWhite":"PianoRollKeys-module_whiteAndPreviousIsWhite__-fP3q"};
44
+ styleInject(css_248z$2);
45
+
46
+ function createViewPortDimension(getState) {
47
+ const getStateWithFunctions = () => ({
48
+ ...getState(),
49
+ calculatePixelValue,
50
+ calculatePosition,
51
+ calculatePixelDimensions,
52
+ calculateVisibleRange,
53
+ calculateMaxPosition
54
+ });
55
+ const [state, setState] = createStore(getStateWithFunctions());
56
+ createEffect(() => setState(getStateWithFunctions()));
57
+ function calculatePixelValue(position = state.position) {
58
+ const virtualSize = state.pixelSize * state.zoom;
59
+ return position / state.range * virtualSize;
70
60
  }
71
- function getVisibleRange() {
72
- return state().virtualRange / state().zoom;
61
+ function calculatePixelOffset(position) {
62
+ return calculatePixelValue(position) - calculatePixelValue(state.position);
73
63
  }
74
- function getPosition(offset) {
75
- const percentX = (offset - state().viewPortOffset) / state().viewPortSize;
76
- const position = state().virtualPosition + percentX * getVisibleRange();
64
+ function calculatePosition(offset) {
65
+ const percentX = (offset - state.pixelOffset) / state.pixelSize;
66
+ const position = state.position + percentX * calculateVisibleRange();
77
67
  return position;
78
68
  }
79
- function getMaxPosition() {
80
- return state().virtualRange - state().virtualRange / state().zoom;
69
+ function calculateVisibleRange() {
70
+ return state.range / state.zoom;
81
71
  }
82
- function getScrollSize() {
83
- return clamp(state().zoom * state().viewPortSize, state().viewPortSize, 100000);
72
+ function calculateMaxPosition() {
73
+ return state.range - state.range / state.zoom;
84
74
  }
85
- function getVirtualDimensions(position, length) {
86
- const virtualLeft = getCoordinates(position);
87
- const virtualWidth = getScaledValue(length);
88
- const offset = clamp(virtualLeft, 0, state().viewPortSize);
89
- const size = clamp(virtualWidth - (offset - virtualLeft), 0, state().viewPortSize - offset);
75
+ function calculatePixelDimensions(position, length) {
76
+ const offset = calculatePixelOffset(position);
77
+ const size = calculatePixelValue(length);
90
78
  return {
91
79
  offset,
92
80
  size
93
81
  };
94
82
  }
95
- return {
96
- getScaledValue,
97
- getCoordinates,
98
- getPosition,
99
- getVirtualDimensions,
100
- getVisibleRange,
101
- getMaxPosition,
102
- getScrollSize
103
- };
83
+ return state;
104
84
  }
105
- const clamp = (value, min, max) => {
106
- return Math.max(min, Math.min(max, value));
107
- };
85
+ const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
108
86
 
109
- const PianoRollContext = createContext(undefined);
110
- const PianoRollContextProvider = props => {
111
- const [noteDragMode, setNoteDragMode] = createSignal();
112
- const [isDragging, setIsDragging] = createSignal(false);
113
- const [_ownProps, contextProps] = splitProps(props, ["children", "scrollContainer"]);
114
- const clientRect = useBoundingClientRect(() => props.scrollContainer);
115
- const horizontalViewPort = useViewPortScaler(() => ({
116
- viewPortOffset: clientRect().left,
117
- viewPortSize: clientRect().width,
118
- virtualPosition: props.position,
119
- virtualRange: props.duration,
120
- zoom: props.zoom * (500 / clientRect().width)
121
- }));
122
- const verticalViewPort = useViewPortScaler(() => ({
123
- viewPortOffset: clientRect().top,
124
- viewPortSize: clientRect().height,
125
- virtualPosition: props.verticalPosition,
126
- zoom: props.verticalZoom * (500 / clientRect().height),
127
- virtualRange: 128
128
- }));
129
- const getContextValue = () => {
130
- return {
131
- ...contextProps,
132
- horizontalViewPort,
133
- verticalViewPort,
134
- clientRect: clientRect(),
135
- isDragging: isDragging(),
136
- onIsDraggingChange: setIsDragging,
137
- noteDragMode: noteDragMode(),
138
- onNoteDragModeChange: setNoteDragMode
139
- };
140
- };
141
- const [contextValue, setContextValue] = createStore(getContextValue());
142
- createEffect(() => {
143
- setContextValue(getContextValue());
144
- });
145
- return createComponent(PianoRollContext.Provider, {
146
- value: contextValue,
87
+ const ScrollZoomViewPort = props => {
88
+ const viewPorts = Object.entries(props.dimensions).map(([name, viewPortProps]) => createViewPortDimension(() => ({
89
+ name: name,
90
+ ...viewPortProps()
91
+ })));
92
+ const parentViewPorts = useContext(ScrollZoomViewPortContext);
93
+ return createComponent(ScrollZoomViewPortContext.Provider, {
94
+ get value() {
95
+ return [...parentViewPorts, ...viewPorts];
96
+ },
147
97
  get children() {
148
98
  return props.children;
149
99
  }
150
100
  });
151
101
  };
152
- const usePianoRollContext = () => {
153
- const context = useContext(PianoRollContext);
154
- if (!context) throw new Error("No PianoRollContext found");
155
- return context;
156
- };
157
-
158
- const _tmpl$$6 = /*#__PURE__*/template(`<input type="range" max="500" min="1" step="0.01">`, 1);
159
- const HorizontalZoomControl = () => {
160
- const context = usePianoRollContext();
161
- return (() => {
162
- const _el$ = _tmpl$$6.cloneNode(true);
163
- _el$.$$input = event => context.onZoomChange?.(event.currentTarget.valueAsNumber);
164
- _el$.style.setProperty("margin-left", "50px");
165
- _el$.style.setProperty("margin-right", "16px");
166
- effect(() => _el$.value = context.zoom);
167
- return _el$;
168
- })();
102
+ const ScrollZoomViewPortContext = createContext([]);
103
+ const useViewPortDimension = name => {
104
+ const viewPorts = useContext(ScrollZoomViewPortContext);
105
+ const viewPort = name ? viewPorts.find(viewPort => viewPort.name === name) : viewPorts[viewPorts.length - 1];
106
+ if (!viewPort) throw new Error(`ViewPort dimension "${name}" not found.`);
107
+ return viewPort;
169
108
  };
170
- delegateEvents(["input"]);
171
-
172
- var css_248z = ".PianoRollKeys-module_PianoRollKeys__5vnQ0 {\n position: relative;\n height: 100%;\n width: 50px;\n}\n\n.PianoRollKeys-module_PianoRollKeys-Key__xunQt {\n position: absolute;\n box-sizing: border-box;\n left: 0;\n width: 100%;\n border-color: #000;\n border-style: solid;\n}\n";
173
- var styles = {"PianoRollKeys":"PianoRollKeys-module_PianoRollKeys__5vnQ0","PianoRollKeys-Key":"PianoRollKeys-module_PianoRollKeys-Key__xunQt"};
174
- styleInject(css_248z);
175
109
 
176
- const _tmpl$$5 = /*#__PURE__*/template(`<div></div>`, 2);
110
+ const _tmpl$$6 = /*#__PURE__*/template(`<div></div>`, 2);
177
111
  const PianoRollKeys = () => {
178
- const context = usePianoRollContext();
112
+ const viewPort = createMemo(() => useViewPortDimension("vertical"));
179
113
  return (() => {
180
- const _el$ = _tmpl$$5.cloneNode(true);
114
+ const _el$ = _tmpl$$6.cloneNode(true);
181
115
  insert(_el$, createComponent(Index, {
182
116
  each: keys,
183
117
  children: key => {
184
- const virtualDimensions = createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - key().number, 1));
118
+ const virtualDimensions = createMemo(() => viewPort().calculatePixelDimensions(127 - key().number, 1));
185
119
  return createComponent(Show, {
186
120
  get when() {
187
121
  return virtualDimensions().size > 0;
188
122
  },
189
123
  get children() {
190
- const _el$2 = _tmpl$$5.cloneNode(true);
124
+ const _el$2 = _tmpl$$6.cloneNode(true);
191
125
  effect(_p$ => {
192
- const _v$ = styles["PianoRollKeys-Key"],
126
+ const _v$ = {
127
+ [styles$2["Key"]]: true,
128
+ [styles$2["black"]]: key().isBlack,
129
+ [styles$2["white"]]: !key().isBlack,
130
+ [styles$2["whiteAndNextIsWhite"]]: !key().isBlack && !blackKeys.includes((key().number + 1) % 12),
131
+ [styles$2["whiteAndPreviousIsWhite"]]: !key().isBlack && !blackKeys.includes((key().number - 1) % 12)
132
+ },
193
133
  _v$2 = key().name,
194
134
  _v$3 = key().number % 12,
195
135
  _v$4 = `${virtualDimensions().offset}px`,
196
- _v$5 = `${virtualDimensions().size}px`,
197
- _v$6 = key().isBlack ? "#000" : "#fff",
198
- _v$7 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
199
- _v$ !== _p$._v$ && className(_el$2, _p$._v$ = _v$);
136
+ _v$5 = `${virtualDimensions().size}px`;
137
+ _p$._v$ = classList(_el$2, _v$, _p$._v$);
200
138
  _v$2 !== _p$._v$2 && setAttribute(_el$2, "title", _p$._v$2 = _v$2);
201
139
  _v$3 !== _p$._v$3 && setAttribute(_el$2, "data-index", _p$._v$3 = _v$3);
202
140
  _v$4 !== _p$._v$4 && _el$2.style.setProperty("top", _p$._v$4 = _v$4);
203
141
  _v$5 !== _p$._v$5 && _el$2.style.setProperty("height", _p$._v$5 = _v$5);
204
- _v$6 !== _p$._v$6 && _el$2.style.setProperty("background-color", _p$._v$6 = _v$6);
205
- _v$7 !== _p$._v$7 && _el$2.style.setProperty("border-width", _p$._v$7 = _v$7);
206
142
  return _p$;
207
143
  }, {
208
144
  _v$: undefined,
209
145
  _v$2: undefined,
210
146
  _v$3: undefined,
211
147
  _v$4: undefined,
212
- _v$5: undefined,
213
- _v$6: undefined,
214
- _v$7: undefined
148
+ _v$5: undefined
215
149
  });
216
150
  return _el$2;
217
151
  }
218
152
  });
219
153
  }
220
154
  }));
221
- effect(() => className(_el$, styles.PianoRollKeys));
155
+ effect(() => className(_el$, styles$2.PianoRollKeys));
222
156
  return _el$;
223
157
  })();
224
158
  };
@@ -232,149 +166,186 @@ const keys = Array.from({
232
166
  isBlack: blackKeys.includes(index % 12)
233
167
  }));
234
168
 
235
- const _tmpl$$4 = /*#__PURE__*/template(`<div class="PianoRoll-Notes-Container"><div class="PianoRoll-Notes"></div></div>`, 4),
236
- _tmpl$2$1 = /*#__PURE__*/template(`<div class="PianoRoll-Note"></div>`, 2);
169
+ var css_248z$1 = ".PianoRollNotes-module_PianoRollNotes__6pF-y {\n position: absolute;\n flex: 1;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n.PianoRollNotes-module_PianoRollNotes__6pF-y .PianoRollNotes-module_Note__-jxLb {\n position: absolute;\n box-sizing: border-box;\n border-width: 0.5px;\n border-style: solid;\n border-color: #a00;\n cursor: pointer;\n}\n.PianoRollNotes-module_PianoRollNotes__6pF-y .PianoRollNotes-module_Note__-jxLb.PianoRollNotes-module_trimStart__vuBlj {\n cursor: w-resize;\n}\n.PianoRollNotes-module_PianoRollNotes__6pF-y .PianoRollNotes-module_Note__-jxLb.PianoRollNotes-module_trimEnd__zPdjr {\n cursor: e-resize;\n}";
170
+ var styles$1 = {"PianoRollNotes":"PianoRollNotes-module_PianoRollNotes__6pF-y","Note":"PianoRollNotes-module_Note__-jxLb","trimStart":"PianoRollNotes-module_trimStart__vuBlj","trimEnd":"PianoRollNotes-module_trimEnd__zPdjr"};
171
+ styleInject(css_248z$1);
172
+
173
+ const _tmpl$$5 = /*#__PURE__*/template(`<div></div>`, 2);
237
174
  const PianoRollNotes = props => {
238
175
  const context = usePianoRollContext();
176
+ const verticalViewPort = createMemo(() => useViewPortDimension("vertical"));
177
+ const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
239
178
  const gridDivisionTicks = createMemo(() => context.ppq * 4 / context.gridDivision);
240
179
  const snapValueToGridIfEnabled = (value, altKey) => context.snapToGrid && !altKey ? Math.round(value / gridDivisionTicks()) * gridDivisionTicks() : value;
180
+ const insertOrUpdateNote = event => {
181
+ const position = horizontalViewPort().calculatePosition(event.clientX);
182
+ const midi = 127 - Math.floor(verticalViewPort().calculatePosition(event.clientY));
183
+ const eventPositionTicks = Math.floor(position / gridDivisionTicks()) * gridDivisionTicks();
184
+ const velocity = 127;
185
+ const existingNote = newNote();
186
+ const ticks = existingNote?.ticks ?? eventPositionTicks;
187
+ const durationTicks = existingNote?.ticks ? eventPositionTicks - existingNote?.ticks : gridDivisionTicks();
188
+ const note = {
189
+ midi,
190
+ ticks,
191
+ durationTicks,
192
+ velocity
193
+ };
194
+ if (existingNote) {
195
+ context.onNoteChange?.(newNoteIndex(), note);
196
+ return newNoteIndex();
197
+ } else {
198
+ return context.onInsertNote?.(note) ?? -1;
199
+ }
200
+ };
201
+ const [isDragging, setIsDragging] = createSignal(false);
202
+ const [noteDragMode, setNoteDragMode] = createSignal();
203
+ const [newNoteIndex, setNewNoteIndex] = createSignal(-1);
204
+ const [isMouseDown, setIsMouseDown] = createSignal(false);
205
+ const newNote = createMemo(() => context.notes[newNoteIndex()]);
206
+ const getClasses = noteDragMode => {
207
+ return noteDragMode ? [styles$1.Note, styles$1[noteDragMode]] : [styles$1.Note];
208
+ };
241
209
  return (() => {
242
- const _el$ = _tmpl$$4.cloneNode(true),
243
- _el$2 = _el$.firstChild;
244
- _el$.style.setProperty("position", "absolute");
210
+ const _el$ = _tmpl$$5.cloneNode(true);
211
+ _el$.$$click = event => {
212
+ if (newNote()) {
213
+ setNewNoteIndex(-1);
214
+ return;
215
+ }
216
+ if (!event.altKey) return;
217
+ insertOrUpdateNote(event);
218
+ };
219
+ _el$.$$dblclick = event => {
220
+ insertOrUpdateNote(event);
221
+ };
222
+ _el$.$$mouseup = event => {
223
+ setIsMouseDown(false);
224
+ if (!event.altKey) return;
225
+ insertOrUpdateNote(event);
226
+ };
227
+ _el$.$$mousemove = event => {
228
+ if (isDragging()) return;
229
+ if (!isMouseDown()) {
230
+ setNoteDragMode(undefined);
231
+ return;
232
+ }
233
+ const index = insertOrUpdateNote(event);
234
+ console.log({
235
+ index
236
+ });
237
+ setNewNoteIndex(index);
238
+ };
239
+ _el$.$$mousedown = () => {
240
+ console.log("down");
241
+ setIsMouseDown(true);
242
+ };
245
243
  const _ref$ = props.ref;
246
- typeof _ref$ === "function" ? use(_ref$, _el$2) : props.ref = _el$2;
247
- _el$2.style.setProperty("position", "relative");
248
- _el$2.style.setProperty("height", "100%");
249
- insert(_el$2, createComponent(Index, {
244
+ typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$;
245
+ insert(_el$, createComponent(Index, {
250
246
  get each() {
251
247
  return context.notes;
252
248
  },
253
249
  children: (note, index) => {
254
- const horizontalVirtualDimensions = createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - note().midi, 1));
255
- const verticalVirtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(note().ticks, note().durationTicks));
250
+ const verticalVirtualDimensionss = createMemo(() => verticalViewPort().calculatePixelDimensions(127 - note().midi, 1));
251
+ const horizontalDimensions = createMemo(() => horizontalViewPort().calculatePixelDimensions(note().ticks, note().durationTicks));
256
252
  return createComponent(Show, {
257
253
  get when() {
258
- return memo(() => !!!!horizontalVirtualDimensions().size)() && !!verticalVirtualDimensions().size;
254
+ return memo(() => !!!!verticalVirtualDimensionss().size)() && !!horizontalDimensions().size;
259
255
  },
260
256
  get children() {
261
- const _el$3 = _tmpl$2$1.cloneNode(true);
262
- _el$3.$$mousedown = event => {
263
- context.onIsDraggingChange(true);
264
- const initialPosition = context.horizontalViewPort.getPosition(event.clientX);
257
+ const _el$2 = _tmpl$$5.cloneNode(true);
258
+ _el$2.$$mousedown = event => {
259
+ event.stopPropagation();
260
+ setIsDragging(true);
261
+ const initialPosition = horizontalViewPort().calculatePosition(event.clientX);
265
262
  const diffPosition = initialPosition - note().ticks;
266
263
  const handleMouseMove = mouseMoveEvent => {
267
- const ticks = snapValueToGridIfEnabled(Math.max(context.horizontalViewPort.getPosition(mouseMoveEvent.clientX) - diffPosition, 0), mouseMoveEvent.altKey);
264
+ const ticks = snapValueToGridIfEnabled(Math.max(horizontalViewPort().calculatePosition(mouseMoveEvent.clientX) - diffPosition, 0), mouseMoveEvent.altKey);
268
265
  context.onNoteChange?.(index, {
269
266
  ...note(),
270
- ...(context.noteDragMode === "move" && {
271
- midi: Math.round(127 - context.verticalViewPort.getPosition(mouseMoveEvent.clientY))
267
+ ...(noteDragMode() === "move" && {
268
+ midi: Math.round(127 - verticalViewPort().calculatePosition(mouseMoveEvent.clientY))
272
269
  }),
273
- ...((context.noteDragMode === "move" || context.noteDragMode === "trimStart") && {
270
+ ...((noteDragMode() === "move" || noteDragMode() === "trimStart") && {
274
271
  ticks
275
272
  }),
276
- ...(context.noteDragMode === "trimStart" && {
273
+ ...(noteDragMode() === "trimStart" && {
277
274
  durationTicks: note().durationTicks + note().ticks - ticks
278
275
  }),
279
- ...(context.noteDragMode === "trimEnd" && {
280
- durationTicks: snapValueToGridIfEnabled(context.horizontalViewPort.getPosition(mouseMoveEvent.clientX) - note().ticks, mouseMoveEvent.altKey)
276
+ ...(noteDragMode() === "trimEnd" && {
277
+ durationTicks: snapValueToGridIfEnabled(horizontalViewPort().calculatePosition(mouseMoveEvent.clientX) - note().ticks, mouseMoveEvent.altKey)
281
278
  })
282
279
  });
283
280
  };
284
281
  const handleMouseUp = () => {
285
- context.onIsDraggingChange(false);
282
+ setIsDragging(false);
286
283
  window.removeEventListener("mousemove", handleMouseMove);
287
284
  window.removeEventListener("mouseup", handleMouseUp);
288
285
  };
289
286
  window.addEventListener("mousemove", handleMouseMove);
290
287
  window.addEventListener("mouseup", handleMouseUp);
291
288
  };
292
- _el$3.$$dblclick = () => {
289
+ _el$2.$$dblclick = event => {
290
+ event.stopPropagation();
293
291
  context.onRemoveNote?.(index);
294
292
  };
295
- _el$3.$$mousemove = event => {
296
- if (context.isDragging) return;
297
- const relativeX = context.horizontalViewPort.getScaledValue(context.horizontalViewPort.getPosition(event.clientX));
298
- const noteStartX = context.horizontalViewPort.getScaledValue(note().ticks);
299
- const noteEndX = context.horizontalViewPort.getScaledValue(note().ticks + note().durationTicks);
300
- context.onNoteDragModeChange(relativeX - noteStartX < 3 ? "trimStart" : noteEndX - relativeX < 3 ? "trimEnd" : "move");
293
+ _el$2.$$mousemove = event => {
294
+ if (isDragging()) return;
295
+ event.stopPropagation();
296
+ const relativeX = horizontalViewPort().calculatePixelValue(horizontalViewPort().calculatePosition(event.clientX));
297
+ const noteStartX = horizontalViewPort().calculatePixelValue(note().ticks);
298
+ const noteEndX = horizontalViewPort().calculatePixelValue(note().ticks + note().durationTicks);
299
+ setNoteDragMode(relativeX - noteStartX < 3 ? "trimStart" : noteEndX - relativeX < 3 ? "trimEnd" : "move");
301
300
  };
302
- _el$3.style.setProperty("z-index", "2");
303
- _el$3.style.setProperty("position", "absolute");
304
- _el$3.style.setProperty("box-sizing", "border-box");
305
- _el$3.style.setProperty("border-width", "0.5px");
306
- _el$3.style.setProperty("border-style", "solid");
307
- _el$3.style.setProperty("border-color", "#a00");
308
301
  effect(_p$ => {
309
- const _v$4 = `${horizontalVirtualDimensions().offset}px`,
310
- _v$5 = `${horizontalVirtualDimensions().size}px`,
311
- _v$6 = `${verticalVirtualDimensions().offset}px`,
312
- _v$7 = `${verticalVirtualDimensions().size}px`,
313
- _v$8 = `rgba(255,0,0, ${(128 + note().velocity) / 256})`;
314
- _v$4 !== _p$._v$4 && _el$3.style.setProperty("top", _p$._v$4 = _v$4);
315
- _v$5 !== _p$._v$5 && _el$3.style.setProperty("height", _p$._v$5 = _v$5);
316
- _v$6 !== _p$._v$6 && _el$3.style.setProperty("left", _p$._v$6 = _v$6);
317
- _v$7 !== _p$._v$7 && _el$3.style.setProperty("width", _p$._v$7 = _v$7);
318
- _v$8 !== _p$._v$8 && _el$3.style.setProperty("background-color", _p$._v$8 = _v$8);
302
+ const _v$ = getClasses(noteDragMode()).join(" "),
303
+ _v$2 = `rgba(255,0,0, ${(128 + note().velocity) / 256})`,
304
+ _v$3 = `${verticalVirtualDimensionss().offset}px`,
305
+ _v$4 = `${verticalVirtualDimensionss().size}px`,
306
+ _v$5 = `${horizontalDimensions().offset}px`,
307
+ _v$6 = `${horizontalDimensions().size}px`;
308
+ _v$ !== _p$._v$ && className(_el$2, _p$._v$ = _v$);
309
+ _v$2 !== _p$._v$2 && _el$2.style.setProperty("background-color", _p$._v$2 = _v$2);
310
+ _v$3 !== _p$._v$3 && _el$2.style.setProperty("top", _p$._v$3 = _v$3);
311
+ _v$4 !== _p$._v$4 && _el$2.style.setProperty("height", _p$._v$4 = _v$4);
312
+ _v$5 !== _p$._v$5 && _el$2.style.setProperty("left", _p$._v$5 = _v$5);
313
+ _v$6 !== _p$._v$6 && _el$2.style.setProperty("width", _p$._v$6 = _v$6);
319
314
  return _p$;
320
315
  }, {
316
+ _v$: undefined,
317
+ _v$2: undefined,
318
+ _v$3: undefined,
321
319
  _v$4: undefined,
322
320
  _v$5: undefined,
323
- _v$6: undefined,
324
- _v$7: undefined,
325
- _v$8: undefined
321
+ _v$6: undefined
326
322
  });
327
- return _el$3;
323
+ return _el$2;
328
324
  }
329
325
  });
330
326
  }
331
327
  }));
332
- effect(_p$ => {
333
- const _v$ = `${context.clientRect.width}px`,
334
- _v$2 = `${context.clientRect.height}px`,
335
- _v$3 = `${context.clientRect.width}px`;
336
- _v$ !== _p$._v$ && _el$.style.setProperty("width", _p$._v$ = _v$);
337
- _v$2 !== _p$._v$2 && _el$.style.setProperty("height", _p$._v$2 = _v$2);
338
- _v$3 !== _p$._v$3 && _el$2.style.setProperty("width", _p$._v$3 = _v$3);
339
- return _p$;
340
- }, {
341
- _v$: undefined,
342
- _v$2: undefined,
343
- _v$3: undefined
344
- });
328
+ effect(() => className(_el$, styles$1.PianoRollNotes));
345
329
  return _el$;
346
330
  })();
347
331
  };
348
- delegateEvents(["mousemove", "dblclick", "mousedown"]);
332
+ delegateEvents(["mousedown", "mousemove", "mouseup", "dblclick", "click"]);
349
333
 
350
- const _tmpl$$3 = /*#__PURE__*/template(`<div class="PianoRoll-Vertical-Scroller"><div></div></div>`, 4);
334
+ const _tmpl$$4 = /*#__PURE__*/template(`<div class="PianoRoll-Scroller"><div><div><div></div></div></div></div>`, 8);
351
335
  const ScrollContainer = props => {
352
336
  let scrollContentRef;
353
- const context = usePianoRollContext();
354
- const gridDivisorTicks = createMemo(() => context.ppq * 4 / context.gridDivision);
355
- const forwardEventToNote = event => {
356
- const x = "clientX" in event ? event.clientX : event.touches[0]?.clientX ?? 0;
357
- const y = "clientY" in event ? event.clientY : event.touches[0]?.clientY ?? 0;
358
- const elementUnderMouse = [...(context.notesContainer?.querySelectorAll?.("div") ?? [])].find(element => {
359
- const rect = element.getBoundingClientRect();
360
- return x >= rect.left && rect.left + rect.width > x && y >= rect.top && rect.top + rect.height > y;
361
- });
362
- if (elementUnderMouse) {
363
- return elementUnderMouse.dispatchEvent(new MouseEvent(event.type, event));
364
- }
365
- };
337
+ const verticalViewPort = createMemo(() => useViewPortDimension("vertical"));
338
+ const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
366
339
  const handleScroll = event => {
367
340
  event.preventDefault();
368
341
  if (didUpdateScroll) {
369
342
  didUpdateScroll = false;
370
343
  return;
371
344
  }
372
- const maxVerticalPosition = context.verticalViewPort.getMaxPosition();
373
- const maxPosition = context.horizontalViewPort.getMaxPosition();
374
- const {
375
- width,
376
- height
377
- } = context.clientRect;
345
+ const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
346
+ const maxPosition = horizontalViewPort().calculateMaxPosition();
347
+ const height = verticalViewPort().pixelSize;
348
+ const width = horizontalViewPort().pixelSize;
378
349
  const {
379
350
  scrollTop,
380
351
  scrollLeft,
@@ -383,23 +354,35 @@ const ScrollContainer = props => {
383
354
  } = event.currentTarget;
384
355
  const scrollTopAmount = scrollTop / (scrollHeight - height);
385
356
  const scrollLeftAmount = scrollLeft / (scrollWidth - width);
386
- context.onVerticalPositionChange?.(maxVerticalPosition * scrollTopAmount);
387
- context.onPositionChange?.(maxPosition * scrollLeftAmount);
357
+ verticalViewPort().onPositionChange?.(maxVerticalPosition * scrollTopAmount);
358
+ horizontalViewPort().onPositionChange?.(maxPosition * scrollLeftAmount);
359
+ };
360
+ const handleWheel = event => {
361
+ if (event.altKey) {
362
+ event.preventDefault();
363
+ if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
364
+ horizontalViewPort()?.onZoomChange?.(horizontalViewPort().zoom * (1 + event.deltaX / horizontalViewPort().pixelSize));
365
+ const maxPosition = horizontalViewPort().calculateMaxPosition();
366
+ horizontalViewPort()?.onPositionChange?.(Math.min(maxPosition, horizontalViewPort()?.position));
367
+ } else {
368
+ verticalViewPort()?.onZoomChange?.(verticalViewPort().zoom * (1 + event.deltaY / verticalViewPort().pixelSize));
369
+ const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
370
+ verticalViewPort()?.onPositionChange?.(Math.min(maxVerticalPosition, verticalViewPort()?.position));
371
+ }
372
+ }
388
373
  };
389
374
  let didUpdateScroll = false;
390
375
  createEffect(() => {
391
- const maxVerticalPosition = context.verticalViewPort.getMaxPosition();
392
- const maxPosition = context.horizontalViewPort.getMaxPosition();
393
- const scrollTopAmount = maxVerticalPosition > 0 ? context.verticalPosition / maxVerticalPosition : 0;
394
- const scrollLeftAmount = maxPosition > 0 ? context.position / maxPosition : 0;
395
- const {
396
- height,
397
- width
398
- } = context.clientRect;
376
+ const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
377
+ const maxPosition = horizontalViewPort().calculateMaxPosition();
378
+ const scrollTopAmount = maxVerticalPosition > 0 ? verticalViewPort().position / maxVerticalPosition : 0;
379
+ const scrollLeftAmount = maxPosition > 0 ? horizontalViewPort().position / maxPosition : 0;
380
+ const height = verticalViewPort().pixelSize;
381
+ const width = horizontalViewPort().pixelSize;
399
382
  if (!scrollContentRef?.parentElement) return;
400
- const scrollDivHeight = context.verticalViewPort.getScrollSize();
383
+ const scrollDivHeight = verticalViewPort().zoom * verticalViewPort().pixelSize;
401
384
  const scrollTop = scrollTopAmount * (scrollDivHeight - height);
402
- const scrollDivWidth = context.horizontalViewPort.getScrollSize();
385
+ const scrollDivWidth = horizontalViewPort().zoom * horizontalViewPort().pixelSize;
403
386
  const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
404
387
  didUpdateScroll = true;
405
388
  scrollContentRef.style.height = `${scrollDivHeight}px`;
@@ -409,143 +392,71 @@ const ScrollContainer = props => {
409
392
  top: scrollTop
410
393
  });
411
394
  });
412
- const insertOrUpdateNote = event => {
413
- const position = context.horizontalViewPort.getPosition(event.clientX);
414
- const midi = 127 - Math.floor(context.verticalViewPort.getPosition(event.clientY));
415
- const eventPositionTicks = Math.floor(position / gridDivisorTicks()) * gridDivisorTicks();
416
- const velocity = 127;
417
- const existingNote = newNote();
418
- const ticks = existingNote?.ticks ?? eventPositionTicks;
419
- const durationTicks = existingNote?.ticks ? eventPositionTicks - existingNote?.ticks : gridDivisorTicks();
420
- const note = {
421
- midi,
422
- ticks,
423
- durationTicks,
424
- velocity
425
- };
426
- if (existingNote) {
427
- context.onNoteChange?.(newNoteIndex(), note);
428
- return newNoteIndex();
429
- } else {
430
- return context.onInsertNote?.(note) ?? -1;
431
- }
432
- };
433
- const [newNoteIndex, setNewNoteIndex] = createSignal(-1);
434
- const newNote = createMemo(() => context.notes[newNoteIndex()]);
435
- const [isMouseDown, setIsMouseDown] = createSignal(false);
436
395
  return (() => {
437
- const _el$ = _tmpl$$3.cloneNode(true),
438
- _el$2 = _el$.firstChild;
439
- _el$.addEventListener("dragend", forwardEventToNote);
440
- _el$.addEventListener("drag", forwardEventToNote);
441
- _el$.addEventListener("dragstart", forwardEventToNote);
442
- _el$.addEventListener("touchcancel", forwardEventToNote);
443
- _el$.$$touchend = forwardEventToNote;
444
- _el$.$$touchmove = forwardEventToNote;
445
- _el$.$$touchstart = forwardEventToNote;
446
- _el$.$$mouseup = event => {
447
- setIsMouseDown(false);
448
- if (newNote()) return;
449
- if (forwardEventToNote(event)) return;
450
- if (!event.altKey) return;
451
- insertOrUpdateNote(event);
452
- };
453
- _el$.$$click = event => {
454
- if (newNote()) {
455
- setNewNoteIndex(-1);
456
- return;
457
- }
458
- if (forwardEventToNote(event)) return;
459
- if (!event.altKey) return;
460
- insertOrUpdateNote(event);
461
- };
462
- _el$.$$dblclick = event => {
463
- if (newNote()) return;
464
- if (context.isDragging) return;
465
- if (forwardEventToNote(event)) return;
466
- insertOrUpdateNote(event);
467
- };
468
- _el$.$$mousemove = event => {
469
- event.preventDefault();
470
- if (forwardEventToNote(event)) return;
471
- if (context.isDragging) return;
472
- if (!isMouseDown()) {
473
- context.onNoteDragModeChange(undefined);
474
- return;
475
- }
476
- const index = insertOrUpdateNote(event);
477
- setNewNoteIndex(index);
478
- };
479
- _el$.$$mousedown = event => {
480
- if (forwardEventToNote(event)) return;
481
- setIsMouseDown(true);
482
- };
396
+ const _el$ = _tmpl$$4.cloneNode(true),
397
+ _el$2 = _el$.firstChild,
398
+ _el$3 = _el$2.firstChild,
399
+ _el$4 = _el$3.firstChild;
400
+ _el$.addEventListener("wheel", handleWheel);
483
401
  _el$.addEventListener("scroll", handleScroll);
484
402
  const _ref$ = props.ref;
485
403
  typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$;
404
+ _el$.style.setProperty("height", "100%");
405
+ _el$.style.setProperty("width", "100%");
406
+ _el$.style.setProperty("z-index", "4");
407
+ _el$.style.setProperty("overflow", "scroll");
408
+ _el$.style.setProperty("pointer-events", "auto");
486
409
  const _ref$2 = scrollContentRef;
487
410
  typeof _ref$2 === "function" ? use(_ref$2, _el$2) : scrollContentRef = _el$2;
488
- effect(_$p => style(_el$, {
489
- flex: 1,
490
- ...(context.noteDragMode && {
491
- cursor: context.noteDragMode === "trimStart" ? "w-resize" : context.noteDragMode === "trimEnd" ? "e-resize" : "pointer"
492
- }),
493
- "z-index": 4,
494
- overflow: "scroll",
495
- "pointer-events": "auto"
496
- }, _$p));
497
- return _el$;
498
- })();
499
- };
500
- delegateEvents(["mousedown", "mousemove", "dblclick", "click", "mouseup", "touchstart", "touchmove", "touchend"]);
501
-
502
- const _tmpl$$2 = /*#__PURE__*/template(`<input type="range" min="1" max="11" step="0.01">`, 1);
503
- const VerticalZoomControl = () => {
504
- const context = usePianoRollContext();
505
- return (() => {
506
- const _el$ = _tmpl$$2.cloneNode(true);
507
- _el$.$$input = event => context.onVerticalZoomChange?.(event.currentTarget.valueAsNumber);
508
- spread(_el$, mergeProps({
509
- get value() {
510
- return context.verticalZoom;
511
- }
512
- }, {
513
- orient: "vertical"
411
+ _el$3.style.setProperty("top", "0");
412
+ _el$3.style.setProperty("left", "0");
413
+ _el$3.style.setProperty("position", "sticky");
414
+ _el$3.style.setProperty("display", "flex");
415
+ _el$4.style.setProperty("position", "relative");
416
+ insert(_el$4, () => props.children);
417
+ effect(_p$ => {
418
+ const _v$ = `${verticalViewPort().pixelSize}px`,
419
+ _v$2 = `${horizontalViewPort().pixelSize}px`,
420
+ _v$3 = `${verticalViewPort().pixelSize}px`,
421
+ _v$4 = `${horizontalViewPort().pixelSize}px`;
422
+ _v$ !== _p$._v$ && _el$3.style.setProperty("height", _p$._v$ = _v$);
423
+ _v$2 !== _p$._v$2 && _el$3.style.setProperty("width", _p$._v$2 = _v$2);
424
+ _v$3 !== _p$._v$3 && _el$4.style.setProperty("height", _p$._v$3 = _v$3);
425
+ _v$4 !== _p$._v$4 && _el$4.style.setProperty("width", _p$._v$4 = _v$4);
426
+ return _p$;
514
427
  }, {
515
- get style() {
516
- return {
517
- width: "16px",
518
- ...{
519
- "writing-mode": "bt-lr" /* IE */,
520
- "-webkit-appearance": "slider-vertical" /* WebKit */
521
- }
522
- };
523
- }
524
- }), false, false);
428
+ _v$: undefined,
429
+ _v$2: undefined,
430
+ _v$3: undefined,
431
+ _v$4: undefined
432
+ });
525
433
  return _el$;
526
434
  })();
527
435
  };
528
- delegateEvents(["input"]);
529
436
 
530
- const _tmpl$$1 = /*#__PURE__*/template(`<div class="PianoRoll-Grid-Container"><div class="PianoRoll-Grid"></div></div>`, 4),
531
- _tmpl$2 = /*#__PURE__*/template(`<div class="PianoRoll-Grid-Time"></div>`, 2),
532
- _tmpl$3 = /*#__PURE__*/template(`<div class="PianoRoll-Grid-Key"></div>`, 2);
437
+ var css_248z = ".PianoRollGrid-module_PianoRollGrid__tG119 {\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.PianoRollGrid-module_PianoRollGrid-Key__VRbiB {\n position: absolute;\n border-style: solid;\n border-color: gray;\n}\n\n.PianoRollGrid-module_PianoRollGrid-Time__jLz3E {\n position: absolute;\n box-sizing: border-box;\n top: 0px;\n height: 100%;\n border-left-style: solid;\n border-left-width: 0.5px;\n}\n";
438
+ var styles = {"PianoRollGrid":"PianoRollGrid-module_PianoRollGrid__tG119","PianoRollGrid-Key":"PianoRollGrid-module_PianoRollGrid-Key__VRbiB","PianoRollGrid-Time":"PianoRollGrid-module_PianoRollGrid-Time__jLz3E"};
439
+ styleInject(css_248z);
440
+
441
+ const _tmpl$$3 = /*#__PURE__*/template(`<div></div>`, 2);
533
442
  const PianoRollGrid = () => {
534
443
  const context = usePianoRollContext();
444
+ const verticalViewPort = createMemo(() => useViewPortDimension("vertical"));
445
+ const horizontalViewPort = createMemo(() => useViewPortDimension("horizontal"));
535
446
  const measureTicks = createMemo(() => context.ppq * 4);
536
447
  const selectedGridDivisorTicks = createMemo(() => measureTicks() / context.gridDivision);
537
448
  function calculateVisibleGridDivisorTicks(value) {
538
- if (context.horizontalViewPort.getVisibleRange() / value > 100) {
449
+ if (horizontalViewPort().calculateVisibleRange() / value > 100) {
539
450
  return calculateVisibleGridDivisorTicks(value * 2);
540
451
  }
541
- if (context.horizontalViewPort.getVisibleRange() / value < 30) {
452
+ if (horizontalViewPort().calculateVisibleRange() / value < 30) {
542
453
  return calculateVisibleGridDivisorTicks(value / 2);
543
454
  }
544
455
  return value;
545
456
  }
546
457
  const gridDivisorTicks = createMemo(() => calculateVisibleGridDivisorTicks(selectedGridDivisorTicks()));
547
458
  const gridArray = createMemo(() => {
548
- const numberOfLines = Math.ceil(context.horizontalViewPort.getVisibleRange() / gridDivisorTicks()) + 1;
459
+ const numberOfLines = Math.ceil(horizontalViewPort().calculateVisibleRange() / gridDivisorTicks() + 1);
549
460
  const startIndex = Math.floor(context.position / gridDivisorTicks());
550
461
  return Array.from({
551
462
  length: numberOfLines
@@ -555,144 +466,235 @@ const PianoRollGrid = () => {
555
466
  }));
556
467
  });
557
468
  return (() => {
558
- const _el$ = _tmpl$$1.cloneNode(true),
559
- _el$2 = _el$.firstChild;
560
- _el$.style.setProperty("position", "absolute");
561
- _el$2.style.setProperty("position", "relative");
562
- _el$2.style.setProperty("height", "100%");
563
- insert(_el$2, createComponent(Index, {
469
+ const _el$ = _tmpl$$3.cloneNode(true);
470
+ insert(_el$, createComponent(Index, {
564
471
  get each() {
565
472
  return gridArray();
566
473
  },
567
474
  children: entry => {
568
- const virtualDimensions = createMemo(() => context.horizontalViewPort.getVirtualDimensions(entry().ticks, gridDivisorTicks()));
475
+ const virtualDimensions = createMemo(() => horizontalViewPort().calculatePixelDimensions(entry().ticks, gridDivisorTicks()));
569
476
  return createComponent(Show, {
570
477
  get when() {
571
478
  return virtualDimensions().size > 0;
572
479
  },
573
480
  get children() {
574
- const _el$3 = _tmpl$2.cloneNode(true);
575
- _el$3.style.setProperty("z-index", "1");
576
- _el$3.style.setProperty("position", "absolute");
577
- _el$3.style.setProperty("box-sizing", "border-box");
578
- _el$3.style.setProperty("top", "0px");
579
- _el$3.style.setProperty("height", "100%");
580
- _el$3.style.setProperty("border-left", "1px gray solid");
481
+ const _el$2 = _tmpl$$3.cloneNode(true);
581
482
  effect(_p$ => {
582
- const _v$4 = Math.ceil((entry().index + 1) * selectedGridDivisorTicks() / measureTicks()) % 2 === 0 ? "#ddd" : "#ccc",
583
- _v$5 = `${virtualDimensions().offset}px`,
584
- _v$6 = `${virtualDimensions().size}px`;
585
- _v$4 !== _p$._v$4 && _el$3.style.setProperty("background", _p$._v$4 = _v$4);
586
- _v$5 !== _p$._v$5 && _el$3.style.setProperty("left", _p$._v$5 = _v$5);
587
- _v$6 !== _p$._v$6 && _el$3.style.setProperty("width", _p$._v$6 = _v$6);
483
+ const _v$ = styles["PianoRollGrid-Time"],
484
+ _v$2 = Math.ceil((entry().index + 1 * selectedGridDivisorTicks()) / measureTicks()) % 2 === 0 ? "#ddd" : "#ccc",
485
+ _v$3 = `${virtualDimensions().offset}px`,
486
+ _v$4 = `${virtualDimensions().size}px`,
487
+ _v$5 = entry().index * gridDivisorTicks() % selectedGridDivisorTicks() === 0 ? "gray" : "#bbb";
488
+ _v$ !== _p$._v$ && className(_el$2, _p$._v$ = _v$);
489
+ _v$2 !== _p$._v$2 && _el$2.style.setProperty("background", _p$._v$2 = _v$2);
490
+ _v$3 !== _p$._v$3 && _el$2.style.setProperty("left", _p$._v$3 = _v$3);
491
+ _v$4 !== _p$._v$4 && _el$2.style.setProperty("width", _p$._v$4 = _v$4);
492
+ _v$5 !== _p$._v$5 && _el$2.style.setProperty("border-left-color", _p$._v$5 = _v$5);
588
493
  return _p$;
589
494
  }, {
495
+ _v$: undefined,
496
+ _v$2: undefined,
497
+ _v$3: undefined,
590
498
  _v$4: undefined,
591
- _v$5: undefined,
592
- _v$6: undefined
499
+ _v$5: undefined
593
500
  });
594
- return _el$3;
501
+ return _el$2;
595
502
  }
596
503
  });
597
504
  }
598
505
  }), null);
599
- insert(_el$2, createComponent(Index, {
600
- each: keys,
601
- children: key => {
602
- const virtualDimensions = createMemo(() => context.verticalViewPort.getVirtualDimensions(127 - key().number, 1));
603
- return createComponent(Show, {
604
- get when() {
605
- return virtualDimensions().size > 0;
606
- },
607
- get children() {
608
- const _el$4 = _tmpl$3.cloneNode(true);
609
- _el$4.style.setProperty("position", "absolute");
610
- _el$4.style.setProperty("width", "100%");
611
- _el$4.style.setProperty("border-style", "solid");
612
- _el$4.style.setProperty("border-color", "gray");
613
- _el$4.style.setProperty("z-index", "1");
614
- effect(_p$ => {
615
- const _v$7 = `${virtualDimensions().offset}px`,
616
- _v$8 = `${virtualDimensions().size}px`,
617
- _v$9 = key().isBlack ? "rgba(0,0,0,0.2)" : "none",
618
- _v$10 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
619
- _v$7 !== _p$._v$7 && _el$4.style.setProperty("top", _p$._v$7 = _v$7);
620
- _v$8 !== _p$._v$8 && _el$4.style.setProperty("height", _p$._v$8 = _v$8);
621
- _v$9 !== _p$._v$9 && _el$4.style.setProperty("background-color", _p$._v$9 = _v$9);
622
- _v$10 !== _p$._v$10 && _el$4.style.setProperty("border-width", _p$._v$10 = _v$10);
623
- return _p$;
624
- }, {
625
- _v$7: undefined,
626
- _v$8: undefined,
627
- _v$9: undefined,
628
- _v$10: undefined
506
+ insert(_el$, createComponent(Show, {
507
+ get when() {
508
+ return !context.condensed;
509
+ },
510
+ get children() {
511
+ return createComponent(Index, {
512
+ each: keys,
513
+ children: key => {
514
+ const virtualDimensions = createMemo(() => verticalViewPort().calculatePixelDimensions(127 - key().number, 1));
515
+ return createComponent(Show, {
516
+ get when() {
517
+ return virtualDimensions().size > 0;
518
+ },
519
+ get children() {
520
+ const _el$3 = _tmpl$$3.cloneNode(true);
521
+ _el$3.style.setProperty("width", "100%");
522
+ effect(_p$ => {
523
+ const _v$6 = styles["PianoRollGrid-Key"],
524
+ _v$7 = `${virtualDimensions().offset}px`,
525
+ _v$8 = `${virtualDimensions().size}px`,
526
+ _v$9 = key().isBlack ? "rgba(0,0,0,0.2)" : "none",
527
+ _v$10 = `${!key().isBlack && !blackKeys.includes((key().number + 1) % 12) ? "0.1px" : 0} 1px ${!key().isBlack && !blackKeys.includes((key().number - 1) % 12) ? "0.1px" : 0} 0`;
528
+ _v$6 !== _p$._v$6 && className(_el$3, _p$._v$6 = _v$6);
529
+ _v$7 !== _p$._v$7 && _el$3.style.setProperty("top", _p$._v$7 = _v$7);
530
+ _v$8 !== _p$._v$8 && _el$3.style.setProperty("height", _p$._v$8 = _v$8);
531
+ _v$9 !== _p$._v$9 && _el$3.style.setProperty("background-color", _p$._v$9 = _v$9);
532
+ _v$10 !== _p$._v$10 && _el$3.style.setProperty("border-width", _p$._v$10 = _v$10);
533
+ return _p$;
534
+ }, {
535
+ _v$6: undefined,
536
+ _v$7: undefined,
537
+ _v$8: undefined,
538
+ _v$9: undefined,
539
+ _v$10: undefined
540
+ });
541
+ return _el$3;
542
+ }
629
543
  });
630
- return _el$4;
631
544
  }
632
545
  });
633
546
  }
634
547
  }), null);
635
- effect(_p$ => {
636
- const _v$ = `${context.clientRect.width}px`,
637
- _v$2 = `${context.clientRect.height}px`,
638
- _v$3 = `${context.clientRect.width}px`;
639
- _v$ !== _p$._v$ && _el$.style.setProperty("width", _p$._v$ = _v$);
640
- _v$2 !== _p$._v$2 && _el$.style.setProperty("height", _p$._v$2 = _v$2);
641
- _v$3 !== _p$._v$3 && _el$2.style.setProperty("width", _p$._v$3 = _v$3);
642
- return _p$;
548
+ effect(() => className(_el$, styles.PianoRollGrid));
549
+ return _el$;
550
+ })();
551
+ };
552
+
553
+ const defaultRect = {
554
+ left: 0,
555
+ width: 0,
556
+ top: 0,
557
+ height: 0,
558
+ bottom: 0,
559
+ right: 0,
560
+ x: 0,
561
+ y: 0
562
+ };
563
+ function useBoundingClientRect(containerRef) {
564
+ const [boundingClientRect, setBoundingClientRect] = createSignal(defaultRect);
565
+ createEffect(() => {
566
+ const container = containerRef();
567
+ if (!container) return;
568
+ const updateBoundingClientRect = () => {
569
+ setBoundingClientRect(container.getBoundingClientRect() ?? defaultRect);
570
+ };
571
+ const resizeObserver = new ResizeObserver(updateBoundingClientRect);
572
+ resizeObserver.observe(container);
573
+ updateBoundingClientRect();
574
+ return () => {
575
+ resizeObserver.disconnect();
576
+ };
577
+ });
578
+ return boundingClientRect;
579
+ }
580
+
581
+ const _tmpl$$2 = /*#__PURE__*/template(`<input min="1" max="11" step="0.01">`, 1);
582
+ const VerticalZoomControl = props => {
583
+ const context = usePianoRollContext();
584
+ return (() => {
585
+ const _el$ = _tmpl$$2.cloneNode(true);
586
+ spread(_el$, mergeProps(props, {
587
+ get value() {
588
+ return context.verticalZoom;
589
+ },
590
+ "onInput": event => context.onVerticalZoomChange?.(event.currentTarget.valueAsNumber),
591
+ "type": "range"
643
592
  }, {
644
- _v$: undefined,
645
- _v$2: undefined,
646
- _v$3: undefined
647
- });
593
+ orient: "vertical"
594
+ }, {
595
+ get style() {
596
+ return {
597
+ width: "16px",
598
+ ...{
599
+ "writing-mode": "bt-lr" /* IE */,
600
+ "-webkit-appearance": "slider-vertical" /* WebKit */
601
+ }
602
+ };
603
+ }
604
+ }), false, false);
648
605
  return _el$;
649
606
  })();
650
607
  };
651
608
 
652
- const _tmpl$ = /*#__PURE__*/template(`<div><div><div></div></div></div>`, 6);
609
+ const _tmpl$$1 = /*#__PURE__*/template(`<input max="500" min="1" step="0.01">`, 1);
610
+ const HorizontalZoomControl = props => {
611
+ const context = usePianoRollContext();
612
+ return (() => {
613
+ const _el$ = _tmpl$$1.cloneNode(true);
614
+ spread(_el$, mergeProps(props, {
615
+ get value() {
616
+ return context.zoom;
617
+ },
618
+ "onInput": event => context.onZoomChange?.(event.currentTarget.valueAsNumber),
619
+ "type": "range",
620
+ "style": {
621
+ "margin-left": "50px",
622
+ "margin-right": "16px"
623
+ }
624
+ }), false, false);
625
+ return _el$;
626
+ })();
627
+ };
628
+
629
+ const _tmpl$ = /*#__PURE__*/template(`<div><div></div></div>`, 4);
653
630
  const PianoRoll = allProps => {
654
631
  const [contextProps, divProps] = splitProps(allProps, ["ppq", "notes", "position", "duration", "zoom", "verticalPosition", "verticalZoom", "onVerticalZoomChange", "onVerticalPositionChange", "onZoomChange", "onPositionChange", "onNoteChange", "gridDivision", "snapToGrid", "onInsertNote", "onRemoveNote"]);
655
- const [scrollContainer, setScrollContainer] = createSignal();
656
- const [notesContainer, setNotesContainer] = createSignal();
657
- return createComponent(PianoRollContextProvider, mergeProps(contextProps, {
658
- get scrollContainer() {
659
- return scrollContainer();
660
- },
661
- get notesContainer() {
662
- return notesContainer();
663
- },
632
+ const [scrollerRef, setScrollerRef] = createSignal();
633
+ const clientRect = useBoundingClientRect(scrollerRef);
634
+ const zoomFactor = 500;
635
+ const minZoom = createMemo(() => 1 / (zoomFactor / clientRect().width));
636
+ const maxZoom = createMemo(() => 500 * (zoomFactor / clientRect().width));
637
+ const minVerticalZoom = createMemo(() => 1 / (zoomFactor / clientRect().height));
638
+ const maxVerticalZoom = createMemo(() => 10 * (zoomFactor / clientRect().height));
639
+ return createComponent(PianoRollContextProvider, {
640
+ value: contextProps,
664
641
  get children() {
665
642
  const _el$ = _tmpl$.cloneNode(true),
666
- _el$2 = _el$.firstChild,
667
- _el$3 = _el$2.firstChild;
643
+ _el$2 = _el$.firstChild;
668
644
  spread(_el$, mergeProps(divProps, {
669
645
  get ["class"]() {
670
- return styles$1.PianoRoll;
646
+ return styles$3.PianoRoll;
671
647
  }
672
648
  }), false, true);
673
- insert(_el$2, createComponent(PianoRollKeys, {}), _el$3);
674
- insert(_el$3, createComponent(PianoRollNotes, {
675
- ref: setNotesContainer
676
- }), null);
677
- insert(_el$3, createComponent(PianoRollGrid, {}), null);
678
- insert(_el$3, createComponent(ScrollContainer, {
679
- ref: setScrollContainer
649
+ insert(_el$2, createComponent(ScrollZoomViewPort, {
650
+ dimensions: {
651
+ horizontal: () => ({
652
+ pixelOffset: clientRect().left,
653
+ pixelSize: clientRect().width,
654
+ position: contextProps.position,
655
+ range: contextProps.duration,
656
+ zoom: contextProps.zoom * (zoomFactor / clientRect().width),
657
+ onPositionChange: contextProps.onPositionChange,
658
+ onZoomChange: zoom => contextProps.onZoomChange?.(clamp(zoom / (zoomFactor / clientRect().width), minZoom(), maxZoom()))
659
+ }),
660
+ vertical: () => ({
661
+ pixelOffset: clientRect().top,
662
+ pixelSize: clientRect().height,
663
+ position: contextProps.verticalPosition,
664
+ range: 128,
665
+ zoom: contextProps.verticalZoom * (zoomFactor / clientRect().height),
666
+ onPositionChange: contextProps.onVerticalPositionChange,
667
+ onZoomChange: verticalZoom => contextProps.onVerticalZoomChange?.(clamp(verticalZoom / (zoomFactor / clientRect().height), minVerticalZoom(), maxVerticalZoom()))
668
+ })
669
+ },
670
+ get children() {
671
+ return [createComponent(PianoRollKeys, {}), createComponent(ScrollContainer, {
672
+ ref: setScrollerRef,
673
+ get children() {
674
+ return [createComponent(PianoRollGrid, {}), createComponent(PianoRollNotes, {})];
675
+ }
676
+ }), createComponent(VerticalZoomControl, {
677
+ get min() {
678
+ return minVerticalZoom();
679
+ },
680
+ get max() {
681
+ return maxVerticalZoom();
682
+ }
683
+ })];
684
+ }
685
+ }));
686
+ insert(_el$, createComponent(HorizontalZoomControl, {
687
+ get min() {
688
+ return minZoom();
689
+ },
690
+ get max() {
691
+ return maxZoom();
692
+ }
680
693
  }), null);
681
- insert(_el$2, createComponent(VerticalZoomControl, {}), null);
682
- insert(_el$, createComponent(HorizontalZoomControl, {}), null);
683
- effect(_p$ => {
684
- const _v$ = styles$1.PianoRollContainer,
685
- _v$2 = styles$1.PianoRollInnerContainer;
686
- _v$ !== _p$._v$ && className(_el$2, _p$._v$ = _v$);
687
- _v$2 !== _p$._v$2 && className(_el$3, _p$._v$2 = _v$2);
688
- return _p$;
689
- }, {
690
- _v$: undefined,
691
- _v$2: undefined
692
- });
694
+ effect(() => className(_el$2, styles$3.PianoRollContainer));
693
695
  return _el$;
694
696
  }
695
- }));
697
+ });
696
698
  };
697
699
 
698
700
  const usePianoRoll = () => {
@@ -704,7 +706,7 @@ const usePianoRoll = () => {
704
706
  const [gridDivision, onGridDivisionChange] = createSignal(16);
705
707
  const [snapToGrid, onSnapToGridChange] = createSignal(true);
706
708
  const [notes, onNotesChange] = createSignal([]);
707
- const [duration, setDuration] = createSignal(0);
709
+ const [duration, onDurationChange] = createSignal(0);
708
710
  const onNoteChange = (index, note) => {
709
711
  onNotesChange([...notes().slice(0, index), note, ...notes().splice(index + 1)]);
710
712
  };
@@ -719,7 +721,10 @@ const usePianoRoll = () => {
719
721
  onNotesChange([...notes().slice(0, index), ...notes().splice(index + 1)]);
720
722
  };
721
723
  createEffect(() => {
722
- setDuration(Math.max((notes()[notes().length - 1]?.ticks ?? 0) + (notes()[notes().length - 1]?.durationTicks ?? 0), ppq() * 4 * 4));
724
+ const minDuration = Math.max((notes()[notes().length - 1]?.ticks ?? 0) + (notes()[notes().length - 1]?.durationTicks ?? 0), ppq() * 4 * 4);
725
+ if (duration() < minDuration) {
726
+ onDurationChange(minDuration);
727
+ }
723
728
  });
724
729
  return {
725
730
  ppq,
@@ -741,7 +746,8 @@ const usePianoRoll = () => {
741
746
  onNoteChange,
742
747
  onInsertNote,
743
748
  onRemoveNote,
744
- duration
749
+ duration,
750
+ onDurationChange
745
751
  };
746
752
  };
747
753