Describe the Sound tab and cutting a sound (#343)

- Sounds: new section The Sound tab, and Start, End and Edge fade in
  the property table.
- Concepts, Home and Instruments: a sound plays the stretch it is cut
  to.
- Development: 1840 checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-10-06 21:42:37 +02:00
1 parent 3def7ea96e
commit eddfd07f5c
5 files changed
+48 -7

No files matched your search

+2 -2
@@ -4,7 +4,7 @@
| **Asset** | Named thing with a type (`scene`, `camera`, `clip`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`, `shaderfunction`, `font`, `sound`, `soundfx`, `instrument`, `particles`, `variables`) and typed properties. Assets have **no position**. | | **Asset** | Named thing with a type (`scene`, `camera`, `clip`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`, `shaderfunction`, `font`, `sound`, `soundfx`, `instrument`, `particles`, `variables`) and typed properties. Assets have **no position**. |
| **Texture** | An uploaded image. Scenes, textures, animations, state machines, shaders, fonts, sounds and Variables assets are the eight types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). | | **Texture** | An uploaded image. Scenes, textures, animations, state machines, shaders, fonts, sounds and Variables assets are the eight types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). |
| **Font** | An uploaded typeface, worn by a text through the ordinary property `font`; no font means the editor's own. Uploaded and picked exactly like a texture, and stored the same way. See [Fonts](Fonts). | | **Font** | An uploaded typeface, worn by a text through the ordinary property `font`; no font means the editor's own. Uploaded and picked exactly like a texture, and stored the same way. See [Fonts](Fonts). |
| **Sound** | An uploaded audio file. Uploaded, picked and stored exactly like a typeface — but nothing *wears* it: it is neither on screen nor in the scene, and a graph plays it with `Play sound`. Its own rows say how loud it is and whether it loops; the node scales the one and can overrule the other either way. A **sound FX** asset is a graph of effects (gain, pitch, EQ, mix) a `Play sound` can send it through. See [Sounds](Sounds). An **instrument** plays sounds as notes, see [Instruments](Instruments), and a **pattern** is notes for one, drawn in a piano roll, see [Patterns](Patterns). | | **Sound** | An uploaded audio file. Uploaded, picked and stored exactly like a typeface — but nothing *wears* it: it is neither on screen nor in the scene, and a graph plays it with `Play sound`. Its own rows say how loud it is and whether it loops; the node scales the one and can overrule the other either way. Its tab shows the wave and cuts the sound to a stretch of its file, which is then what plays everywhere. A **sound FX** asset is a graph of effects (gain, pitch, EQ, mix) a `Play sound` can send it through. See [Sounds](Sounds). An **instrument** plays sounds as notes, see [Instruments](Instruments), and a **pattern** is notes for one, drawn in a piano roll, see [Patterns](Patterns). |
| **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state`, moves it along a named transition with `State transition` and branches on it with `State branch`. Its **Scope** says where the current state lives: **Local** gives every behavior instance one of its own, **Global** keeps a single one for the whole run. See [State machines](State-machines). | | **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state`, moves it along a named transition with `State transition` and branches on it with `State branch`. Its **Scope** says where the current state lives: **Local** gives every behavior instance one of its own, **Global** keeps a single one for the whole run. See [State machines](State-machines). |
| **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite, a text or a particle source wears it through `shader`; the numbers the graph declares are rows below that dropdown. A **shader function** is a part of such a graph kept on its own and used as a node in other shaders. See [Shaders](Shaders). | | **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite, a text or a particle source wears it through `shader`; the numbers the graph declares are rows below that dropdown. A **shader function** is a part of such a graph kept on its own and used as a node in other shaders. See [Shaders](Shaders). |
| **Particles** | A node graph that says what *one* particle is as a function of its age and of a random number drawn once for it — no forces, no simulation. Unlike the other graph assets it **is** placeable: the object made from it is a source, whose rows say how many go out and for how long they live. Because a particle is a formula, the same second always draws the same picture and the tab's slider can jump to any of them exactly. See [Particles](Particles). | | **Particles** | A node graph that says what *one* particle is as a function of its age and of a random number drawn once for it — no forces, no simulation. Unlike the other graph assets it **is** placeable: the object made from it is a source, whose rows say how many go out and for how long they live. Because a particle is a formula, the same second always draws the same picture and the tab's slider can jump to any of them exactly. See [Particles](Particles). |
@@ -234,7 +234,7 @@ group; folding the group the selected asset sits in lets go of the selection.
A click on a row only A click on a row only
selects (the inspector shows it, no tab moves); a **double-click** opens the asset as a document selects (the inspector shows it, no tab moves); a **double-click** opens the asset as a document
in a tab of its own (see **The tab row**) — a behavior, state machine, shader, particles, in a tab of its own (see **The tab row**) — a behavior, state machine, shader, particles,
texture, animation, tile or container — and on a scene switches to the Scene tab and makes it the sound, texture, animation, tile or container — and on a scene switches to the Scene tab and makes it the
active one. `Enter` does the same once the list has keyboard focus (`Tab`). active one. `Enter` does the same once the list has keyboard focus (`Tab`).
A sprite, camera, text or collider has no tab of its own, so a double-click on one does nothing. A sprite, camera, text or collider has no tab of its own, so a double-click on one does nothing.
Right-clicking a row offers *Set as active scene*, *Set as start scene* / *Clear start scene*, Right-clicking a row offers *Set as active scene*, *Set as start scene* / *Clear start scene*,
+1 -1
@@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun
# has and the web server's container has not (SKIP there) # has and the web server's container has not (SKIP there)
php tests/php/snapshots.test.php # the snapshot store (#231), the same way php tests/php/snapshots.test.php # the snapshot store (#231), the same way
php tests/php/releases.test.php # the release store (#266), the same way php tests/php/releases.test.php # the release store (#266), the same way
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1833 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1840 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result ./e2e.sh status # live progress, or the last run's result
+1 -1
@@ -21,7 +21,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
| [Vector art](Vector-Art) | The vector art asset, canvas and shapes, the Pen and Select tools, the shape list, how a drawing is worn | | [Vector art](Vector-Art) | The vector art asset, canvas and shapes, the Pen and Select tools, the shape list, how a drawing is worn |
| [Pixel art](Pixel-Art) | The pixel art asset and its palette, the tools, layers and frames, onion skin and the preview, resizing, how frames are worn and where the pixels are stored | | [Pixel art](Pixel-Art) | The pixel art asset and its palette, the tools, layers and frames, onion skin and the preview, resizing, how frames are worn and where the pixels are stored |
| [Fonts](Fonts) | Uploading a typeface, what a text wears, what the editor falls back to | | [Fonts](Fonts) | Uploading a typeface, what a text wears, what the editor falls back to |
| [Sounds](Sounds) | Uploading a sound, what the asset says, `Play sound` and `Stop sound`, sound FX (gain, pitch, EQ, mix, delay) and how long a voice rings on, what a pause and a stop do to a voice | | [Sounds](Sounds) | Uploading a sound, what the asset says, its tab and the cut, `Play sound` and `Stop sound`, sound FX (gain, pitch, EQ, mix, delay) and how long a voice rings on, what a pause and a stop do to a voice |
| [Instruments](Instruments) | The instrument asset: Notes, Sample, Parameters, Sound FX, Envelope and Output, triggers, zones and layers, the keys of the Instrument tab, `Play note` | | [Instruments](Instruments) | The instrument asset: Notes, Sample, Parameters, Sound FX, Envelope and Output, triggers, zones and layers, the keys of the Instrument tab, `Play note` |
| [Patterns](Patterns) | The pattern asset and its piano roll: drawing, moving and sizing notes, the grid, the ruler with start and loop, velocities, playing it in a loop | | [Patterns](Patterns) | The pattern asset and its piano roll: drawing, moving and sizing notes, the grid, the ruler with start and loop, velocities, playing it in a loop |
| [Songs](Songs) | The song asset: tracks with volume, pan, mute and solo, pattern and sound items laid out, moved, cut and split, playing it in a loop | | [Songs](Songs) | The song asset: tracks with volume, pan, mute and solo, pattern and sound items laid out, moved, cut and split, playing it in a loop |
+1 -1
@@ -19,7 +19,7 @@ A at 440 Hz, 127 the highest. A note is named with its octave after it and no da
| Node | What it does | | Node | What it does |
| --- | --- | | --- | --- |
| **Notes** | Where the notes come in. `any` triggers what it is connected to for every note from **From** to **To**. `note` is the number of the note played, `frequency` its pitch in Hz (`440 * 2^((note - 69) / 12)`), `velocity` how hard it is struck, 0 to 1. With **Single notes** there is one trigger output for every note in the range as well, and each can have a **name** of its own (*Name of C2* in the inspector): the output shows it, and so do the keys of the [piano roll](Patterns#the-pattern-tab). | | **Notes** | Where the notes come in. `any` triggers what it is connected to for every note from **From** to **To**. `note` is the number of the note played, `frequency` its pitch in Hz (`440 * 2^((note - 69) / 12)`), `velocity` how hard it is struck, 0 to 1. With **Single notes** there is one trigger output for every note in the range as well, and each can have a **name** of its own (*Name of C2* in the inspector): the output shows it, and so do the keys of the [piano roll](Patterns#the-pattern-tab). |
| **Sample** | Plays a sound asset when its `trigger` is on. With **Follow note** (on by default) it plays higher or lower by the distance from **Root**, the note it sounds as it is (C4 by default), and **keeps its length** — it is played by the same worklet as a Pitch (#317). Off, every note plays it as it is, as for a drum. **Play** says how it is played while the note is held and once it is let go (table below). **Loop start** and **Loop end** are in seconds, 0 at the end being the end of the sound. The volume of the sound asset counts. | | **Sample** | Plays a sound asset when its `trigger` is on. With **Follow note** (on by default) it plays higher or lower by the distance from **Root**, the note it sounds as it is (C4 by default), and **keeps its length** — it is played by the same worklet as a Pitch (#317). Off, every note plays it as it is, as for a drum. **Play** says how it is played while the note is held and once it is let go (table below). **Loop start** and **Loop end** are in seconds, 0 at the end being the end of the sound, counted in the stretch the sound is [cut to](Sounds#the-sound-tab). The volume of the sound asset counts. |
| **Parameters** | The instrument's own parameters, each a number output with a default — like the parameters of a sound FX's Input. One per instrument. | | **Parameters** | The instrument's own parameters, each a number output with a default — like the parameters of a sound FX's Input. One per instrument. |
| **Sound FX** | Sends the sound through a sound FX asset, as if it were the sound at the FX's Input. Every parameter of that FX is an input here; one left open is the FX's default. The node is titled after the FX, and a double click opens it. | | **Sound FX** | Sends the sound through a sound FX asset, as if it were the sound at the FX's Input. Every parameter of that FX is an input here; one left open is the FX's default. The node is titled after the FX, and a double click opens it. |
| **Envelope** | Shapes a note as it is played: **Attack**, **Decay** and **Release** in seconds, **Sustain** from 0 to 1. On the way of the sound it is its volume; its output `value` moves a number with the same curve, from **From** to **To**. See [Envelopes](#envelopes). | | **Envelope** | Shapes a note as it is played: **Attack**, **Decay** and **Release** in seconds, **Sustain** from 0 to 1. On the way of the sound it is its volume; its output `value` moves a number with the same curve, from **From** to **To**. See [Envelopes](#envelopes). |
+43 -2
@@ -22,15 +22,56 @@ short string in the project document, an undoable delete). A sound is **never re
way**: what the server stores is the file that was picked. The format is read from the file's own way**: what the server stores is the file that was picked. The format is read from the file's own
signature, so nothing a request claims can decide what is written. signature, so nothing a request claims can decide what is written.
**What the asset says.** Its inspector has two rows and a preview: **What the asset says.** Its inspector has these rows and a preview:
| Row | Meaning | | Row | Meaning |
| --- | --- | | --- | --- |
| **Volume** | How loud this sound is by itself, 0-1. | | **Volume** | How loud this sound is by itself, 0-1. |
| **Loop** | Whether it is music: it repeats until something stops it. | | **Loop** | Whether it is music: it repeats until something stops it. |
| **Start (s)**, **End (s)** | The stretch of the file the sound is, set in its [tab](#the-sound-tab). End 0 is the end of the file. |
| **Edge fade (ms)** | How long each cut edge inside the file fades in or out, 10 by default. |
The preview plays through the very same library a running game uses — same decode, same device — The preview plays through the very same library a running game uses — same decode, same device —
so what the inspector plays is what the graph will play. so what the inspector plays is what the graph will play, cut and all.
## The Sound tab
Opening a sound (double-click in the Assets panel, or Enter) brings up its tab (#343): the file as
one waveform under a ruler in seconds, and the **stretch of it the asset is** between two
handles. What lies outside is darker. **That stretch is the sample**: `Play sound`, an item in a
[song](Songs) and the Sample of an [instrument](Instruments) all play it and nothing else, and
everything that counts in seconds counts in it — `length`, the rounds of a song item and their
waveform, a Sample's loop points, a Pitch.
- **Drag a handle** to move that edge. **Drag anywhere else** to span a new stretch from where you
pressed. One drag is one undo step, the two edges together. An edge cannot pass the other.
- **Whole file** in the header puts both handles back on the ends of the file.
- **Edge fade (ms)** in the header (also in the inspector) is the ramp at each edge **inside**
the file: the stretch rises from silence over it and falls back to it, so a cut through a wave
does not click. It is counted into the stretch, so the stretch stays as long as it is. An edge
on the start or the end of the file is left as it is. A fade longer than half the stretch is
shortened to half. The ramps are drawn in the wave.
- **Play** (or Space) plays the stretch once, at the asset's volume, with a playhead. It ends
whatever pattern or song the music tabs were playing, as they end each other.
- The **wheel** zooms about the pointer, the **middle button** pans, as in the music tabs. Zoomed
in far enough, the samples themselves are drawn as a line.
- A stereo file shows **one** wave, the loudest of its channels; it plays in stereo.
- The header's readout says how long the stretch is, and of how long a file.
**The file is never changed.** The cut lives on the asset, so a stretch can be made longer again
at any time, and **two cuts of one file** are two assets (duplicate the asset, pick another
stretch) that share the file on the server.
**How it plays.** A cut sound is played by a name of its own, `<file>#<start>~<end>~<fade>`. The
library fetches and decodes the file once and makes the stretch from it the first time that name
plays: a copy of those samples with the ramps worked in. Length and waveform of a cut are read
from the file itself, without a copy. Copies are made again whenever they are needed, so the
library lets the oldest go once they hold more than 64 Mi samples together — a cut dragged about
in its tab leaves no pile of copies behind. A sound over the whole file is no cut at all and plays
the file as it always did; assets saved before the tab existed are such sounds.
**Song items** keep their place and their length when their sound is cut anew: they play from the
new start, and one longer than the new stretch plays it round and round.
## Playing it ## Playing it