The shelf corner (~1682 Hz) sits at or above Nyquist for rates under
~3.4 kHz. The bilinear prewarp tan() then wraps negative (unstable
poles, +750 LUFS readings, -144 integrated) or aliases. Hold the
design frequency below Nyquist (0.49 * sr, as Coeffs::design does).
Coefficients at 8 kHz and above are unchanged.
Fixes#240
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging a track's bottom edge showed nothing until release; then the
track jumped to the nearest height preset. Pro Tools' track height
modifiers and keys were missing too.
Dragging:
- The edge follows the pointer as in Pro Tools: the track steps through
Micro…Extreme while the pointer moves, and the tracks below move with
it. The whole drag is one undo step (execute_merged, ended on release
like fader moves).
- Cmd (Ctrl on Windows and Linux) drags to any height from Micro to
Extreme instead of snapping. The model gains TrackHeight::Custom
(points), and track.height takes a number of points as well as a
preset name.
- Alt resizes every track the Edit window shows; Alt+Shift resizes the
selected tracks too. The track menu's Track Height items follow the
same rule.
- The drag is measured from where it started, not from the edge's
current place: when the tracks above the dragged one resize they move
its edge, and edge-relative math flipped between two heights on every
frame. This also fixes rows showing playlist lanes, which a plain
click on the edge used to make taller.
Keys:
- New commands track.height_up and track.height_down move each track
one preset taller or shorter; a track already at Extreme or Micro is
left alone, with no undo step. Control+Up/Down runs them on macOS, as
in Pro Tools. Windows and Linux use Ctrl+Up/Down, because the Start
key Pro Tools uses there isn't available. Alt steps every shown track,
Alt+Shift the selected ones.
- Ctrl+Up/Down used to run Extend Edit Up/Down, keys Pro Tools doesn't
use for them. Those commands now have Pro Tools' Control+Shift+P and
Control+Shift+; on macOS (macOS sends the latter as Control+:).
Windows and Linux reach them from Edit > Selection.
- Off macOS, menus no longer show Mac Control chords that can't be
pressed there (Time Lock's Ctrl+Alt+L, Toggle Recent Playlist's
Ctrl+Alt+Shift+Left, the new Extend Edit keys); the arrow chords that
fixed() maps to Ctrl keep their labels.
Track headers show each row of controls only once it fits: the keys
from 46 points and the view and automation selectors from 86, where
each row ends. The old 30 and 60 point cutoffs suited the presets only
and cut controls off at heights in between. Every preset draws exactly
as before.
Tests: the model's custom heights, steps and JSON; the engine's point
heights and steps, with no undo step when nothing changes; in the UI,
the live drag and its single undo step, Cmd, Alt and Alt+Shift drags
that hold steady while the pointer does, the height keys on macOS,
Windows and Linux, and the Extend Edit keys on macOS.
No change in behaviour or look: the Edit and Mix windows render
pixel-identical in both themes.
- The Mix strip and the Edit window's I/O column share input_name
(In 1 for a recording track with no input) and route_popup, which
keeps the route menu open while its bus search is typed in.
- The Mix slots and the I/O column's output key share widgets::groove;
the Edit header meter and widgets::meter share level_pos.
- The column list is read once per frame and passed to each track row;
header_width takes it.
- The record, input, solo and mute keys are a table, the slot columns
one loop, and send marks a char instead of a String.
- Inserts look their plugin up once; the unused triangle_right icon is
gone.
In Pro Tools a send slot's name picks its destination, and only the
small fader icon at its right opens the send's level. Here the level
control covered the slot's right 42 points, more than half of an Edit
window cell, while the icon is 5 points wide. It now covers the icon and
3 points either side.
The icon does what the whole level area did: a click opens the Send
window, Alt-click sets 0 dB, right-click opens the send's options and a
drag sets the level. A short track's square has no icon, so all of it
stays the level control.
Measured from the user's Pro Tools screenshots:
- Track headers are 136 points wide (were 236). A 14-point colour strip
holds an options button (a folder's open/close) and, at the bottom, a
key that shows or hides volume automation. The name is bold beside a
playlist menu and highlighted when the track is selected (the blue
outline that marked a selected tinted header is gone). Then come
16-point record, input, solo and mute keys (record stays dark red when
off, and a mute implied by another track's solo shows dark orange);
the track view; freeze, voice (dyn, or off to make the track
inactive) and automation mode; timebase and the Elastic Audio
algorithm (an instrument track's instrument); and a slim meter down
the right edge. Rows drop away on shorter tracks.
- The vol/pan and inserts summaries and the AUX/INST labels are gone:
the I/O and insert columns show them.
- The I/O column has input and output selectors, a fader key that shows
the track in the Mix window, and a black vol/pan readout.
- Insert, send and instrument columns are 75 points wide on a neutral
well, with 15-point cells every 18 points. An empty insert shows a
dot, an empty send its letter. Tracks too short for three cells show
a row of squares, with arrows for sends.
- A used slot is a rounded, raised grey button: its dot or letter, a
groove, the name centred in 9.5-point bold, and for sends a small
level box (the level in dB is in the tooltip; the box still opens the
Send window or drags the level). A bypassed insert turns blue. The Mix
window's slots share this drawing; filled ones don't brighten on
hover, which would cost the name its contrast.
- New slot_well, slot_empty, slot_face, slot_bypassed and slot_mark
colours replace insert_on, insert_bypass, send_on and slot_border.
mix_instrument_contrast now reads the slot's painted gradient, and
finds the empty instrument slot by its cell, which shows a dot
rather than "no instrument".
- The header's record, input, solo and mute keys carry accessibility
labels.
Part of #138.
The edit modes only partly worked. Shuffle changed Cut, Clear and Paste
but not moves or trims. Spot froze drags and had no dialog. Grid snapped
relative to whichever clip was under the pointer, and Relative Grid
acted like Absolute. Behaviour now follows the Edit Modes chapter of the
Pro Tools Reference Guide.
Shuffle
- Grabber drags lift the clips out (later clips close the gap) and land
them at the nearest clip boundary, pushing later clips right; an empty
track takes them at the session start. The drag outline shows where
each track's clips will land.
- Trims move the later clips (a trimmed start stays put and the material
slides under it). Duplicate, Duplicate and Extend, Repeat and Clip List
drops make room, and Paste over a selection replaces it.
- Moves lift each clip by id and close the hole it leaves, so crossfade
partners and unselected clips keep their audio, and a crossfaded pair
moved together stays a crossfade. Ripples never push clips past the
session start, never split a crossfade (refused with a reason), and
drops never land inside a clip or crossfade.
- A time- or edit-locked clip and everything after it stay put when
Shuffle closes or opens gaps. Edits that would push into one are
refused ("not enough room before a locked clip"), and trims stop short
of it. Insert Time and Cut Time are timeline operations and still move
everything.
- With Automation Follows Edit, automation moves with every ripple, up to
a locked clip, and travels with moved clips; with it off, Shuffle edits
leave automation alone. Shuffle Clear used to move the clips but leave
their automation behind (#232).
- Shuffle Lock: Cmd-click (Ctrl-click on Windows and Linux) the Shuffle
button while in another mode. While it's on, the button shows a padlock
and every way into Shuffle (keys, button, commands) is refused.
- A drag works out every lift and landing point once per target track
(`lift_plan`, `shuffle_slots`, and a `DragPlan` kept for the drag). In a
release build, dragging 100 clips on a track of 20,000 took 29 ms a
frame and a 135 ms drop; now it's under a microsecond a frame and a 5 ms
drop.
Slip, Spot and Grid
- Slip: trims stop at the neighbouring clips instead of overlapping them.
- Spot: a Grabber click or drag, a Trim click or drag and a Clip List drop
open a Spot dialog: Start, Sync Point and End in any time scale, the
Duration, and the clip's Original Time Stamp. Trim clicks pick the
nearer edge.
- Grid: a drag snaps the dragged clip's start (its sync point, when it
has one inside the clip) to the grid. Relative Grid moves by whole grid
steps and keeps the clip's offset from the grid; a cut or copy pasted
in Relative Grid lands as far after a grid line as the copied range
started after one. Holding Cmd (Ctrl on Windows and Linux) while
dragging or dropping from the Clip List swaps Grid and Slip. Grid lines
no longer fall a step short at tempos where a line isn't a whole number
of samples.
- Choosing Grid from another mode picks whichever of Absolute and
Relative Grid was used last; F4 in Grid toggles them, like clicking the
Grid button again. Options > Edit/Tool Mode Keyboard Lock, which did
nothing, now stops F4 and Alt+4 from switching them (toolbar clicks
still do).
- Snap to Grid in Shuffle, Slip and Spot: Shift+F4, Shift-click on the
Grid button or on another mode's button, or hold F4 with F1-F3. The
Edit cursor and selections snap to the grid while clip moves and trims
follow the main mode; the Grid button is outlined while it's on.
- Option+1-4 (Alt+1-4 on Windows and Linux) work like F1-F4, and the `
key cycles Shuffle, Slip, Spot and Grid, skipping a locked Shuffle.
- Insert Silence pushes later material only in Shuffle; in the other
modes it clears the selection, as in Pro Tools.
Also
- Dragging clips from several tracks keeps each track's distance from the
grabbed clip's track. Drops past the last track, or onto a track that
can't play the clips (audio onto MIDI, an aux or the master), are
refused; clips moved onto an aux used to vanish into its unused
playlist.
- Undo and Redo keep the Edit mode, Shuffle Lock, Snap to Grid and the
Grid kind. Start trims keep a clip's sync point on its audio, or drop
it. Imported BWF time stamps are converted from the file's rate (read
from its header) to the session's, and the Original Time Stamp no
longer adds the session start twice. Typed positions that overflow are
ignored instead of panicking.
Commands: `edit.move_clips` follows the mode, and its `track` moves the
first clip's track there and keeps the others' distances. `edit.trim_clip`,
`edit.snap_to_grid` (Shift+F4) and `edit.shuffle_lock` are new, and
`edit.mode` takes `snap_to_grid`. Mode buttons expose their names to
accessibility tools.
Tests: crates/{engine,ui-egui}/tests/edit_modes.rs and the op tests in
edit.rs; the random-edits property test also toggles clip locks.
midi.new_clip had no UI path, so an empty instrument/MIDI track could
never get a MIDI clip (#282). The menu bar is built from the catalog,
so the entry is added to menus::tree() and placed first in Clip.
The new test checks every mutating command is reachable from the GUI
(built menu tree, shortcut, or UI reference), with today's gaps listed.
Closes#282
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.