b50c7dbc35421cc340c04cd6099eda64bafc2d49
65
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b50c7dbc35 |
Let a song track automate parameters of its sounds and FX (#337)
Stage 3 of #337. Lanes moved volume and pan only. Now a lane can steer a parameter of anything on its track, and a voice already planned or still waiting for its file follows it too. - A lane may aim at param:<name>. A parameter has no range of its own, so the lane keeps min and max. A parameter lane stays without points, so its range is not lost. Remove lane takes it away. - It reaches the track FX, the FX of an item, the instrument of a note with its own FX, and the FX of a clip. When several have the name, all of them change, as with Set sound FX parameters. - The scheduler hands the lane value to a note or clip at its start. Values a voice only reads at its start act from the next note. - While a voice sounds, the knobs of buses and voices of the track follow the lane on the audio clock, read every 10 ms, because a knob is not always the parameter itself (trackParams.ts). - A voice waiting for its file takes the marks when it starts. - When a lane is removed, the parameter goes back to its default. An edit while playing does not reset a parameter a lane rules. - This holds in the song tab, for Play song, in compositions and in the bake. - Song tab: Add parameter lane in the track head menu opens a dialog for parameter and range (ParamLaneDialog.ts). The lane head menu has Range and Remove lane. - Inspector: while a lane rules a value, a hint at the track names the lanes and says the fields act once the lane is gone. Known limit: a knob that depends on two automated parameters reckons each lane with the last value of the other, so it may lag one window. Tests: - automation-params.test.ts (9, new), 3 new in sound-library and 3 in song-lanes. Affected files 407/407. - 17 counter checks, all compile, all red. - e2e 65 on melektaus-atx: ALL PASSED, 67 checks. 65h3 is new: a clip with FX parameter cut starts at the lane value and its knob stays within 0.04 dB of the lane. It goes red when automateBus does not pass parameters on. - Gate on melektaus-atx: CHECK EXIT 0, 330 files, 5080 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
389a7c0fd1 |
Let volume and pan of a song track follow automation lanes (#337)
A song track can now change its volume and pan over time, played on the audio clock wherever the song is heard. This is stage 1 of #337: the model and the playback. Lanes cannot be drawn in the song tab yet. - A track holds automation lanes for volume (dB) and pan. Each point has a tick, a value and an easing from the shared list, curve assets included. Two points on one tick make a jump. Before the first point its value holds, after the last point the last value holds. - The rules are pure functions in core/music/automation.ts: the value at a tick and the pieces of a lane inside one scheduling window. - The scheduler hands those pieces to the host in every window. The host sets them as ramps or jumps on the gain and panner of the track's bus. Shapes the clock cannot ramp directly (dB as gain, curved easings) are sampled every 10 ms. At the loop end the value jumps back with the playhead. - This works in the song tab, for Play song, inside a composition (across the song loop, on the item's own bus) and in a bake. - While a lane has points the fader no longer sets that value. When the lane goes, the fader takes over from the current value. - Automation enters the bake fingerprint only when there is some, so existing bakes stay valid. - The level helpers moved to core/music/level.ts, so song.ts and automation.ts do not import each other. Known gap: a delaying sound FX on a bus after the track (a pitch shifter) makes the automation late by that delay. Tests: - automation.test.ts (13, new), 3 new in sound-library.test.ts. Song, bake and composition tests together 286/286. - 12 counter-checks, all compile, all red. - e2e 65 on atx: ALL PASSED, 60 checks. New 65h2: in the song tab the bus level follows a volume lane, 0.09 dB off, also with the fader at -60 dB. Red when the song tab does not pass the automation on. - Gate on atx: 328 files, 5053 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b5e071ca68 |
Let the agent read what is heard in the running game (#436)
The agent has no ears. It could start music and sounds, but not tell whether anything reached the speakers: a song without an instrument, a bus at volume 0 or a browser that holds the audio clock all looked the same from outside. - get_runtime has a section audio. Under levels it gives the peak and RMS in dB of the master, each bus of the mix, each pattern, song or composition playing and the tracks of a song. Below -90 dB it says silent. - The levels come from a tap of their own after the level and pan of each bus, an AnalyserNode per side as for the meters of the Song tab. It looks back about 0.7 s, so the gap between two notes is not silence. A pause holds the values, which fits play with until. - Under voices it names what sounds now: a sound by its asset, a note of an instrument with its note name, each with its bus and object. Next to them planned, playing, waiting (bytes not here yet) and failed. - Under clock it gives the state of the audio clock, with a hint when it stands still while the game runs. The state is told as it was before a pause, else the pause would hide that case. - The buses are tapped only while the agent is switched on. - The note now travels with a voice from the Start of a pattern or song to the SoundLibrary, so a voice can be named by it. - tools.json and the relay's instructions tell the agent to check under audio that music and sounds are really heard. Found on the user's project: started right after F5, before a click in the page, the browser kept the audio clock suspended for the whole run. Nothing was heard and the notes stayed planned. That is what clock now shows. Tests: - Unit: sound-library (taps, levels, voices, clock), agent-play (audioView). Pattern and song tests expect the note in their timing. - 9 counter-checks, 8 red. Not dropping a tap with its bus stays green, as the bus is cut off and gone anyway. - e2e 71 (melektaus-atx): 28/28. New 71h4: a sound on the master and one on a bus at volume 0, both voices named, the master loud, the bus silent, the values held in the pause. - Gate (melektaus-atx): CHECK EXIT 0, 327 files, 5037 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dd2096e0df |
Make a bus of the mix at its level, so a quiet bus starts quiet
A bus of the project's mix was made at unity and then glided to its level. Where the audio context comes with the first sound of a run, that sound already plays on the new bus. On a bus at volume 0 it was heard for a moment, loud, while the level glided down. - setMix now sets the level of a bus it makes at once, without a glide. A bus that is there already glides to a new level as before. - Found while building #436: the e2e check with a sound on a bus at volume 0 measured -7.8 dB on that bus. With the fix it is silent. Tests: - New unit test in sound-library: buses made at their level with no glide, a later change glides. Red without the fix. - sound-library 130/130, tsc clean on this change alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0701fe3856 |
Share the parts the sound devices and timelines repeated (#85)
Round 12 of the clean-up. Duplicate code had grown from 33 clones (249 lines) after round 11 to 140 clones (1263 lines), almost all of it in the panels and faces of the sound FX devices (#379 to #401). It is down to 57 clones (416 lines) now, and src is 816 lines shorter. - deviceParts.ts holds what the device panels built by hand: the head, rows of knobs, the screen style and the row layout for groups of knobs. Twelve panels use it. panel.knobs stays because the debug hooks read it. - DeviceFace is the base of the twelve faces. It finds the node and does wired, preview, commit, set, hide, reload and audition. compressorWired lives there as paramWired. - TextPool replaces ten copies of "make texts as needed, hide the rest" in the wave and video tabs, the rulers, the piano roll and the song and composition timelines. clipStrip.ts is the clip label strip the wave and video tabs share. - core/util/bytes.ts has the concat that ogg, matroska and avcColour each wrote out. - Dead code is gone: clear on the video players (never called since #402), posterFileOf and videoTimeAt (only tests called them) and easeOut (the same as glide(t, 3)). Values only used in their own file lose their export. - Comments over 125 columns are wrapped, and comments that told the history ("as before", "used to") say what is, in src and in the e2e modules. - Left alone on purpose: AnimationEditor and CompositionEditor share only the play button, and the curve panels are built differently. - Chip now places its boxes through put, which rounds. They were whole numbers already. The three row layouts are byte-identical before and after. - README lists the video and composition assets and the Videos and Compositions pages. Tests: - Gate (melektaus-atx): CHECK EXIT 0, 313 files, 4835 tests. - e2e 25, 34, 64, 65, 66, 69 (melektaus-atx): ALL PASSED, 330 checks. - TextPool without keeping its texts and framesToRoom without making room both go red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
87b2ee9402 |
Record every frame with Real time off on Start recording (#423)
At 4K and 60 fps the user's computer reaches about 39 fps in real time.
Reading each frame back from the GPU takes longer than a frame, and
neither reading back later nor the canvas route helped. With Real time
off the run moves on by one frame of the recording at a time, however
long the computer takes, so the file has every frame.
- Start recording has a new check Real time, on by default. Only the
node records so, the editor recording stays in real time.
- SteadyPace in recording.ts is the pace as a pure class. A step is
1/fps once the frame before is taken and the sound up to it is made,
otherwise 0 and the run holds. A frame's time is its number.
- The sound is made apart, as decided in the issue. AudioSwitch routes
what the run plays to the live library, or to the offline library of
a SteadySound while such a recording lasts. That library has the same
mix, buses, FX and limiter and is rendered up to each next frame.
- A driven video holds at every picture asked for, and the frame waits
for it. Its sound is decoded from the MP4 and played in step through
its bus.
- Two Chrome quirks measured and worked around. A held video hands
over the picture of a seek before seeked, so the frame callback is
asked for when the seek begins. A seek right onto where a picture
begins shows that one or the one before, so such a seek lands half a
millisecond further. Together 30 s became 10.7 s for 2 s of video.
- The notice tells how far the recording has got, and the line at the
end how long it took.
Deviations and limits:
- Input is not ignored, it still reaches the game, which runs slower.
- At most ten minutes per recording. Chrome holds the whole buffer of
the OfflineAudioContext while rendering, 230 MB for ten minutes.
- A free video plays on in real time and does not fit. A sound playing
before the start stays at the speakers.
Found along the way:
- A check in e2e 60 was red since
|
||
|
|
4d0a696327 |
Keep composition sound in step and give compositions a bus of their own
A composition's sound ran on the audio clock while its picture ran on the frames. A frame counts at most 0.1 s, so after a hitch the sound stayed ahead of the picture until the next stop. Songs in a trailer drifted off the cut. The items of a composition also had no bus of their own, and a video sprite could not pick where its sound goes. - CompositionSound.follow compares the audio clock with the picture each frame. More than 40 ms apart, it starts the sound again where the picture is. This holds in the tab and in a run. - A time scale other than 1 now restarts the sound again and again. It stays in time but comes in pieces. - The sprite of a video item shows "Driven (by composition)" as a fixed Video clock row. Video time is hidden there, because the composition sets it every frame. - Every composition has a bus in the mix and a node in the master routing. A new one goes to the master unless other compositions are linked elsewhere, apart from where the sounds go. - Audio items pick From asset, To composition, Master or a bus. New items start with To composition. Items saved before keep From asset, so music already sent through a bus does not bypass it. - Video sprites have a new row Video bus: From asset by default, the master or a bus. In a composition the default is the composition's bus, also for video sprites inside a container item. - The chosen bus is part of the key of the video player, so a sprite with another bus gets a player of its own. Found along the way: - Six tests with full lists of sprite defaults expected the old rows and now list Video bus too. Tests: - tsc, eslint, Prettier clean - Unit: 971 green in the 28 files touched - npm run check on melektaus-atx: green, 309 files, 4804 tests - e2e 68 (melektaus-atx): 13 checks green, 68i new. The sound item and the video item go into mix:cp68-cut, which goes to the master. - e2e 69 (melektaus-atx): ALL PASSED, 23 checks, 69q and 69r new. After a hitch of 500 ms the sound restarted twice and then lay 0.5 to 5 ms off the playhead. - Counter-checks in unit tests: without the comparison the hitch test fails, without the bus in the preload the run test fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
acd12326dc |
Fade sounds and videos in and out on the items of a composition
A trailer needs its music and its clips to come in softly and leave
softly. Until now an item of a composition could only play at one
level, and a video item could not even be turned down on its own.
- Every item with sound gets a grip at its upper left and upper right
corner. Dragging one inwards fades the item in or out. Dragging it
back to the edge removes the fade. Lower down the same edge still
trims as before.
- The fade is drawn as its curve, with the part above it darkened.
The inspector shows Fade in and Fade out in seconds, each with the
curves sound clips already know.
- The model keeps fadeIn and fadeOut in frames plus curveIn and
curveOut on the item. Old projects load unchanged. An item shorter
than its fades plays them cut to its length but keeps the setting.
- Sound, pattern and song items ramp the gain of their own bus. The
scheduler plans the ramps on the audio clock next to the notes.
Starting in the middle of a fade picks it up from there.
- Video items set the new sprite property Video volume each frame.
It multiplies with the fade and is only shown for a driven video.
The player glides to each new level over 15 ms so it does not click
between frames.
Not covered: a container item that carries a video further down gets
no grips. Only a video on the item's own sprite counts.
Tests:
- tsc, eslint, Prettier clean.
- Unit tests for composition, music, song and sound (12 files): 269
passed, 7 of them new.
- e2e 69 on melektaus-atx: ALL PASSED, 21 checks. 69p drags a grip to
a fade in of 10 frames and hears the item bus rise from 0.127 over
0.229 and 0.339 to 1 within one second.
- Counter-checks as unit tests: without the audio ramps, and without
the level on the video sprite in the game, both go red.
- Gate on melektaus-atx: 309 files, 4796 tests passed, 8 more than
at
|
||
|
|
77ac9d1aaf |
Play sounds, patterns and songs on the tracks of a composition (#418)
A trailer needs its music and effects on the same timeline as its pictures. Until now a composition could only place objects, so sound had to be started by hand from a behavior and drifted against the picture. - Audio items: a sound, a clip of a sound, a pattern or a song dragged from the asset panel onto a track becomes an item as long as one pass of it. It may share a track with object and video items and overlap them. - Editing works as for other items: move, resize, trim on the left (moves the offset), fit to media, delete, copy and paste, box selection. Sounds draw their waveform, the colour tells the kind. - The inspector shows what it plays, start, length, offset, volume (-60 to +12 dB) and bus. The bus is "From asset", the master or one of the buses, as asked for in the issue. - An item longer than its material plays in rounds. Sounds and patterns start over, a song runs on in its loop. Loop marks on the item follow the same rule, a song's first mark sits at its loop end. - compositionAudio.ts turns the items into sequences, each with its own tempo. CompositionSound plays them, in the tab and in a game. - In the tab the items are heard from the playhead on, straight into the master like every tab. A scrub is silent. Playing a song or pattern in its own tab stops the composition, and the other way round. - In a game they are planned on the audio clock for the moment the composition clock reaches them, with the scheduler's LEAD made up. While the clock stands (waiting for a video, paused, placement off) they are silent and resume at the right spot. Found along the way: - The asset panel never let a song out of its list, so a song could not be dragged anywhere. Song and scene tab reject it as they reject a pattern. - The asset panel scrolled to the selected asset on every rebuild of its list, so folding a group far below the selection jumped back up. Only a new selection scrolls now. - At the end of the list, folding a group pulled every row down by the rows that went away. ScrollContainer has a new opt-in holdOffset, set only in the asset panel, that keeps the offset until the user scrolls up. Limits: a time scale other than 1 or a frame longer than 0.1 s moves the picture and not the sound. A baked song plays its notes here, not the bake. Tests: - tsc, eslint, Prettier clean. Gate on melektaus-atx green, 309 files, 4788 tests (4772 plus 16 new). - Unit: composition-audio.test.ts 13 new, scroll-container 3 new. Counter-checks: without catching up on load time or without the silence on pause the unit tests go red. - e2e 68h: the sound is planned 1.2 ms after the composition reaches 0.5 s. Without the LEAD make-up it is 54 ms late and red. - e2e 04 and 69 ALL PASSED, 86 checks. 69o-ii drags a pattern and a song onto a track, 20 frames each. - e2e 04: folding Sound at the end of the list keeps its header at y 234. With the old panel it jumps to 984 and the check is red. - Not run in a browser: the loop marks of a song item, copy and paste of audio items, the bus choice. - README and wiki count 2072 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
043c224cc3 |
Rename Game Maker to Feuerwerk and serve it at /feuerwerk/ (#422)
The editor gets a name of its own that fits beside Brandmauer, Glutspeicher and Feuerwehr. This is stage 1, 2 and 4 of #422: the name, the URL and the stored state in the browser. Repository, CI runners, containers and the GAME_MAKER_* variables follow in stage 3. - All paths come from SITE_PATH in src/core/site.ts: the API endpoint, the data folders, the play page and the base in vite.config.ts. A later move is one line instead of fifteen. - The window title, the sign-in screen, the playground and the README say Feuerwerk. The package, __feuerwerkPlay, the take worklet and the writing app in Ogg and Matroska follow. - The session cookie is now feuerwerk_session, so everyone signs in once more. - localStorage keys are now feuerwerk.*. moveOldStoredKeys copies the old game-maker.* keys over on the first start, so settings, panel sizes and save games survive. - tools/ci/serve.sh serves the build under /feuerwerk/. The variable GAME_MAKER_BASE_URL keeps its name until stage 3. Outside the repository, nockal.com/feuerwerk/ is a second symlink to the same tree. nginx sends the bare /game-maker/ folder there with a 301. Everything else under /game-maker/ is still served directly, so shared play links and the API keep working. Finding: when nginx redirects a path without a trailing slash or with a query, the Brandmauer answers with its own 307 to the same URL plus a slash, placed after the query. A play link would lose its token that way, which is why only the bare folder is redirected. Tests: - Gate on melektaus-atx: CHECK EXIT 0, 307 files, 4756 tests (4 new in site.test.ts). - Counter-check: when the migration misses the old prefix, 3 of the 4 new tests fail. - e2e 60 under /feuerwerk/: ALL PASSED, 20 checks. - From outside: /feuerwerk/ 200, /game-maker/ 301, an old play link 200. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f92d69d902 |
Rebuild the Acid node after the acid voice of nodeseq (#421)
The first Acid hardly squelched. Its tanh ate the resonance, and its sweep fell evenly in octaves. The user pointed at the acid tone of nodeseq.com/studio, so the node now follows how that voice is built from Web Audio parts. - The filter is a two-pole 12 dB low-pass with Q in dB up to 17.5, like a BiquadFilterNode. The ladder and its tanh are gone. - One ADSR drives both the volume and the filter. The sweep falls back in Hz, fast from the top and long at the bottom. - Every note glides from the one before. A new note cuts the last one off, and the node plays its release itself. - Knobs: cutoff, reso, env, attack, decay, sustain, release and glide. The defaults are the acid preset of nodeseq. - Accent, slide, drive and tuning are dropped. Old projects were not kept, the user had only placed the node for a try. - A hook __gm.acid.compare renders a line offline through our worklet and through the nodeseq graph, for e2e 34. Found on the way: in Chrome an oscillator that starts inside a render block runs its first samples at 440 Hz. The comparison evens this out. Tests: - tsc, eslint, Prettier clean - Unit: acid.test.ts rewritten with 12 tests. chip-presets and sound-library follow the new node. - e2e 34 on melektaus-atx: ALL PASSED, 148 checks. Against nodeseq the loudness per 10 ms window is at most 0.14 dB apart, and 95 to 97 % of the windows are equally bright. - With a linear Q instead of Q in dB the comparison is red. - Gate on melektaus-atx: green, 306 files, 4752 tests. That is 4754 less the 14 old and plus the 12 new Acid tests. - Not checked: the sound by ear, the master chain of nodeseq, the keyboard and a bake with the new node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d844f2734a |
Add an Acid instrument node for TB-303 style bass lines (#421)
The instruments could only come near a 303 with a Chip, a Filter and two envelopes. The biquad filter does not saturate, every note strikes anew, and there was no slide and no accent. - Acid is a new instrument node that plays a saw or a square through a four-pole ladder low-pass with tanh saturation, oversampled twice. Knobs: cutoff, resonance, env mod, decay, accent, tuning and slide time. - An accent comes from the velocity, from 0.9 on. It is louder, opens the filter further with a shorter decay, and accents close behind each other build up. - Two notes that touch or overlap slide. Each note stays a voice of its own, so the scheduler is unchanged. The notes of a line share one synth in the worklet, and the new voice takes it over from its first sample on. - The node sets its own volume, so it needs no envelope behind it. - The graph shows it as a device with the filter curve at rest and at the top of the sweep. - A preset Acid Bass puts an Acid in front of a short echo. Tests: - tsc, eslint, Prettier clean - Unit: 14 new tests in acid.test.ts for pitch, low-pass, resonance, sweep, accent, slide and the handover between two voices. - e2e 34 on melektaus-atx: ALL PASSED, 148 checks (3 new). The handover sits on sample 4800 and jumps by 0.0012, of three notes only the second slides. - Without the handover, or with a slide that strikes anew, the unit tests are red. - Gate on melektaus-atx: tsc, eslint, Prettier clean, 4753 of 4754 tests green. The red one was sound-library.test.ts, which did not know the new slid count in the stats. Fixed, and the file alone is green again with 128 tests. No second full gate. - Not checked: the sound by ear, slide at the keyboard, a bake with Acid, the CPU load of many notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
93190e185c |
Route a video's sound through its own Master routing node
An editor recording had no sound while the video tab played a video. The tab played its <video> straight to the speakers, past the Web Audio graph, so the recording tap behind the limiter never heard it. Fixing only that would have left videos as the one sound source the Master routing cannot reach, so they get their own node, like sounds. - A video asset with sound has a routing node labelled "Video". One without sound has none, it would have nothing to send. - Its links take the sound after volume and FX. Several links or another level give the video its own bus, as with a sound. - A new video follows the bus most other videos use. With none linked yet it goes to the Master, not to where the sounds go. Songs and sounds no longer follow videos either. - Players only know their file, so videoBusesOf maps each file to the bus of its first asset. setMix takes that map and moves the gains of running videos at once when the routing changes. - In the game, played in the editor or released, a video's sound follows its links. The tabs (video, composition, animation preview) play straight into the Master, as the sound tabs have since #368. - The video tab's sound now goes through the Master, so its volume, FX and limiter apply there and the editor recording hears it. - The recording badge beside the menu is an HTML element over the canvas now. The second render that drew it on the desynchronized canvas let the screen show a frame without it, so it flickered. The element is never in the copy, and clicks go through it. Tests: - Unit master-routing and sound-library: 3 new, green. Red without video in the routing kinds, without moving the gains, and with the old rule for the links of a new video. - e2e 66e-iii: the tab plays a video linked to a bus, the level is 0.09 on the Master and 0 on the bus. 66g: in the game the bus has 0.09. Both red with the old ways. - e2e 67e reads the badge from a real screenshot, still green. - The README names 2043 e2e checks. - Gate on melektaus-atx: 304 files, 4694 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e191dc4b2e |
Record the game view from a behavior into a video asset (#405)
A game can now film itself. A behavior starts and stops a take, and the editor turns it into a video asset with the run's picture and the sound the player hears. The exported game hands the file over as a download, since there is no project to keep it in. - New action nodes `Start recording` (Name, Resolution, FPS) and `Stop recording`. An empty name becomes `Recording <timestamp>`. - Resolution is `Game view`, `720p` or `1080p`. For the fixed sizes the height is set and the width follows the camera. - The picture is the camera only, without hints, overlays or the letterbox. It is drawn into a render texture of the target size, so a smaller window still gives full 1080p. - `PictureReader` reads the texture back through pixel buffers with a fence. The editor never waits on the GPU, and a frame with all three buffers busy is dropped and counted. - Nothing of the take reaches the visible canvas. A first version drew tiles into it and copied them out, and with the low-latency canvas that midway state flickered on screen. - The sound is tapped behind the master limiter in the FX worklet and encoded as Opus. - H.264 is chosen when the browser has it, VP9 otherwise. A small own Matroska writer packs both tracks, no new dependency. It names the frame rate and BT.709 so ffmpeg keeps both. - A second `Start recording` is ignored with a warning in the log. A take goes on through `Load scene` and a restart, pauses with the editor and is saved when the run ends. It stops at 2 GB. - The editor uploads the take on the existing video route and makes the asset once the run is over. The video tab gets a Download button for the converted MP4. - `downloadBlob` in `src/ui` replaces the song editor's own copy. Findings: - `On start` runs before the game screen knows the world. A take therefore fixes its size on the first frame that has a camera. - Without a frame rate in the file, ffmpeg turned 30 fps into 59.94. - The worker's poster JPEG is off in colour for every upload (red 255 comes out as 229, BT.601). Not touched here. Tests: - Unit recording + matroska: 21 green, plus the node catalog cases. The Matroska test reads the file back with its own EBML reader. - e2e 67 (new) on melektaus-atx: ALL PASSED. 1080p30 from a 1280x800 window gives a 1920x1080 MP4 with sound and no dropped frame. Every 4th pixel is checked, a green quarter catches an upside-down picture. A take ended by Esc is saved with the timestamp name. - e2e 60j: the exported game downloads an `.mkv` with EBML magic. - The flicker is gone on the user's machine without `?vsync`. - Gate on melektaus-atx: CHECK EXIT 0, 299 files, 4586 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d48f174caf |
Play videos on sprites, converted on the server with clips (#402)
A video becomes an asset like a sound: uploaded once, converted to one format by a worker of its own, cut into clips in its tab, and worn by a sprite through its Texture row. Only Chrome is a target, without alpha. - Server: POST ?r=videos queues the upload under the hash of its bytes, so the MP4's name is known at once. The worker (VideoWorker over VideoFormat) writes H.264/AAC, yuv420p, constant frame rate, a keyframe every 15 frames, at most 1920x1080, with a poster and the details beside it. A failed job says why and is tried again on the next upload. - An upload may be up to 2 GB. It is streamed from the request to disk and hashed there, so php-fpm holds none of it in memory. The other routes keep their 256 MB. - The worker runs in the container game-maker-video on stein2, a compose project of its own (tools/video/), and in CI beside the PHP server. The CI image has ffmpeg for it. - Model: asset type video with size, length, frame rate and sound, and clips like a sound's. The clip helpers and commands serve both. - A sprite's Texture row offers videos and clips after the pictures. resolvePicture names a playing video by a key, and the texture library hands it to a VideoLibrary with one player per key. - The editor shows a placed video as a quiet preview. Play starts it over at its clip's start and lets it be heard through the master bus, a pause holds it, Esc makes it quiet again. - Video tab: the picture, a ruler, the clip strip and a track on the Sound tab's time axis. A drag spans a clip, a handle moves an edge, Play plays the picked clip. - The picker shows a video on its way with its progress and adopts an upload from it once it is ready. Dropping a video file does the same. Decided on my own: - Two sprites wearing the same clip share one player and show the same picture. Control per instance belongs to the follow-up with nodes. - A video plays in real time, the game's time scale does not slow it. - The container may use 3 of the 4 cores of stein2. Findings: - The first real upload (279 MB) got 413: the API's own ceiling of 256 MB for every request, not nginx or the Brandmauer. - Chrome has no picture of a video that never played, and a Pixi VideoSource drawn before the first frame stays empty for good. The texture is made in the first frame callback (openVideo). - A player nobody asks for in 2 s is let go. The inspector's PREVIEW asked only on an edit, so with the sprite not on screen its texture was destroyed under it and the batcher threw on every frame. The preview now redraws every frame while it shows a video, like a Time shader. A let-go texture keeps an empty source instead of none. - VideoSource.load() reloads the element and cuts off an earlier play(). Playing starts after loadeddata. Tests: - PHP videos.test.php: 68 checks, with the real ffmpeg - 300 MB upload through Brandmauer and nginx: 201 in 21 s, php-fpm at 13.7 MB RSS at most - Unit videos.test.ts: 20 tests - e2e 66 (stein2, atx was off): ALL PASSED, 18 checks; e2e 04 with the new asset count green - Gate (atx): 294 files, 4520 tests. serialize.test.ts used video as its example of an unknown type and now uses a made-up one. - Counter-checks: a keyframe gap of 30 fails the PHP check, a key that keeps the video's end fails 2 unit tests, a clip without its end fails "plays only its stretch" in e2e 66 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
28909f0adf |
Show a Limiter node as a device that holds what it took away (#397)
A Limiter only said what it does in rows of numbers. How much it took away, and when, could not be seen at all, and a short sound is over before any needle could show it. Now the node is a device of its own size, and its screen catches the reduction from the moment a sound starts and holds it once the sound ends. - LimiterPanel draws a 720x280 plate of brushed brick. The threshold knob is large, ceiling and release are small beside it, auto-gain is a lever. A LIMIT lamp lights while it takes something away. - The screen hangs the reduction down from the top, 0 to 24 dB by the square root like the Compressor's meter. The level coming in runs along the bottom, orange above the limit. Time runs from the start of the sound over 1, 2, 4 or 8 s, and a longer sound rolls. - ReductionCapture in limiterView holds the capture after 0.4 s of silence, without the silence, until the next sound. It writes the most taken away and when, and NOW or LASTED on the right. - The Limiter is tapped now, like the Compressor. The face reads the loudest sample since the last frame on both sides. - SpectrumTaps.levels takes a lateBy. The side in front is read 5 ms earlier, so a peak going in is met by what it became. - limiterReduction leaves the auto-gain out and solves Wet's blend for the limited part. Less than 0.2 dB counts as none. - A capture is also held when the node leaves the picture. Changing a knob or the lever leaves it as it is. - Hook __gm.limiter.panel(id) for the sound FX and instrument graphs. Decided myself: - The capture stays held until the next sound, not for a fixed time. - The screen has no wet knob, as the Compressor's has none. - The Limiter's two taps cost two gains of 1 per voice. The analysers only exist while the node is in the picture. Measured on stein2: the seeded blip at -4.4 dBFS against -12 dB showed 7.56 dB. Three hits at 0, -8 and -2 dBFS showed 11.9 dB at the first. In e2e 34 on melektaus-atx, -10.44 dBFS in against -20 dB showed 9.56 dB, the difference to the hundredth. Tests: - Unit: limiter-view 13 tests (new), spectrum 6 (1 new), soundfx 67 with the Limiter's taps. - Counter-test: without Wet's share in the formula the blend test fails. - e2e 34: ALL PASSED, 138 checks (1 new). - Gate: CHECK EXIT 0, 290 files, 4445 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3f7eb03143 |
Check what a streamed sound holds by the library's own count (#378)
The e2e check that a streamed sound costs less than a decoded one compared how much the renderer process grew. Alone, module 34 saw it grow by 1 to 8 MB while streaming. After the 33 modules before it in CI it grew by 25 to 66 MB, and suite went red (CI tasks 611, 614 and 617, the last with 66.7 MB streamed against 95.9 MB decoded). - SoundLibrary.stats tells decodedBytes, the samples of every file decoded whole, and streaming.held, every head of a copy and what is sent ahead to the worklet. - StreamFile.headBytes and StreamFeed.ahead give their share. - The e2e check now asks the library: streamed it holds less than 4 MB more than before and nothing decoded whole, decoded it holds more than 40 MB, the five minutes being 50 MB at 44.1 kHz. - The growth of the renderer stays in the log of that check. The counter-proof that the renderer grows by more than 40 MB when the sound is decoded stays as it was. Tests: - Unit sound-library: the streaming test also checks the bytes held. - e2e 34 (melektaus-atx): 136 checks. Streamed 0.76 MB more held and none decoded whole, decoded 50.5 MB. The renderer grew by 4 MB and 49 MB. - Gate (melektaus-atx): 288 files, 4416 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1df38bd430 |
Edit a Compressor in a tab of its own, with knobs and levels (#378)
A Compressor had only its number rows in the node. Its numbers are
easier to set by feel on a dial, and what it does is easier to judge
when the level going in and the level going out are seen side by side.
- Knob is a new widget in the toolkit: a slider bent into a circle,
its name above and a number field below. A drag up or down turns
it, Shift a tenth as far, a notch of the wheel a fiftieth of the
turn, a double-click sets the default, the number takes a typed
value. A taper gives the low end more of the turn.
- The Compressor's tab opens on a double-click, one per node, like
the Parametric EQ's. It has a knob for each of the seven numbers,
greyed while its input is linked.
- Its curve shows level in to level out from -60 to 0 dB. The point
where it bends sets the threshold, the end of the curve sets the
ratio that ends there.
- Its levels show the last 4 s on both sides of the node. Where it
takes more than 0.5 dB away, the stretch below the level coming in
is tinted. The makeup is left out of that.
- The Compressor is tapped like the EQ. SpectrumTaps listens to more
than one node at once and reads the loudest sample of the last
25 ms on each side.
- The node tabs share one map in the editor (nodeTabs), so a third
kind needs no new plumbing.
Findings:
- CI task 614 for
|
||
|
|
82e96e680f |
Decode a clip from a file of its own, not its whole sound (#349)
A clip was cut from its sound's file, so a game decoded the whole file for it and held it for as long as the page was open. A long recording played through a few short clips cost the recording. Every clip now has a copy of its cut as Opus, with Stream on or off, and a game decodes that instead. - StreamKeeper makes the copy of each clip whether its sound streams or not. Stream only decides whether a sound gets a copy of its own. - clipCopyTable says which cut decodes from which copy. The sound library loads the copy for a clip unless the whole file is decoded already. Then the clip is cut from that as before. - projectFiles decodes the whole file ahead only where something may play the sound itself: no clips, a clip without a fitting copy, or the sound's id anywhere in an asset that can name one. That last test walks the data, not the fields, so no place is missed. - A copy that cannot be had falls back to the whole file. A library that bakes keeps to the whole files, as the bake's fingerprint does. Tests: - Gate: CHECK EXIT 0, 280 files, 4319 tests (7 new: streams +4, sound-library +3). - e2e 34: ALL PASSED, 108 checks (2 new). With Stream off a fresh library decodes the ten-second reversed clip's copy and not the five-minute file. It is the cut made from the original to within 0.0091 a sample and 0.013 dB in any 20 ms. Once a node plays the sound itself, the whole file is decoded ahead again. - e2e 65: all passed with this change. - Counter-checks red as they should be: the library ignoring copies, the whole file always decoded ahead, clip copies only with Stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2764919bb8 |
Stream long sounds from an Opus copy instead of decoding them (#349)
A sound was decoded whole as soon as the project opened, in the editor and in a released game: five minutes of stereo held some 115 MB for as long as the page was open. A sound with Stream on now plays from a copy as Opus in Ogg, fetched a piece at a time and decoded a few seconds ahead by WebCodecs, so a game holds a few megabytes for it. - The Stream switch is a row of the sound, off by default. The Sound tab suggests it from a minute on and says how far the copy has got. - StreamKeeper makes the copies in the browser, a moment after the last change, one at a time: the sound's own and one per clip of its cut, fades and Reverse baked in. A copy that is no longer wanted is taken off and deleted once the project is saved without it. - streamTable says which played names stream from which copy. A clip without a fitting copy, a Sample and a Reverb impulse stay decoded. A sound with Stream on is never decoded ahead. - The page fetches by Range and takes the whole file from a server that ignores it, as the CI's PHP server does. It keeps the bytes and the first two seconds decoded, the rest goes four seconds ahead. - A jump decodes 32 packets before the place first. Measured in Chrome, what follows is then the same to the bit as decoding from the start. With 4 packets it was 0.06 off. - The stream-player on the FX worklet reads the samples at the rate of the context along a Catmull-Rom curve. What comes late is silent and skipped once it is there, so a clip keeps its beat. - The opening stretch goes with the player as it is made. Sent by message it came a few milliseconds late, and 557 samples of the attack were lost. - Pitch on a streamed sound is a shifter after the player, as on a bus: the length stays and it is heard 42.7 ms late. - Bakes of patterns and songs (#340) always stream now. - The song timeline draws a streamed sound from its copy decoded at 8 kHz, keeping only the peaks. Tests: - Gate: CHECK EXIT 0, 280 files, 4312 tests (31 new: ogg +3, stream-queue 6, streams 11, streaming 6, sound-library +5). - e2e 34: ALL PASSED, 106 checks (7 new). Five minutes of mono, 4.8 MB as WAV, 3.6 MB as its copy: streamed the renderer grows by 5.8 MB, decoded by 69.6 MB. Offline at 48 kHz, start, loop seam and a reversed clip are the copy decoded whole to the sample (0). - Counter-checks red as they should be: no pre-roll, no catch-up of late samples at the end, no opening stretch, a streamed sound decoded ahead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
84cd36f902 |
Bake a pattern or song to Opus, played in its place by a game (#340)
A pattern or song played by a game runs every note through the scheduler, the instruments and their FX, live. Bake works that out once, keeps it as a file on the asset, and Play pattern and Play song play the file instead, as one voice, for as long as nothing it is made of has changed. The tabs always play live. - Bake is an icon in the header of the Pattern and the Song tab, lit while the bake holds. A click bakes, cancels a bake that runs, or offers Bake again and Remove bake. The toolbar shows the progress. The info of the tab says "baked" or "bake outdated". - The bake holds everything up to the output of the pattern or song. The sound FX of the node, fx params, volume, fade in, bus, Master routing and stopping with a fade stay live. - A print of the worked-out plan decides whether it holds: notes and clips, tempo, length, loop, tracks, the build of each instrument for each note, sound FX, files, cuts and levels. A rename keeps it, an undo back to the baked state makes it hold again. - The file holds the straight run and then one more round of the loop as heard the second time, tails included. Looped, it plays to the loop end and then round that round, to the sample. Once, it fades out over its last 10 ms. Let ring fades it out over 0.3 s. - It is worked out by a second SoundLibrary on an OfflineAudioContext, its scheduler woken by suspends of the offline clock, and what reaches the master is taken. A worklet probe tells whether that was mono, so a mono bake goes on exactly as the notes did. - Opus in Ogg, 160 kbit/s stereo or 96 kbit/s mono, encoded with WebCodecs, the Ogg pages written by hand. Normalized to full scale, the peak kept and played back. - The file goes up into the sounds of the account as "[bake] name", which the sound picker leaves out. An old bake file is deleted once the project is saved without it. Only a bake that holds is loaded. - Ogg files are decoded at 48 kHz: resampled to 44.1 kHz, the loop of a bake lost a sample a round. - Baking and removing a bake are no undo steps, like the EQ's zoom. Findings: - Chrome's AudioEncoder refuses opus.format 'ogg' (NotSupportedError) but hands out the OpusHead with a pre-skip of 312 for 'opus'. - The Song tab's header is full at 1280 px: a captioned Bake pushed Fit off the tab. Hence the icon. Tests: - Unit: tests/core/ogg.test.ts (6), tests/core/bake.test.ts (16), four new in sound-library.test.ts, one in song-play.test.ts. One test cuts, reverses and fades a clip on a song's track: each change outdates the bake, each undo makes it hold again. - e2e 64 (melektaus-atx): ALL PASSED, 36 checks (6 new). Live and baked rendered offline differ by at most 0.06 dB over 114 windows of 20 ms, two loop ends among them. e2e 65 had no red check with the icon in its header. - Gate (melektaus-atx): CHECK EXIT 0, 277 files, 4281 tests. - Counter-checks: print without instrument builds, print taking the sound's file instead of the clip's cut, round taken from the first pass, bake voice without its looping source, Play pattern or Play song ignoring the bake: one unit test red each. Pre-skip set to 0: the e2e comparison 4.8 dB off, only that check red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
687377682c |
Draw the EQ's curve in its node and a spectrum behind it (#375, #374)
Both were asked for in #367 "for later". The node of a Parametric EQ now shows its curve in small, and its tab shows what passes the node while it is seen. #375, the curve in the node: - NodeDef has an optional `picture`: a height, and shapes made from the settings as plain point lists (guide, area, line). It is general, the Parametric EQ is its first user. - NodeView draws it in a box below the rows, again only when the box or the settings change. A drag in the EQ tab changes them, so the node follows it live. - Without a band the box stays, flat on its 0 dB line, so the node does not grow at the first band. - The axes of the curve, `eqCurvePoints` and `fillShapes` move from editor/soundfx/eqView to core/soundfx/parametricEq. Tab and node draw the sum from the same function. #374, the spectrum: - A node marked `tapped` gets a `tap` stage in front of its own stages and one behind them when compiled, in a sound FX and in an instrument alike. Its id is asset and node, since a copied asset keeps its node ids. - A tap is a gain of 1. SpectrumTaps (new, viewport/spectrum.ts) holds the tap of every voice and bus while it plays. - While the EQ tab is seen, in the tab row or in a window, the taps of that node feed one analyser per side (8192 points). Nothing is made before a tap of the node plays, so no AudioContext is opened early. - The tab draws both behind the curve, on a scale of 0 to -96 dBFS: the sound coming in faint and grey, the sound going on in the colour of a sound port. The analyser smooths it, and once the sound has gone the sum still hands it silence, so it falls away. - Once the tab is not seen, Editor.tick lets the analysers go, like the meters of the Song tab. - A tap does nothing to the sound, so `stats().last.stages` leaves it out, and a bypassed EQ keeps its taps. Decided on my own: - Every sound passing the node counts: Play of the sound FX tab, an instrument's keys, a song, a run. The issue proposed that. - The spectrum has no labels of its own. Its scale is in the wiki. Tests: Gate (melektaus-atx) 275 files, 4247 tests (11 new), EXIT 0 e2e 34 (melektaus-atx) ALL PASSED, 98 checks (2 new), twice Counter-checks, each put back by copy and compared with cmp: - The node's curve drawn without its bands: 2 unit tests fail. - The spectrum without the loudest bin between two points: 1 fails. - SpectrumTaps.stop leaving the taps linked: 1 fails. - Taps that never feed the analyser: e2e 34 fails in exactly the spectrum check (frames 0). Run in the container only. The screenshots of the second run were looked at: the blip as one peak behind the curve, and the dip at 4 kHz in the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
25084f8576 |
Refactoring round 10: split SoundLibrary, fold sound duplicates (#85)
Refactoring round 10 covers everything since round 9 ( |
||
|
|
db15422372 |
Keep a bus's level when a tab plays the first sound of a page (#369)
A bus of the mix played at full level, without its sound FX and straight into the master, until something on it changed. It happened when the first sound after loading came from the Song or Pattern tab. - `bus()` looked a bus up before it asked for the audio context. The context made then sets up the whole mix, this bus too, and `bus()` overwrote it with a fresh one at gain 1. - `setMix` does nothing for the same mix again, so the wrong bus stayed until the mix changed. - Since #368 the tabs play into the master, so it was the master's volume and FX that went missing on the first preview. - `bus()` now asks for the context first and looks the bus up after. Tests: - Unit sound-library, buses, master-routing: 139 green, 1 new. - The new test was red before the fix: mix:b at gain 1 into the master instead of 0.25 into mix:a. - Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4231 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0e73fd85b9 |
Route sounds, songs and buses in one Master routing graph (#351)
A project bus could only go into one other bus, and a sound, an
instrument, a pattern or a song into exactly one bus. A shared reverb
needs a sound to go there in part and on dry at the same time. Instead
of a list of sends on every asset, the project gets one routing graph
like the routing of a song (#350), one level up.
- Menu -> Master routing opens a tab with one graph per project. It has
a node per sound, instrument, pattern, song and bus, named after its
asset, and one Master. Embedded assets have none, their song routes
them.
- The nodes follow the assets. Every edit settles the routing in the
same undo step (KeepRoutingCommand), so an undone delete brings the
links of its asset back. A new asset gets a node and a link to the
master. What went only into a deleted bus goes to the master.
- New nodes start in three columns: what plays, the buses, and the
master a row above, so no link into it runs behind a bus.
- Links are sends: -60 to +12 dB, and a Mode on links out of a bus or a
song. Out of a sound, an instrument or a pattern a link always takes
the sound after its volume and FX. Loops are refused.
- A node without a link is silent and says "Goes nowhere".
- The rows Bus and Into are gone. Play nodes with a picked bus still
overrule the routing.
- A click on a node shows its asset in the inspector, the Master shows
the master volume and sound FX. A double click opens the asset's tab.
- The graph lives in project.routing and is held to the assets when a
project is read, but nothing is added there. The editor adds the
nodes when it opens a project. An asset without a node plays on the
master, so old projects sound as before without a migration.
- In the mix an asset with one link at 0 dB goes straight into that bus.
A sound or instrument with more gets a bus of its own with the sends.
A pattern or song sends from the master of its playing, which is
where its Mode taps. Buses of the mix can have sends now too.
Found:
- The check "the scheduler plans a looped bar" in e2e 34 wobbles. It
failed in 2 of 5 runs here and in CI task 580 at
|
||
|
|
490fcba88f |
Meter each track and the song, and limit the output at -1 dBFS (#359)
A song could clip at the output without anyone noticing: a track and a sound item at +12 dB each add up to +24 dB. The Song tab now shows how loud each track and the whole song are, and a fixed limiter behind the master keeps every sample under -1 dBFS in the editor and in the game. - Each track head has a stereo meter up its right edge. The head is 190 px wide instead of 176. - Out sits in the corner above the heads, with the loudest peak since Play in dB. In the tab header it made the row too wide for a 1280 px window, and the inspector lost its fields. - The meters read AnalyserNodes beside the buses, one per side after a channel splitter, so a peak panned hard is not halved. They are tapped only while the song plays and its tab is shown. - A clip lamp lights past 0 dBFS and stays through Stop. The next Play or a click on the meter puts it out. - The meter of a track that mute or solo keeps from being heard is grey. - Music always plays through a bus of its own, with or without a sound FX. That bus is what Out measures. - The limiter is the one from #326 and needs the FX worklet. Until that is loaded the master goes straight out. - It is 5 ms late. latency() counts that in, so the playhead and the alignment of a recorded take follow it. - Unit tests build SoundLibrary without the limiter, as their wiring checks follow every node of a voice. - Out shows "-inf" while nothing was loud. The UI font has no infinity sign, which the font test found. Tests: - Unit meter-view 8 new, sound-library 112 (2 new), 3 counter-tests each red - e2e 65 (melektaus-atx): ALL PASSED, 55 checks (3 new). Out wrote +16.6 dB, behind the limiter the loudest sample was 0.8913 - Gate (melektaus-atx): 272 files, 4209 tests, one red in the font test. Fixed after it, then the font and meter tests ran green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 ( |