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