Fixes#282: on an empty MIDI or instrument track there was no UI way to
get a MIDI clip, so notes couldn't be entered by hand. `midi.new_clip`
existed but had no menu item, key or tool.
- Consolidate now works on MIDI and instrument tracks. The MIDI in the
selection becomes one clip covering the whole selection, gaps
included. It keeps the sound: muted clips stay silent, and notes are
cut where their clip ended. An empty selection gives an empty clip,
as in Pro Tools. Audio tracks are unchanged.
- The Pencil draws a MIDI clip on an empty part of a MIDI or instrument
track: a click makes one bar, a drag covers the bars it crosses, with
an outline while dragging. Clips start and end on barlines, the rule
Pro Tools uses for MIDI it creates. A clip stops at its neighbour
rather than overwriting it, and clicks on existing clips add nothing.
The new clip is selected, so the MIDI Editor shows it.
- With an empty MIDI or instrument track selected, the MIDI Editor says
so, and a click adds a one-bar clip at the insertion point (or a clip
over the selection).
convert ignored a --rate that did not parse and mapped any unknown
--bit-depth to 24-bit, then reported success. Both options are now
validated before the input is read; an invalid value prints which
option was wrong and exits non-zero without writing output. Omitted
options keep their defaults.
Timecode::to_frames multiplied caller-supplied hours in i64 without a
check, so "9223372036854775807:00:00:00" panicked under overflow checks
and wrapped without them. The count is now computed in i128 and clamped
to i64, and samples_at_frame clamps instead of returning 0 on overflow,
so huge labels saturate toward the right end of the timeline.
bar_start_tick subtracted 1 from the bar before its saturating multiply,
so the minimum i64 bar panicked under overflow checks. Parsing
"-9223372036854775808|01|000" as Bars|Beats now returns a saturated
negative position instead.
Imported MIDI keeps its controller events: sustain pedal and other CCs,
pitch bend, channel and poly pressure, program changes. Playback sent
instruments only note-ons and note-offs, so a piano part lost its pedal
and bends went flat.
- Plugin::midi takes any other MIDI 1.0 channel message at a sample
offset. The mix engine sends each clip's controllers at their sample,
after the Real-Time Properties delay (and transpose, for poly
pressure), on channel 1 like the notes.
- After a seek, a stop or a loop wrap, an instrument first gets Reset All
Controllers (CC 121), then the last value each controller had on its
track before the playhead. A pedal held before the start point is
still down.
- CLAP: raw MIDI events when the note port speaks the MIDI dialect.
- VST3: CCs, aftertouch and pitch bend become parameter changes at their
offset, through the plugin's IMidiMapping. It is asked once in prepare,
on the main thread, and there is one point per parameter per block.
Poly pressure is a PolyPressureEvent. Program changes have no VST3
mapping and are dropped.
- AU: MusicDeviceMIDIEvent for instruments and music effects.
- The built-in synth follows the sustain pedal, pitch bend (2 semitones
each way), Reset All Controllers and All Notes Off.
- The sampler ignores the pedal, pitch bend and Reset All Controllers
for now.
The AU check of MIDI volume lives inside the existing DLS test. Creating
two DLSMusicDevice instances at once on two threads crashes inside the
unit, with or without this change. Two instances created one after
another render in parallel fine.
Hosted plugins never learned the session's tempo or where the song was:
- CLAP got a null transport;
- VST3 got a fixed 120 BPM 4/4 context that never played and counted
processed samples as the song position;
- Audio Units had no host callbacks.
Tempo-synced delays, LFOs and arpeggiators ran at 120 BPM and ignored
the transport.
Before each block the mix engine now hands every insert, instrument and
master-fader plugin a soundcraft_dsp::Transport (Plugin::set_transport):
play and record state, tempo and meter at the block start, and the
timeline position in samples, seconds and quarter notes, with the
current bar. Built-in plugins ignore it.
- CLAP: a clap_event_transport (fixed-point beats and seconds, bar start
and number, time signature, play and record flags).
- VST3: ProcessContext tempo, time signature, project time in samples and
quarter notes, bar position, kPlaying and kRecording. Continuous time
stays the processed-sample count.
- AU: kAudioUnitProperty_HostCallbacks (beat and tempo, musical time
location, transport state 1 and 2), read from atomics the instance
owns and frees after disposing the unit.
Loop (cycle) ranges aren't reported yet: playback keeps the loop range
to itself.
The Edit window copied the whole session once per frame for the rulers and
twice for every visible track (track_row and lane_interaction), plus once more
for each track shown as playlists and once for the Timecode 2 ruler. The MIDI
editor copied it once per frame, and every Edit row and Mix strip copied its
track. A copy includes every plugin's parameters and saved state: in a 23-track
session with three plugins per track (200 parameters and 64 KB of state each),
one copy took 0.2 ms and each Edit window frame 15.6 ms, which is paid on every
frame of playback, dragging and scrolling.
These now hold the engine's Arc<Session> (session_arc). It is still a snapshot:
a command run later in the frame copies on write, as it already does to keep
its undo step. timebase_ruler takes the frame rate as a parameter, so the
Timecode 2 ruler no longer needs a copy with another frame rate.
Median frame times in a release build, with 21 of 23 tracks on screen:
- The demo session with its tracks tripled: Edit window from 1.25 to 0.68 ms.
- Plus 5,000 volume automation points per audio track: from 2.03 to 0.68 ms.
- Plus three plugins per track: Edit window from 15.6 to 0.69 ms, Mix window
from 1.14 to 0.71 ms.
The Edit window (every ruler, a playlist lane and an automation lane), the MIDI
editor and the Mix window render byte-identical images before and after.
Consolidate clamped its range to the track's last clip end plus one
second, so on an empty track a 0-5 s selection made a 1 s clip and a
2-7 s selection made nothing. It now renders, clears and places exactly
the selection, and refuses a selection too long to render instead of
placing a shorter clip. Prepare DPE keeps the old per-track bound.
Fixes#164
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#181 already pushes instrument params each block, so drop the duplicate
apply_instrument_params. Import Session Data now brings a Sampler's sample
along and remaps its source id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>