Parallel test runs could flake: theme::LIGHT_THEME is process-wide, and
the edit_window harness tests ran whole frames without holding
lock_test_palette(), so a theme test could flip the palette between a
harness test painting rows and looking them up by colour (and a harness
frame could reset it to dark mid-theme-test).
Make the test palette lock re-entrant per thread and take it inside
SoundApp::new (test builds only), so every test that builds an app owns
the palette for the app's whole lifetime. Adds a regression test that
the lock is re-entrant on the owning thread and excludes others.
Fixes#323
Waveforms looked blocky and jagged next to Pro Tools': each column was a
point wide (two pixels on Retina screens), and the overview only knew
32-sample blocks, so zooming in showed stair steps and, at the sample
level, wide flat bars instead of the signal. Several View > Waveforms
options did nothing or drew something else.
- Peaks keeps one tree of blocks (min, max and sum of squares). Its
queries read the partial blocks at a range's edges from the samples,
so every answer is exact and still O(log n). `Peaks::spans` gives each
column the (min, max) of the line through the samples over its span;
neighbouring spans share their boundary, so the waveform stays
connected at any zoom.
- The Edit window draws a column per physical pixel, each over a
1-point window anchored to absolute sample positions (no shimmer while
scrolling), with anti-aliased ends, in one triangle strip where runs
of equal columns such as silence share vertices. At the sample level
(a sample per pixel or less) the waveform is a 1-point line through
the samples, without Rectified, Power or Outlines, as in Pro Tools.
- Outlines draws an edge around a lighter fill, as Pro Tools does; it
used to replace the fill with a line of dots along the tops.
- Power shows the RMS, which is how Pro Tools calculates it
(`Peaks::rms_spans`). Ours squashed the peak heights with a square
root, which drew loud material bigger than Peak view. Power draws
+/-RMS around the centre, or 0..RMS above the base line when
Rectified.
- Dark theme clips have a dark body tinted with the clip colour and a
light waveform in that colour, as in Pro Tools, instead of black
waveforms on a mid-tone body; a selected clip inverts to a bright body
with a dark waveform, as before. MIDI notes on unselected clips take
the waveform colour, with velocity as opacity. The Light theme is
unchanged.
- Blocks view draws clips as empty blocks with their names, and in
Playlists view the alternate playlists' lanes draw their clips like
the main lane, waveforms included, instead of empty boxes.
- Overlapped Crossfades draws the outgoing clip's waveform, translucent,
over the crossfade, so both clips' audio shows inside it. The outgoing
clip is found by binary search in the clip's own playlist. A crossfade
also shows both fade curves: the outgoing clip's fade-out used to be
hidden under the incoming clip.
Tests: the peaks unit tests check every range, span and RMS against the
samples; tests/waveform.rs checks the sample-level line, a one-sample
spike zoomed out, Blocks and playlist lanes, and Overlapped Crossfades.
In Pro Tools, clicking a send opens its Send window: the send's fader,
pan, Mute and Pre/Post. SoundCraft turned the send's level readout into
a text field instead, so there was no fader to grab.
- A click on a send's level in the Mix window opens the Send window:
the destination, Pre and Mute buttons, a pan knob, the fader (Alt-
click for 0 dB, Cmd/Ctrl for fine moves, down to −∞) and the level,
which takes a typed value. It stays open while you play.
- One Send window at a time, as with Pro Tools' Target lit: clicking
another send's level moves the window to that send, and clicking the
same send again closes it. The send shows an outline while its window
is open. Removing the send or its track closes the window.
- Escape closes it too, unless the Escape is for something on top: an
open menu, a level being typed, a dialog or the search. egui closes
menus and drops text focus on Escape before the window is drawn, so
that is read from the previous frame.
- Dragging the level and Alt-click on it still work in the strip, and
right-click still opens the options menu.
- The send's level and the window's fader have accessibility labels
("Send A level", "Send level").
Commands that already worked but had no key get the ones Pro Tools uses,
on macOS, Windows and Linux alike (Alt is Option on macOS; Cmd is Ctrl
elsewhere):
- Shift+= and Shift+- step the Grid value to the next larger or smaller
one, and Alt+Shift+= and Alt+Shift+- the Nudge value, as the Pro Tools
Reference Guide gives them. `view.grid` and `view.nudge` take `step`.
`GridValue::stepped` walks the note values, keeping a dot or triplet, and
steps seconds, frames and samples by tens. macOS sends Alt+Shift+= as =
(the key under ±), so that arm takes = as well as +. Both commands list
the keys as descriptive shortcuts, like Nudge's Plus/Minus, so they show
wherever commands list their shortcuts; the strings don't parse, so the
registry never dispatches them and `shortcuts::fixed` handles the keys.
- Commands Focus: , and . nudge back and forward, M and / nudge by the next
larger Nudge value (`edit.nudge` takes `next`), 0 copies the Timeline
selection to the Edit selection and O the other way, [ and ] play the
Edit and Timeline selections, and Shift+Z redoes. Shift+P and Shift+;
add the track above or below the selection, keeping its range (P and ;
already move it). They run Extend Edit Up/Down, which now skip tracks
the Edit window doesn't show (hidden, or in a closed folder); before,
they could add an invisible track. Shift+; can arrive as :, so both are
matched.
- Playlists: Alt+Shift+Up and Alt+Shift+Down copy the selection to the
target playlist and to a new one, Alt+Shift+T and Alt+Shift+N move it
there, Shift+Right shows the target playlist and Cmd+Shift+Right
designates the shown one. The MIDI editor's piano roll moved notes on any
arrow key, whatever the modifiers; it now leaves Alt, Cmd and Ctrl arrows,
and Shift+Left/Right, to these shortcuts.
- F12 records, as in Pro Tools. Cmd+Space is Spotlight on macOS.
- Ctrl+Tab goes back to the previous clip boundary off macOS, where Windows,
GNOME and KDE take Alt+Tab; Pro Tools for Windows uses Ctrl+Tab too.
Cmd+Alt+Tab now toggles Tab to Transient: the Tab arm took it as Alt+Tab
and tabbed back instead.
- Windows: Alt+7 Universe, Alt+6 Clip Effects, Alt+= MIDI Event List,
Shift+K MIDI Keyboard, Cmd+Alt+J Window Configuration List, Alt+' Task
Manager, Alt+I Workspace, Alt+J Workspaces to Front.
Tests drive the keys through `shortcuts::handle` for each OS
(tests/pro_tools_keys.rs), plus the piano roll with modifier arrows held.
The Edit window painted the bar and grid lines on the empty lane and then
the opaque clip bodies on top, so the grid vanished wherever an audio or
MIDI clip sat. Pro Tools draws grid lines over the clips.
- While grid lines are shown, the lines are drawn after the clips (audio,
MIDI, video and their overlays) and before automation and the selection.
- The lines are a translucent white (Dark) or black (Light), worked out
from the palette's line and lane colours, so a line on an empty lane looks
as before and waveforms and notes stay readable under it. Bar lines now
also stand out where a grid line lands on them; the opaque grid line used
to cover them.
- With grid lines hidden, the bar lines stay behind the clips as before.
Test: tests/grid_lines.rs renders the demo Edit window in both themes and
checks that the lines over the Kick clip are painted after it only while
grid lines are shown, and that each one looks like the theme's line colour
on an empty lane.
Unknown keys in `params` were silently dropped, so a typo like `gain_db`
reported `ok: true, set: 0` — indistinguishable from a successful no-op.
The command now fails and lists the unrecognized keys (and non-numeric
values), matching how `mix.insert` already validates its params. Preset
recall in the plugin menu drops stale keys before calling the command.
Fixes#266
The Tracks and Groups lists met the Edit and Mix windows at a faint
one-point line. A 10-point grey gutter with dark edges now runs down
the panel's right side and carries each list's scroll thumb when the
list is longer than its space: drag it, or click above or below it to
page. egui's own scroll bars are off there.
Finding a plugin for an insert took three submenus (format, vendor,
name). The send and I/O bus menus had a search field, but it had to be
clicked before typing, and it kept the last search.
- The send and I/O bus menus take typing as soon as they open, as Pro
Tools' path selectors do. The search starts empty each time; Up/Down
move through the matches, Enter picks one, Escape closes the menu.
- Insert and instrument menus start with the same search field. Typing
lists the plugins whose name or vendor matches: built-in ones first,
then Audio Units and whichever of CLAP and VST3 have been scanned,
with a Scan button for each of the others. Typing never starts a scan
(`cached()` in the CLAP and VST3 hosts returns the last scan without
scanning); opening a format's submenu still scans it.
- A click inside the insert and instrument menus no longer closes them,
so the field can be clicked; picking still does, and errors from
Bypass and "no insert" now reach the status line. The send bus menu
is no longer 600 points wide.
- Insert, send, instrument and I/O slots in the Mix window get
accessibility labels, which the tests use.
As in Pro Tools, a track's colour now runs through its whole row
instead of only the strip at its left edge: the header (and any
header columns) takes a muted shade of it and the lane a faint one,
so clips in the full colour still stand out.
- Window > Color Palette has a "Track tint in the Edit window" slider;
at zero tracks look as before. The setting is saved with the UI
prefs.
- A selected track's header gets an accent outline, since its blue
background would otherwise be lost under the tint.
The Light palette was near-white with a blue tint. Pro Tools' lighter
look is silver, so panels, strips, lanes, rulers and keys move to
neutral greys between 184 and 236, and meters sit in dark wells as they
do in Pro Tools. Borders and text are darkened to keep their contrast,
borders a little more so that tracks, Mix sections and controls don't
run into each other. Dark is unchanged.
Part of #138.
The shared controls were flat rectangles and discs. They now follow Pro
Tools' shapes and colours, drawn in code with vertex-coloured meshes (no
assets), and keep a modern, soft shading:
- Faders: a silver cap, taller than wide, with a dark centre line and
faint grip ridges, in a slot that leaves room for the cap at both
ends.
- Pan knobs: a softly shaded disc with a light rim and a white pointer
beside the green value arc.
- Keys (S/M/I, record arm, toolbar and transport buttons, edit modes,
Grid, Count Off, selector boxes, filled insert, send and instrument
slots): lit from above and sunk while pressed. Record arm shows an
unlit salmon light when off. The edit-mode keys take their width from
the widest label, so SHUFFLE and REL GRID fit.
- Meters: LED segments with thin gaps, the unlit ones faintly visible.
- Mix sections and the pan and volume readouts sit in outlined wells.
Strips get a soft gradient and a lit left edge, track headers a lit
top edge, and a line separates the Edit window's headers from the
lanes.
- Filled insert slots are grey, as in Pro Tools. They no longer
brighten on hover: on the grey, that dropped their label below 4.5:1
in Dark. mix_instrument_contrast now finds the key's face and rim,
and checks that hovering keeps the face.
Part of #138.
Pro Tools lets you work with a track's plugins from the Edit window.
Here its Inserts and Sends columns (View > Edit Window Views) only
printed the names.
- The Inserts A-E/F-J and Sends A-E/F-J columns now use the Mix
window's slots, so they work as they do there: click a plugin to open
its window, click an empty slot to insert one, right-click to bypass,
remove or replace it, and route or set the level of a send.
- A new Instruments column shows an instrument track's instrument.
- The columns run in Pro Tools' order: Instruments, Inserts, Sends, I/O,
Comments. Each gets a title, boxed in its own row under the rulers,
and the ruler names now end at the track headers.
- A Views button at the top left of the rulers shows and hides the
columns, like Pro Tools' Edit Window View selector. Its menu stays
open until a click elsewhere, so several columns take one trip.
- In both windows, insert slots show a power light, green while the
plugin runs and dark while it's bypassed, with the name left-aligned
and dimmed when bypassed. A plugin whose code is missing shows its id
instead of looking like an empty slot.
- Cmd+click (Ctrl+click on Windows and Linux) bypasses an insert, as in
Pro Tools.
- Each column draws in a Ui of its own, so its widget ids can't clash
with the track header's.
Stereo strips draw two smaller pan knobs (32pt or less instead of 34pt),
and the rows under them were placed from the knob size. So the pan
readouts, the I/record and S/M keys and everything below them sat 2pt
higher than on mono strips. The rows now use the mono positions on every
strip.
The fader's dB numbers and ticks were painted in fixed greys (150 and
110), which are faint on the Light theme's strips: the numbers had a
2.5:1 contrast. They now use the theme's text_dim, at 70% for the ticks.
Light reads at about 5:1 and Dark looks the same as before.
Each tick now starts 2pt after its number instead of running through the
digits, and is left out when the strip is too narrow for it.
Opening a session decoded every source entry on its own, so a 400 KB
session listing one 11 MB WAV 2,000 times took 16 s and 22 GB, and
20,000 entries never finished. Import Session Data and clip group files
did the same.
All three now decode a file the first time it is named, by its
canonical path, and share the decoded audio with later sources that name
it; the pool's audio is never changed in place, so sharing it is safe.
Saving keeps sources that play one file on that file, and an autosave or
a copy writes shared audio once instead of once per source. That session
now opens in 0.3 s at 110 MB, and a normal session bounces to the same
bytes as before.
Commands clamp every position they take to ±2^40 samples so the
arithmetic after them can't overflow, but session and clip group files
skipped that. A clip, MIDI note, marker, automation point or selection
near i64::MAX made Duplicate, Insert Silence, Insert Time, Heal, Trim to
File Boundaries, Quantize to Grid, Separate on Grid, Thin Automation,
Copy, Move Selection, Extract Chords and Track Commit overflow: a panic
in debug builds, wrapped positions in release.
MAX_POSITION moves to soundcraft-time so the model can use it. Loading
a session clamps clip starts, lengths, offsets, sync points, gain
envelopes and MIDI notes, automation points, markers, key changes, the
selection, playhead, pre/post-roll, scroll and timecode start. A clip's
repairs are now Clip::sanitize, which clip group import uses too, and
Session::sanitize visits each collection once. The Edit window adds a
clip's offset to its file's time stamp without overflowing.
A MIDI file of 317 bytes, with one note whose end lies 41 maximal
deltas after its start, imported as an 8.5-year clip. Everything that
then rendered or walked that clip (Track Bounce, Commit, Separate at
Transients, Extract Chords) worked for seconds to forever and used up
to 3 GB.
Importing now refuses a MIDI track that ends more than 24 hours after
the import point; its end is computed with saturation, so a huge one
can't wrap around and slip past the check. A file with more than 10,000
meter changes is refused too: placing each one walks those before it,
so 200,000 of them (1.8 MB) took 57 s and fit easily within a day.
Extract Chords walked the selection bar by bar, looking at every note
of the selected tracks in each bar, until it had 2000 chords or reached
the end. After Select All over a session file with a clip at the far
end of the timeline (2^50 samples) it ran for ever over empty bars, and
a 317-byte MIDI file with one note held for 8.5 years kept it busy for
millions of bars that never form a chord.
It now takes the notes in time order, keeps only those sounding in the
bar it looks at, jumps over bars where nothing sounds, and stops after
100,000 bars that hold notes (27 hours at 120 BPM) or 10 million
sounding notes. The chords it finds are the same.
A MIDI file whose track name was a million characters long imported
fine, and the next frame crashed the app: drawing that name needed a
320 MB vertex buffer, past wgpu's 256 MB limit, and the panic comes
from painting, outside anything that catches it. Saved, the session
crashed again on every open. A 4 MB status line in ui.json did the
same at every launch.
Names read from files are now cut to 256 characters and comments to
4096: everything Session::sanitize repairs on load (session, track,
playlist, clip, marker, bus, output, group, source, video and key
names), MIDI track names on import and clip names from clip group
files. The status bar draws at most 1024 characters.
Finding a free name tried "Name 1", "Name 2" and so on, rescanning
every track (or every source path) for each try, so each new name cost
time proportional to the square of the session and a bulk import the
cube. Importing a 119 KB MIDI file with 4,095 tracks all named "Piano"
took 28 seconds; a clip group file with 8,000 empty lanes or 8,000
audio files of one name ran for over a minute, and 100,000 lanes never
finished. Import Session Data from a session with 2,000 same-named
tracks took 7 seconds.
unique_track_name and the new audio file name now collect the numbers
already in use once and take the first free one, which gives the same
names. A clip group file also counts each empty lane and each audio
file against its limit of 10,000. The same imports now take 1 second
(MIDI), 2 seconds (8,000 lanes) and a third of a second (Import Session
Data); 100,000 lanes are refused.
An 80 KB WAV with a sample rate of 1 Hz froze the app on import: the
size limit was checked before conversion to the session rate, which
then made it 48,000 times longer, past 2 GiB of memory and more than a
minute of work (4.5 GiB and 75 s in an earlier measurement). Opening a
session, Import Session Data, the sampler and clip group files
converted without any limit.
io::at_rate checks the size the audio will have at the session's rate
before converting it. Imports keep their limit (2^30 samples, about
4 GiB) and their message; the sampler keeps its own limit; a session's
source that is too large is reported like other unreadable media.
A session whose audio file path pointed at /dev/zero filled 18 GB of
memory while opening, and one pointing at a named pipe waited forever.
The same went for a path given to the import commands, the sampler,
Import Session Data, clip group and track preset files, and the session
file itself, all of which read whatever the path named to its end.
Those reads now go through io::read_file / read_text, which refuse
anything but a regular file. A session's missing audio is reported as
missing, as before; an import fails with "not a regular file".
A session file with a sample rate of 0 or a tempo of 4e9 BPM opened
fine, but Strip Silence on the first and Extract Chords on the second
never finished (one also climbed past 1 GiB). Session::sanitize, which
repairs everything else read from a file, skipped both fields.
On load, a sample rate outside what sessions accept becomes 48 kHz, and
the tempo map keeps only the tempos and meters that set_tempo and
set_meter accept, in tick order, with one of each at tick 0.
WAV files that aren't plain PCM (A-law, µ-law, ADPCM) go to symphonia,
which reads every entry of an INFO list into a buffer of its declared
size, bounded only by the list's and file's declared lengths. A file
claiming a 4 GB RIFF and a 2 GB title asked for 2 GB before failing.
macOS gives that lazily; a 32-bit build or a system that can't commit
it aborts.
Before symphonia reads such a WAV, its INFO entries are checked against
the bytes the file holds, and one that claims more is refused as
malformed. Chunks are walked where symphonia reads them, not where their
lengths say: A-law, µ-law and ADPCM `fmt` chunks are read by their own
extra size, extensible ones by 40 bytes, and a list is padded twice.
An MP3 whose ID3v2 tag had a frame claiming 0x7FFFFFFF bytes made
symphonia's probe allocate 2 GB to read that frame (4 GB at
0xFFFFFFFF). It reads every tag it finds ahead of the audio, though
SoundCraft never uses the result. macOS gives such memory lazily; a
32-bit build or a system that can't commit it aborts.
The probe loop now skips an ID3v2 tag by the size in its header and
carries on looking for the audio, so a tag can't cost more than the
bytes it occupies, and a damaged tag no longer stops an MP3 from
opening.
symphonia reads a FLAC file's picture and Vorbis comment blocks field
by field, allocating each field's declared length before it finds out
the block is shorter: a picture claiming 0xFFFFFFFF bytes of image
data asked for 4 GB. macOS gives that lazily, but a 32-bit build or a
system without that much memory to commit aborts.
When the probe finds a FLAC stream, the lengths of its picture fields
and comments are now checked against their metadata block first, and a
file whose fields don't fit is refused as malformed.
A 59 KB ALAC file in an M4A made the app use 25 GB of memory before it
gave up: symphonia's ALAC decoder sizes its buffers from the frame
length in the file's codec settings, and nothing bounds it. The PCM and
ADPCM decoders likewise allocate the frames per packet the container
declares, which an MP4 version-2 LPCM entry takes from the file.
Before a decoder is made, the frames a packet may hold, and an ALAC
frame length, must be at most 2^20 (about 22 seconds at 48 kHz;
Apple's ALAC encoder uses 4096), or the file is refused as malformed.
An M4A whose handler atom claimed a 64-bit size of 2^60 bytes ended the
process on import: symphonia reads `hdlr` and the `data`, `name` and
`mean` atoms of iTunes metadata into buffers of their declared size, and
an allocation that fails aborts. Its sample tables reserve room for
whatever entry count they claim (up to 68 GB from a 19 KB file), and
every sample is read into a buffer of the size `stsz` gives it. macOS
hands out most of that lazily; on Linux, Windows and the 32-bit Windows
build such a request fails and aborts.
When the probe finds an MP4 file, the atoms symphonia descends into are
now walked first, the way symphonia reads them: a header that starts
inside its container is read whole, and the walk ends where symphonia
stops, after an atom that runs past its container. A file is refused as
malformed when one of those atoms, a sample table or a sample claims
more bytes than the file holds. Atoms symphonia skips, and an `mdat` cut
short, are left alone, so truncated recordings still read.
Importing a 72-byte CAF file ended the process. symphonia allocates
whatever a CAF magic cookie, packet table or packet claims before it
reads it, and when that allocation fails Rust aborts, which no
catch_unwind can stop. A cookie claiming 54 PB, a table of 2^50
packets and a single packet of 2^50 bytes each did it, on import and
in `soundcraft-cli info`.
symph::open now runs symphonia's probe loop itself, and when the
container it finds is a CAF stream, it first walks the chunks the way
symphonia reads them. A file whose cookie, packet table or packets
claim more bytes than it holds is refused as malformed. So is a packet
table symphonia would refuse only after reserving 32 bytes per packet:
one that runs past the file, or one listing packets of a constant bit
rate stream. The probe's hint was never read by symphonia 0.5, so it is
no longer passed.
Fixes#316: in a new session, clicking Record did nothing visible. With
no record-enabled track there is nothing to record, and the reason went
only to the end of the dim status line at the bottom of the Edit
window. In a new session it also pointed at "the red button in its
header", which no track existed to show.
- Record with nothing to record on says what to do: add a track with
Track › New… when the session has none, else click R on the track
to record.
- The message shows for 4 seconds below the toolbar's Record button
(under the whole transport group, so it covers none of its buttons)
and under the Transport window's Record button, as well as in the
status line. It goes early once a track is record-enabled.
- Nothing is armed or created on the user's behalf, and the transport
stays stopped.
When a third-party plugin was removed and re-added (same id, same channel
count) and both session updates reached the audio thread in one drain, sync
saw an unchanged slot, kept the old instance and retired the new one as an
unclaimed leftover. The host-side editor belonged to the new, retired
instance, so reopening it failed with "the plugin is gone", and the old
instance kept running with no editor.
The host only hands over an instance when it no longer tracks the one in
the slot, so an instance waiting for a slot now always replaces it (inserts
and instruments). Debug logs record instance creation and release on the
UI thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
write_automation merged a pass's points under one key, but the fader
driving the pass merged under its own, so each frame of a ride started
two new undo steps: one second of riding made 120, and in two seconds
everything before the pass was gone from the 200-step history. Stop's
last point was a step of its own as well, because every command,
transport.stop included, ended the gesture.
Engine::hold_merge makes every merged gesture take the held key until
it is released. While playing with a track in a write mode,
write_automation holds the pass's key, so the pass and whatever is
dragged during it are one undo step. A command now ends a gesture only
when it changes the document, so Stop no longer splits the pass it
ends.
Strip Silence rendered the whole selection at once, and a render stops
at 2^29 samples over all channels: 93 minutes of stereo at 48 kHz, 46
at 96 kHz. Everything past that read as silence, so the clips there
were deleted. A 105-minute selection on a stereo track lost its clip at
100 minutes.
It now renders at most 2^24 samples at a time, only where unmuted audio
clips play, into a streaming version of the silence detector
(`SilenceScan`, which `non_silent_ranges` now runs on). A window takes
at most 64 MB instead of up to 2 GiB, and more than a day of clips is
refused rather than scanned for minutes. `render_clips` now skips clips
outside its range before looking up their clip effects, so a track of
5,000 clips strips as fast as before. Results are unchanged on the demo
sessions and on 10 minutes of tone bursts that cross window edges.
From #186 and #305: right-clicking a track's name did nothing. In the
Edit window the track menu (Rename, Duplicate, Make Inactive, Hide,
Delete, height, colour…) was only on the header's background, and the
name plate drawn over it took the click. The Mix window had no track
menu at all. Pro Tools opens it from the name in both windows.
Both name plates now open the same track menu on a right-click. Clicks,
double-click to rename and dragging to reorder are unchanged.
From #186 ("Adjusting send levels can get stuck"). The send readout in
the Mix window had three problems:
- A drag stopped at −60 dB, so a send couldn't be dragged off, and a
new send (−∞) jumped to −60 on the first drag. Drags now move along
the fader's taper, like the level bar under the send name: smoothly
down to −∞ and back up, at about the old 0.15 dB a point near 0 dB.
- Its tooltip promised "double-click for 0 dB", but the first click
already turned the readout into a text field, so that never ran.
Alt-click now resets it to 0 dB, as on the fader. The volume and pan
readouts reset on Alt-click too (0 dB, centre), since all three share
the readout, and Alt-click no longer opens their text field.
- The text field stayed open when it lost focus while hidden (switching
windows with it open, for longer than a frame), and a later Enter
anywhere committed its stale text. Now it gives the readout back, and
Enter only commits while the field has focus.
Applying new audio device settings replaced the player while plugin
editor windows were open: the old instances went away with it (possibly
on the audio thread, where windows cannot be closed) and their editor
windows stayed on screen orphaned. Player::close_editors closes them on
the main thread first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From #307: adding a track took the Track menu or Shift+Cmd+N
(Shift+Ctrl+N on Windows and Linux). Nothing on screen offered it, not
even in an empty session.
- The TRACKS header in the Tracks list has a + button that opens New
Tracks. The list is in both the Edit and the Mix window.
- A double-click in the empty space below the last track in the Edit
window (header column or timeline) opens New Tracks, as in Pro Tools.
A single click there still clears the selection.
- A double-click right of the last strip in the Mix window does the
same. With no tracks, that is the whole window.
Window > Mix in Separate Window (window.mix_separate, kept with the UI
preferences) shows the Mix window in its own native window through an
egui immediate viewport, so Edit and Mix can be seen at once, e.g. one
per monitor. The main window then keeps Edit; Window > Mix and
Cmd/Ctrl+= bring the Mix window to the front; closing it puts Mix back
into the main window. Backends without multiple windows show it as a
floating egui window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#220: Render in the Signal Generator window did nothing on a time
selection over an empty part of a track. AudioSuite only processed the
clips under the selection, so with none it stopped with "select audio
clips first", and that went to the status line only.
- A generator (soundcraft_dsp::is_generator, today only the Signal
Generator) ignores its input, so it no longer needs clips. With a
time selection it renders a new clip over exactly the selection on
each selected audio track, over whatever was there, as Pro Tools'
Signal Generator does. Before, it replaced whole clips under the
selection and left the empty parts empty.
- Other tracks in the selection are skipped. With no audio track in
it, or no time selection and no clips, it reports why.
- Explicit `clips` still process those clips. Effects behave as before.
- The AudioSuite window's hint says what Render will do for a
generator.
One offline render holds MAX_RENDER_SAMPLES samples, about 93 minutes
of stereo at 48 kHz, and the offline time stretch, pitch shift and
resample stop at MAX_OFFLINE_SAMPLES frames. Longer audio was cut short
without a word: Render Clip Gain and Clip Effects Render left the end
of the clip silent, every AudioSuite process shortened the clip, the
track Commit made fell silent at the limit, and Export Clips wrote
shortened files. Above 74 kHz an hour no longer fits, so TCE, Elastic
Properties and Remove Pitch Shift padded the cut end with silence.
Each now checks the length before it renders and refuses with the
limit in seconds, as Consolidate does in #212. AudioSuite counts what
Time Stretch, Pitch Shift and Varispeed make, so a fourfold stretch is
refused at a quarter of the limit. Export Clips checks every clip
before it writes a file.