95edf9959be308f4dbcc8bcab47f5bc81b9d7b8d
446
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
852dce5142 |
Shrink a clip with the wave and drop it with a click beside (#357)
Zoomed out, the wave left room above and below it, but the bright file, the picked clip and its handles still took the whole height. And a click in the wave beside the picked clip kept it picked, so only the strip could take it back to the sound. - fileReach in waveView.ts is how far up and down the file reaches: full scale and its inset, never past the area. At level 1 it is the whole height, zoomed in too. - The bright file, the shade outside the clip and the two handles are drawn within that reach. - A click over the wave beside the picked clip picks the sound. Inside the clip or on a handle nothing changes, and a drag still spans the clip anew. Tests: - Unit wave-view: 11 green (1 new), a counter-check red - e2e 34 (melektaus-atx): ALL PASSED, 91 checks (1 new) - 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> |
||
|
|
668682c4ff |
Give a song, a track or an item a sound FX of its own (#355)
A sound FX for one place in a song had to be a project asset, which then stood in every picker. Now every pick of a sound FX in a song can make one the song keeps for that place alone, on top of the embedded assets of the first two parts. - Create sound FX sits at the end of the Sound FX row of the song, a track and an item, and of the menu of a track's FX button. It makes "Sound FX 1", 2, … per song with the Input wired to the Output, picks it and opens it. That is one undo step. - Embed a copy, while the place names a project FX, gives the place a copy under the same name. - While the place names its own, the menu shows it with the mark and offers Edit sound FX and Make project asset. - Picking anything else lets it go in the same undo step. For a track or an item `settleEmbedded` did that already. For the song's own row `AssetPropertyCommand` now settles the song after `fx`. - The mark `•` moved to the core. `assetRefLabel` puts it in front of an embedded asset, so the inspector rows show it like the timeline. - `FieldHandlers.moreChoices` lets a caller end a reference menu with entries of its own. `embeddedPatternName` is `embeddedName` with a base now. Tests: - Unit own-fx (new): 6 green, 6 counter-checks each red - Unit embedded and 80 neighbouring files: 1434 green - e2e 65 (melektaus-atx): ALL PASSED, 51 checks (2 new) - Gate (melektaus-atx): CHECK EXIT 0, 270 files, 4178 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
49669017b1 |
Make a pattern right in a song with a right-click (#355)
A pattern for one song had to be made in the Assets panel first and dragged in, and then stood in every picker of the project. Now a right-click on an empty spot of a track makes one the song keeps to itself, on top of the embedded assets of the first part. - Create pattern lays an item from the grid line before the spot, a bar long, and opens the pattern in its tab. Pattern, item and name are one undo step. The name is "Pattern 1", 2, … per song. - It plays the instrument of the track's pattern item that starts last before the spot, else of the first one after it. An item's own pick counts before its pattern's. - An embedded pattern runs on the song's clock. It takes the tempo and the meter of the song, and a change on the song moves it along in the same undo step (`AssetPropertyCommand`). - Its rows Tempo, Beats per bar, Beat and Bus are hidden, as the song decides them. Length, grid, instrument and sound FX stay. - A copy that comes to a song (Embed a copy, a paste from another song) takes that song's clock. Made a project asset, it keeps the values it had. - `CreateAssetCommand` takes an owner. `embeddedCopy` takes the song now, not its id. - The cleanup of e2e 65 also takes what the song keeps. Tests: - Unit embedded: 18 green (5 new), 6 counter-checks each red - Unit take, song, pattern and the asset commands: green - e2e 65 (melektaus-atx): ALL PASSED, 49 checks (2 new) - Gate (melektaus-atx): CHECK EXIT 0, 269 files, 4172 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
23cb34f781 |
Keep a recorded take inside its song (#355)
A take used to land in the Assets panel as "Song – Take N", next to every other sound of the project. Now a song can keep a sound, a pattern or a sound FX to itself: an ordinary asset whose new field `owner` names the song. Everything that finds an asset by its id finds it, so playing, the tabs and the game need no change. Only the places that list assets leave it out. - `core/music/embedded.ts` holds the rules: which types can be embedded, `listedAssets`, the title "Song › Take 1", and the rows an embedded sound hides (Loop, Bus). - One user per embedded asset. `SetSongCommand` settles the song after every edit: a place naming an asset an earlier place claimed, or one of another song, gets a copy, and what nothing names any more goes in the same undo step. The outcome is worked out once, so a redo brings back the same copies. - A split, Ctrl+D and a paste get that for free. The clipboard keeps the embedded assets of the copied items, so a paste after a delete brings the take back. - Deleting a song deletes what it keeps. Duplicating it copies that and points the copy at the copies. A duplicate of an embedded asset is a project asset. - The item menu has Open, Make project asset and Embed a copy. - An embedded item carries a bullet in front of its name. The first pick was a diamond, which the gate's font test refused: the UI font has no glyph for it. - A take is "Take 1", 2, … numbered per song. With the song gone while it uploads, it becomes a project sound under a free name. - Left out of: the Assets panel, every asset picker, the sound FX menu of a track, the test sound of the Sound FX tab and the naming of new assets. Delete on an embedded asset only says where it lives. A ping lights up its song. - Loading drops an `owner` whose song is gone, the asset stays as a project asset. No migration: older projects have no owner. Tests: - Unit embedded (new): 13 green, 6 counter-checks each red - Unit take, song, song-edit, asset-fx, asset-ref, serialize and neighbours: 116 green - e2e 65 (melektaus-atx): ALL PASSED, 47 checks (1 new) - Counter-check e2e 65 (asset panel unfiltered): exactly that check red - Gate (melektaus-atx): CHECK EXIT 0, 269 files, 4167 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> |
||
|
|
c95fbc167e |
Ping or open an object's asset from its hierarchy row (#353)
The asset link in the inspector already pings an asset on a click and opens it on a double-click. The rows of the Scene objects panel only selected and folded. Now they lead to the asset the same way. - A click on the row that is selected already pings the object's asset in the asset panel. The selection stays. The ping comes on release, so picking a row up to drag it pings nothing. - A double-click or Enter on an object's row opens its asset, selected or not. - ListView has a new onReselect for a plain click on the row that is selected alone. onSelect stays quiet then, as nothing changes. Decided without asking: - A double-click on an object's row no longer folds it. The chevron and the arrow keys still do. A group's row has no asset and keeps folding on a double-click. - A click with Ctrl or Shift, and one that narrows a multi-selection to its row, does not ping. Both change the selection. - A row whose asset is missing pings and opens nothing. Findings: - The double-click window goes by time (400 ms), not by the event's detail. Two quick click() calls from the harness are a double-click. The comment in e2e 01 said otherwise and is gone. Tests: - e2e 01 (melektaus-atx): ALL PASSED, 13 checks. The two checks for folding on a double-click became one for the ping and two for opening. Counter-check without the ping: that check red. - Gate (melektaus-atx): CHECK EXIT 0, 268 files, 4142 tests. - Not checked: Enter on a row, a group's row on a double-click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1d53b9b3d0 |
Count the Bus asset type in e2e 04
The suite job has been red since |
||
|
|
a295d233ed |
Give sounds, instruments, patterns and songs a sound FX (#352)
A sound FX could only be picked where something was played: on a Play
node, a track or an item of a song. A sound that should always sound
through the same effect needed that pick everywhere it was used. Now
the asset says it once, and every place that plays it follows.
- New row Sound FX on a sound, an instrument, a pattern and a song,
right before Bus. A sound goes through it on every voice, an
instrument on every note after its graph, a pattern on all its
notes together and a song on its output.
- The fx input of Play sound, Play note, Play pattern and Play song
and the Sound FX of a song item now read "From the asset" while
empty. None is a pick of its own, stored as `#none` like `#master`.
A pick replaces the FX of the asset, it does not come on top.
- A pattern item's Instrument reads "From the asset" instead of None.
The meaning is unchanged.
- A double-click on a sound item opens the sound, as one on a pattern
item opens the pattern.
- The tabs play through the FX of their asset. The Pattern and Song
tab put it on a bus of their own between the output and the bus of
the asset. The keys of the Pattern tab play there too.
- Notes of a pattern or song go through the FX of their instrument.
Its chain is loaded with the other needs of the sequence.
Decided without asking:
- Old items and Play nodes with an empty fx now mean "From the
asset". No asset has an FX yet, so nothing sounds different.
- The label is "From the asset" as on Loop and Bus, not "From asset".
- A sound used as a Sample, as the impulse of a Reverb or as the test
sound of the Sound FX tab plays without its own FX.
Findings:
- In e2e, `__gm.inspectorField('Sound FX')` and `('Instrument')` find
the group of the Assets panel first. The check asks for the last
match.
Tests:
- Unit asset-fx (new): 8 green. Six counter-checks each turned red.
- Field lists of sound, pattern and song: Sound FX added.
- e2e 65 (melektaus-atx): ALL PASSED, 46 checks (3 new). Counter-check
without the FX bus of the tab: that check red.
- Gate (melektaus-atx): CHECK EXIT 0, 268 files, 4142 tests.
- Not checked: how it sounds, the Sound, Instrument and Pattern tab
in the browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d349251a01 |
Put a sound in a room, a hall or a cave with a Reverb node (#328)
A sound FX had no space around it. The Reverb node folds the sound with an impulse response: one it makes itself, or a sound asset as the answer of a real space. It works on a voice and on a bus. - Type: Room, Hall, Cave and Plate have fixed numbers. Custom shows Decay (up to 10 s), Damping and Pre-delay. Sound shows the pick of a sound asset and Pre-delay. The rows are derived ports, the sound pick an idle param, so they only appear for their type. - The made impulse response is seeded noise per side, 60 dB down at Decay, through a low-pass closing from 20 kHz toward 500 Hz as far as Damping says. It is made once per rate, decay and damping, the last 16 are kept. - A sound as impulse response: the sound FX compilers now get the project, so a node can turn a sound asset into its file. The build key holds that file, so a cut or swapped sound builds again. The instrument compiler does the same for a Reverb inside an instrument. - The library loads that file with the voice, a sequence or before a bus builds its chain. A missing file leaves the reverb silent. - Mix blends dry and reverb and is the only number that moves while the sound plays, as the issue said. The voice rings for pre-delay plus decay, or as long as its sound. - The open point from #325 about sends on project buses is #351, not part of this. Tests: - Unit reverb (new) 4, soundfx 57 (4 new), sound-library 104 (2 new), green. - Counter-checks, each turns one test red: the impulse file not loaded with the voice, the tail without the length of the sound. - e2e 34 (melektaus-atx): ALL PASSED, 81 checks (1 new). Chrome offline: silent during the pre-delay, 35.9 dB fall over 0.6 s of a 1 s decay (36 expected), the dry half at 0.25 for mix 0.5. A sound with one sample at 0.2 s gives the impulse back 0.25 s later and nowhere else. - Gate (melektaus-atx) over the end of this series #318 to #328: CHECK EXIT 0, 267 files, 4134 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
071f194326 |
Swing the volume or the pitch with Tremolo and Vibrato (#320)
Alarms, sirens and wobbly retro sounds need a volume or a pitch that swings. Two new nodes do that, on a voice and on a bus alike. - Tremolo: Rate (0 to 20 Hz), Depth (0 to 100 %), Shape (Sine, Triangle, Square, Saw). The volume swings between full and 1 - depth. - Vibrato: Rate (0.5 to 20 Hz), Depth (0 to 4 semitones), Shape. The shape is the curve of the pitch, a Square jumps between two pitches. - As the issue decided: two nodes, phase 0 on the first sample of the voice, no fade-in time. Both run on the FX worklet, the classes are in lfo.ts and tested in Node. - Deviation from the issue text, as in the plan: the Vibrato is a delay that swings, not playbackRate. A bus has no playbackRate, and the sound keeps its length. - The delay swings around a center taken from the start values. The center counts as the latency of the node, so a voice starts that much earlier and a Mix and a bus make up for it. - Known limit, in the node text and the wiki: a depth turned up or a rate turned down after the start asks for a delay below 0. The lower half of the swing then flattens. Finding: the first e2e run was red at the edge of a window. The phase is summed up, so the square flips one sample later than 2400 steps of 10/48000 suggest. The check keeps 10 samples away from the edges. Tests: - Unit lfo (new) 5, soundfx 53 (2 new), sound-library 102 (1 new), green. - Counter-checks, each turns one test red: the swing turned over (up and down swapped), the start time not passed to the worklet. - e2e 34 (melektaus-atx): ALL PASSED, 80 checks (1 new). Chrome offline: Tremolo square silent before its voice, then 0.5 and 0 for 50 ms each. Vibrato square at 5 Hz, 1 semitone: 1059.5 Hz and 940.5 Hz. - The gate runs once on the last commit of this series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d3a4cb7d0e |
Warm up or distort a sound with a Saturation node (#323)
A sound FX had no way to bend a sound. The Saturation node pushes it into a curve, from warmth to a hard distortion, on a voice or a bus. - Eight types, each a fixed curve made once: Soft (tanh), Cubic, Hard clip, Tube, Fuzz, Foldback, Sine fold and Rectify. The issue asked for as many as useful. - Drive (0 to 48 dB) is a gain in front of the curve. The curve spans 256 times full scale, so the drive glides like any gain. - Output and Auto-gain blend as on the Compressor. Auto-gain brings a sine at -12 dBFS back to the RMS it came in with. - Tone is a low-pass after the curve, 1 kHz at 0 to 20 kHz at 1, as recommended in the plan. A 10 Hz high-pass takes out the DC the lopsided types make. - Mix blends with the dry sound. The 4x oversampling is 192 samples late in Chrome, so the dry way is held back as far. The node counts as late by that much for voices, Mixes and buses. - A test hook gives e2e the auto-gain at a drive. Tests: - Unit saturation (new) 5, soundfx 51 (2 new), sound-library 101 (1 new), green. - Counter-checks, each turns tests red: no hold-back of the dry way (1), the level without auto-gain (2). - e2e 34 (melektaus-atx): ALL PASSED, 79 checks (1 new). Chrome offline: 192 samples late at 48 and at 44.1 kHz. Half dry at 1125 Hz 0.00 dB. A sine at -12 dBFS driven 24 dB into Soft comes out at 0.00 dB with full auto-gain. - The gate runs once on the last commit of this series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a645026568 |
Tame the loudness with a Compressor and a Limiter node (#326)
A sound FX could not control dynamics. A Compressor squeezes the loudness together, and a Limiter keeps every peak below a line. Both work on a voice and on a bus, where they tame everything at once. - Compressor: DynamicsCompressorNode with Threshold, Ratio, Knee, Attack and Release, then Makeup and Auto-gain from 0 to 1. After the node the sound gets (1 - w) * makeup + w * A dB. A is what the curve takes off at 0 dBFS. - The node adds 0.6 of A by itself, as the spec says. That gain is taken back out, so 0 really means no makeup. - The knee shape is left to the browser by the spec. dynamics.ts ports Chromium's curve. It gives the gain Chrome was measured to add at four settings to three places. - Limiter: a class on the FX worklet with Threshold, Ceiling, Release and Auto-gain. It looks 5 ms ahead: the smallest gain of the window ahead, averaged over the window, then a release. No sample passes the lower of threshold and ceiling. Auto-gain raises it toward the ceiling. - Deviation from the plan in the issue: the Limiter has a Threshold as well. Without it Auto-gain has nothing to measure against. - Latency is made up on voices now too. A voice starts as much earlier as its stages are late (Compressor 6 ms, Limiter 5 ms). The other way of a Mix and the other layers of a note are held back to the latest, and a bus counts it into its latency. - A voice with a Limiter counts as ringing, so the worklet is stopped only after what it still holds is out. Tests: - Unit dynamics (new) 8, soundfx 49 (3 new), sound-library 100 (4 new), green. - Counter-checks, each turns tests red: no look-ahead in the Limiter (3), no taking back of the node's gain (1), a voice not started earlier (1), no stage latency on a bus (1), no hold-back in a Mix (1). - e2e 34 (melektaus-atx): ALL PASSED, 78 checks (1 new). Chrome offline: Compressor 288 samples late, Limiter 240. Full scale through -12 dB 4:1 hard knee: +0.19 dB with full auto-gain, -8.81 dB without. A sine at 2 through the Limiter at -6 dB peaks at -6.00 dB, with auto-gain 1 at -1.00 dB. - The gate runs once on the last commit of this series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c99bda6489 |
Move a sound left or right with a Pan node (#319)
A sound FX had no way to place a sound in the stereo field. The new Pan node does that on a voice, a bus and a track alike. - One input, Pan from -1 (left) to 1 (right), 0 by default. Behind it is a StereoPannerNode. A typed-in 0 leaves the node out, a pan fed from a parameter stays at any value. - It moves with Set sound FX parameters, and an Envelope can move it in an instrument. - As the issue decided, no pan from the position in Play sound and no width control for stereo sounds. Finding: the issue and the plan said a stereo sound gets a balance. Web Audio pans stereo differently: the side it goes to stays and the other side moves over into it. The node description and the wiki say so, the e2e check measures it. Tests: - Unit soundfx 46 (1 new), sound-library 97 (1 new), green. - Counter-check: a pan from a parameter at 0 left out, 1 test red. - e2e 34 (melektaus-atx): ALL PASSED, 77 checks (1 new). Chrome offline, a mono sine at 0.5 gives 0.5 / 0 at -1 and 0.354 / 0.354 at 0. A stereo one at 0.5 panned 0.5 gives 0.354 / 0.854. - The gate runs once on the last commit of this series. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e1ca6a5f5a |
Shape a sound with every filter type, a Q and a slope (#318)
The Filter node from #335 only knew low-pass, high-pass and band-pass and had a Resonance from 0 to 1. It now has every type Web Audio offers, a Q as a plain factor and a slope up to 48 dB per octave. - Types: low-pass, high-pass, band-pass, notch, low shelf, high shelf, peaking and all-pass. The cutoff row is now labelled Frequency, its key stays `cutoff`. - Q from 0.1 to 30, 0.707 by default, replaces Resonance. Web Audio takes the Q of a low- or high-pass in dB, so it is converted when the voice is wired. No project used the node. - Gain in dB is an input only for the shelves and the peak. It is a derived port, so the sound FX compiler now resolves derived ports. - Slope 12, 24 or 48 dB/oct runs 1, 2 or 4 filters in a row. They share one cutoff carried by a ConstantSource, only the last has the Q. Shelves, peak and all-pass hide the row. - The Chip Presets get the Q their resonance gave before. Mutate moves Q in octaves and gain by up to a dB. - bindStage in the instrument compiler binds every amount of a stage generically, so new stages need no case of their own there. Finding: FLAT_Q was in dB and the delay damping used it. It is a factor now, the damping takes it through passQ and is unchanged. Tests: - Unit envelope 13, sound-library 96 (2 new), chip-presets 14, soundfx 45, all green. - Counter-checks: the Q on every filter in a row, the gain row for every type. Each turns one test red. - e2e 34 (melektaus-atx): ALL PASSED, 76 checks (1 new). Chrome offline, 2 kHz through a low-pass at 1 kHz: -12.37 dB at 12 dB/oct, -49.5 dB at 48. A +6 dB peak gives 6.00 dB, a notch -124 dB. - The gate runs once on the last commit of this series. 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> |
||
|
|
b519cd192f |
Send a track, a pattern item or a whole song through a sound FX (#346)
A sound FX could only be put on a single sound. Now a track, a pattern item, a Play pattern and a Play song send everything they play through one chain of it, as an insert in a DAW. A Play note gets one of its own. - Track: an FX box in the head next to M and S opens a menu of the project's sound FX, and the track's inspector has the row too. Order: items, track FX, volume, pan. - Pattern item: Sound FX in its inspector. Its notes play on a bus of their own into the track's bus. - Play pattern and Play song take fx and fx params. The chain sits on the master of the playing, after the tracks and before the level and the fades. Set sound FX parameters reaches it by the playback. - Play note takes fx and fx params next to the instrument's params. The FX parameters go by "fx:" so the two sets stay apart. Set sound FX parameters on a note changes a name both have in both. - A Pitch on a bus is a new PitchShifter: the phase vocoder of the PitchPlayer, fed while it plays. It cannot look ahead and is exactly one frame late at every pitch, 2048 samples. - That latency is made up. The other way of a Mix is held back by a delay of the same length. Every note and clip starts as much earlier as the buses it passes are late. The scheduler plans that much further ahead, and the first note waits that much after Play. - The Song tab waits for the chains before it plays and lets its buses go a moment after Stop. A playing in a game lets them go after its echoes and what the shifters still hold. - songBuses moved to its own file. Kept in song.ts it pulled the FX compiler into the node registry through an import cycle. Decisions of the user: one chain for the whole (1a), FX everywhere asked for (2a), Set sound FX parameters on playbacks (3a), and a pitch shifter of its own with exact latency compensation for a Pitch on a bus. Tests: - Unit pitch-shifter (new): 11 green. At 0 semitones the output is the input 2048 samples later, off by at most 1.2e-7. - Unit sound-library: 84 green, 8 new. Counter-checks (no early start, no Mix delay, chain made every time, tune misses the master) each turned their test red. - Unit bus-fx (new): 6 green, 4 counter-checks red. Scheduler ahead: 1 new, its counter-check red. - e2e 65 (melektaus-atx): ALL PASSED, 35 checks, 4 new, first run. Chrome at 44.1 kHz: the bus was 0.046440 s late, the note started exactly that much early. Offline the shifter moved 1 kHz to 2000 Hz and passed a click 2048 samples later. - Gate (melektaus-atx): CHECK EXIT 0, 259 files, 4021 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
01fd6e6464 |
Swirl and comb a sound with a Phaser and a Flanger node (#341)
sfxr's "phaser" is a comb filter, the sound plus a copy delayed by up to 2.9 ms. That is a flanger, not the phaser a guitarist knows. Both effects come in as nodes of their own, so each name fits its sound and sfxr is covered in full. Both run on the FX worklet like the Bitcrusher, because a feedback loop in Web Audio costs a render quantum per round. - Phaser: first-order allpasses (Stages 2, 4, 6, 8, 12) around Frequency, moved by a sine LFO Depth octaves wide at Rate Hz, with Feedback and Mix. An Envelope can move Frequency and Mix. - Flanger: a delay line of Offset ms. Sweep moves it on in ms/s from the start of the voice, an LFO adds up to Depth ms. As in sfxr the delay is mirrored at 0, and it never goes past 20 ms. Feedback comes back exactly one delay later. An Envelope can move Offset and Mix. - Mix 0 leaves the sound as it is, 1 is half sound and half copy. The notches are deepest there and the peaks stay at the sound's level. sfxr adds both at full, so its sound comes out 6 dB louder. - The Flanger's processor gets the start of its voice and begins the sweep on that sample. stageNodes takes it as a new argument `at`. - Explosion gets a Flanger in one draw out of two, with sfxr's offset and sweep and no LFO. Offset is offset^2 * 2.89 ms, Sweep is ramp^2 * 125 ms/s, signed so the direction holds. - Randomize draws a Flanger's offset and sweep like sfxr. Its LFO and feedback and all of a Phaser are drawn within bounds of their own. Mix and Stages stay, as a Bitcrusher's Mix does. Mutate moves them too, and a 0 stays 0. - New instrument preset Phased Pad: saw, Envelope, low-pass, Phaser of six stages. The other 14 are unchanged. - The Bitcrusher's worklet setup is now shared with both nodes (workletNodes), its behaviour is unchanged. Tests: - Unit phaser (new): 8 green. Counter-check with clamping at 0 instead of mirroring and a flipped allpass coefficient: 3 red. - Unit soundfx, sound-library, chip-presets: 45 / 76 / 14 green. Counter-checks: start time not passed on, phaser frequency eased linearly, sweep sign dropped, Phaser left out of Mutate, each red. - Unit core, viewport, graph: 179 files, 2979 tests green. - e2e 34 (melektaus-atx): ALL PASSED, 70 checks (1 new), first run. Chrome offline: 1 kHz notch RMS 0.0000 with two stages, 0.5657 with four. Flanger echo after 48 samples, swept echo at 533/534. About 47 Phasers (12 stages) or 63 Flangers fill the audio thread. - Gate (melektaus-atx): CHECK EXIT 0, 257 files, 3991 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> |
||
|
|
6b49a0bea7 |
Give each account its own folder of uploaded files to delete (#344)
Uploaded images, fonts and sounds belonged to nobody. Every account saw every file in the pickers, and only an administrator could delete one, through the API alone. Now every account uploads into a folder of its own, sees its own files and nobody's, and can delete a file of its own that no project uses. - Files are stored as data/<kind>/<folder>/<sha256>.<ext>, and the folder is part of asset.file. A name without a folder belongs to nobody. - The folder is a random 12-character name kept in users.json and given with the first upload. Renaming an account moves no file, and a released game's URLs carry no account name. - The list shows the caller's own files and nobody's. An administrator sees all, each with owner and deletable. - DELETE takes the name with its folder. It answers 403 unless the file is the caller's or the caller is an administrator, and 409 while a project, snapshot or release of any account names it (FileUsage). The 409 names the caller's own projects only. - The pickers have a Delete button. The first press asks, the second deletes, and a refusal shows the server's reason. - A deleted account's folder stays and only administrators see it. - tools/migrate-file-owners.php moved every used file into the folder of the account using it (a copy each for two accounts) and rewrote projects, snapshots and releases. A project's revision goes up, so an open editor gets a conflict instead of saving old names back. - Font families are named after the hash alone, so a folder never ends up in a CSS family name. Run on stein2 on 2026-10-07, backup in /root/config-backup-20261007/data-before-344: 25 files used, 27 copies, 8 projects, 53 snapshots and 1 release rewritten, 30 unused files stay nobody's. Afterwards all 600 references resolve to a file in their owner's folder. Found while building: the Delete button cut its new label to "Del…" because a button does not measure itself again on a new text. The picker now lays its button row out again. Tests: - PHP files.test.php (new): 46 checks, counter-check 3 red - Unit core + editor: 220 files, 3403 tests - e2e 34 (melektaus-atx): ALL PASSED - 67 checks (2 new) - Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3977 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a2d0a02645 |
Play tunes on 14 instrument presets next to the sfxr sounds (#329)
The Preset menu only had sfxr's effects: each a fixed pitch on every key that dies away by itself. Now its top half holds instruments to play melodies, basses and drums on. - Instruments: Square Lead, Pulse Lead, Echo Lead, Octave Arp, Synth Brass, Detuned Saws, String Pad, Organ, Flute, Bell, Pluck, Triangle Bass, Pluck Bass and Drum Kit. A line in the menu parts them from the seven effects below. - Their Chips follow the note, and all but Bell, Pluck and the drums hold it while the key is held. - They come out the same every time they are picked. Randomize and Mutate make a variant, as before. - They use what the graph already has: two Chips through a Mix for Detuned Saws, String Pad, Organ, Flute and Bell, an Envelope on a Filter's cutoff for Brass, Pluck and Pluck Bass, a Pitch for the detune and the octave, a Delay for Echo Lead and Bell. - Drum Kit uses single notes 36 to 46: kick, snare, closed and open hat on their General MIDI keys, each ending by itself. - A small Sketch helper lays the nodes on a grid. The sfxr presets use it too and come out as before. - Numbers keep to the steps of their rows. Pulse Lead's duty is 0.13, not 0.125, because the field rounds to 0.01. Tests: - Unit chip-presets: 12 green (5 new). Every instrument compiles without a problem, doubles its frequency an octave up, holds unless struck and is the same on every pick. The drum kit sounds on exactly 36, 38, 42 and 46. Countercheck with Follow note forced off: red. - e2e 34 (CI image on stein2, melektaus-atx was off): ALL PASSED, 65 checks (1 new). A first run had the scheduler's looped-bar check red by 10 ticks between two clock reads, which the presets do not touch; the second run was green. - Gate (stein2): CHECK EXIT 0, 256 files, 3974 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> |
||
|
|
f46b2316f2 |
Make 8-bit sounds without a file with a Chip node in instruments (#329)
An instrument could only play sound files so far. The Chip makes the sound of a note itself, the way old game consoles and sfxr did: a raw wave and what its frequency does over the note. A laser, a jump or a pickup is now an instrument and ends by itself. - Chip node with wave (square, triangle, saw, sine, noise and the periodic noise of the NES), follow note, frequency, duty, duty sweep, slide, delta slide, min frequency, vibrato, arpeggio and repeat. It counts in Hz, semitones and seconds, not sfxr's 0 to 1. - It runs as a third processor in the FX worklet. The math is a plain class, ChipPlayer, tested in Node. Every sample is worked out 8 times and averaged, as sfxr does. - Frequency and duty are AudioParams, so an Envelope can move them and a parameter can tune them while the note plays. A Pitch behind a Chip moves its frequency directly, without the phase vocoder. - Below its min frequency a Chip ends and tells the page. Without the worklet it is silent and its note ends at once. - An Envelope with sustain 0 now ends its note once the decay is over, for every source under it, Samples too. Before, a note with duration 0 ran on silently until a Stop sound. - A note made of Chips alone needs no file. playNote and the wait for the bytes no longer drop it for having none. - Inputs that do nothing as things stand are grey in the inspector. idleParams is told which inputs are connected, so a connected one always counts. The credit names sfxr (Tomas Pettersson, MIT) and jsfxr, which turned out to be public domain, not MIT as the plan in #329 said. Tests: - Unit chip (new) 12, sound-library +4. All of tests/core, tests/viewport, tests/editor/graph and tests/editor/inspector: 178 files, 2953 green. - Counter-checks: semitones read as /24 turn 4 chip tests red. Without the sustain rule and the file check 4 library tests fail. - e2e 34 (melektaus-atx): ALL PASSED, 60 checks (2 new). Offline in Chrome a square at 220 Hz plus 12 semitones and duty 0.25 gave 329 periods in 0.75 s, up 0.249. 64 noise voices for 2 s rendered in 2.0 s, about 63 at once. Live a 0.2 s note with sustain 0 was gone after 0.26 s. - Gate (melektaus-atx): CHECK EXIT 0, 255 files, 3962 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
165f022fc0 |
Crush a sound to fewer bits and a lower rate with a Bitcrusher (#322)
A new effect for sound FX and instruments makes a sound rough like an
old console. It rounds the sound to fewer steps and holds it at a lower
sample rate, and all three of its numbers move while the sound plays.
- Bitcrusher node with bits (1 to 16, not only whole), rate (100 Hz to
48 kHz, sample-and-hold) and mix. A mix typed in at 0 leaves it out.
- It runs on the audio thread as a second processor next to the pitch
player. The module is now fxWorklet.ts and the test hook is called
fxWorkletUrl.
- A play that needs the worklet waits for it, for a Pitch or a
Bitcrusher alike. Without the worklet the sound goes through as is.
- Bits and mix move in what they hold, the rate in octaves. An
Envelope can move all three in an instrument.
- A stage that runs on the worklet is stopped with its voice, like the
drivers of an Envelope or a Mix.
- The math is a plain class, Crusher, so it is tested in Node.
The cleanup check of e2e 64 from
|
||
|
|
af9b202100 |
Keep the game's music when Play ends a run in the editor
A behavior with "Run in editor" kept its world until the frame after Play. By then the game's On start had already started its pattern or song, and ending the editor run called stopAll() on the sound library. The game came up silent. This is what turned the game blocks of e2e 64 and 65 red in suites 542 and 550. - EditorRun.modeChanged() ends the run the moment the mode leaves edit. enterPlay() announces the mode before the game is built, so the editor run's sounds stop first and the game's start after. - The Editor wires the mode event to it instead of invalidate(). Tests: - e2e 64 starts its game with an idle "Run in editor" behavior in the scene. Without the fix it fails like CI did, "game": [] and 2 FAILED. With it: ALL PASSED, 30 checks (stein2). - Unit editor-run: 21 green, 1 new. - Typecheck, ESLint, Prettier clean. 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> |
||
|
|
5fde3e975e |
Reset a track slider with a double-click in the Song tab
The heads of the Song tab had four rough spots. Panned down, the first track had no line above it. The M and S letters sat high in their buttons. A volume or pan slider could only be dragged back to the middle by eye. Muting a track paled its volume slider. - The first track draws a border along its top, in the lanes and in its head, like the one below every track. - M and S sit in the middle of their buttons. A label of the heads can now carry a height and is centred in its box. - A double-click on a head's volume or pan sets it back to 0: the volume to 0 dB, the pan to the middle. It is one undo step of its own after the step the first click set. - Mute only lights its own button. The volume slider keeps its colour, so a muted head no longer looks out of step. Tests: - e2e 65 on melektaus-atx: ALL PASSED, 27 checks (+1) - typecheck, ESLint, Prettier: clean (also after the mute change) - screenshot on stein2: border above track 1, M and S with 4 px free above and below the letter in a 16 px button Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5b96d0b329 |
Paste under the pointer and pull song items out left in music tabs
A round of fixes to the Pattern and Song tabs: where a paste lands, what Shift does on a drag, how far the rows zoom, and how an item of a song is sized at its ends. - Shift does what Alt does on a drag: off the grid, on the tick. A Shift click on a note or an item still adds or unpicks it. A Shift press only decides on letting go: no drag toggles, a drag moves. - Ctrl+V lands under the pointer. The earliest copy goes on the grid line before it, the highest note on its row or the topmost item on its track, held to the keyboard or the tracks there are. Off the grid or left of bar 1 it lands at the start of playing as before. - The rows of the piano roll zoom to 64 px instead of 28, the tracks of a song to 320 px instead of 160. - Fit in the Pattern tab zooms the rows as well, so the notes fill the height, no longer held to the default of 14 px. - An item of a song can be pulled out by its left end past the start of what it plays, which then repeats. The offset goes round to the end of a round. A sound not loaded yet still stops at its start. - Both ends of an item snap to the start of a round of what it plays as well as to the grid, whichever is nearer. The end of a round between two ticks is cut to the tick before. A start put within half a tick of a round goes right on it, so it plays from the very beginning. - An offset past a whole round is not folded back to 0, so a pattern that grows later still plays from where the item said. - A middle-drag pans past the first and the last row, as it pans left of bar 1: the highest note or the first track up to half the height down, the other end up to half the height up. The room past them is dark in the piano roll and empty in the song. Tests: - Unit: song-edit 14, piano-roll 19, song 14 tests. New cases for the paste spot, pulling out left, the round snap and Fit. - e2e 64+65 on melektaus-atx: ALL PASSED - 54 checks (+4). New: Shift drag and Shift click, Ctrl+V under the pointer in both tabs, Fit zooming the rows, pulling a pattern out left and snapping a sound to the end of its second round. e2e 64 zooms the rows out again after Fit, so the keys it plays later are on screen. - Gate on melektaus-atx: 251 files, 3912 tests, CHECK EXIT 0. - Then the pan down the rows: e2e 64 on melektaus-atx ALL PASSED - 30 checks. Its pan check now also pans the top row down, half the grid's height, and a click above it draws nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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
|
||
|
|
f97961c8ff |
Play and stop a song from a behavior in the game #336
A song could only be heard in its tab. Now a game plays it through Play song and ends it through Stop song, with its tracks as they are set. A playing pattern or song also no longer undoes the pause of the toolbar. - Play song plays from the start of the song and then round the loop of its ruler, so an intro is heard once. Without loop it plays once to its end. Inputs are song, volume, fade in, loop (on by default) and detached (off by default). It gives out playback. - Stop song stops one song, every song for none, or one playback by its number, over its fade out. Stop sound with no sound named stops songs too. Stop pattern with no pattern named stops only patterns. - Songs play side by side, as patterns do. They are owned like a sound, so leaving the scene ends one unless it is detached. Pause game and the time scale leave the music alone. - Each playing is heard through a master of its own, silent until its first note. The fades move it, and a song's tracks sit inside it on buses of their own. They go once the playing and its voices have. - Notes and clips are struck the same way in the editor and the game (strikeNote, playClip). What a sequence needs comes from one place (core/music/needs). Findings: - Reading the audio clock resumed a suspended context. A playing pattern read it every 25 ms and so undid the toolbar's pause. The library now leaves a context alone while the toolbar holds it. - A pattern played once to its end asked the list of playings about its voices, and that list asked the pattern again. Once its last voice was gone this ran until the stack overflowed. The scheduler of a playing now asks only after voices. - e2e 64 reads the playings through __gm.sound.playbacks() now. Tests: - song-play.test.ts (new): 8 tests, node catalog +8 cases - sound-library.test.ts: 66 tests, 4 new - e2e 64 (melektaus-atx): 23 of 23 checks passed - e2e 65 (melektaus-atx): ALL PASSED, 22 checks (+4) - Gate (melektaus-atx): CHECK EXIT 0, 251 files, 3903 tests - Counter-checks: the old way of asking after voices, a clock read that wakes a held context, and a start after a fade out began each fail 1 test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
2a3b90b017 |
Play and stop a pattern from a behavior in the game #336
A pattern could only be heard in its tab. Songs come with #334, and only then can #336 play them in a game. Until then a game plays a single pattern, looped or once, through two new nodes. - Play pattern plays the whole pattern through its instrument at its own tempo. The loop on the ruler is only for editing. Inputs are pattern, volume, loop (on by default) and detached. It gives out playback, 0 where there is no instrument or no note. - Stop pattern stops one pattern, every pattern for none, or one playback by its number. Let ring lets notes with an Envelope fall away in their release. - A playing is numbered and owned like a voice. Destroying the object, Stop sound with nothing named and the end of the run stop it. Stop sound with a voice takes a playback too. - Each playing runs on a scheduler of its own (core/music/playbacks). It waits for its files first, and a stop that came earlier wins. - The scheduler has an end for a sequence that does not loop. It stops waking there and is done once its last notes are. - The worker clock moved to viewport/wake.ts, so SoundLibrary can use it without importing music.ts. Tests: - pattern-play.test.ts (new): 8 tests, node catalog +7 cases - e2e 64 (melektaus-atx): ALL PASSED, 23 checks (+3) - Gate (melektaus-atx): CHECK EXIT 0, 248 files, 3854 tests - Counter-checks: no end in the scheduler, and no owner for the playing, each fail 1 test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
9c71eb1a0d |
Shape notes with an Envelope and move numbers with its curve #335
Without an envelope a note set in hard and broke off 10 ms after its end. An instrument now has an Envelope node with attack, decay, sustain and release. On the way of the sound it is the volume. Its output "value" moves a number of the note with the same curve, from "From" to "To", for a filter sweep among others. A new Filter node gives that sweep something to move. - Envelope (instrument only): attack a straight line, decay and release ease out and are 60 dB of the way at their time. A release takes 10 ms at least. Defaults 0.005 / 0.1 / 1 / 0.1. - The end of a note goes to the Envelope instead of the 10 ms fade. A Gate or Loop Sample under it plays on through the release, "Loop, then release" leaves its loop too, and the voice ends with the release. A One-shot under an Envelope of its own plays through. - A note ending before attack and decay releases from the level it has reached. Chrome holds it with cancelAndHoldAtTime, Firefox gets the level worked out from the shape. - New port type "envelope" (coral). It fits only a number input that says it can follow one (PortDef.followsEnvelope): Filter cutoff and resonance, Pitch, Gain, the EQ bands and Mix. Not the Delay, not the Envelope's own numbers. portsMeet() decides it for ConnectCommand, the drag and the drawn edges. - On the audio thread every Envelope of a note is one ConstantSourceNode. A number it moves gets the curve through a WaveShaper, in equal ratios for a cutoff and a gain. A number an Envelope moves is no longer reached by a parameter. - Filter (sound FX and instrument): low-, high- or band-pass, cutoff in Hz, resonance 0 to 1 as a Q. A low- or high-pass is flat at 0 and rises 20 dB at 1, a band-pass goes from Q 0.7 to 20. - Stop sound with "let ring" lets a note with an Envelope fall away in its release. Without it, and with Stop in the tab, it is cut. - The same note again during a release is a voice of its own. - The Envelope's numbers are set when the note starts. They can come from velocity or a parameter, later changes do not bend the note. Tests: - envelope.test.ts (new, 12): curve, ratios, Filter Q, compiling an Envelope, its value into each number, a Delay refused - sound-library +7: attack and decay on the clock, release from the level reached, One-shot held, Stop sound with and without let ring, a cutoff and a pitch moved by the curve - e2e 34: ALL PASSED, 50 checks (+2). Offline in Chrome the curve rendered 0.5 / 1 / 0.5005 at 0.05 / 0.1 / 0.3 s, released at 0.5 s it was 0.0158 at 0.65 s and 0.0005 at 0.8 s, released mid-attack it fell from 0.5 to 0.05 in 0.1 s. A Gain moved 0.25 to 1 was 0.5 halfway. A looped note of 0.2 s with a release of 1 s was gone after 1.32 s. - Five counter-checks (release end, One-shot hold, the follows check, ratios, pitch sum) failed 3, 1, 1, 2 and 2 tests. - Gate on melektaus-atx: CHECK EXIT 0, 245 files, 3812 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fb4d65a2ab |
Keep both sounds full at the middle of a Mix with a curve #331
A Mix crossfaded: at 0.5 each sound was at half. Layering two sounds in an instrument now goes through a Mix ( |
||
|
|
23bdc3f775 |
Play notes in time on the audio clock with a scheduler #332
Patterns and songs (#333, #334) have to play sample-accurate in time,
whatever the frame rate does. What is counted in game time runs away
from the sound. This lays the ground: musical time, a scheduler that
plans notes ahead on the audio clock, and a player that starts and ends
a note at a given second. Nothing in the editor plays a sequence yet,
the check scripts drive it through `__gm.music`.
- Ticks, 480 to the quarter as in MIDI files (decided in #332).
Triplets and quintuplets land on whole ticks. Any meter, a beat being
the note value under the line. The tempo always counts quarters.
- `core/music/sequence.ts` is a pure rule: the notes starting in a
window of playing time. Across the end of a loop it looks into the
next round, so the seam has no gap. A note past the loop end ends
there. Windows are whole ticks, and the seam is stepped exactly, so
no rounding can drop the first note of a round.
- `core/music/Scheduler.ts` plans the next 100 ms every 25 ms through a
host, so it is tested with a clock of its own. The first note sounds
50 ms after the start. Changes to the sequence are heard from the
next window on. Stop and seek fade what it started. A new tempo or
loop counts from what is planned, and the playhead keeps its old
pace until the clock gets there.
- The position is the audio clock less `outputLatency`, as bar, beat
and tick.
- The beat comes from a Web Worker (`viewport/musicTimer.ts`), which
keeps its pace in a hidden tab.
- The sound library starts a note at `timing.at`: a buffer source by
`start(when)`, the pitch worklet by counting down inside the render
block it falls in. A note's end is set on the clock at once, its
fade to the sample. Leaving a loop still waits for the page.
- Delay synced to beats (decided in #332, noted from #327): a setting
`Sync` (Seconds / Beats), `time` then in quarter notes, held to the
2 s of the line. Play sound and Play note have an input `tempo`,
120 BPM where nothing is connected. A scheduled note carries the
tempo of its sequence.
- Swing is left for #333/#334, as decided.
Also fixes the red suite of
|
||
|
|
6f1f31516f |
Find asset types below the fold of the + menu in e2e #331
Since the Instrument joined the + menu, its last entries can sit
below the fold of the test window. CI for
|
||
|
|
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> |