solid-pianoroll 0.0.23 → 0.0.25

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 (54) hide show
  1. package/dist/{cjs/index.js → dev.cjs} +446 -426
  2. package/dist/dev.css +212 -0
  3. package/dist/dev.js +1330 -0
  4. package/dist/dev.jsx +1130 -0
  5. package/dist/index.cjs +1330 -0
  6. package/dist/index.css +212 -0
  7. package/dist/index.d.ts +105 -0
  8. package/dist/{esm/index.js → index.js} +450 -425
  9. package/dist/index.jsx +1130 -0
  10. package/package.json +43 -34
  11. package/dist/cjs/index.js.map +0 -1
  12. package/dist/esm/index.js.map +0 -1
  13. package/dist/source/PianoRoll.jsx +0 -78
  14. package/dist/source/PianoRollContext.jsx +0 -11
  15. package/dist/source/PianoRollGrid.jsx +0 -64
  16. package/dist/source/PianoRollKeys.jsx +0 -72
  17. package/dist/source/PianoRollNotes.jsx +0 -162
  18. package/dist/source/PianoRollNotesScroller.jsx +0 -16
  19. package/dist/source/PianoRollScale.jsx +0 -29
  20. package/dist/source/PianoRollScrollZoomViewPort.jsx +0 -49
  21. package/dist/source/PianoRollTrackList.jsx +0 -27
  22. package/dist/source/PianoRollTrackListScroller.jsx +0 -16
  23. package/dist/source/index.jsx +0 -5
  24. package/dist/source/types.js +0 -1
  25. package/dist/source/useBoundingClientRect.js +0 -29
  26. package/dist/source/useNotes.js +0 -23
  27. package/dist/source/usePianoRollGrid.js +0 -45
  28. package/dist/source/usePianoRollState.js +0 -106
  29. package/dist/source/viewport/PlayHead.jsx +0 -43
  30. package/dist/source/viewport/ScrollZoomContainer.jsx +0 -104
  31. package/dist/source/viewport/ScrollZoomViewPort.jsx +0 -31
  32. package/dist/source/viewport/ZoomSliderControl.jsx +0 -29
  33. package/dist/source/viewport/createViewPortDimension.js +0 -51
  34. package/dist/types/PianoRoll.d.ts +0 -9
  35. package/dist/types/PianoRollContext.d.ts +0 -122
  36. package/dist/types/PianoRollGrid.d.ts +0 -2
  37. package/dist/types/PianoRollKeys.d.ts +0 -9
  38. package/dist/types/PianoRollNotes.d.ts +0 -5
  39. package/dist/types/PianoRollNotesScroller.d.ts +0 -3
  40. package/dist/types/PianoRollScale.d.ts +0 -2
  41. package/dist/types/PianoRollScrollZoomViewPort.d.ts +0 -3
  42. package/dist/types/PianoRollTrackList.d.ts +0 -2
  43. package/dist/types/PianoRollTrackListScroller.d.ts +0 -3
  44. package/dist/types/index.d.ts +0 -5
  45. package/dist/types/types.d.ts +0 -6
  46. package/dist/types/useBoundingClientRect.d.ts +0 -3
  47. package/dist/types/useNotes.d.ts +0 -9
  48. package/dist/types/usePianoRollGrid.d.ts +0 -13
  49. package/dist/types/usePianoRollState.d.ts +0 -63
  50. package/dist/types/viewport/PlayHead.d.ts +0 -8
  51. package/dist/types/viewport/ScrollZoomContainer.d.ts +0 -8
  52. package/dist/types/viewport/ScrollZoomViewPort.d.ts +0 -39
  53. package/dist/types/viewport/ZoomSliderControl.d.ts +0 -6
  54. package/dist/types/viewport/createViewPortDimension.d.ts +0 -40
package/dist/dev.jsx ADDED
@@ -0,0 +1,1130 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/PianoRoll.module.css
5
+ var _default = {};
6
+
7
+ // src/PianoRoll.tsx
8
+ import { Show as Show6, mergeProps as mergeProps4 } from "solid-js";
9
+
10
+ // src/PianoRollContext.tsx
11
+ import { createContext, splitProps, useContext } from "solid-js";
12
+
13
+ // src/usePianoRollState.ts
14
+ import { mergeProps } from "solid-js";
15
+ import { createStore } from "solid-js/store";
16
+ var defaultState = {
17
+ ppq: 0,
18
+ mode: "keys",
19
+ position: 0,
20
+ zoom: 10,
21
+ verticalZoom: 5,
22
+ verticalPosition: 44,
23
+ verticalTrackZoom: 0.5,
24
+ verticalTrackPosition: 0,
25
+ playHeadPosition: 0,
26
+ gridDivision: 4,
27
+ snapToGrid: true,
28
+ duration: 0,
29
+ tracks: [],
30
+ selectedTrackIndex: 0,
31
+ pressedKeys: {},
32
+ notesScrollerClientRect: { left: 0, width: 0, top: 0, height: 0 },
33
+ tracksScrollerClientRect: { left: 0, width: 0, top: 0, height: 0 }
34
+ };
35
+ var propNameToHandlerName = /* @__PURE__ */ __name((name) => `on${name[0]?.toUpperCase()}${name.slice(1)}Change`, "propNameToHandlerName");
36
+ var pianoRollStatePropNames = [
37
+ ...Object.keys(defaultState),
38
+ ...Object.keys(defaultState).map(propNameToHandlerName),
39
+ "onNoteChange",
40
+ "onInsertNote",
41
+ "onRemoveNote",
42
+ "onNoteDown",
43
+ "onNoteUp",
44
+ "isKeyDown",
45
+ "snapValueToGridIfEnabled"
46
+ ];
47
+ var createPianoRollstate = /* @__PURE__ */ __name((initialState) => {
48
+ const [state, setState] = createStore({
49
+ ...defaultState,
50
+ ...initialState
51
+ });
52
+ const handlers = Object.fromEntries(
53
+ Object.entries(state).map((entry) => {
54
+ const handlerName = propNameToHandlerName(entry[0]);
55
+ return [
56
+ handlerName,
57
+ (value, originalEvent) => {
58
+ const handler = initialState?.[handlerName];
59
+ setState(entry[0], value);
60
+ if (handler) {
61
+ handler(value, originalEvent);
62
+ }
63
+ }
64
+ ];
65
+ })
66
+ );
67
+ const onPlayheadPositionChange = /* @__PURE__ */ __name((playheadPosition, originalEvent) => {
68
+ handlers.onPlayHeadPositionChange(
69
+ snapValueToGridIfEnabled(playheadPosition, !!originalEvent?.altKey)
70
+ );
71
+ }, "onPlayheadPositionChange");
72
+ const updateNotes = /* @__PURE__ */ __name(async (trackIndex, getNotes) => {
73
+ const track = state.tracks[trackIndex];
74
+ if (!track)
75
+ return;
76
+ const notes = track.notes;
77
+ handlers.onTracksChange([
78
+ ...state.tracks.slice(0, trackIndex),
79
+ { ...track, notes: getNotes(notes) },
80
+ ...state.tracks.slice(trackIndex + 1)
81
+ ]);
82
+ }, "updateNotes");
83
+ const onNoteChange = /* @__PURE__ */ __name((trackIndex, noteIndex, note) => {
84
+ updateNotes(trackIndex, (notes) => [
85
+ ...notes.slice(0, noteIndex),
86
+ note,
87
+ ...notes.slice(noteIndex + 1)
88
+ ]);
89
+ }, "onNoteChange");
90
+ const onInsertNote = /* @__PURE__ */ __name((trackIndex, note) => {
91
+ const track = state.tracks[trackIndex];
92
+ if (!track)
93
+ return -1;
94
+ const notes = track.notes;
95
+ const newNoteIndex = Math.max(
96
+ notes.findIndex(({ ticks }) => ticks > note.ticks),
97
+ 0
98
+ );
99
+ updateNotes(trackIndex, (notes2) => [
100
+ ...notes2.slice(0, newNoteIndex),
101
+ note,
102
+ ...notes2.slice(newNoteIndex)
103
+ ]);
104
+ return newNoteIndex;
105
+ }, "onInsertNote");
106
+ const onRemoveNote = /* @__PURE__ */ __name((trackIndex, noteIndex) => {
107
+ updateNotes(trackIndex, (notes) => [
108
+ ...notes.slice(0, noteIndex),
109
+ ...notes.slice(noteIndex + 1)
110
+ ]);
111
+ }, "onRemoveNote");
112
+ const updateKeyPressedState = /* @__PURE__ */ __name((trackIndex, keyNumber, value) => {
113
+ handlers.onPressedKeysChange?.({
114
+ ...state.pressedKeys,
115
+ [trackIndex]: {
116
+ ...state.pressedKeys[trackIndex],
117
+ [keyNumber]: value
118
+ }
119
+ });
120
+ }, "updateKeyPressedState");
121
+ const onNoteDown = /* @__PURE__ */ __name((trackIndex, keyNumber) => {
122
+ updateKeyPressedState(trackIndex, keyNumber, true);
123
+ }, "onNoteDown");
124
+ const onNoteUp = /* @__PURE__ */ __name((trackIndex, keyNumber) => {
125
+ updateKeyPressedState(trackIndex, keyNumber, false);
126
+ }, "onNoteUp");
127
+ const isKeyDown = /* @__PURE__ */ __name((trackIndex, keyNumber) => !!state.pressedKeys[trackIndex]?.[keyNumber], "isKeyDown");
128
+ const snapValueToGridIfEnabled = /* @__PURE__ */ __name((value, altKey) => {
129
+ const gridDivisionTicks = state.ppq * 4 / state.gridDivision;
130
+ return state.snapToGrid && !altKey ? Math.round(value / gridDivisionTicks) * gridDivisionTicks : value;
131
+ }, "snapValueToGridIfEnabled");
132
+ return mergeProps(state, {
133
+ ...handlers,
134
+ onNoteChange,
135
+ onInsertNote,
136
+ onRemoveNote,
137
+ onNoteDown,
138
+ onNoteUp,
139
+ isKeyDown,
140
+ snapValueToGridIfEnabled,
141
+ onPlayheadPositionChange
142
+ });
143
+ }, "createPianoRollstate");
144
+ var usePianoRollState_default = createPianoRollstate;
145
+
146
+ // src/PianoRollContext.tsx
147
+ var PianoRollContext = createContext();
148
+ var PianoRollContextProvider = PianoRollContext.Provider;
149
+ var usePianoRollContext = /* @__PURE__ */ __name(() => {
150
+ const context = useContext(PianoRollContext);
151
+ if (!context)
152
+ throw new Error("No PianoRollContext found");
153
+ return context;
154
+ }, "usePianoRollContext");
155
+ var splitContextProps = /* @__PURE__ */ __name((allProps) => splitProps(allProps, [...pianoRollStatePropNames, "showAllTracks", "showTrackList"]), "splitContextProps");
156
+
157
+ // src/PianoRollKeys.module.css
158
+ var _default2 = {};
159
+
160
+ // src/PianoRollKeys.tsx
161
+ import { createEffect as createEffect3, createMemo as createMemo2, createSignal, Index, Show } from "solid-js";
162
+
163
+ // src/viewport/ScrollZoomViewPort.tsx
164
+ import { createContext as createContext2, useContext as useContext2 } from "solid-js";
165
+
166
+ // src/viewport/createViewPortDimension.ts
167
+ import { createEffect } from "solid-js";
168
+ import { createStore as createStore2 } from "solid-js/store";
169
+ function createViewPortDimension(getState) {
170
+ const getStateWithFunctions = /* @__PURE__ */ __name(() => {
171
+ const state2 = getState();
172
+ const onZoomChange = /* @__PURE__ */ __name((zoom) => state2.onZoomChange?.(clamp(zoom, state2.minZoom, state2.maxZoom)), "onZoomChange");
173
+ const onPositionChange = /* @__PURE__ */ __name((position) => state2.onPositionChange?.(position), "onPositionChange");
174
+ return {
175
+ ...state2,
176
+ onZoomChange,
177
+ onPositionChange,
178
+ calculatePixelOffset,
179
+ calculatePixelValue,
180
+ calculatePosition,
181
+ calculatePixelDimensions,
182
+ calculateVisibleRange,
183
+ calculateMaxPosition,
184
+ isVisible
185
+ };
186
+ }, "getStateWithFunctions");
187
+ const [state, setState] = createStore2(getStateWithFunctions());
188
+ createEffect(() => setState(getStateWithFunctions()));
189
+ function calculatePixelValue(position = state.position) {
190
+ const virtualSize = state.pixelSize * state.zoom;
191
+ return position / state.range * virtualSize;
192
+ }
193
+ __name(calculatePixelValue, "calculatePixelValue");
194
+ function calculatePixelOffset(position) {
195
+ return calculatePixelValue(position) - calculatePixelValue(state.position);
196
+ }
197
+ __name(calculatePixelOffset, "calculatePixelOffset");
198
+ function calculatePosition(offset) {
199
+ const percentX = (offset - state.pixelOffset) / state.pixelSize;
200
+ const position = state.position + percentX * calculateVisibleRange();
201
+ return position;
202
+ }
203
+ __name(calculatePosition, "calculatePosition");
204
+ function calculateVisibleRange() {
205
+ return state.range / state.zoom;
206
+ }
207
+ __name(calculateVisibleRange, "calculateVisibleRange");
208
+ function calculateMaxPosition() {
209
+ return state.range - state.range / state.zoom;
210
+ }
211
+ __name(calculateMaxPosition, "calculateMaxPosition");
212
+ function calculatePixelDimensions(position, length) {
213
+ const offset = calculatePixelOffset(position);
214
+ const size = calculatePixelValue(length);
215
+ return { offset, size };
216
+ }
217
+ __name(calculatePixelDimensions, "calculatePixelDimensions");
218
+ function isVisible({ offset, size }) {
219
+ return offset + size > 0 && offset < state.pixelSize;
220
+ }
221
+ __name(isVisible, "isVisible");
222
+ return state;
223
+ }
224
+ __name(createViewPortDimension, "createViewPortDimension");
225
+ var clamp = /* @__PURE__ */ __name((value, min, max) => Math.max(min, Math.min(max, value)), "clamp");
226
+
227
+ // src/viewport/ScrollZoomViewPort.tsx
228
+ var defaultViewPortProps = {
229
+ pixelOffset: 0,
230
+ position: 0,
231
+ range: 1,
232
+ zoom: 1,
233
+ minZoom: 1,
234
+ maxZoom: Infinity
235
+ };
236
+ var ScrollZoomViewPort = /* @__PURE__ */ __name((props) => {
237
+ const viewPorts = Object.entries(props.dimensions).map(
238
+ ([name, viewPortProps]) => createViewPortDimension(() => ({
239
+ name,
240
+ ...defaultViewPortProps,
241
+ ...viewPortProps()
242
+ }))
243
+ );
244
+ const parentViewPorts = useContext2(ScrollZoomViewPortContext);
245
+ return <ScrollZoomViewPortContext.Provider value={[...parentViewPorts, ...viewPorts]}>{props.children}</ScrollZoomViewPortContext.Provider>;
246
+ }, "ScrollZoomViewPort");
247
+ var ScrollZoomViewPortContext = createContext2([]);
248
+ var useViewPortDimension = /* @__PURE__ */ __name((name) => {
249
+ const viewPorts = useContext2(ScrollZoomViewPortContext);
250
+ const viewPort = name ? viewPorts.find((viewPort2) => viewPort2.name === name) : viewPorts[viewPorts.length - 1];
251
+ if (!viewPort)
252
+ throw new Error(`ViewPort dimension "${name}" not found.`);
253
+ return viewPort;
254
+ }, "useViewPortDimension");
255
+
256
+ // src/viewport/ScrollZoomContainer.tsx
257
+ import { createEffect as createEffect2, createMemo, mergeProps as mergeProps2, splitProps as splitProps2 } from "solid-js";
258
+
259
+ // src/viewport/ScrollZoomContainer.module.css
260
+ var _default3 = {};
261
+
262
+ // src/viewport/ScrollZoomContainer.tsx
263
+ var ScrollZoomContainer = /* @__PURE__ */ __name((props) => {
264
+ let scrollContentRef;
265
+ const [ownProps, divProps] = splitProps2(props, [
266
+ "ref",
267
+ "verticalDimensionName",
268
+ "horizontalDimensionName",
269
+ "showScrollbar"
270
+ ]);
271
+ const propsWithDefaults = mergeProps2(
272
+ {
273
+ verticalDimensionName: "vertical",
274
+ horizontalDimensionName: "horizontal",
275
+ showScrollbar: true
276
+ },
277
+ ownProps
278
+ );
279
+ const verticalViewPort = createMemo(
280
+ () => useViewPortDimension(propsWithDefaults.verticalDimensionName)
281
+ );
282
+ const horizontalViewPort = createMemo(
283
+ () => useViewPortDimension(propsWithDefaults.horizontalDimensionName)
284
+ );
285
+ const handleScroll = /* @__PURE__ */ __name((event) => {
286
+ event.preventDefault();
287
+ if (didUpdateScroll) {
288
+ didUpdateScroll = false;
289
+ return;
290
+ }
291
+ const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
292
+ const maxPosition = horizontalViewPort().calculateMaxPosition();
293
+ const height = verticalViewPort().pixelSize;
294
+ const width = horizontalViewPort().pixelSize;
295
+ const { scrollTop, scrollLeft, scrollWidth, scrollHeight } = event.currentTarget;
296
+ const scrollTopAmount = scrollTop / (scrollHeight - height);
297
+ const scrollLeftAmount = scrollLeft / (scrollWidth - width);
298
+ verticalViewPort().onPositionChange?.(maxVerticalPosition * scrollTopAmount);
299
+ horizontalViewPort().onPositionChange?.(maxPosition * scrollLeftAmount);
300
+ }, "handleScroll");
301
+ const handleWheel = /* @__PURE__ */ __name((event) => {
302
+ if (event.altKey) {
303
+ event.preventDefault();
304
+ if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
305
+ horizontalViewPort()?.onZoomChange?.(
306
+ horizontalViewPort().zoom / (1 + event.deltaX / horizontalViewPort().pixelSize)
307
+ );
308
+ const maxPosition = horizontalViewPort().calculateMaxPosition();
309
+ horizontalViewPort()?.onPositionChange?.(
310
+ Math.min(maxPosition, horizontalViewPort()?.position)
311
+ );
312
+ } else {
313
+ verticalViewPort()?.onZoomChange?.(
314
+ verticalViewPort().zoom / (1 + event.deltaY / verticalViewPort().pixelSize)
315
+ );
316
+ const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
317
+ verticalViewPort()?.onPositionChange?.(
318
+ Math.min(maxVerticalPosition, verticalViewPort()?.position)
319
+ );
320
+ }
321
+ } else if (!props.showScrollbar) {
322
+ event.preventDefault();
323
+ event.currentTarget.scrollLeft += event.deltaX;
324
+ event.currentTarget.scrollTop += event.deltaY;
325
+ }
326
+ }, "handleWheel");
327
+ let didUpdateScroll = false;
328
+ createEffect2(() => {
329
+ const maxVerticalPosition = verticalViewPort().calculateMaxPosition();
330
+ const maxPosition = horizontalViewPort().calculateMaxPosition();
331
+ const scrollTopAmount = maxVerticalPosition > 0 ? verticalViewPort().position / maxVerticalPosition : 0;
332
+ const scrollLeftAmount = maxPosition > 0 ? horizontalViewPort().position / maxPosition : 0;
333
+ const height = verticalViewPort().pixelSize;
334
+ const width = horizontalViewPort().pixelSize;
335
+ if (!scrollContentRef?.parentElement)
336
+ return;
337
+ const scrollDivHeight = verticalViewPort().zoom * verticalViewPort().pixelSize;
338
+ const scrollTop = scrollTopAmount * (scrollDivHeight - height);
339
+ const scrollDivWidth = horizontalViewPort().zoom * horizontalViewPort().pixelSize;
340
+ const scrollLeft = scrollLeftAmount * (scrollDivWidth - width);
341
+ didUpdateScroll = true;
342
+ scrollContentRef.style.height = `${scrollDivHeight}px`;
343
+ scrollContentRef.style.width = `${scrollDivWidth}px`;
344
+ scrollContentRef.parentElement.scrollTo({
345
+ left: scrollLeft,
346
+ top: scrollTop
347
+ });
348
+ });
349
+ return <div
350
+ ref={props.ref}
351
+ onScroll={handleScroll}
352
+ onWheel={handleWheel}
353
+ {...divProps}
354
+ style={{
355
+ overflow: propsWithDefaults.showScrollbar ? "scroll" : "hidden",
356
+ ...typeof divProps.style === "object" && divProps.style
357
+ }}
358
+ classList={{
359
+ [_default3.ScrollZoomContainer]: true,
360
+ ...divProps.classList
361
+ }}
362
+ ><div ref={scrollContentRef}><div
363
+ style={{
364
+ top: 0,
365
+ left: 0,
366
+ height: `${verticalViewPort().pixelSize}px`,
367
+ width: `${horizontalViewPort().pixelSize}px`,
368
+ position: "sticky",
369
+ display: "flex"
370
+ }}
371
+ ><div
372
+ style={{
373
+ position: "relative",
374
+ height: `${verticalViewPort().pixelSize}px`,
375
+ width: `${horizontalViewPort().pixelSize}px`
376
+ }}
377
+ >{props.children}</div></div></div></div>;
378
+ }, "ScrollZoomContainer");
379
+ var ScrollZoomContainer_default = ScrollZoomContainer;
380
+
381
+ // src/PianoRollKeys.tsx
382
+ var PianoRollKeys = /* @__PURE__ */ __name(() => {
383
+ const verticalViewPort = createMemo2(() => useViewPortDimension("vertical"));
384
+ const horizontalViewPort = createMemo2(() => useViewPortDimension("horizontalKeys"));
385
+ const [isMouseDown, setIsMouseDown] = createSignal(false);
386
+ const context = usePianoRollContext();
387
+ const handleMouseUp = /* @__PURE__ */ __name(() => {
388
+ setIsMouseDown(false);
389
+ }, "handleMouseUp");
390
+ createEffect3(() => {
391
+ if (isMouseDown()) {
392
+ window.addEventListener("mouseup", handleMouseUp);
393
+ } else {
394
+ window.removeEventListener("mouseup", handleMouseUp);
395
+ }
396
+ });
397
+ return <div class={_default2.PianoRollKeys} style={{ width: `${horizontalViewPort().pixelSize}px` }}><ScrollZoomContainer_default horizontalDimensionName="horizontalKeys" showScrollbar={false}><Index each={keys}>{(key) => {
398
+ const isDown = createMemo2(
399
+ () => context.isKeyDown(context.selectedTrackIndex, key().number)
400
+ );
401
+ const virtualDimensions = createMemo2(
402
+ () => verticalViewPort().calculatePixelDimensions(127 - key().number, 1)
403
+ );
404
+ const previousIsBlack = blackKeys.includes((key().number - 1) % 12);
405
+ const nextIsBlack = blackKeys.includes((key().number + 1) % 12);
406
+ return <Show when={verticalViewPort().isVisible(virtualDimensions())}><div
407
+ classList={{
408
+ [_default2["Key"]]: true,
409
+ [_default2["black"]]: key().isBlack,
410
+ [_default2["white"]]: !key().isBlack,
411
+ [_default2["down"]]: isDown()
412
+ }}
413
+ onMouseDown={() => {
414
+ setIsMouseDown(true);
415
+ context.onNoteDown(context.selectedTrackIndex, key().number);
416
+ }}
417
+ onMouseUp={() => {
418
+ context.onNoteUp(context.selectedTrackIndex, key().number);
419
+ }}
420
+ onMouseEnter={() => {
421
+ if (isMouseDown()) {
422
+ context.onNoteDown(context.selectedTrackIndex, key().number);
423
+ }
424
+ }}
425
+ onMouseLeave={() => {
426
+ if (isMouseDown()) {
427
+ context.onNoteUp(context.selectedTrackIndex, key().number);
428
+ }
429
+ }}
430
+ title={key().name}
431
+ data-index={key().number % 12}
432
+ style={{
433
+ top: `${virtualDimensions().offset - virtualDimensions().size * (!key().isBlack ? nextIsBlack ? 1 / 2 : 0 : 0)}px`,
434
+ height: `${virtualDimensions().size + (!key().isBlack && nextIsBlack ? virtualDimensions().size / 2 : 0) + (!key().isBlack && previousIsBlack ? virtualDimensions().size / 2 : 0)}px`,
435
+ "box-shadow": `0px 0px ${Math.min(virtualDimensions().size / 20, 1)}px ${Math.min(
436
+ virtualDimensions().size / 50,
437
+ 1
438
+ )}px rgba(0, 0, 0, 0.5) ${key().isBlack ? "" : "inset"}` + isDown() ? `0px 0px ${Math.min(virtualDimensions().size / 8, 2)}px ${Math.min(
439
+ virtualDimensions().size / 20,
440
+ 2
441
+ )}px rgba(0, 0, 0, 0.5) inset` : ""
442
+ }}
443
+ /></Show>;
444
+ }}</Index></ScrollZoomContainer_default></div>;
445
+ }, "PianoRollKeys");
446
+ var blackKeys = [1, 3, 6, 8, 10];
447
+ var keyNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
448
+ var keys = Array.from({ length: 128 }).map((_, index) => ({
449
+ number: index,
450
+ name: `${keyNames[index % 12]} ${Math.floor(index / 12) - 2}`,
451
+ isBlack: blackKeys.includes(index % 12)
452
+ }));
453
+ var PianoRollKeys_default = PianoRollKeys;
454
+
455
+ // src/PianoRollNotes.tsx
456
+ import { createMemo as createMemo3, createSignal as createSignal2, For, Show as Show2 } from "solid-js";
457
+
458
+ // src/PianoRollNotes.module.css
459
+ var _default4 = {};
460
+
461
+ // src/PianoRollNotes.tsx
462
+ var PianoRollNotes = /* @__PURE__ */ __name((props) => {
463
+ const context = usePianoRollContext();
464
+ const verticalViewPort = createMemo3(
465
+ () => useViewPortDimension(context.mode === "keys" ? "vertical" : "verticalTracks")
466
+ );
467
+ const horizontalViewPort = createMemo3(() => useViewPortDimension("horizontal"));
468
+ const gridDivisionTicks = createMemo3(() => context.ppq * 4 / context.gridDivision);
469
+ const [isDragging, setIsDragging] = createSignal2(false);
470
+ const [noteDragMode, setNoteDragMode] = createSignal2();
471
+ const [currentNoteIndex, setCurrentNoteIndex] = createSignal2(-1);
472
+ const [currentNoteTrackIndex, setCurrentNoteTrackIndex] = createSignal2(-1);
473
+ const [diffPosition, setDiffPosition] = createSignal2(0);
474
+ const [getInitialNote, setInitialNote] = createSignal2();
475
+ const calculateNoteDragValues = /* @__PURE__ */ __name((event) => {
476
+ const targetTrackIndex = context.mode === "tracks" ? clamp(
477
+ Math.floor(verticalViewPort().calculatePosition(event.clientY)),
478
+ 0,
479
+ context.tracks.length - 1
480
+ ) : currentNoteTrackIndex() > -1 ? currentNoteTrackIndex() : context.selectedTrackIndex;
481
+ const midi = context.mode === "keys" ? Math.floor(128 - verticalViewPort().calculatePosition(event.clientY)) : getInitialNote()?.midi ?? 60;
482
+ const horiontalPosition = horizontalViewPort().calculatePosition(event.clientX);
483
+ return {
484
+ targetTrackIndex,
485
+ midi,
486
+ horiontalPosition
487
+ };
488
+ }, "calculateNoteDragValues");
489
+ const handleMouseMove = /* @__PURE__ */ __name((mouseMoveEvent) => {
490
+ mouseMoveEvent.preventDefault();
491
+ mouseMoveEvent.stopPropagation();
492
+ const note = getInitialNote();
493
+ if (!note)
494
+ return;
495
+ const { targetTrackIndex, midi, horiontalPosition } = calculateNoteDragValues(mouseMoveEvent);
496
+ const ticks = context.snapValueToGridIfEnabled(
497
+ horiontalPosition - diffPosition(),
498
+ mouseMoveEvent.altKey
499
+ );
500
+ const updatedNote = {
501
+ ...note,
502
+ midi,
503
+ ...noteDragMode() === "move" && { ticks },
504
+ ...noteDragMode() === "trimStart" && {
505
+ ticks: ticks < note.ticks + note.durationTicks ? ticks : note.ticks + note.durationTicks,
506
+ durationTicks: ticks < note.ticks + note.durationTicks ? note.ticks + note.durationTicks - ticks : context.snapValueToGridIfEnabled(
507
+ horiontalPosition - note.ticks,
508
+ mouseMoveEvent.altKey
509
+ )
510
+ },
511
+ ...noteDragMode() === "trimEnd" && {
512
+ ticks: ticks < note.ticks ? ticks : note.ticks,
513
+ durationTicks: ticks < note.ticks ? note.ticks - ticks : context.snapValueToGridIfEnabled(
514
+ horiontalPosition - note.ticks,
515
+ mouseMoveEvent.altKey
516
+ )
517
+ }
518
+ };
519
+ const previousTrackIndex = currentNoteTrackIndex();
520
+ const previousNoteIndex = currentNoteIndex();
521
+ if (targetTrackIndex === previousTrackIndex) {
522
+ context.onNoteChange?.(previousTrackIndex, previousNoteIndex, updatedNote);
523
+ } else {
524
+ if (context.onInsertNote) {
525
+ context.onRemoveNote?.(previousTrackIndex, previousNoteIndex);
526
+ const newNoteIndex = context.onInsertNote(targetTrackIndex, updatedNote);
527
+ setCurrentNoteTrackIndex(targetTrackIndex);
528
+ setCurrentNoteIndex(newNoteIndex);
529
+ }
530
+ }
531
+ }, "handleMouseMove");
532
+ const stopDragging = /* @__PURE__ */ __name(() => {
533
+ window.removeEventListener("mousemove", handleMouseMove);
534
+ window.removeEventListener("mouseup", stopDragging);
535
+ setIsDragging(false);
536
+ setCurrentNoteIndex(-1);
537
+ setCurrentNoteTrackIndex(-1);
538
+ }, "stopDragging");
539
+ const startDragging = /* @__PURE__ */ __name(() => {
540
+ window.addEventListener("mousemove", handleMouseMove);
541
+ window.addEventListener("mouseup", stopDragging);
542
+ }, "startDragging");
543
+ const getClasses = /* @__PURE__ */ __name((noteDragMode2) => {
544
+ return noteDragMode2 ? [_default4.Note, _default4[noteDragMode2]] : [_default4.Note];
545
+ }, "getClasses");
546
+ return <div
547
+ classList={{ [_default4.PianoRollNotes]: true }}
548
+ ref={props.ref}
549
+ onMouseDown={(mouseDownEvent) => {
550
+ mouseDownEvent.preventDefault();
551
+ mouseDownEvent.stopPropagation();
552
+ const { targetTrackIndex, midi, horiontalPosition } = calculateNoteDragValues(mouseDownEvent);
553
+ const ticks = context.snapValueToGridIfEnabled(
554
+ horiontalPosition,
555
+ mouseDownEvent.altKey,
556
+ context
557
+ );
558
+ const durationTicks = gridDivisionTicks();
559
+ const newNote = {
560
+ midi,
561
+ ticks,
562
+ durationTicks,
563
+ velocity: 100
564
+ };
565
+ const newNoteIndex = context.onInsertNote(targetTrackIndex, newNote);
566
+ setIsDragging(true);
567
+ setCurrentNoteTrackIndex(targetTrackIndex);
568
+ setCurrentNoteIndex(newNoteIndex);
569
+ setInitialNote(newNote);
570
+ setDiffPosition(0);
571
+ setNoteDragMode("trimEnd");
572
+ startDragging();
573
+ }}
574
+ ><For each={context.tracks}>{(track, trackIndex) => {
575
+ return <Show2
576
+ when={trackIndex() === context.selectedTrackIndex || context.showAllTracks || context.mode === "tracks"}
577
+ ><For each={track.notes}>{(note, noteIndex) => {
578
+ const verticalVirtualDimensions = createMemo3(
579
+ () => verticalViewPort().calculatePixelDimensions(
580
+ context.mode === "keys" ? 127 - note.midi : trackIndex(),
581
+ 1
582
+ )
583
+ );
584
+ const horizontalDimensions = createMemo3(
585
+ () => horizontalViewPort().calculatePixelDimensions(note.ticks, note.durationTicks)
586
+ );
587
+ return <Show2
588
+ when={verticalViewPort().isVisible(verticalVirtualDimensions()) && horizontalViewPort().isVisible(horizontalDimensions())}
589
+ ><div
590
+ class={getClasses(noteDragMode()).join(" ")}
591
+ draggable={false}
592
+ onMouseMove={(event) => {
593
+ if (isDragging())
594
+ return;
595
+ event.stopPropagation();
596
+ const relativeX = horizontalViewPort().calculatePixelValue(
597
+ horizontalViewPort().calculatePosition(event.clientX)
598
+ );
599
+ const noteStartX = horizontalViewPort().calculatePixelValue(note.ticks);
600
+ const noteEndX = horizontalViewPort().calculatePixelValue(
601
+ note.ticks + note.durationTicks
602
+ );
603
+ setNoteDragMode(
604
+ relativeX - noteStartX < 3 ? "trimStart" : noteEndX - relativeX < 3 ? "trimEnd" : "move"
605
+ );
606
+ }}
607
+ onDblClick={(event) => {
608
+ event.stopPropagation();
609
+ context.onRemoveNote?.(trackIndex(), noteIndex());
610
+ }}
611
+ onMouseDown={(event) => {
612
+ event.stopPropagation();
613
+ const initialPosition = horizontalViewPort().calculatePosition(
614
+ event.clientX
615
+ );
616
+ setDiffPosition(
617
+ noteDragMode() === "trimEnd" ? -(note.ticks + note.durationTicks - initialPosition) : initialPosition - note.ticks
618
+ );
619
+ setIsDragging(true);
620
+ setCurrentNoteIndex(noteIndex());
621
+ setCurrentNoteTrackIndex(trackIndex());
622
+ setInitialNote(note);
623
+ startDragging();
624
+ }}
625
+ style={{
626
+ "background-color": `${track.color}`,
627
+ opacity: (note.velocity / 128 + 2) / 3,
628
+ top: `${verticalVirtualDimensions().offset}px`,
629
+ height: `${verticalVirtualDimensions().size}px`,
630
+ left: `${horizontalDimensions().offset}px`,
631
+ width: `${horizontalDimensions().size}px`
632
+ }}
633
+ /></Show2>;
634
+ }}</For></Show2>;
635
+ }}</For></div>;
636
+ }, "PianoRollNotes");
637
+ var PianoRollNotes_default = PianoRollNotes;
638
+
639
+ // src/PianoRollGrid.tsx
640
+ import { createMemo as createMemo5, For as For2, Index as Index2, Show as Show3 } from "solid-js";
641
+
642
+ // src/PianoRollGrid.module.css
643
+ var _default5 = {};
644
+
645
+ // src/usePianoRollGrid.ts
646
+ import { createMemo as createMemo4 } from "solid-js";
647
+ var usePianoRollGrid = /* @__PURE__ */ __name(() => {
648
+ const context = usePianoRollContext();
649
+ const horizontalViewPort = createMemo4(() => useViewPortDimension("horizontal"));
650
+ const measureTicks = createMemo4(() => context.ppq * 4);
651
+ const selectedGridDivisorTicks = createMemo4(() => measureTicks() / context.gridDivision);
652
+ function calculateVisibleGridDivisorTicks(value) {
653
+ const visibleRange = horizontalViewPort().calculateVisibleRange();
654
+ if (visibleRange <= 0)
655
+ return 0;
656
+ if (visibleRange / value > 100) {
657
+ return calculateVisibleGridDivisorTicks(value * 2);
658
+ }
659
+ if (visibleRange / value < 30) {
660
+ return calculateVisibleGridDivisorTicks(value / 2);
661
+ }
662
+ return value;
663
+ }
664
+ __name(calculateVisibleGridDivisorTicks, "calculateVisibleGridDivisorTicks");
665
+ const gridDivisorTicks = createMemo4(
666
+ () => calculateVisibleGridDivisorTicks(selectedGridDivisorTicks())
667
+ );
668
+ const gridArray = createMemo4(() => {
669
+ const numberOfLines = Math.ceil(
670
+ horizontalViewPort().calculateVisibleRange() / gridDivisorTicks() + 1
671
+ );
672
+ const startIndex = Math.floor(context.position / gridDivisorTicks());
673
+ return Array.from({ length: numberOfLines }).map((_, i) => {
674
+ const index = i + startIndex;
675
+ const ticks = index * gridDivisorTicks();
676
+ const measurePosition = ticks / context.ppq / 4 + 1;
677
+ const bars = Math.floor(measurePosition);
678
+ const beats = Math.floor((measurePosition - bars) * 4);
679
+ const label = `${bars}${beats ? `.${beats}` : ""}`;
680
+ return {
681
+ index,
682
+ ticks,
683
+ isHighlighted: Math.ceil((index + 1) * selectedGridDivisorTicks() / measureTicks()) % 2 === 0,
684
+ hasHighlightedBorder: index * gridDivisorTicks() % selectedGridDivisorTicks() === 0,
685
+ showLabel: index * selectedGridDivisorTicks() / measureTicks() % 2 === 0,
686
+ virtualDimensions: horizontalViewPort().calculatePixelDimensions(ticks, gridDivisorTicks()),
687
+ label
688
+ };
689
+ });
690
+ });
691
+ return gridArray;
692
+ }, "usePianoRollGrid");
693
+ var usePianoRollGrid_default = usePianoRollGrid;
694
+
695
+ // src/PianoRollGrid.tsx
696
+ var PianoRollGrid = /* @__PURE__ */ __name(() => {
697
+ const context = usePianoRollContext();
698
+ const verticalViewPort = createMemo5(
699
+ () => useViewPortDimension(context.mode === "keys" ? "vertical" : "verticalTracks")
700
+ );
701
+ const horizontalViewPort = createMemo5(() => useViewPortDimension("horizontal"));
702
+ const gridArray = usePianoRollGrid_default();
703
+ return <div class={_default5.PianoRollGrid}>
704
+ <Index2 each={gridArray()}>{(entry) => {
705
+ return <Show3 when={horizontalViewPort().isVisible(entry().virtualDimensions)}><div
706
+ classList={{
707
+ [_default5["PianoRollGrid-Time"]]: true,
708
+ [_default5["Highlighted"]]: entry().isHighlighted,
709
+ [_default5["HighlightedBorder"]]: entry().hasHighlightedBorder
710
+ }}
711
+ style={{
712
+ left: `${entry().virtualDimensions.offset}px`,
713
+ width: `${entry().virtualDimensions.size}px`
714
+ }}
715
+ /></Show3>;
716
+ }}</Index2>
717
+ <Show3 when={context.mode === "keys"}><Index2 each={keys}>{(key) => {
718
+ const virtualDimensions = createMemo5(
719
+ () => verticalViewPort().calculatePixelDimensions(127 - key().number, 1)
720
+ );
721
+ return <Show3 when={verticalViewPort().isVisible(virtualDimensions())}><div
722
+ classList={{
723
+ [_default5["PianoRollGrid-Row"]]: true,
724
+ [_default5["Key"]]: true,
725
+ [_default5["IsDark"]]: key().isBlack,
726
+ [_default5["IsBlackAndNextIsWhite"]]: !key().isBlack && !blackKeys.includes((key().number + 1) % 12)
727
+ }}
728
+ style={{
729
+ top: `${virtualDimensions().offset}px`,
730
+ height: `${virtualDimensions().size}px`
731
+ }}
732
+ /></Show3>;
733
+ }}</Index2></Show3>
734
+ <Show3 when={context.mode === "tracks"}><For2 each={context.tracks}>{(track, index) => {
735
+ const virtualDimensions = createMemo5(
736
+ () => verticalViewPort().calculatePixelDimensions(index(), 1)
737
+ );
738
+ return <Show3 when={verticalViewPort().isVisible(virtualDimensions())}><div
739
+ classList={{
740
+ [_default5["PianoRollGrid-Row"]]: true,
741
+ [_default5["IsDark"]]: index() % 2 === 0
742
+ }}
743
+ style={{
744
+ top: `${virtualDimensions().offset}px`,
745
+ height: `${virtualDimensions().size}px`
746
+ }}
747
+ /></Show3>;
748
+ }}</For2></Show3>
749
+ </div>;
750
+ }, "PianoRollGrid");
751
+ var PianoRollGrid_default = PianoRollGrid;
752
+
753
+ // src/viewport/ZoomSliderControl.tsx
754
+ import { createMemo as createMemo6, mergeProps as mergeProps3 } from "solid-js";
755
+ var ZoomSliderControl = /* @__PURE__ */ __name((props) => {
756
+ const propsWithDefaults = mergeProps3({ orientation: "horizontal" }, props);
757
+ const dimension = createMemo6(
758
+ () => useViewPortDimension(props.dimensionName ?? propsWithDefaults.orientation)
759
+ );
760
+ return <input
761
+ min={dimension().minZoom}
762
+ max={dimension().maxZoom}
763
+ step={0.01}
764
+ {...props}
765
+ value={dimension().zoom}
766
+ onInput={(event) => {
767
+ const value = event.currentTarget.valueAsNumber;
768
+ dimension().onZoomChange?.(value);
769
+ }}
770
+ type="range"
771
+ {...{ orient: propsWithDefaults.orientation }}
772
+ style={propsWithDefaults.orientation === "vertical" ? {
773
+ margin: 0,
774
+ "margin-left": "4px",
775
+ "margin-right": "4px",
776
+ width: "16px",
777
+ transform: "rotate(180deg)",
778
+ ...{
779
+ "writing-mode": "bt-lr",
780
+ "-webkit-appearance": "slider-vertical"
781
+ },
782
+ ...typeof props.style === "object" && props.style
783
+ } : {
784
+ margin: 0,
785
+ "margin-top": "4px",
786
+ "margin-bottom": "4px",
787
+ ...typeof props.style === "object" && props.style
788
+ }}
789
+ />;
790
+ }, "ZoomSliderControl");
791
+ var ZoomSliderControl_default = ZoomSliderControl;
792
+
793
+ // src/PianoRollTrackList.module.css
794
+ var _default6 = {};
795
+
796
+ // src/PianoRollTrackList.tsx
797
+ import { createMemo as createMemo7, Index as Index3, Show as Show4 } from "solid-js";
798
+ var PianoRollTrackList = /* @__PURE__ */ __name(() => {
799
+ const viewPort = createMemo7(() => useViewPortDimension("verticalTracks"));
800
+ const context = usePianoRollContext();
801
+ return <div class={_default6.PianoRollTrackList}><Index3 each={context.tracks}>{(track, index) => {
802
+ const virtualDimensions = createMemo7(() => viewPort().calculatePixelDimensions(index, 1));
803
+ return <Show4 when={virtualDimensions().size > 0}><div
804
+ classList={{
805
+ [_default6["Track"]]: true,
806
+ [_default6["selected"]]: index === context.selectedTrackIndex
807
+ }}
808
+ title={track().name}
809
+ style={{
810
+ top: `${virtualDimensions().offset}px`,
811
+ height: `${virtualDimensions().size}px`
812
+ }}
813
+ onClick={() => context.onSelectedTrackIndexChange?.(index)}
814
+ >
815
+ {index}
816
+ {" "}
817
+ {track().name || "[unnamed track]"}
818
+ </div></Show4>;
819
+ }}</Index3></div>;
820
+ }, "PianoRollTrackList");
821
+ var PianoRollTrackList_default = PianoRollTrackList;
822
+
823
+ // src/PianoRollScrollZoomViewPort.tsx
824
+ var PianoRollScrollZoomViewPort = /* @__PURE__ */ __name((props) => {
825
+ const zoomFactor = 500;
826
+ const context = usePianoRollContext();
827
+ return <ScrollZoomViewPort
828
+ dimensions={{
829
+ horizontal: () => ({
830
+ pixelOffset: context.notesScrollerClientRect.left,
831
+ pixelSize: context.notesScrollerClientRect.width,
832
+ position: context.position,
833
+ range: context.duration,
834
+ zoom: context.zoom * (zoomFactor / context.notesScrollerClientRect.width),
835
+ onPositionChange: context.onPositionChange,
836
+ onZoomChange: (zoom) => context.onZoomChange?.(zoom / (zoomFactor / context.notesScrollerClientRect.width)),
837
+ minZoom: 1,
838
+ maxZoom: 500
839
+ }),
840
+ vertical: () => ({
841
+ pixelOffset: context.notesScrollerClientRect.top,
842
+ pixelSize: context.notesScrollerClientRect.height,
843
+ position: context.verticalPosition,
844
+ range: 128,
845
+ zoom: context.verticalZoom * (zoomFactor / context.notesScrollerClientRect.height),
846
+ onPositionChange: context.onVerticalPositionChange,
847
+ onZoomChange: (verticalZoom) => context.onVerticalZoomChange?.(
848
+ verticalZoom / (zoomFactor / context.notesScrollerClientRect.height)
849
+ ),
850
+ minZoom: 1,
851
+ maxZoom: 10
852
+ }),
853
+ horizontalTracks: () => ({
854
+ pixelOffset: 0,
855
+ pixelSize: context.tracksScrollerClientRect.width
856
+ }),
857
+ verticalTracks: () => ({
858
+ pixelOffset: context.tracksScrollerClientRect.top,
859
+ pixelSize: context.tracksScrollerClientRect.height,
860
+ position: context.verticalTrackPosition,
861
+ range: context.tracks.length,
862
+ zoom: context.verticalTrackZoom * (zoomFactor / context.tracksScrollerClientRect.height),
863
+ onPositionChange: context.onVerticalTrackPositionChange,
864
+ onZoomChange: (verticalTrackZoom) => context.onVerticalTrackZoomChange?.(
865
+ verticalTrackZoom / (zoomFactor / context.tracksScrollerClientRect.height)
866
+ ),
867
+ minZoom: 0.8,
868
+ maxZoom: 3
869
+ }),
870
+ horizontalKeys: () => ({ pixelSize: 60 })
871
+ }}
872
+ >{props.children}</ScrollZoomViewPort>;
873
+ }, "PianoRollScrollZoomViewPort");
874
+ var PianoRollScrollZoomViewPort_default = PianoRollScrollZoomViewPort;
875
+
876
+ // src/PianoRollNotesScroller.tsx
877
+ import { createEffect as createEffect5, createSignal as createSignal4 } from "solid-js";
878
+
879
+ // src/useBoundingClientRect.ts
880
+ import { createEffect as createEffect4, createSignal as createSignal3 } from "solid-js";
881
+ var defaultRect = {
882
+ left: 0,
883
+ width: 0,
884
+ top: 0,
885
+ height: 0,
886
+ bottom: 0,
887
+ right: 0,
888
+ x: 0,
889
+ y: 0
890
+ };
891
+ function useBoundingClientRect(containerRef) {
892
+ const [boundingClientRect, setBoundingClientRect] = createSignal3(defaultRect);
893
+ createEffect4(() => {
894
+ const container = containerRef();
895
+ if (!container)
896
+ return;
897
+ const updateBoundingClientRect = /* @__PURE__ */ __name(() => {
898
+ setBoundingClientRect(container.getBoundingClientRect() ?? defaultRect);
899
+ }, "updateBoundingClientRect");
900
+ const resizeObserver = new ResizeObserver(updateBoundingClientRect);
901
+ resizeObserver.observe(container);
902
+ updateBoundingClientRect();
903
+ return () => {
904
+ resizeObserver.disconnect();
905
+ };
906
+ });
907
+ return boundingClientRect;
908
+ }
909
+ __name(useBoundingClientRect, "useBoundingClientRect");
910
+
911
+ // src/PianoRollNotesScroller.tsx
912
+ var PianoRollNotesScroller = /* @__PURE__ */ __name((props) => {
913
+ const context = usePianoRollContext();
914
+ const [ref, setRef] = createSignal4();
915
+ const clientRect = useBoundingClientRect(ref);
916
+ createEffect5(() => {
917
+ context.onNotesScrollerClientRectChange(clientRect());
918
+ });
919
+ return <ScrollZoomContainer_default
920
+ ref={setRef}
921
+ verticalDimensionName={context.mode === "keys" ? "vertical" : "verticalTracks"}
922
+ >{props.children}</ScrollZoomContainer_default>;
923
+ }, "PianoRollNotesScroller");
924
+ var PianoRollNotesScroller_default = PianoRollNotesScroller;
925
+
926
+ // src/PianoRollTrackListScroller.tsx
927
+ import { createEffect as createEffect6, createSignal as createSignal5 } from "solid-js";
928
+ var PianoRollTrackListScroller = /* @__PURE__ */ __name((props) => {
929
+ const context = usePianoRollContext();
930
+ const [ref, setRef] = createSignal5();
931
+ const clientRect = useBoundingClientRect(ref);
932
+ createEffect6(() => {
933
+ context.onTracksScrollerClientRectChange(clientRect());
934
+ });
935
+ return <ScrollZoomContainer_default
936
+ ref={setRef}
937
+ horizontalDimensionName="horizontalTracks"
938
+ verticalDimensionName="verticalTracks"
939
+ showScrollbar={context.mode === "keys"}
940
+ >{props.children}</ScrollZoomContainer_default>;
941
+ }, "PianoRollTrackListScroller");
942
+ var PianoRollTrackListScroller_default = PianoRollTrackListScroller;
943
+
944
+ // src/PianoRollScale.tsx
945
+ import { createMemo as createMemo8, Index as Index4, Show as Show5 } from "solid-js";
946
+
947
+ // src/PianoRollScale.module.css
948
+ var _default7 = {};
949
+
950
+ // src/PianoRollScale.tsx
951
+ var PianoRollScale = /* @__PURE__ */ __name(() => {
952
+ const horizontalViewPort = createMemo8(() => useViewPortDimension("horizontal"));
953
+ const grid = usePianoRollGrid_default();
954
+ const context = usePianoRollContext();
955
+ const updatePlayheadPosition = /* @__PURE__ */ __name((event) => {
956
+ const position = horizontalViewPort().calculatePosition(event.clientX);
957
+ context.onPlayHeadPositionChange(position, event);
958
+ }, "updatePlayheadPosition");
959
+ return <div
960
+ class={_default7.PianoRollScale}
961
+ onMouseDown={(event) => {
962
+ updatePlayheadPosition(event);
963
+ const handleMouseUp = /* @__PURE__ */ __name(() => {
964
+ window.removeEventListener("mousemove", updatePlayheadPosition);
965
+ window.removeEventListener("mouseup", handleMouseUp);
966
+ }, "handleMouseUp");
967
+ window.addEventListener("mousemove", updatePlayheadPosition);
968
+ window.addEventListener("mouseup", handleMouseUp);
969
+ }}
970
+ ><Index4 each={grid()}>{(entry) => {
971
+ return <Show5 when={horizontalViewPort().isVisible(entry().virtualDimensions)}><div
972
+ classList={{
973
+ [_default7["PianoRollScale-Time"]]: true,
974
+ [_default7["Highlighted"]]: entry().isHighlighted,
975
+ [_default7["HighlightedBorder"]]: entry().hasHighlightedBorder
976
+ }}
977
+ style={{
978
+ left: `${entry().virtualDimensions.offset}px`,
979
+ width: `${entry().virtualDimensions.size}px`
980
+ }}
981
+ ><Show5 when={entry().showLabel}><div class={_default7["PianoRollScale-Label"]}>{entry().label}</div></Show5></div></Show5>;
982
+ }}</Index4></div>;
983
+ }, "PianoRollScale");
984
+ var PianoRollScale_default = PianoRollScale;
985
+
986
+ // src/PianoRoll.tsx
987
+ var PianoRoll = /* @__PURE__ */ __name((allProps) => {
988
+ const propsWithDefaults = mergeProps4({ showAllTracks: false }, allProps);
989
+ const [context, divProps] = splitContextProps(propsWithDefaults);
990
+ return <PianoRollContextProvider value={context}><div {...divProps} class={_default.PianoRoll}><PianoRollScrollZoomViewPort_default>
991
+ <div class={_default.PianoRollContainer}>
992
+ <ZoomSliderControl_default orientation="vertical" dimensionName="verticalTracks" />
993
+ <div
994
+ classList={{
995
+ [_default.PianoRollLeftColumn]: true,
996
+ [_default.showTrackList]: context.showTrackList
997
+ }}
998
+ >
999
+ <Show6 when={context.showTrackList}><div
1000
+ style={{
1001
+ height: "30px",
1002
+ "border-right": "1px black solid"
1003
+ }}
1004
+ ><button
1005
+ title={context.mode === "keys" ? "Tracks Mode" : "Keys Mode"}
1006
+ onClick={() => context.onModeChange(context.mode === "keys" ? "tracks" : "keys")}
1007
+ style={{
1008
+ "font-size": "16px",
1009
+ "line-height": "16px",
1010
+ cursor: "pointer",
1011
+ overflow: "hidden"
1012
+ }}
1013
+ ><div>{context.mode === "keys" ? "\u2261" : "\u{1F3B9}"}</div></button></div></Show6>
1014
+ <div style={{ flex: 1, display: "flex", overflow: "hidden" }}>
1015
+ <Show6 when={context.showTrackList}><PianoRollTrackListScroller_default><PianoRollTrackList_default /></PianoRollTrackListScroller_default></Show6>
1016
+ <Show6 when={context.mode === "keys"}><PianoRollKeys_default /></Show6>
1017
+ </div>
1018
+ </div>
1019
+ <PianoRollNotesScroller_default>
1020
+ {allProps.children}
1021
+ <div
1022
+ style={{
1023
+ width: "100%",
1024
+ display: "flex",
1025
+ height: "100%",
1026
+ "flex-direction": "column",
1027
+ overflow: "hidden"
1028
+ }}
1029
+ >
1030
+ <div style={{ height: "30px" }}><PianoRollScale_default /></div>
1031
+ <div style={{ flex: 1, position: "relative", overflow: "hidden" }}>
1032
+ <PianoRollGrid_default />
1033
+ <PianoRollNotes_default />
1034
+ </div>
1035
+ </div>
1036
+ </PianoRollNotesScroller_default>
1037
+ <ZoomSliderControl_default orientation="vertical" disabled={context.mode !== "keys"} />
1038
+ </div>
1039
+ <ZoomSliderControl_default
1040
+ orientation="horizontal"
1041
+ style={{
1042
+ "margin-left": `${context.tracksScrollerClientRect.width + 24}px`,
1043
+ "margin-right": "24px"
1044
+ }}
1045
+ />
1046
+ </PianoRollScrollZoomViewPort_default></div></PianoRollContextProvider>;
1047
+ }, "PianoRoll");
1048
+ var PianoRoll_default = PianoRoll;
1049
+
1050
+ // src/viewport/PlayHead.tsx
1051
+ import { createEffect as createEffect7, createMemo as createMemo9, splitProps as splitProps3 } from "solid-js";
1052
+ var PlayHead = /* @__PURE__ */ __name((allProps) => {
1053
+ const [props, divProps] = splitProps3(allProps, [
1054
+ "position",
1055
+ "sync",
1056
+ "onPositionChange",
1057
+ "dimensionName"
1058
+ ]);
1059
+ const viewPort = useViewPortDimension(props.dimensionName ?? "horizontal");
1060
+ createEffect7(() => {
1061
+ if (!props.sync)
1062
+ return;
1063
+ const maxPosition = viewPort.range;
1064
+ const newPosition = clamp(props.position - viewPort.range / viewPort.zoom / 2, 0, maxPosition);
1065
+ viewPort.onPositionChange?.(newPosition);
1066
+ });
1067
+ const leftPosition = createMemo9(() => viewPort.calculatePixelOffset(props.position));
1068
+ return <div
1069
+ class="PlayHead"
1070
+ {...divProps}
1071
+ style={{
1072
+ position: "absolute",
1073
+ height: "100%",
1074
+ width: "2px",
1075
+ left: `${leftPosition()}px`,
1076
+ "background-color": "green",
1077
+ cursor: "pointer",
1078
+ ...typeof divProps.style === "object" && divProps.style
1079
+ }}
1080
+ onMouseDown={(event) => {
1081
+ event.preventDefault();
1082
+ event.stopPropagation();
1083
+ const handleMouseMove = /* @__PURE__ */ __name((event2) => {
1084
+ const newPosition = viewPort.calculatePosition(event2.clientX);
1085
+ props.onPositionChange?.(newPosition, event2);
1086
+ }, "handleMouseMove");
1087
+ const handleMouseUp = /* @__PURE__ */ __name(() => {
1088
+ window.removeEventListener("mousemove", handleMouseMove);
1089
+ window.removeEventListener("mouseup", handleMouseUp);
1090
+ }, "handleMouseUp");
1091
+ window.addEventListener("mousemove", handleMouseMove);
1092
+ window.addEventListener("mouseup", handleMouseUp);
1093
+ }}
1094
+ />;
1095
+ }, "PlayHead");
1096
+ var PlayHead_default = PlayHead;
1097
+
1098
+ // src/useNotes.ts
1099
+ import { createSignal as createSignal6 } from "solid-js";
1100
+ var useNotes = /* @__PURE__ */ __name(() => {
1101
+ const [notes, onNotesChange] = createSignal6([]);
1102
+ const onNoteChange = /* @__PURE__ */ __name((index, note) => {
1103
+ onNotesChange([...notes().slice(0, index), note, ...notes().splice(index + 1)]);
1104
+ }, "onNoteChange");
1105
+ const onInsertNote = /* @__PURE__ */ __name((note) => {
1106
+ const index = Math.max(
1107
+ notes().findIndex(({ ticks }) => ticks > note.ticks),
1108
+ 0
1109
+ );
1110
+ onNotesChange([...notes().slice(0, index), note, ...notes().splice(index)]);
1111
+ return index;
1112
+ }, "onInsertNote");
1113
+ const onRemoveNote = /* @__PURE__ */ __name((index) => {
1114
+ onNotesChange([...notes().slice(0, index), ...notes().splice(index + 1)]);
1115
+ }, "onRemoveNote");
1116
+ return {
1117
+ notes,
1118
+ onNotesChange,
1119
+ onNoteChange,
1120
+ onInsertNote,
1121
+ onRemoveNote
1122
+ };
1123
+ }, "useNotes");
1124
+ var useNotes_default = useNotes;
1125
+ export {
1126
+ PianoRoll_default as PianoRoll,
1127
+ PlayHead_default as PlayHead,
1128
+ usePianoRollState_default as createPianoRollstate,
1129
+ useNotes_default as useNotes
1130
+ };