2d4909d24879664be73b1cbb024db90d4cacaeb5
95
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2d4909d248 |
Fit the Compressor's tab to any size, make auto-gain a switch (#378)
Two things came up on the new tab. Floated and sized down, it spilled its knobs out of its window, and sized up it left the window empty. Auto-gain lifted far too much: it brought 0 dB back to 0 dB, which at -40 dB, 4:1 and knee 6 is +28.1 dB, so a sound at -24 dB came out at -6 dB. And as a knob from 0 to 1 it blended between makeup and auto-gain, so at 1 the makeup knob did nothing. Layout: - The knobs stay on the bottom line, centred, with the help line above them. - The levels fill the height left above. The curve is a square at their right, centred up and down. It grows with the tab from 180 to 360 px and takes no more than 40 % of its width (picturesLayout). - A floated window cannot be sized below what its pane needs. A pane says so in its minWidth and minHeight, and the Compressor's is every knob and the smallest pictures. Other panes keep 320 x 200. - A pane put into an open window that is too small grows the window. Auto-gain, as decided in #378: - A Compressor's auto-gain gives back what it takes from a sound at -18 dB, so that one comes out as loud as it went in. At the numbers above that is +14.6 dB. - Auto-gain is a switch on the Compressor, the Limiter and the Saturation alike, a setting and no longer an input. The makeup or output is added on top of it instead of being blended with it. - A project from before reads a number as on above 0. Nodes never set keep their defaults: off on the Compressor, on on the other two. - The tab shows the switch at the right of the knobs, with the dB it gives below it. With it on, the curve has a line at -18 dB from the curve up to the diagonal. - Every level reading keeps the gain of its time. Before, the whole picture was read by the current makeup, so after a change the tinting of the last seconds was wrong or missing. Findings: - My first e2e run of the layout clicked Dock where it was before the window was sized up, so the window stayed open and the next check went red. The check reads the button again. Tests: - Gate (melektaus-atx): 282 files, 4344 tests (6 new) - e2e 34 (melektaus-atx): ALL PASSED, 118 checks (2 new) - Offline in Chrome: a sine at -18 dB through -24 dB and 4:1 came out at -17.92 dB with auto-gain on and -22.42 dB with it off. - Counter-checks: the curve set at the top instead of the middle, and every reading read at a gain of 0, each fail exactly their test. - Looked at the window in screenshots: at its least every knob and the switch show. At 1280 x 797 the levels fill the height, the curve is centred at the right and the knobs are at the bottom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1df38bd430 |
Edit a Compressor in a tab of its own, with knobs and levels (#378)
A Compressor had only its number rows in the node. Its numbers are
easier to set by feel on a dial, and what it does is easier to judge
when the level going in and the level going out are seen side by side.
- Knob is a new widget in the toolkit: a slider bent into a circle,
its name above and a number field below. A drag up or down turns
it, Shift a tenth as far, a notch of the wheel a fiftieth of the
turn, a double-click sets the default, the number takes a typed
value. A taper gives the low end more of the turn.
- The Compressor's tab opens on a double-click, one per node, like
the Parametric EQ's. It has a knob for each of the seven numbers,
greyed while its input is linked.
- Its curve shows level in to level out from -60 to 0 dB. The point
where it bends sets the threshold, the end of the curve sets the
ratio that ends there.
- Its levels show the last 4 s on both sides of the node. Where it
takes more than 0.5 dB away, the stretch below the level coming in
is tinted. The makeup is left out of that.
- The Compressor is tapped like the EQ. SpectrumTaps listens to more
than one node at once and reads the loudest sample of the last
25 ms on each side.
- The node tabs share one map in the editor (nodeTabs), so a third
kind needs no new plumbing.
Findings:
- CI task 614 for
|
||
|
|
23922ee4a2 |
Show the zoom of the music tabs and the EQ as a transition (#377)
The zoom transition of #309 left out the Pattern, Song and Sound tabs
and the Parametric EQ's curve. There a wheel step or Fit still jumped,
whatever "Zoom (ms)" in the general settings said.
- axisTween wraps ViewTween for a single axis. The music tabs zoom
their time across and their rows, tracks or height down apart, so
each keeps one tween per axis and either goes its own way.
- Fit by its button is shown as a transition too, as in the other
tabs. The fit on opening a document still jumps.
- A middle-button pan first lands a transition under way, and saving
a document's view lands it on the step it was heading for.
- The EQ's range is saved on the node at once. While the zoom is on
its way the curve draws a range of its own and writes nothing.
Its dB lines are those of the new range, sliding to their place,
so no line shows a crooked number.
Found on the way, not part of #377:
- CI task 611 for
|
||
|
|
82e96e680f |
Decode a clip from a file of its own, not its whole sound (#349)
A clip was cut from its sound's file, so a game decoded the whole file for it and held it for as long as the page was open. A long recording played through a few short clips cost the recording. Every clip now has a copy of its cut as Opus, with Stream on or off, and a game decodes that instead. - StreamKeeper makes the copy of each clip whether its sound streams or not. Stream only decides whether a sound gets a copy of its own. - clipCopyTable says which cut decodes from which copy. The sound library loads the copy for a clip unless the whole file is decoded already. Then the clip is cut from that as before. - projectFiles decodes the whole file ahead only where something may play the sound itself: no clips, a clip without a fitting copy, or the sound's id anywhere in an asset that can name one. That last test walks the data, not the fields, so no place is missed. - A copy that cannot be had falls back to the whole file. A library that bakes keeps to the whole files, as the bake's fingerprint does. Tests: - Gate: CHECK EXIT 0, 280 files, 4319 tests (7 new: streams +4, sound-library +3). - e2e 34: ALL PASSED, 108 checks (2 new). With Stream off a fresh library decodes the ten-second reversed clip's copy and not the five-minute file. It is the cut made from the original to within 0.0091 a sample and 0.013 dB in any 20 ms. Once a node plays the sound itself, the whole file is decoded ahead again. - e2e 65: all passed with this change. - Counter-checks red as they should be: the library ignoring copies, the whole file always decoded ahead, clip copies only with Stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2764919bb8 |
Stream long sounds from an Opus copy instead of decoding them (#349)
A sound was decoded whole as soon as the project opened, in the editor and in a released game: five minutes of stereo held some 115 MB for as long as the page was open. A sound with Stream on now plays from a copy as Opus in Ogg, fetched a piece at a time and decoded a few seconds ahead by WebCodecs, so a game holds a few megabytes for it. - The Stream switch is a row of the sound, off by default. The Sound tab suggests it from a minute on and says how far the copy has got. - StreamKeeper makes the copies in the browser, a moment after the last change, one at a time: the sound's own and one per clip of its cut, fades and Reverse baked in. A copy that is no longer wanted is taken off and deleted once the project is saved without it. - streamTable says which played names stream from which copy. A clip without a fitting copy, a Sample and a Reverb impulse stay decoded. A sound with Stream on is never decoded ahead. - The page fetches by Range and takes the whole file from a server that ignores it, as the CI's PHP server does. It keeps the bytes and the first two seconds decoded, the rest goes four seconds ahead. - A jump decodes 32 packets before the place first. Measured in Chrome, what follows is then the same to the bit as decoding from the start. With 4 packets it was 0.06 off. - The stream-player on the FX worklet reads the samples at the rate of the context along a Catmull-Rom curve. What comes late is silent and skipped once it is there, so a clip keeps its beat. - The opening stretch goes with the player as it is made. Sent by message it came a few milliseconds late, and 557 samples of the attack were lost. - Pitch on a streamed sound is a shifter after the player, as on a bus: the length stays and it is heard 42.7 ms late. - Bakes of patterns and songs (#340) always stream now. - The song timeline draws a streamed sound from its copy decoded at 8 kHz, keeping only the peaks. Tests: - Gate: CHECK EXIT 0, 280 files, 4312 tests (31 new: ogg +3, stream-queue 6, streams 11, streaming 6, sound-library +5). - e2e 34: ALL PASSED, 106 checks (7 new). Five minutes of mono, 4.8 MB as WAV, 3.6 MB as its copy: streamed the renderer grows by 5.8 MB, decoded by 69.6 MB. Offline at 48 kHz, start, loop seam and a reversed clip are the copy decoded whole to the sample (0). - Counter-checks red as they should be: no pre-roll, no catch-up of late samples at the end, no opening stretch, a streamed sound decoded ahead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
84cd36f902 |
Bake a pattern or song to Opus, played in its place by a game (#340)
A pattern or song played by a game runs every note through the scheduler, the instruments and their FX, live. Bake works that out once, keeps it as a file on the asset, and Play pattern and Play song play the file instead, as one voice, for as long as nothing it is made of has changed. The tabs always play live. - Bake is an icon in the header of the Pattern and the Song tab, lit while the bake holds. A click bakes, cancels a bake that runs, or offers Bake again and Remove bake. The toolbar shows the progress. The info of the tab says "baked" or "bake outdated". - The bake holds everything up to the output of the pattern or song. The sound FX of the node, fx params, volume, fade in, bus, Master routing and stopping with a fade stay live. - A print of the worked-out plan decides whether it holds: notes and clips, tempo, length, loop, tracks, the build of each instrument for each note, sound FX, files, cuts and levels. A rename keeps it, an undo back to the baked state makes it hold again. - The file holds the straight run and then one more round of the loop as heard the second time, tails included. Looped, it plays to the loop end and then round that round, to the sample. Once, it fades out over its last 10 ms. Let ring fades it out over 0.3 s. - It is worked out by a second SoundLibrary on an OfflineAudioContext, its scheduler woken by suspends of the offline clock, and what reaches the master is taken. A worklet probe tells whether that was mono, so a mono bake goes on exactly as the notes did. - Opus in Ogg, 160 kbit/s stereo or 96 kbit/s mono, encoded with WebCodecs, the Ogg pages written by hand. Normalized to full scale, the peak kept and played back. - The file goes up into the sounds of the account as "[bake] name", which the sound picker leaves out. An old bake file is deleted once the project is saved without it. Only a bake that holds is loaded. - Ogg files are decoded at 48 kHz: resampled to 44.1 kHz, the loop of a bake lost a sample a round. - Baking and removing a bake are no undo steps, like the EQ's zoom. Findings: - Chrome's AudioEncoder refuses opus.format 'ogg' (NotSupportedError) but hands out the OpusHead with a pre-skip of 312 for 'opus'. - The Song tab's header is full at 1280 px: a captioned Bake pushed Fit off the tab. Hence the icon. Tests: - Unit: tests/core/ogg.test.ts (6), tests/core/bake.test.ts (16), four new in sound-library.test.ts, one in song-play.test.ts. One test cuts, reverses and fades a clip on a song's track: each change outdates the bake, each undo makes it hold again. - e2e 64 (melektaus-atx): ALL PASSED, 36 checks (6 new). Live and baked rendered offline differ by at most 0.06 dB over 114 windows of 20 ms, two loop ends among them. e2e 65 had no red check with the icon in its header. - Gate (melektaus-atx): CHECK EXIT 0, 277 files, 4281 tests. - Counter-checks: print without instrument builds, print taking the sound's file instead of the clip's cut, round taken from the first pass, bake voice without its looping source, Play pattern or Play song ignoring the bake: one unit test red each. Pre-skip set to 0: the e2e comparison 4.8 dB off, only that check red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3377524958 |
Zoom the Parametric EQ's dB with the wheel, saved on its node (#376)
Close moves of a few dB were hard to set and to see on a curve that always spans -24 to +24 dB. The wheel on free space now zooms the dB the curve shows, and the node's small curve shows the same range. - The range is one of ±3, ±6, ±12, ±24 and ±48 dB, always the same either side of 0. Up zooms in. - It is saved on the node as `range`, so it is back after loading. Like the game scale (#226) it is no undo step. EditorState gets `setNodeView` for such a setting of a node. - The lines across the curve follow the range: every 1, 2, 3, 6 or 12 dB. A handle whose gain lies beyond the range stays on the edge, in sight and in reach. - The wheel over a handle still turns the band's Q. It no longer does so anywhere while a band is picked: after opening, a band nearly always is, and the zoom would be out of reach. - `EqArea` carries the range, so the tab and the node map dB alike. The spectrum keeps its own scale. Tests: - Unit parametric-eq: 16 green, 5 new (range in compile and save, node picture, wheel steps, grid lines, handle on the edge) - Counter-check: the node's picture without the saved range turns the new picture test red - e2e 34 (melektaus-atx): ALL PASSED, 99 checks, 1 new - Gate (melektaus-atx): CHECK EXIT 0, 275 files, 4252 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
687377682c |
Draw the EQ's curve in its node and a spectrum behind it (#375, #374)
Both were asked for in #367 "for later". The node of a Parametric EQ now shows its curve in small, and its tab shows what passes the node while it is seen. #375, the curve in the node: - NodeDef has an optional `picture`: a height, and shapes made from the settings as plain point lists (guide, area, line). It is general, the Parametric EQ is its first user. - NodeView draws it in a box below the rows, again only when the box or the settings change. A drag in the EQ tab changes them, so the node follows it live. - Without a band the box stays, flat on its 0 dB line, so the node does not grow at the first band. - The axes of the curve, `eqCurvePoints` and `fillShapes` move from editor/soundfx/eqView to core/soundfx/parametricEq. Tab and node draw the sum from the same function. #374, the spectrum: - A node marked `tapped` gets a `tap` stage in front of its own stages and one behind them when compiled, in a sound FX and in an instrument alike. Its id is asset and node, since a copied asset keeps its node ids. - A tap is a gain of 1. SpectrumTaps (new, viewport/spectrum.ts) holds the tap of every voice and bus while it plays. - While the EQ tab is seen, in the tab row or in a window, the taps of that node feed one analyser per side (8192 points). Nothing is made before a tap of the node plays, so no AudioContext is opened early. - The tab draws both behind the curve, on a scale of 0 to -96 dBFS: the sound coming in faint and grey, the sound going on in the colour of a sound port. The analyser smooths it, and once the sound has gone the sum still hands it silence, so it falls away. - Once the tab is not seen, Editor.tick lets the analysers go, like the meters of the Song tab. - A tap does nothing to the sound, so `stats().last.stages` leaves it out, and a bypassed EQ keeps its taps. Decided on my own: - Every sound passing the node counts: Play of the sound FX tab, an instrument's keys, a song, a run. The issue proposed that. - The spectrum has no labels of its own. Its scale is in the wiki. Tests: Gate (melektaus-atx) 275 files, 4247 tests (11 new), EXIT 0 e2e 34 (melektaus-atx) ALL PASSED, 98 checks (2 new), twice Counter-checks, each put back by copy and compared with cmp: - The node's curve drawn without its bands: 2 unit tests fail. - The spectrum without the loudest bin between two points: 1 fails. - SpectrumTaps.stop leaving the taps linked: 1 fails. - Taps that never feed the analyser: e2e 34 fails in exactly the spectrum check (frames 0). Run in the container only. The screenshots of the second run were looked at: the blip as one peak behind the curve, and the dip at 4 kHz in the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9ab1bbc507 |
Edit a Parametric EQ in a tab of its own (#370)
The EQ's editor floated over the editor as a window since #367. The user wants it as a tab for now. Whether any tab can float and dock again is weighed apart from this. - A double-click on the node asks for the tab through `EditorState.openEq`, as `openValues` does for a Variables tab. - The tab is a document `eq:<asset>:<node>`, one per EQ node. A second double-click brings the open one up. - Its name is the asset's, with `EQ 2`, `EQ 3` when the asset has more than one. The icon is the sound FX's. - The tab closes with its node, its asset, or a new project, through the same `syncDocuments` as every document. - `EqWindow` becomes `EqEditor`, a `VStack` with a `TabHeader`. The curve takes the room of the tab, 320 x 160 at least. - The window's title bar, dragging and Close button are gone. - Delete in the EQ's tab removes the picked band. It never falls through to the selection, which is still the EQ's node. - The hint below the curve is shorter, it was cut off at 1280 px. Tests: - e2e 34 (melektaus-atx): ALL PASSED, 96 checks, 2 rewritten. The tab opens on a double-click and goes with its node. - Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4231 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1f13794b0c |
Shape a sound with a Parametric EQ and its own curve editor (#367)
A sound FX or an instrument had a 5-Band-EQ with fixed bands and a Filter with one band. The Parametric EQ holds up to ten bands of any type, and a double-click on the node opens an editor that draws the curve they make together. - The bands are a list on the node (`bands`): type, frequency, gain, Q and slope. A hand-edited project is held to the editor's ranges and to ten bands. - Each band compiles to the existing `pass` stage, so the audio core is unchanged. A peak or a shelf at 0 dB is left out. - The curve is worked out in `parametricEq.ts` with the biquad formulas of the Web Audio spec, so the editor draws what is heard. - The editor is a window that floats over the editor without a backdrop, so the tab's Play stays usable. A double-click adds a peak, a drag moves a band, the wheel turns its Q, a right-click removes it. Fields below the curve set the picked band. It opens in the middle of the screen and moves by its title bar. - A drag or a scrub writes the node as a preview and makes one undo step on release. The panels hear of the preview once a frame, and the window lays itself out only when one of its texts changes. - The area under the curve is filled as one shape for each side of 0 dB. As a single shape it crossed itself and came apart. - The curve works out each band's coefficients once and adds the sum up from the bands. A drag took 295 ms of script time in a profile before and 64 ms after. Decided on my own: - Bands are added by a double-click, so a single click can let go of the picked band without adding one. - The cuts are labelled "Low cut" and "High cut" as in an EQ, not "High-pass" and "Low-pass" as on the Filter. - The bands have no input, so no parameter or Envelope moves them. Tests: - Unit parametric-eq (new) and soundfx: 69 green. Counter-probe with no stages emitted: the compile test red. Counter-probe with one fill shape: the fill test red. - e2e 34 (melektaus-atx): ALL PASSED, 96 checks (3 new). - Counter-probe e2e: no stages emitted, the playing check red. - Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4230 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f840ec3c44 |
Snap a new song to 1/16, and route new assets as the others (#365)
A new song snapped to whole bars, too coarse to lay out a beat. And a new or duplicated sound, instrument, pattern or song went to the master alone, so every one had to be linked into the buses by hand again. - A song without a grid of its own snaps to 1/16. The songs in the user's project have Bar written in and keep it. - A new or duplicated sound, instrument, pattern or song gets the links of the one whose targets most others of its type share, at their volume. On a tie the latest of them decides, so a single song decides for the next one. - Where nothing of its type is linked yet, it goes as most of all that plays, and where nothing is, to the master. A new bus still goes to the master alone. - Assets made at once are matched against what stood before, not against each other. A project from before the routing still opens with everything on the master. - The song tab has a gridAt hook. e2e 65 checks the new grid and sets Bar, which its checks go by. Tests: - Unit master-routing (1 new) and song (1 rewritten): 26 green. Counter checks (rule off, grid back to Bar) red. - e2e 65 (melektaus-atx): ALL PASSED, 58 checks (1 new). - Gate (melektaus-atx): CHECK EXIT 0, 273 files, 4226 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0e73fd85b9 |
Route sounds, songs and buses in one Master routing graph (#351)
A project bus could only go into one other bus, and a sound, an
instrument, a pattern or a song into exactly one bus. A shared reverb
needs a sound to go there in part and on dry at the same time. Instead
of a list of sends on every asset, the project gets one routing graph
like the routing of a song (#350), one level up.
- Menu -> Master routing opens a tab with one graph per project. It has
a node per sound, instrument, pattern, song and bus, named after its
asset, and one Master. Embedded assets have none, their song routes
them.
- The nodes follow the assets. Every edit settles the routing in the
same undo step (KeepRoutingCommand), so an undone delete brings the
links of its asset back. A new asset gets a node and a link to the
master. What went only into a deleted bus goes to the master.
- New nodes start in three columns: what plays, the buses, and the
master a row above, so no link into it runs behind a bus.
- Links are sends: -60 to +12 dB, and a Mode on links out of a bus or a
song. Out of a sound, an instrument or a pattern a link always takes
the sound after its volume and FX. Loops are refused.
- A node without a link is silent and says "Goes nowhere".
- The rows Bus and Into are gone. Play nodes with a picked bus still
overrule the routing.
- A click on a node shows its asset in the inspector, the Master shows
the master volume and sound FX. A double click opens the asset's tab.
- The graph lives in project.routing and is held to the assets when a
project is read, but nothing is added there. The editor adds the
nodes when it opens a project. An asset without a node plays on the
master, so old projects sound as before without a migration.
- In the mix an asset with one link at 0 dB goes straight into that bus.
A sound or instrument with more gets a bus of its own with the sends.
A pattern or song sends from the master of its playing, which is
where its Mode taps. Buses of the mix can have sends now too.
Found:
- The check "the scheduler plans a looped bar" in e2e 34 wobbles. It
failed in 2 of 5 runs here and in CI task 580 at
|
||
|
|
5cb7ef7bed |
Show empty asset groups on request, and add by the heading (#361)
The asset panel only listed the categories and types that hold assets. To make the first asset of a kind you had to know its category in the `+` menu. A switch now lists all of them, and a right-click on a heading offers just what sits under it. - General settings has "Show empty asset categories and types", off by default and kept in this browser. Applying it refreshes the panel at once through a new `onGeneralSettings` listener. - `groupAssets(assets, empty)` gives every category and type when asked. The empty ones stand faint with a count of 0, and an empty type has no chevron, since there is nothing to unfold. - `ListItem.faint` draws a row's label, hint and icons at half opacity, like a disabled widget. - A right-click on a category heading offers the types of that category without a submenu, on a type heading only that type. The empty space below the rows still opens the whole `+` menu. `ListView.onListMenu` passes the heading's id for this. - Every menu of new assets says "Create asset" above its entries, so a click on a type reads as making one. `MenuOptions.title` draws that muted line. It is not an entry and matches no search. Tests: - Unit asset-groups and view-tween: 23 green, 2 new. Counter-test with the empty flag ignored: the new groups test fails. - e2e 04 (melektaus-atx): ALL PASSED, 67 checks, 4 new and 1 rewritten. Counter-test with the heading id dropped: the 3 menu checks fail. The title is checked on the `+` button and on a type heading (2 checks extended), the rerun passed with 67 checks. - Gate (melektaus-atx): CHECK EXIT 0, 272 files, 4212 tests. It ran before the menu title. Afterwards only the menu and asset-groups units, typecheck and lint ran, all clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cf96442835 |
Keep the Sound tab smooth while it pans (#360)
Panning the Sound tab drew the whole wave anew on every move, one filled rectangle per pixel column, and gave the ruler's labels new texts whenever a tick left the area. At 2560 px across that took the tab down to 16 fps. - The wave is drawn half the area's width past either side of it. A pan within that moves the drawing. Zoom, height, size or a pan past the margin draw it anew. - The bars of the wave share one fill instead of one fill each. - A ruler label keeps the second it already shows. Before, a pan shifted every label by one and each had to be rendered anew. - A clip's name is given its style only when the style changes. - The rule for what a drawing covers is pure in `waveView.ts` (`shownSeconds`, `drawnSeconds`, `coversShown`). Measured with a copy of the user's project, "Take 1" with 4 clips, 240 pan moves with the middle button, headless on stein2: - 2560x1440: main thread 712 -> 373 ms/s, texts rendered 694 -> 65 - 1280x800: main thread 429 -> 225 ms/s What is left in the profile is Pixi's render pass and the pointer events, which hovering pays as well. Tests: - Unit wave-view: 12 green (1 new), counter-probe red - e2e 34 (stein2, melektaus-atx was down): ALL PASSED, 92 checks (1 new). Counter-probe with the labels given out by position: red, 7 labels set anew instead of 1. - Gate (stein2): 272 files, 4210 tests green Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
490fcba88f |
Meter each track and the song, and limit the output at -1 dBFS (#359)
A song could clip at the output without anyone noticing: a track and a sound item at +12 dB each add up to +24 dB. The Song tab now shows how loud each track and the whole song are, and a fixed limiter behind the master keeps every sample under -1 dBFS in the editor and in the game. - Each track head has a stereo meter up its right edge. The head is 190 px wide instead of 176. - Out sits in the corner above the heads, with the loudest peak since Play in dB. In the tab header it made the row too wide for a 1280 px window, and the inspector lost its fields. - The meters read AnalyserNodes beside the buses, one per side after a channel splitter, so a peak panned hard is not halved. They are tapped only while the song plays and its tab is shown. - A clip lamp lights past 0 dBFS and stays through Stop. The next Play or a click on the meter puts it out. - The meter of a track that mute or solo keeps from being heard is grey. - Music always plays through a bus of its own, with or without a sound FX. That bus is what Out measures. - The limiter is the one from #326 and needs the FX worklet. Until that is loaded the master goes straight out. - It is 5 ms late. latency() counts that in, so the playhead and the alignment of a recorded take follow it. - Unit tests build SoundLibrary without the limiter, as their wiring checks follow every node of a voice. - Out shows "-inf" while nothing was loud. The UI font has no infinity sign, which the font test found. Tests: - Unit meter-view 8 new, sound-library 112 (2 new), 3 counter-tests each red - e2e 65 (melektaus-atx): ALL PASSED, 55 checks (3 new). Out wrote +16.6 dB, behind the limiter the loudest sample was 0.8913 - Gate (melektaus-atx): 272 files, 4209 tests, one red in the font test. Fixed after it, then the font and meter tests ran green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
95edf9959b |
Show the clips not picked faint over a sound's wave (#357)
Only the picked clip showed in the wave, between its handles. Where the other clips lay could only be read from the strip above, so cutting one next to another meant looking up and down all the time. - Every clip but the picked one is drawn over the wave as a pale tint with a thin edge, as high as the file reaches. - With the sound picked, all its clips are faint. While a drag spans a clip, that one is left out. - Test hook soundTab.faintClips() names the faint clips. Tests: - e2e 34 (melektaus-atx): ALL PASSED, 91 checks (1 extended) - Gate (melektaus-atx): CHECK EXIT 0, 271 files, 4195 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
42a3759d9d |
Cut a sound into named clips, as a texture into frames (#357)
A sound could only be cut to one stretch, set on the asset itself. To
play two parts of one file, the file had to be imported twice. Now a
sound is always the whole file, and its clips are the stretches of it,
each with a name and a cut of its own.
- A clip is { id, name, props } on SoundAsset.clips. Its id shares the
generator with asset ids, so a sound field holds a sound or a clip,
as a texture field holds a texture or a frame.
- props are the cut: start, end, the fades and their curves, reverse.
Volume, loop, sound FX and bus stay on the sound for all its clips.
The cut rows left the sound's schema for CLIP_ROWS.
- findSound resolves either id. Play sound, the Sample of an
instrument, a Reverb impulse, song items, the scheduler and the test
sound of the Sound FX tab all go through it.
- Every sound field offers each sound followed by its clips, "Kick",
"Kick / Clip 1". A clip that is gone reads as "<id>?".
- The Sound tab has a strip under the ruler with the clips as bands.
A drag there makes a clip, a click picks one, beside the bands the
sound. Only the picked clip has handles. Reverse, fades and curves
in the header work on it and are greyed out without one.
- Whole file is gone from the header, as the user asked.
- Picking a clip is a selection of its own, like a frame. The
inspector shows its name, its cut rows and Delete clip. Delete
takes it away without asking, as with a frame. Ctrl+Z brings it
back.
- Copies get fresh clip ids: Duplicate, Embed a copy, a song
duplicated with its takes, and the copy a split or paste makes of
an embedded take. An item on a clip plays the same clip of its copy.
- A double-click on a song item playing a clip opens its sound with
the clip picked. The Sound row of an item on an embedded take
offers the take and its clips, which no list shows.
- A new take stays the whole sound. Clips are made by hand, as the
user decided.
- No migration in the code, as the user decided. The one project
with cut sounds was changed over by hand.
Deviation from the issue: deleting a used clip does not warn. A frame
does not warn either, and the issue had promised "like a frame".
Tests:
- Unit sound-clips (new): 11 green, 7 counter-checks each red
- Unit sounds, embedded: adjusted to clips, green
- e2e 34 (melektaus-atx): ALL PASSED, 90 checks (3 new, 12 rewritten)
- Counter-check in the browser: clips left out of the sound fields,
the picker check fails
- Gate (melektaus-atx): CHECK EXIT 0, 271 files, 4194 tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
c7fa1d15b0 |
Zoom the height of a sound's wave over its level scale (#357)
The Sound tab always drew the wave at the full height of the tab. Quiet stretches were hard to see, and there was no room above a loud wave. - A level scale in dB stands left of the wave, a mark every 6 dB down from 0, as long as a mark lies inside the area and at least 14 px from the middle. - The wheel over the scale zooms the height, from 0.25x to 16x. Over the wave it zooms the time as before. - What is louder than the area stops at its edge. The fade ramps grow with the wave. - Fit puts the height back to 1x. The tab keeps the height with its view when another tab is shown. - The rules live in waveView.ts (zoomLevel, levelMarks), the hooks give the height, the scale and the top of a full-scale wave. Tests: - Unit wave-view: 10 green (2 new), 2 counter-checks each red - e2e 34 (melektaus-atx): ALL PASSED, 87 checks (1 new) - Gate (melektaus-atx): CHECK EXIT 0, 270 files, 4183 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
25c1d02b06 |
Keep the audio thread free of ended voices and too many vocoders (#356)
A song that had been played a few times in a long session stuttered at regular intervals until the page was reloaded. A quick run up the keys of a sampled instrument made every sound drop out for a second or two. Both came from work the audio thread should not have had. - Every worklet node of a voice listened on its port. Chrome never collects a port that is listened to, and through its listener not the voice either. Every node of the voice stayed on its bus and was worked on in every block. A way or stage on the worklet now closes its port once it ends or is stopped, and the processor closes its own side when it is told to stop. - A voice without echoes now leaves its bus as soon as its sound and what its stages hold back are through. Before, only a voice with echoes did. - At most 12 ways play through the pitch worklet at once. One more cuts the oldest in 10 ms, a note already let go before one still held. Each runs a phase vocoder, whose first block alone took 2.7 ms of the 2.67 ms a block has on stein2. - Play for the game ends everything the tabs play: the piece holding the transport and every key or preview (`stopPreviews`). - Closing a Sound, Sound FX, Instrument, Pattern or Song tab stops what it plays of that asset (`AssetTabEditor.closed`). - A preset in the Instrument tab plays nothing any more. With Audition on it is heard like any other change. - Test hooks: `__gm.instrument.play`, `__gm.sound.playing`, and `vocoders` in the stats of the sound library. Measured: - A copy of the project, its Song 2 played for 12 s and stopped, six times (Chrome on stein2, CDP WebAudio): before, 746 nodes were left after the first time and 4469 after the sixth, the load at rest grew from 0.01 to 0.06. After, 7 nodes are left every time (the master and one bus) and the load at rest stays at 0.01 to 0.02. - Pitch-player voices live in Chrome on melektaus-atx: 12 take 0.50 of the audio thread, 16 take 0.71, 24 take 0.99. 30 struck 40 ms apart held it at 0.99 from the first second on. Tests: - Unit sound-library: 110 green (3 new), 4 counter-probes each red - e2e 34 (melektaus-atx): ALL PASSED, 86 checks (3 new, 2 changed) - e2e counter-probe with game start and preset undone: their 3 checks red, and the old check of Play note in the game as well, which now counts on the preview being gone - e2e counter-probe with tab close undone: exactly its check red. A first form of it held a key and stayed green, since the keys let go of a held key by themselves once the tab shows no asset - Gate (melektaus-atx): 270 files, 4181 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8c0d1cfd3f |
Snap the routing graph of a song with the header's magnet (#350)
The routing of a song is the same graph editor as a sound FX, but its tab had no magnet, so its nodes could not be put on the grid. - The Song tab carries the magnet next to Routing. It is off at first and acts on the routing graph. - It is greyed out on the timeline, as Grid is greyed out in the routing. Switching the view only updates these two buttons, so a playing song is not handed its sequence again. - The check hooks give the magnet and the snapping of the routing. Tests: - e2e 65 (melektaus-atx): ALL PASSED, 52 checks (1 new) - Counter-check (magnet enabled on the timeline): exactly this check red - Gate (melektaus-atx): CHECK EXIT 0, 270 files, 4178 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7a17030472 |
Give every sound FX effect a Bypass and a Wet (#354)
A node could only be taken out of a sound FX by unplugging it, and only six of them could be blended with the sound they get. Every effect now has both, in the sound FX graph and in the instrument graph alike. The Mix, the Input and the Output have neither. - Bypass is a checkbox setting. A bypassed node is left out when the graph is compiled, so it costs nothing and holds nothing back. The graph draws it faded, and its body says "Bypass: true". - Wet is a number input like the others. It can be a parameter, be moved by Set sound FX parameters and, where the node allows it for its other numbers, follow an Envelope. - A blend is a stage of its own (`wet`) around what the node adds. The sound as it came in is held back as far as the node is late, so a Compressor, a Limiter or a Vibrato do not comb it out. - The six nodes that had a Mix keep their key `mix` and only call it Wet. Saved values, parameters and edges stay as they were. - The Delay crossfades now: the sound alone at 0, the sound and its echoes in full at 0.5, the echoes alone at 1. Before, the sound was always in full and `mix` was the echoes. - The Pitch takes its semitones by Wet instead of blending, through a share `times` on the amount that an instrument binds as well. Findings: - A Delay saved before this sounds different. It sounds the same at half its old value. The two chip presets with a Delay (Echo Lead, Bell) are halved to keep their sound. - Project cbbf160bb7e8 has two Delays (mix 1 and 0.5). They are left as they are. Tests: - Unit: soundfx 65 (8 new), sound-library 107 (3 new), instrument 35 (1 new), envelope 13. Two delay tests and the filter ports test follow the new numbers. - Counter-checks: 8, each turns its test red. - e2e 34 (melektaus-atx): ALL PASSED, 83 checks (2 new). The ping-pong check at wet 0.25 measures what it did at mix 0.5. - Gate (melektaus-atx): CHECK EXIT 0, 268 files, 4154 tests. - After the gate the derived ports of a blend are asked for once instead of twice: typecheck, lint and the three unit files green again (113 tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
daeffe0f9d |
Route the tracks of a song through a graph (#350)
Every track of a song went straight into its output. Now the Song tab has a second view, Routing: a graph with a node per track and one Output, the links between them sends. A track can go into other tracks, into several at once, and a link has its own volume and the point of the track it taps. - The song asset has a graph like an instrument (SongTrack, SongOutput). heldSongGraph keeps it in step with the tracks on load and in SetSongCommand: a node per track named after it, one output, a new track and every track of an old song linked to the output. Links of gone tracks, doubled links and links closing a loop are dropped. - A link carries an optional send (volume in dB, tap). The inspector shows Volume and Mode for a picked link of a song: Post volume (Post pan), Pre volume (Post FX), Pre volume (Pre FX). SetSendCommand. - NodeDef has two new flags. `fixed` nodes are never added or deleted by hand. Links between `acyclic` nodes may not close a loop, checked in canConnect, dropTargetPort and ConnectCommand. - songBuses gives each track its sends instead of one `into`, and a bus `song:<song>:<track>:#items` for its own items. That bus is what solo closes on a track that only passes a soloed one on. - Mute and solo now act on the sends (trackGates). A muted track passes nothing on, on any tap. A soloed track is heard with what goes into it, through the tracks it goes through, their own items silent. Only sends on a way through a soloed track stay open, so a track going into it and into the output is heard through it only. - SoundLibrary: a bus has either `into` or sends. Each send is tap, delay, level, target. busLatency takes the latest way (arrivalOf), and each send is held back so all ways of a track arrive together (sendDelays). Taps are wired again whenever a chain comes or goes. - The Song tab has Timeline and Routing in its header. The routing uses GraphCanvas with an empty palette. A track linked nowhere shows "Goes nowhere". Delete takes links, never a track node. - Game playings route the same way, the output being the master of the playing. Decided without asking: a send ranges -60 to +12 dB like a track, default 0 dB Post. Mute closes the sends, the track's own level stays. Found on the way: the inspector built the Mode row with the send as it was when the rows were made. Setting Volume and then Mode wrote the old volume back. Both rows now read the current send when they change. Tests: - Unit song-routing (new): 13 green. Sound library: 4 new, 94 green. 11 counter-probes (loop on load, canConnect, solo, solo past the soloed track, mute, preFx latency, graph sync, delete guard, delays, taps, glide), each red. - Existing tests adjusted to the new bus names and send shape: song, bus-fx, song-play, graph registry, edge description. - e2e 65 on melektaus-atx: ALL PASSED, 43 checks (5 new). The first run caught the inspector bug. Counter-probe with the old closure: 2 FAILED. - Gate on melektaus-atx: typecheck, lint, format clean, 2 tests red (registry rule, edge shape), fixed and rerun locally: 56 green. - Not rerun after the per-send solo fix: e2e 65 and the gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3a52f2290f |
Mix sounds through buses and a master (#325)
Until now every sound went straight to the speakers. There was no point where all of them came together, so nothing could turn the music down against the effects, and a compressor or a shared echo over everything had nowhere to sit. The bus technique of #346 was there already, only the end of the chain was missing. - Bus asset (Audio): Volume, Sound FX and Into. Into is empty for the master and never offers the bus itself nor a bus that already goes into it. The list is worked out when the menu opens, so a change made meanwhile counts. A loop that is in a project anyway sends the buses on it into the master. - The master is no asset. Its volume and sound FX are two new rows under Master in Project settings, one undo step each. A project written before has 1 and none, which is how it sounded. - Sound, instrument, pattern and song have a row Bus, empty for the master. Play sound, Play note, Play pattern and Play song have an input bus: From the asset, Master or a bus. Inside a song or a pattern only the bus of the song or pattern counts. - Set bus volume (bus, volume, time, easing) and Bus volume. An empty bus is the master. The volume set holds for the whole run, across scenes, over that of the asset. GameRun resets it at start and end. Set sound FX parameters without a voice now reaches the sound FX of the bus picked in its new bus input. - SoundLibrary: the master is the bus "mix", each bus "mix:<id>". Every bus, the playing masters of #346 included, ends there, and only the master is heard. setMix sets them up once a context exists, so an editor nobody listened to opens no audio device. The same mix again changes nothing, which lets the editor hand it over on every change of the project. - The tabs play as the game does: Sound, Sound FX, Instrument, Pattern and Song tab through the bus of their asset and the master. - Pause game does nothing to the buses (the user's answer 4b). A game that wants quiet in its pause menu sets a bus volume itself. - Streaming long music files is #349, split off as asked. Finding on the way: the Into menu first worked from a list made when the inspector row was built. After another bus was sent into the open one, e2e 34 saw it still offered. It is now asked on open. The SoundLibrary tests expected voices to end at the destination. They now end in the master, which adds its input, level and pan to every wiring and two gains to every list. Those expectations are updated, nothing else in them changed. Tests: - Unit buses (new): 15 green. Ten counter-checks, each red on its own: loop check, bus picked, reset at run start, bus of Set sound FX parameters, master volume in the settings, and in SoundLibrary default into the master, voices into the master, run volume over the mix, no context before a play, a playing on its bus. - Unit sound-library: 90 green (5 new). Node catalog: 416 green (9 new cases). - e2e 34 (melektaus-atx): ALL PASSED, 75 checks (3 new). The voice of the Sound tab was heard on mix:<bus>, that bus into the other, that one at 0.5 into the master. Master volume 0.6 via the dialog, undo back to 1. - Gate (melektaus-atx): CHECK EXIT 0, 262 files, 4072 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a848cf8469 |
Pick a new asset from category submenus in the + menu
The + menu was one long list in two groups, placeable types and library ones. That split is no longer wanted. The menu now lists the categories of the asset panel, and each opens its types beside it, so a new asset is filed where the panel shows it. - Menu learns submenus: an entry with submenu instead of onSelect opens a second Menu right of the first, or left of it where the canvas ends. Its first entry lines up with the row that opened it. - A submenu opens on hover or click. Resting on another entry swaps or closes it after 150 ms, so a pointer cutting across the rows on its way into a submenu does not lose it. - The row of an open submenu stays lit, and a triangle pointing right marks every such row. - A submenu has no backdrop and no Escape of its own. Popup takes a backdrop option for that. The first menu's backdrop and Escape close the whole stack, and picking an entry does the same. - assetMenuCategories replaces assetMenuGroups and takes categories and their order from ASSET_GROUPS. The types are sorted by label. - New test hooks __gm.menuSubmenus() and __gm.addMenuCategory(). pickAssetType opens the category first, so the modules that pick a type through it need no change of their own. Modules 04, 10, 47, 48, 53 and 54 open the category themselves where they read the menu. Tests: - Unit asset-menu: 4 green - e2e 04: ALL PASSED, 63 checks. New: categories in panel order, each type once and sorted, submenu placement, hover swaps the submenu, picking closes menu and submenu - Gate: CHECK EXIT 0, 261 files, 4040 tests - The other modules that use the + menu did not run here Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5e09ba9a50 |
Group the asset panel by category, then by type
The asset panel listed one heading per type, 30 of them side by side. Now the types sit under five categories, and each type heading reads like its entry in the + menu. - ASSET_GROUPS replaces ASSET_GROUP_LABELS and gives every type a name and a category: Basics, Visuals, Logics, Physics or Audio. - The categories keep that fixed order. The groups in a category and the assets in a group stay sorted by name. - A category heading folds like a group heading. A project opens with every category open and every group folded, as before. - Selecting an asset unfolds its category and its group. Folding the category of the selected asset lets go of the selection. - The headings are singular now (Scene, Sprite, Sound FX). The old plural names only kept them apart from the + menu entries. - locate looks in the popup layer first. A click by text meant for a menu entry hit the heading of the same name, since the panel is drawn first. Below an open menu a click only closes the menu anyway. - New test hook __gm.assetCategories(). Tests: - Unit asset-groups: 6 green, counter-check (empty categories kept) 1 red - e2e 04: ALL PASSED, 63 checks (1 new). Counter-check with locate taking the first hit again: 2 red, the click on Behavior created no asset - Gate: CHECK EXIT 0, 261 files, 4040 tests - The category names got their final plural form after these runs. The unit test ran again on them (6 green), e2e 04 did not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4c7861b5b8 |
Keep the frame rate up while painting a big tilemap (#347)
Painting on the 60x60 map of a project with fifty objects dropped the editor to 20 to 39 fps. A profile of a brush stroke showed four causes, none of them the brush itself. - Each frame of a stroke previews the model. The asset and hierarchy panels then built every row anew, so every label became a new text and a new upload to the GPU. One run spent 46 % of the main thread in those uploads. ListView.setItems now keeps a row whose item draws the same (sameListItem) and builds only the rows that changed. - The brush drew the whole scene on every pointer event, on top of the preview. Now the preview draws what the stroke changed, once a frame. The rectangle tool asks for one drawing a frame for its box. - An animated tile made the view draw in the tick and the preview drew it again in the same frame. animate now asks just before the frame is drawn and stays out when the view was drawn since the tick began. - layoutSymbols ran on every drawing and costs up to the cube of the symbols. SymbolLayoutMemo keeps the spots while no anchor moves. - A painted cell made the map read every "x,y" key again, and once more for each of the eight neighbours of every cell. layerCells reads a layer once per content into an index by place. layerTiles and the blocking cells take their neighbours from there (neighboursIn). parseCellKey reads a key without split, with the same results. The test hook sceneRedraws counts how often the scene view was drawn. Not done: a painted cell still works out the tiles and blocking cells of the whole map again, so that part grows with the map. Tests: - Brush stroke on a copy of the project (stein2, Chrome): 20 to 39 fps before, 47.5 after, 55 to 57 without painting. Labels drawn again during the stroke 2012 before, none after. Scene drawings per frame 1.32 before, 1 after. - One painted cell, headless: 28 to 16 ms on 60x60, 109 to 49 ms on the same map doubled to 120x120. - frame-preview: 2 new, red with the old pointer redraw. - tilemap: 3 new (key reading, index, refresh), list-ping 1 new, symbol-layout 1 new. 5 single reversals turned each red. - e2e 39,40 (melektaus-atx): ALL PASSED, 107 checks (1 new, a fast stroke: 6 drawings in 6 frames, no label drawn again). With the row reuse and the pointer redraw reverted it fails: 24 drawings in 3 frames, and the panels draw their labels again. - Gate (melektaus-atx): CHECK EXIT 0, 260 files, 4038 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
101b12504e |
Play a sound backwards with a Reverse switch on the asset (#321)
A sound asset can now play its stretch backwards. The switch sits on the asset, so every way the sound is heard plays it reversed: Play sound, a song item, an instrument's Sample and a Pitch in a sound FX. There is no Reverse node in the sound FX graph. The user dropped it, because playing backwards needs the whole sound in advance, which a bus or a Chip does not have. - The cut gets a `reverse` flag, stored as the asset property `reverse` (default off). The cut name ends in `~reverse` when it is on, so a reversed sound over the whole file is a cut of its own. - The handles keep marking the same part of the file. The reversed copy is made once, like any cut, and the fades follow what is heard: Fade in ramps at the end handle, Fade out at the start handle. - The Sound tab has a toggle with a left-pointing play triangle. Its playhead runs from right to left, and the wave draws the ramps at the edges they now belong to. The inspector has a Reverse row. - The icon is a tab header icon (`HEADER_ICON_SVG`, next to the snap magnet), not a top bar icon. The first gate run had it in the top bar set, and the test that fixes that set went red. - A song item draws the waveform of a reversed sound backwards. Lengths stay as they are. - The header label reads "Fade in" instead of "Fade in (ms)" and the fade fields are 8 px narrower. With the new button the readout was cut to "0.03 s of 0..." at 1280 px, now it shows in full. The inspector keeps the unit. Tests: - Unit sounds 22, sound-library 85 (1 new each). Counter-checks: without turning the samples round, without moving the fade in to the end, and without mirroring the peaks, all red. - e2e 34: ALL PASSED, 72 checks (2 new). The reversed stretch starts with the last samples of the forward one, and the playhead ran 0.75, 0.60, 0.45, 0.30, 0.15 over the whole file. - Gate: first run red, 1 of 4031 (the top bar icon list). After moving the icon, CHECK EXIT 0, 260 files, 4031 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d505f6ed27 |
Show what the song plays around a pattern as ghost notes (#339)
A bass line is written against the chords it goes with. Until now the piano roll showed only the pattern itself, so writing a part to fit the rest of a song meant switching between the Pattern and the Song tab. - A double-click on a pattern item remembers song, item and the round clicked into for that pattern. The place lives in the session, not in the project, and goes by the item's id, so moving the item moves the ghost notes along. - Opened from the Assets panel, the pattern shows the item of the last double-click. Without one, a pattern that lies in exactly one item of all the songs shows that item's first round. - Ghost notes are the pattern items of the other tracks all along the song, and on the own track the items before and after and the other rounds of the item. The round edited itself does not count. Sound items have no notes. - A ghost note sits on the row it sounds with: its own transpose, less the transpose of the item the pattern was opened from. - The roll draws them grey, without an edge, under the notes. Resting on one names its track and its pattern. - The header has a Ghosts switch, on by default, and names where they come from, e.g. "Tune · Bass · bar 5". - The time axis pans back to the start of the song, with bar and grid lines before the pattern. Fit still frames only the pattern. - A click or a paste outside the pattern no longer lands. Before, only the time left of bar 1 was closed, notes could still be drawn past the end, where they are kept but never played. - The rounds of a pattern item are now worked out in one place, itemNotes() in song.ts, which songSequence() and the ghost notes share. Tests: - Unit ghosts (new): 5 green. Counter-check with the round edited not left out and the own transpose not taken off: 2 red. - Unit piano-roll: 1 new (pan back to the song, lines before bar 1). Counter-check without the song's start: red. - e2e 65 (melektaus-atx): ALL PASSED - 38 checks (3 new). The first run was red on the tooltip check: the ghost note it rested on lay a fifth up, outside the rows Fit frames. The test now uses a tone. - e2e 64 (melektaus-atx): ALL PASSED - 30 checks. - Gate (melektaus-atx): CHECK EXIT 0, 260 files, 4027 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
29b604dc07 |
Fade a sound's start and end apart, each along its own curve (#345)
A cut sound had one edge fade for both ends, always a straight ramp. Now its start and its end each have a fade of their own, and each fade has a curve of its own. - Fade in (ms) and Fade out (ms) replace Edge fade (ms), 5 ms each by default. Like before they apply only to an edge inside the file. - Fade in curve and Fade out curve each offer Linear (the default), Equal power, S-curve, Exponential and Logarithmic. A fade out runs its curve backwards. - The Sound tab's header has a field and a curve dropdown per edge. Each dropdown shows its curve as a small picture, rising or falling, and names it in its tooltip. With two named dropdowns the header was too wide at 1280 px and cut off the readout on the right. - The inspector shows the same four rows, the curves by name. - The wave draws each ramp in its curve rather than as a line. - A cut plays by <file>#<start>~<end>~<fadeIn>~<fadeOut>~<curveIn>~<curveOut>. - No migration, as asked in the issue: the old fade key is no longer read, so saved sounds start with 5 ms each and Linear on both. Tests: - Unit sounds, sound-library: 96 tests - Counter-check (fade out along the fade in's curve): 1 red - e2e 34 (melektaus-atx): ALL PASSED - 69 checks - Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3978 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f3ae19431a |
Make an 8-bit sound with a Preset, Randomize or Mutate (#329)
A Chip could play a sound, but finding a good one meant typing a dozen numbers. sfxr's buttons do that in one click: a kind of sound drawn anew every time, a wild draw, or a small nudge. The Instrument tab now has the same, and plays the result at once. - Preset menu with Pickup, Laser, Explosion, Powerup, Hit, Jump and Blip. It puts a new graph in place of the instrument's, as one undo step: Notes for all 128 notes, Chip, Envelope, Filter, Bitcrusher, Output. The Chip keeps its own frequency with follow note off, so every key plays the same effect, and sustain 0 ends it by itself. - The presets are sfxr's generators, worked out into Hz, semitones and seconds with sfxr's own formulas, in chipPresets.ts. sfxr's envelope holds and falls in a line, ours falls quick at first. So the decay is three times sfxr's hold and fall. - Randomize draws every number of the Chips, Envelopes, Filters and Bitcrushers anew, as sfxr's randomizer does. Waves, filter types, the Bitcrusher's mix and an Envelope's from and to stay. - Mutate moves each of those numbers a little, one in two. A 0 stays 0, so nothing that was off comes on. - An input fed by an edge is left alone by both. Both are grey while the graph has none of those nodes. - Audition, off by default as in the Pattern tab, plays the note last played again 150 ms after a change to the graph rests. - Preset, Randomize and Mutate always play that note, for up to 2 s. - ReplaceGraphCommand swaps nodes and edges and keeps groups and notes. - The info on the right of the header kept 220 px and was cut off with the four new buttons. 140 px still fit "No sound picked". Not done: punch, filter sweeps and the phaser of sfxr (#341). Tests: - Unit chip-presets (new) 7. Counter-check: a wrong Hz formula and fed inputs not left alone turn 3 of them red. Unit tests of the instrument tab, graph commands and sound FX nodes: 49 files, 851 green. - e2e 34 (melektaus-atx): ALL PASSED, 64 checks (4 new): Preset, Randomize and Mutate, Audition, three undos back to the Sample. - Screenshot with a Laser preset: the header fits at 1280 px, the chain is fitted, the Chip is a saw with its duty grey. - Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3969 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f1f4a57589 |
Cut a sound to a stretch of its file in its own Sound tab (#343)
A sound asset now opens in a tab that shows its file as a waveform. Two handles set where the sound starts and ends in the file, and that stretch is the sample: Play sound, a song item and an instrument's Sample all play it and nothing else. The file itself is never changed. - The asset has three new rows: Start (s), End (s) and Edge fade (ms). End 0 is the end of the file, the fade is 10 ms by default. Older assets have no values and play the whole file as before. - Every way of playing asks playedSoundOf() for a name instead of the file: the file, or <file>#<start>~<end>~<fade> for a cut. - SoundLibrary makes the cut from the decoded file the first time that name plays and ramps each edge inside the file over the fade. Length and peaks of a cut are read from the file without a copy. - Cut buffers can be made again at any time, so the library lets the oldest go past 64 Mi samples. A voice holds its buffers itself. - The tab draws min/max per pixel from 256-sample block peaks, and the samples as a line when zoomed in far. Wheel zooms, the middle button pans, as in the music tabs. Wave, cut and ruler sit under a mask, so a handle panned out of view does not draw over the panels beside. - Dragging a handle moves that edge, dragging elsewhere spans a new stretch. One drag is one undo step for both edges. Whole file puts the handles back, Play or Space plays the stretch with a playhead. - The inspector preview and the Sound FX preview play the cut too, and the song lanes draw and repeat it. Tests: - Unit: sounds 20, wave-view 8 (new), sound-library 68, plus 258 in the touched areas (instrument, song, pattern, inspector, tabs) - e2e 34 on melektaus-atx: ALL PASSED, 57 checks (7 new) - Gate on melektaus-atx: CHECK EXIT 0, 253 files, 3936 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2fdf97f7de |
Record a take from the microphone in the Song tab (#342)
A song could only be filled with sounds that were made somewhere else. Record plays the song from the cursor and takes the input meanwhile, so a part played along lands on the bar it was played to. - An audio worklet takes the input's samples on the clock the song plays on. Each run carries the frame it was taken at, so the take is cut where the song was heard to start. The output's and the input's latency are taken off, as far as the browser reports them. - The take goes up as mono 16-bit WAV. Sound, a new track "Recording" under the track clicked last and the item at the cursor are one undo step. The sound is named "Song – Take 1", 2, 3 and so on. - While recording the take grows red with its waveform on its track, and a meter beside the input dropdown shows the level. The lanes take no edits then, only a middle-drag pans. - The loop is not used while recording. The song plays once through and on past its end until Stop, Play or Space keep the take. Esc throws it away and nothing goes up. - Echo cancelling, noise suppression and auto gain are off, nothing is played back. The input picked is kept in localStorage. - A take stops by itself at the size a sound may have. A failed upload offers Retry, Download WAV and Discard. - The API takes sounds up to 256 MB and bodies of the same size. nginx and PHP were raised to match outside the repo (body 256m, upload and post 256M, memory_limit 512M). - The pure rules (cut, cap, WAV header, name, track) live in core/music/take.ts. The scheduler tells where it started on the audio clock. - The e2e browser gets chromium's fake microphone and skips the permission prompt. Each run leaves its take in the sound store. Found on the way: - The header was too full with the new widgets and cut the track and item count on the right. Input dropdown and meter are narrower now. Tests: - Unit take.test.ts: 7 new, svg-icons adjusted to 8 toolbar icons - e2e 65 (melektaus-atx): ALL PASSED – 31 checks (+4) - Gate (melektaus-atx): CHECK EXIT 0, 252 files, 3923 tests - Probe on stein2: a 3 s take of the test tone, peak 32767, 285100 bytes, waveform seen in the screenshot Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a3101c590c |
Size notes by either end and pan before bar one in music tabs
A round of small fixes to the Pattern and Song tabs: what the ruler and a pan do in both, and a few things the tabs carried that were in the way. - A middle-drag can take bar 1 up to half the grid or lanes in. The time before it is drawn dark on the ruler, the grid and the lanes. Nothing lands there: a click draws no note, a drop shows no frame. - A press or drag on the ruler puts the start on the nearest grid line, no longer on the one before the pointer. - A new song has one track instead of four. The "+ Track" row under the heads is gone, a head's menu adds tracks above or below. A song still keeps at least one, so that menu is always there. - A note can be sized by its left end too: the start goes to the nearest grid line and the end stays put, never before tick 0. - An end of a note under the pointer lights up and shows a resize cursor, as on an item of the Song tab. Tests: - Unit: piano-roll, song-edit, song, song-play 52 tests. Two new cases for the left end and the pan, the song fixtures add their tracks themselves now. - e2e 64+65 on melektaus-atx: ALL PASSED - 50 checks (+2). e2e 65 adds its tracks through the head's menu and clicks the ruler three quarters into a bar, which only the nearest line passes. - Gate on melektaus-atx: 251 files, 3909 tests, CHECK EXIT 0. - A probe shot of the piano roll panned in: the time before bar 1 dark, the left end of a note lit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
aa097bd076 |
Zoom with the wheel and set the loop on its own strip in music tabs
The ruler of the Pattern and Song tabs did two things with one drag,
and the wheel did three things with Alt and Shift. Both now do one
thing each, and the Song tab gets a few edits it was missing.
- The ruler has a loop strip along its top. A drag there sets the
loop, a press or drag below it moves only where playing starts.
- The wheel only zooms: the time over the grid, lanes and ruler, the
rows over the keys, the tracks over their heads. Alt and Shift no
longer change it. A middle-drag still pans.
- Tracks can be zoomed from 28 to 160 px. Below 46 px a head shows
only its name, M and S. An old saved view gets the old height.
- A click on nothing in the lanes picks the song itself.
- A right-click on an item opens a menu with Delete instead of
deleting it. The inspector has no delete buttons any more.
- A sound item goes up to +12 dB, a track up to +12 dB. A pattern
item stays at 0 dB, since a note is held to unity.
- An end of an item under the pointer lights up and shows a resize
cursor. A long item marks every point where it starts over.
- The Pattern tab has an Audition switch in its header, off at first.
Only with it on does a note sound when drawn, grabbed or moved.
The keys down the left sound either way.
- A looped sound item draws every round alike. Each round is drawn
from its own start, and each column shows the loudest part of the
sound it covers instead of one sampled bucket.
- A song is at least one bar long instead of four. An empty song
fits that bar across, and Play song ends after the last item.
- The ghost notes held back in #334 got their own issue, #339.
Finding: suite 542 for
|
||
|
|
8f9c250eb6 |
Lay patterns and sounds out on tracks and play them as a song #334
Patterns could only be heard one at a time, in their own tab or from a behavior. A song lays patterns and sounds out on tracks along one time axis and plays them through the scheduler of #332, the third piece of the music tools of #330. - New asset type song under the heading Songs, with four empty tracks, 120 BPM in 4/4 and a grid of a bar. Tempo, meter and grid are rows in the inspector. Tracks, items and the loop are the asset's data, held to their rules on load and in every command. - A track has a name, volume, pan, mute and solo. Each is heard on a bus of its own, so a change reaches what is sounding at once. - A pattern item repeats its pattern for as long as it is, with a transpose and another instrument if wanted. A sound item plays its sound as a clip at its own speed, round and round, through a sound FX if wanted. Both have a volume on top of their track's. - Cut back at the left end or split, an item starts as far into what it plays as the cut is: ticks into a pattern, seconds into a sound. - The tab shows the heads with M, S, volume and pan, the ruler of the Pattern tab and the items, a pattern with its notes, a sound with its waveform, a missing asset red. Patterns and sounds are dragged in from the Assets panel. Body, right and left end move, lengthen and cut back, Alt leaves the grid. Ctrl+C/V/D/A, Ctrl+E splits where playing starts, Delete, arrows. A double-click opens a pattern item in its tab. - What is picked is a new selection of its own, so the inspector shows the item or the track, with a button to delete it. - The scheduler plans clips beside notes. Where the playhead starts or jumps back inside a clip, it is taken up that far into its sound. A clip fades out at the end of its item or of the loop. - The editor plays one piece at a time: a pattern started stops the song and the other way round. - The rules are pure functions in core/music/song, editor/song/ songView and songEdit. Findings: - The suites of |
||
|
|
4ea752feef |
Draw notes for an instrument in a piano roll and loop them #333
Notes could only be played one at a time, by a key or a behavior. A pattern is a stretch of notes for one instrument, drawn in a piano roll and played in time through the scheduler of #332. Songs (#334) will lay patterns out on tracks. - New asset type pattern under the heading Patterns. Instruments are listed under Instruments now, since a heading is one per type. - Notes are a start and a length in ticks, a note number and a velocity, kept to their rules on load and in every command. The instrument, the length in bars, the grid, the meter and the tempo are rows in the inspector. The loop is the asset's own data, the whole pattern while none is set. - Grids from 1/1 to 1/32 and the triplets 1/4T to 1/32T, all whole ticks. The meter only moves the bar lines, the notes stay put. - The tab shows the keys down the left, a ruler with bars, beats, loop and start along the top, the notes between them and a velocity lane below. Keys the instrument plays nothing for are grey, the names of a drum kit stand on its keys. - A click draws a note on the grid line before the pointer, as long as the last one drawn or lengthened and as hard as the last velocity set. The body moves a note, the right end lengthens it, Alt leaves the grid. A drag on empty grid picks with a band, Shift adds, a right-click deletes. - Keys: Space plays, Delete, Ctrl+C/V/D/A, the arrows by a semitone or a grid step and with Shift by an octave or a bar. A paste lands where playing starts. Every gesture is one undo step. - A click on the ruler sets where playing starts, a drag the loop or one of its ends, its menu loops a bar or the whole pattern. - The wheel scrolls, Shift across, on the ruler or with Alt it zooms. Ctrl+wheel stays with the browser, Pixi listens passively. - Play runs the loop through the scheduler. An edit goes to it on the next window without a restart, a new loop or tempo counts from what is planned. Another pattern shown or a project opened stops it. - The time axis and the ruler are their own pieces in editor/music for the song tab. The rules of the roll are pure functions in rollView and rollEdit. Findings: - Music.play could start after a stop that came while it was still loading its files. A run counter makes the late start give up. Music.edit now loads what a new note needs as well. - A click on an end of the loop would have set the loop again. A press there only moves the end once it is dragged. Tests: - pattern.test.ts (new, 9) and piano-roll.test.ts (new, 16): model, grids, meter, held notes, sequence, commands, loop, round trip, drum names, time lines, rows, hits, bands, lane, edit rules. - e2e 64 (new) on melektaus-atx: ALL PASSED, 20 checks. e2e 4 with 29 asset types: ALL PASSED, 62 checks. - Gate on melektaus-atx: CHECK EXIT 0, 247 test files, 3839 tests. - Counter-checks: the move without its clamp, the sequence without the end of the pattern, the duplicate offset without whole grid steps each fail 1 test. Without passing edits to the scheduler the e2e check for a note drawn while playing fails, the rest passes. - Probe on stein2: the tab with a drum kit, before and while playing, looked at in two screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
144dee0a97 |
Let a looped sample play out past its loop when the note ends #331
With "Mode" and "Loop" as two fields, One-shot plus Loop ran until Stop was pressed: letting go of the key did nothing. The two fields are now one, "Play", with four entries that each say what happens while the note is held and when it is let go. - Gate and One-shot play the sound once, as before. Loop repeats it and fades out with the note. - "Loop, then release" is new: the sampler's sustain loop. Let go, it finishes the round it is in and plays what comes after "Loop end" to the end of the sound. - A buffer source stops looping and plays on by itself. The pitch worklet gets a "leave" message. The phase vocoder then lets the round finish that its newest frame has read into, so nothing read already changes under the frames still sounding. - Samples saved with "Mode" and "Loop" are turned into "Play" on load. Gate and One-shot stay, either with Loop becomes Loop, since playing until stopped is gone. - The keys of the Instrument tab stay half lit while a note let go is still heard. Esc on the keys stops everything, like Stop. - Striking a ringing key again plays a new note beside it rather than stopping the old one, so a drum can be hit quickly. This differs from what was proposed in the issue discussion. Tests: - instrument + pitch-player + sound-library + node-catalog: 477 tests, green (12 new). - e2e 34 (melektaus-atx): ALL PASSED, 45 checks (+1). Let go after a loop of 0.05 s, the note played out for 0.31 s on the audio clock, the key half lit meanwhile. - Counter-check: a release that does not leave the loop fails the 2 new player tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8a4e4a6762 |
Play sounds as notes of an instrument asset #331
A sound could only be played as it was, one at a time. An instrument
is a node graph that makes a note a sound: Notes nodes bring the note
in, Samples play sound assets for it, the effects of a sound FX shape
it and the Output sums the layers. Its tab plays it from a keyboard,
a behavior with Play note. It is what patterns and songs (#330) will
play.
- New asset type instrument, under the heading Music, not placeable.
A new one starts as Notes into a Sample into the Output.
- Notes are counted as MIDI does: 60 is C4, names like C#4, sharps
only, the lowest C-1. Notes has From and To, outputs any, note,
frequency and velocity, and with Single notes a trigger per note,
each with an optional name for a drum kit.
- New port type trigger, only into the trigger of a source. Inputs
can take any number of edges now (PortDef.many): a trigger, and
the Output, which plays everything into it side by side.
- Sample plays a sound asset. Follow note shifts it by note - root
through the pitch worklet of #317, so it keeps its length. Gate
fades it out at the end of the note, One-shot plays through. Loop
runs between Loop start and Loop end, also in the worklet.
- Parameters declares the instrument's own parameters with a
default. Sound FX sends the sound through a sound FX asset, its
parameters as inputs, the sound set in where its Input was.
- compileInstrument walks the graph once per note. A trigger is on
only for the notes it stands for, a silent source silences what it
alone feeds, a Mix with a silent side is the other side. velocity
and the parameters stay open for the voice. Builds are cached per
note until the graph or a sound or FX it names changes.
- The player takes a note as one voice of several layers, each way
with its own file and volume. release fades the gated ways out,
one-shots play on. playNote can release after a duration.
- Play note plays one note with velocity, volume, duration and
detached, and hands out voice for Set sound FX parameters and
Stop sound. Velocity is always loudness. Instrument parameters
picks values for the instrument's parameters like Sound FX
parameters does for an FX, renames follow.
- The tab has the graph, a header field per parameter, Stop, and
128 keys below. Keys without sound are grey. A key plays while it
is held. Focused, the computer keyboard plays as in a tracker by
KeyboardEvent.code, the arrows move the octave.
Findings:
- A label of the keys set fill on the shared small text style and
darkened every small label of the editor. Caught in a screenshot
before the commit, the keys have a style of their own now.
- Suite task 523 on
|
||
|
|
183f356c99 |
Let a game hand values to the parameters of a sound FX #311
A sound FX played the same every time: its numbers were settings of the nodes. Now the Input declares parameters with a default, every number of Gain, Pitch and EQ is an input they can feed, and a Play sound hands in values for them. - Input declares number parameters in the inspector, each with a Default (DeclaredPort.default, SetPortDefaultCommand). They are outputs of the Input. Input and Output count as terminals now, one of each per graph. - Gain, Pitch and EQ take their numbers at inputs under the keys the settings had, so saved graphs sound as before without a migration. Each value is held to its range however it arrives. - The compiler gives an FxPlan with the parameters still open. fxChainOf fills them in when a voice starts. The player still only sees an FxChain. - New behavior node Sound FX parameters: names an FX and picks its parameters like a Variables node picks variables. Each picked one is a number input that starts at the FX's default. Its output has the new port type fxparams, which only fits Play sound's fx params. - Play sound warns when the parameters name another FX than the one it plays. Names the FX does not know are left out. - Renaming a parameter at the Input renames it in every Sound FX parameters node naming that FX, edges and values included. - The Sound FX tab has a number field per parameter in its header. Play uses those values, they are never written into the asset. - A value counts from the start of a voice. Changing a playing voice is #315, a Mix node is #316. Tests: - soundfx.test.ts 22 (+11): parameters in the plan, held values, a 0-by-default stage, EQ band, save with defaults, wrong port types, default command with undo, Sound FX parameters into Play sound, defaults of picked ones, node problems, mismatch, rename with undo - node-catalog 368 (+3): Play sound fx params, Sound FX parameters - e2e 34: ALL PASSED - 28 checks (+2: header field plays depth 5, a Sound FX parameters node gives the game's voice depth 7) - Gate on melektaus-atx: typecheck, lint, prettier clean, 240 test files, 3636 tests green - Counter-check: with fxChainOf ignoring the values and the rename not followed, 5 of the new tests fail. - Browser probe: header fields, inspector Default and the picked parameter looked at in two screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
de14f0f9e2 |
Play a sound through a graph of gain, pitch and EQ effects #311
A sound could only be played as it was uploaded. A Sound FX asset is
a node graph like a shader: Input, effects, Output. Play sound has a
new input fx that sends the voice through it.
- New asset type soundfx ("Sound FX"), not placeable, with a graph
and its own tab. The heading in the asset panel is "Sound effects",
so it does not take the click meant for the + menu entry.
- Nodes in core/soundfx/nodes.ts, category sound, port type audio
that only fits audio: Input, Output, Gain (dB), Pitch (semitones),
EQ (80 Hz and 12 kHz shelves, peaks at 250 Hz, 1 kHz and 4 kHz).
- compileSoundFx walks back from the Output with the shared graph
emitter. An input takes one edge, so the result is one line: a
pitch and a list of gain and filter stages. A node without input
or a graph without Output reports a problem on the node.
- Pitch is the playback rate of the source. It applies to the whole
sound and also changes its length, the node description says so.
- The BehaviorSystem resolves fx and hands the line to the AudioSink.
An fx that is missing or does not compile plays the sound dry.
- SoundLibrary wires source, stages, volume, destination in that
order. stats().last now also names pitch and stage count.
- The tab header picks one of the project's sounds and plays it
through the graph as it stands. Stop there ends only its own voice.
Tests:
- soundfx.test.ts (new, 11), sound-library (9, +2 for the wiring),
node-catalog (+1 for fx), graph and asset-groups adjusted
- e2e 34: ALL PASSED - 26 checks (+5: + menu opens the tab, palette,
2 effects, tab Play and game Play through the FX at -12 st)
- Gate on melektaus-atx: typecheck, lint, prettier clean, 239 of 240
test files green. The red one was sound-library (its fake
AudioContext had no playbackRate), fixed afterwards, gate not
re-run in full.
- Counter-check: without the audio rule in portsCompatible the port
test fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
3f12a77aea |
Preview a sprite at the end of its inspector, placed or as an asset
A sprite's inspector listed its rows but never showed what they add up to. The picture, its frame, nine-slice, tint and shader could only be judged in the scene, often far from the object being edited. - SpritePreview draws the sprite through syncSpriteMesh, the function the scene draws it with. Slices, padding, tint and shader come out the same without a second path. syncSpriteMesh now takes only the three fields of a DrawItem it reads. - The subject is resolved by previewPicture from the Shader tab, so a placed sprite shows its own overrides. - previewFit is the rule for size and place: whole device pixels per unit from 1 on, at most 8, and the corner on a whole device pixel. Pixel art stays sharp. A sprite larger than the group shrinks freely. - A shader with a Time node keeps the preview redrawing once a frame while it is on screen. A folded group or a hidden panel stops it. - New test hook __gm.inspectorSpritePreview(). Finding: the preview redrew on a model change in a microtask at first. A drawn picture whose revision moved on is retried once, in a microtask queued by the scene's synchronous redraw. That retry ran before the preview had let go, and the old pixel art sheet stayed waiting (e2e 48l-iii read waiting [1,1,1]). The preview now redraws at once on a model change, as the scene does. Tests: - unit: 238 files, 3592 tests (+5 in preview-fit.test.ts), and the group list of a placed sprite in sections.test.ts ends in PREVIEW - e2e 48: ALL PASSED, 47 checks (+2: 48l-i asset preview with the frame, 48l-iii placed preview with its shader values) - counter-checks: without whole-pixel zoom 2 unit tests fail; with the microtask redraw 48l-iii fails with waiting [1,1,1] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1665576fb9 |
Let a pixel perfect camera draw on the game view's pixel grid
A camera that follows smoothly stands between pixels most of the time, and pixel art then wanders between them. The new camera switch draws what the camera shows on the game view's own grid, while positions in the model keep their fractions. - `Pixel perfect` is a camera row, off by default. It works at every fixed game scale and at Auto, but not at Fit and not in the Scene tab, where one works with the fractions. - The grid has one step per device pixel of a world unit. At 100% that is whole units, at 500% fifths. The view carries it (`View.grid`), so no view draws with the grid of another. - The view looks from a point that puts the world's origin on a whole device pixel (`lookFrom`). This also covers odd camera sizes and views that cut the picture to an odd size. - Sprites, texts and tilemaps snap their top left corner when they are unturned, so an odd size lands on whole pixels too. A turned object snaps its pivot (`snapPlacement` in `pixelGrid.ts`). - Under a camera shader the picture is taken the same way, and the objects in it use the picture's grid. With Camera pixels that is one step per camera pixel. - An overlay camera has its own switch and snaps only while the main picture is at a fixed scale. - Particles are drawn where they are. - New test hook `__gm.gameDrawnRect(id)` reads where a sprite's picture is drawn, which `gameRect` (the model's) cannot tell. Found: the e2e fixture at 300% drew the crate at y 205.5 device pixels without moving the camera, because the view cut to an odd height puts its centre on half a pixel. With the switch on it is at 206. Tests: - Gate: 237 files, 3587 tests (+10 in pixel-grid.test.ts) - e2e 2,3: ALL PASSED, 75 checks (+1, 3j) - Counter-checks: without the origin snap 3 unit tests fail, with the centre snapped instead of the corner 2 fail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c74b6b1cab |
Show where an inspector row can be dragged and answer the pointer
After #310 rows could be dragged by their mark, but nothing said which marks take a drag, and the pointer on one changed nothing but the cursor. - A mark that drags is now a Grip: the same widget wrapped, the same size, so the value column stays where it was. Once sortBlocks takes it, it carries six faint dots at its left edge. - Marks that cannot drag stay plain: the variables of a placed object and the fields of a typed struct show no dots. - With the pointer on a grip its dots light up and the whole block that would move is washed in the accent colour, a struct with its fields. While the block is carried the wash gives way to the dimming, and the grip stays lit. - While a block is carried the other grips let the pointer pass: no lit dots, no wash. Every grip of the column shows grabbing instead of grab until the drop. - The wash first used controlHover like list rows. In the inspector it is darker than the panel and vanished next to the dark fields, so it became the accent at 15 percent, the colour of the drop line. - __gm.inspectorMark also reports the dots: none, idle or lit. Tests: - Gate on melektaus-atx: 236 files, 3576 tests (+4 in block-drag.test.ts, one of them a drag passing over another grip). - e2e 22: 40 checks (+1, sum 1740). 22f-iii reads idle dots, moves the mouse onto the mark and reads them lit. - Two counter-checks bite: no wash on hover fails 1 test, grips never made grabbable fail 3. A third: hover ignoring the drag fails 1. - Screenshots of a lit single row and a lit struct block looked at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2c19c89878 |
Let variables, fields, ports and list items be dragged into order (#310)
Variables of a behavior, the fields of a struct, the ports of a start or end node and the items of a list could only be added and removed. Changing their order meant removing and adding them again. Now each of them is dragged by the mark in front of its row. - sortBlocks (ui/blockDrag.ts) makes the rows of one column sortable. A block is every row it covers, so a struct moves with its fields and a list item with its nested list. The block is dimmed while it is carried, an accent line shows where it lands, and a scrolled panel follows the pointer at its edge. Less than 4 px stays a click. - The arithmetic is pure and lives in ui/sortable.ts: blockGap, gapLineY, dropSlot and edgeScroll. dropIndex of the tile strip moved there too, so the reorder maths sits in one file. - MoveVariableCommand, MoveStructFieldCommand and MovePortCommand build on the existing MoveCommand. Undo moves back. - Lists need no command. InlineList replaces the list as a whole, as it does for every other edit of its items. - The grip is the kind mark, for a list item its number. Moving only happens among siblings: a field stays in its struct. - On a Struct asset the move runs through WithStructTypes. Typed variables, Pack/Expand ports and split outputs follow the new order. - Values and edges hang on names, so only the order changes. Nodes running a behavior read their ports from the start node, the new order reaches them without a copy. - Not sortable by design: the variables of a placed object, the fields of a typed struct (moved on the Struct asset) and the picked ports of Variables/Properties nodes. - Test hook __gm.inspectorMark(label) finds the mark of a row. Tests: - Gate on melektaus-atx: 235 files, 3572 tests, CHECK EXIT 0 (+9). - tests/core/reorder.test.ts: a variable, a field two levels deep, a Struct asset reaching a typed variable and Pack, and start ports reaching the calling node with edges and result kept. - e2e 22: ALL PASSED, 39 checks (+4, sum 1739). 22f-iii drags a variable past a struct of several rows, then a field inside it. - Counter-probes: dropSlot by top edge instead of middle and a field move that ignores its path fail 3 unit tests. With sortBlocks switched off, the three order checks of 22f-iii fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cf6043a066 |
Show a wheel step or Fit as a short zoom transition (#309)
The wheel and the Fit button used to jump to the new view. Now the view takes a short way there, 200 ms by default, everywhere the editor zooms. The zoom steps stay the same, only the change is shown. - viewTween: tweenView is the way between two views as a pure function. The scale moves by equal ratios, and the world point that sits at the same place on screen in both views stays there. After a wheel step that is the point under the pointer. - ViewTween drives it on the ticker. A surface hands it reading, writing and drawing its own view and wraps its wheel and Fit code in change(). A step during a transition starts from where that one was heading, so quick steps add up to exactly as many steps. - Anything else that moves the view ends the transition. A pan jumps to the target first, and saving a view saves the target. - Wired into FreeViewport (Scene, Container, animation preview), SheetCanvas (Texture, Curve, Pixel art, Vector art), GraphCanvas, panAndZoom (Tile board, plugin node board) and the timeline. - Menu > General settings... sets the time in ms, 0 jumps at once. It is kept in this browser like the panel sizes, not in the project, and it is not an undo step. - A document that opens still comes up framed at once. The animation preview frames a new subject itself instead of calling fit(). - The pixel art readout names the step the view is heading for. Measured in the scene view at 200 ms on melektaus-atx, where every frame of the transition is a full redraw: - 500 sprites: redraw 6.7 ms, 16 frames, mean 18 ms - 1000 sprites: redraw 13.0 ms, 9 frames, mean 27 ms - 2000 sprites: redraw 32.2 ms, 5 frames, mean 58 ms Tests: - The harness sets 0 ms on every load, so the existing modules read the step and not the way. - e2e 46i samples one wheel step at 1000 ms a frame later: zoom 1.033 between 1 and 2, the point under the pointer kept at (134, -40). It lands on the view a 0 ms jump reaches. - e2e 46 and 49 expect the new menu entry. - Gate (atx): 234 files, 3563 tests, CHECK EXIT 0 (+15) - e2e 46,49 (atx): ALL PASSED, 43 checks (+3, sum 1735) - Three counter-checks fail view-tween.test.ts as expected. e2e 46i fails when the scene view does not carry its centre along. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2aa635f453 |
Keep an output's name readable beside a long inline value (#306)
An input and an output in the same port row were cut to fit each on its own. The input text could reach x = 123 and the output label could start at x = 83, so a long inline value ran under the output's name. At Animation playing a long animation name covered "progress". - The output label is placed first and keeps its room. The input text is cut with an ellipsis 6 px before it, as decided in the issue. - The rule is a pure function in src/editor/graph/portRow.ts. A row without an output keeps the input's old limit of 108 px. - The value a run shows beside an output uses the same rule. Its left edge can no longer lie right of its right edge. - New test hook __gm.graph.rowSpans(nodeId) gives each row's input and output text as cut and placed, in node units. - NODE_WIDTH stays as it is, as decided in the issue. Tests: - Gate: 230 files, 3520 tests (6 new in port-row.test.ts). - e2e 6: all passed, 149 checks (148 + 1). - Counter-check: with the rule switched off, the input text reaches x = 121.4 under "progress" at x = 114.1, and the new check fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
808e1eefc2 |
Clean up duplicates and dead code, and fix the + menu check (#85)
Refactoring round 9 covers everything since round 8 ( |
||
|
|
2e55071fa5 |
Lay a shader over a camera's whole picture (#299)
A shader so far worked only on its own wearer. Effects over the whole game picture, such as a vignette, grey, scanlines or a red flash on a hit, had no place to live. A camera now wears one, drawn the way a field shader of a particle source is (#170). - The camera gets a Shader row and, while one is picked, a Shader resolution row. The shader's properties follow as camera rows. They load, animate and appear in a Properties node under Type Camera. - Every view keeps masks and objects in one container. CameraShade renders it into the camera's picture, cleared to the background, and draws the shader over the frame. Game tab and play.html take the container off the stage while the picture stands in for it. - Screen pixels gives the picture as many pixels as it covers on screen. Camera pixels uses View width x View height and scales up without smoothing. - A pixel counts as a camera pixel in both (the user's choice). The uniform uPixelScale makes Texture size read View width x View height, so a Blur keeps its reach at any game scale. - Each camera wears its own shader, overlay cameras included. A red flash on the game camera leaves the HUD of an overlay camera alone. - The scene view's eye has a Shader switch on the Camera row, off by default. With it on, the main camera's shader lies over its frame, below symbols and gizmos, and the world around it stays drawn. - New tool tools/perf-camera-shader.mjs measures the cost. Measured on stein2 (HD 630, Game tab at Fit 1310x679): a camera shader that hands the picture through stays inside the scene's own spread of 3 to 5 fps between runs. A Blur of 4 cost 4 to 8 fps. Tests: - Gate on melektaus-atx: 227 files, 3497 tests (3487 + 10). - e2e 63 alone: 11 checks (6 + 5), e2e sum 1725 counted. - 63e-63i: pass-through is byte-equal and upright at 100%, flat red reaches the corners while the overlay keeps its green, Texture size reads 640 x 360 in both resolutions at Fit, no shader draws as before, and the scene view shows it only with the switch on. - Counter-checks: a camera left out of the shader wearers fails 1 unit test. Counting screen pixels fails 63g with #ab6000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b2326085ed |
Show each camera only its layers and lay overlay cameras on top (#302)
A camera could only show the whole scene, so a HUD drawn in the world would run through a camera shader (#299) with everything else. Render layers say which cameras see an object, and overlay cameras draw their own picture over the main one. - The project has a list of up to 16 render layers, edited in Project settings next to the collision layers. It is a list of its own, read by the same rules (`layerNamesOf`). - Sprites, texts, tilemaps, particle sources and containers carry a row `Render layer` under their switches. Its default `Inherit` takes the parent's layer, at the top the first one. - A camera has `Sees` (all layers by default) and `Overlay`. The main camera is the first one switched on without Overlay, so two plain cameras still draw through the first one only. - Every view through a camera keeps only what that camera sees and lays one see-through view per overlay camera over itself. That view is a `WorldViewport` of its own, handed its view and frame from below. - An overlay is fitted into the main camera's whole picture without distortion. One of the same size and zoom covers it exactly. - `z` sorts within one camera only. The pictures lie on top of each other as a whole, which is what a shader per camera (#299) needs. - The Game tab's preview now collects the scene once per redraw. Before it collected it twice, with the cameras it would have been five times. - `SetCollisionLayersCommand` became `SetLayerNamesCommand`, which takes the list it sets. Follow-ups: #304 (the mouse through a chosen camera) and #305 (change the render layer at run time). Tests: - Gate: 226 files, 3487 tests (3480 + 7) - e2e 63 (new): 4 checks. A green overlay sprite at z -10 covers a red main sprite at z 1000, in the preview and in the running game. A `UI` sprite in front of the main camera and a `Default` one in front of the overlay stay away. Without Overlay the second camera draws nothing. The settings dialog adds a render layer at the end. - Counter-checks: inheriting from parents switched off fails 1 unit test. Overlays never drawn fails the two overlay checks in e2e 63. - e2e total 1720 (1716 + 4), computed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d6d38a6422 |
Lay a shader over a particle source's whole field (#170)
A shader on a source runs per particle and stops at each quad, so a glow or blur across the whole cloud was not possible. A source now has a second shader row that is laid over everything it draws at once. - New rows Field shader, Field width and Field height (256) on a particle source. The size rows show only while a field shader is picked (new `shownWhenSet` on a row definition). - The particles are drawn into a RenderTexture of the frame, and a sprite mesh wearing the field shader draws that picture. The frame is centred on the source and turns and grows with it. - UV run 0 to 1 across the frame. Texture color, Texture alpha and Sample texture read the picture of the particles. - The picture has as many device pixels as the frame covers on screen, at most 2048 per side. A frame off screen draws no pass. - The field shader's Padding and Vertex output do nothing: the frame is the edge, and its corners stay put as on a nine-sliced sprite. - A name both shaders declare is one row, and both uniforms read it. - The scene view draws the frame dashed round a selected source. The Particles tab preview wears the field shader too. - `tools/perf-field-shaders.mjs` measures 1, 10 and 50 sources. On the HD 630 they ran at 60.0, 60.0 and 57.6 fps against 60.1, 60.0 and 59.2 without, one run per row. Tests: - Gate: 223 files, 3460 tests (3452 + 8), CHECK EXIT 0. - e2e 31: 43 checks, 3 of them new. Sum 1707 is computed (1704 + 3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |