Paste under the pointer and pull song items out left in music tabs
A round of fixes to the Pattern and Song tabs: where a paste lands, what Shift does on a drag, how far the rows zoom, and how an item of a song is sized at its ends. - Shift does what Alt does on a drag: off the grid, on the tick. A Shift click on a note or an item still adds or unpicks it. A Shift press only decides on letting go: no drag toggles, a drag moves. - Ctrl+V lands under the pointer. The earliest copy goes on the grid line before it, the highest note on its row or the topmost item on its track, held to the keyboard or the tracks there are. Off the grid or left of bar 1 it lands at the start of playing as before. - The rows of the piano roll zoom to 64 px instead of 28, the tracks of a song to 320 px instead of 160. - Fit in the Pattern tab zooms the rows as well, so the notes fill the height, no longer held to the default of 14 px. - An item of a song can be pulled out by its left end past the start of what it plays, which then repeats. The offset goes round to the end of a round. A sound not loaded yet still stops at its start. - Both ends of an item snap to the start of a round of what it plays as well as to the grid, whichever is nearer. The end of a round between two ticks is cut to the tick before. A start put within half a tick of a round goes right on it, so it plays from the very beginning. - An offset past a whole round is not folded back to 0, so a pattern that grows later still plays from where the item said. - A middle-drag pans past the first and the last row, as it pans left of bar 1: the highest note or the first track up to half the height down, the other end up to half the height up. The room past them is dark in the piano roll and empty in the song. Tests: - Unit: song-edit 14, piano-roll 19, song 14 tests. New cases for the paste spot, pulling out left, the round snap and Fit. - e2e 64+65 on melektaus-atx: ALL PASSED - 54 checks (+4). New: Shift drag and Shift click, Ctrl+V under the pointer in both tabs, Fit zooming the rows, pulling a pattern out left and snapping a sound to the end of its second round. e2e 64 zooms the rows out again after Fit, so the keys it plays later are on screen. - Gate on melektaus-atx: 251 files, 3912 tests, CHECK EXIT 0. - Then the pan down the rows: e2e 64 on melektaus-atx ALL PASSED - 30 checks. Its pan check now also pans the top row down, half the grid's height, and a click above it draws nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a3101c590c
commit
5b96d0b329
11 files changed
+462
-101
No files matched your search
@@ -201,6 +201,22 @@ export async function pattern(t) {
|
||||
free?.tick % 120 !== 0 && Math.abs(free.tick - 770) <= 6 && back?.tick === 720,
|
||||
JSON.stringify({ free, back }),
|
||||
);
|
||||
// Shift does the same; a Shift click that does not move still takes the note out of the picked ones.
|
||||
await drag(p.x, p.y, to.x, to.y, { modifiers: { shift: true } });
|
||||
const shiftFree = (await notes()).find((n) => n.id === first);
|
||||
const shiftPicked = await json('__gm.pattern.picked()');
|
||||
await press('z', { ctrl: true });
|
||||
await click(p.x, p.y, 'left', { shift: true });
|
||||
const unpicked = await json('__gm.pattern.picked()');
|
||||
await click(p.x, p.y);
|
||||
check(
|
||||
'with Shift held it moves off the grid too, and a Shift click still unpicks the note',
|
||||
shiftFree?.tick % 120 !== 0 &&
|
||||
Math.abs(shiftFree.tick - 770) <= 6 &&
|
||||
shiftPicked.includes(first) &&
|
||||
!unpicked.includes(first),
|
||||
JSON.stringify({ shiftFree, shiftPicked, unpicked }),
|
||||
);
|
||||
|
||||
// 64e. A band picks both; Ctrl+D puts a copy right behind them, picked; the arrows transpose it.
|
||||
p = await json('__gm.pattern.gridAt(100, 67)');
|
||||
@@ -260,6 +276,24 @@ export async function pattern(t) {
|
||||
startSet === 1920 && pasted.length === 8 && pastedIds.length === 4 && Math.min(...pastedTicks) === 1920,
|
||||
JSON.stringify({ startSet, count: pasted.length, pastedTicks }),
|
||||
);
|
||||
// With the pointer over the grid the paste lands there instead: the earliest note on the grid line
|
||||
// before it, the highest on its row. Ctrl+Z takes it back.
|
||||
const copiedHigh = Math.max(...pasted.filter((n) => pastedIds.includes(n.id)).map((n) => n.note));
|
||||
p = await json('__gm.pattern.gridAt(2930, 70)');
|
||||
await t.mouse('mouseMoved', p.x, p.y);
|
||||
await press('v', { ctrl: true });
|
||||
const atPointer = await notes();
|
||||
const atPointerIds = await json('__gm.pattern.picked()');
|
||||
const landed = atPointer.filter((n) => atPointerIds.includes(n.id));
|
||||
await press('z', { ctrl: true });
|
||||
check(
|
||||
'with the pointer over the grid Ctrl+V pastes there: the earliest note at its grid line, the highest on its row',
|
||||
atPointer.length === 12 &&
|
||||
Math.min(...landed.map((n) => n.tick)) === 2880 &&
|
||||
Math.max(...landed.map((n) => n.note)) === 70 &&
|
||||
(await notes()).length === 8,
|
||||
JSON.stringify({ copiedHigh, landed: landed.map((n) => [n.tick, n.note]) }),
|
||||
);
|
||||
|
||||
// 64h. A right-click takes a note away; the lane sets the velocity of the one it grabs.
|
||||
p = await json('__gm.pattern.gridAt(1980, 60)');
|
||||
@@ -317,13 +351,41 @@ export async function pattern(t) {
|
||||
const countPanned = (await notes()).length;
|
||||
await click(zero.x - 20, zero.y);
|
||||
const countLeft = (await notes()).length;
|
||||
// Down the same: the highest note comes half the grid's height down at most (the grid is the area
|
||||
// less the 30 px ruler and the 64 px velocity lane), and a click above it draws nothing.
|
||||
const gridHeight = area.h - 30 - 64;
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const before = (await json('__gm.pattern.view()')).panY;
|
||||
await drag(zero.x + 40, area.y + 40, zero.x + 40, area.y + area.h - 70, { button: 'middle' });
|
||||
if ((await json('__gm.pattern.view()')).panY === before) break;
|
||||
}
|
||||
const pannedDown = await json('__gm.pattern.view()');
|
||||
await click(zero.x + 40, area.y + 40);
|
||||
const countAbove = (await notes()).length;
|
||||
p = await json('__gm.pattern.fitAt()');
|
||||
await click(p.x, p.y);
|
||||
const fitted = await json('__gm.pattern.view()');
|
||||
check(
|
||||
'a middle-drag pans bar 1 in from the left edge, half the grid at most, and a click left of it draws nothing',
|
||||
Math.abs(panned.panX - (area.w - 84) / 2) < 1 && countLeft === countPanned,
|
||||
JSON.stringify({ panned, area, countPanned, countLeft }),
|
||||
'a middle-drag pans bar 1 in from the left and the top row down, half the grid at most, and a click outside draws nothing',
|
||||
Math.abs(panned.panX - (area.w - 84) / 2) < 1 &&
|
||||
countLeft === countPanned &&
|
||||
Math.abs(pannedDown.panY - gridHeight / 2) < 1 &&
|
||||
countAbove === countPanned,
|
||||
JSON.stringify({ panned, pannedDown, area, countPanned, countLeft, countAbove }),
|
||||
);
|
||||
// The notes span few keys: Fit zooms the rows past the old default of 14 px to fill the height.
|
||||
check(
|
||||
'Fit zooms the rows too, so the notes fill the height',
|
||||
fitted.rowHeight > 14,
|
||||
JSON.stringify(fitted),
|
||||
);
|
||||
// The checks below play keys up to 69: the wheel over the keys takes the rows out until they show.
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const high = await json('__gm.pattern.keyAt(70)');
|
||||
const low = await json('__gm.pattern.keyAt(59)');
|
||||
if (high.y > area.y + 50 && low.y < area.y + area.h - 70) break;
|
||||
await wheel(low.x, (low.y + high.y) / 2, 200);
|
||||
}
|
||||
p = await json('__gm.pattern.loopAt(1920)');
|
||||
to = await json('__gm.pattern.loopAt(2880)');
|
||||
await drag(p.x, p.y, to.x, to.y);
|
||||
|
||||
Reference in new issue
Block a user