Commit Graph

2 Commits

Author SHA1 Message Date
Skyler Lehmkuhl 6924fc0ffe Take management: move takes onto the clip instance
Take management should be per clip instance — deleting a take from one
half of a comped split shouldn't pull it out from under the other half.
That's cleanest if the takes themselves live on the instance rather than
the clip, so they do now.

AudioClipType::TakeFolder is gone entirely. A clip is plain Sampled/Midi
content again, and an instance with a non-empty `takes` list simply
OVERRIDES it with whichever take is active. Splitting already clones the
instance, so each half gets its own take list for free — no index
remapping across instances, no copy-on-write, no shared-state surprise —
and comping still works, because the halves can still each select a
different take. It collapsed machinery too: resolve() moved from the clip
to the instance, and owns_audio_pool_index went back to a one-liner.

Management (DeleteTakeAction, DeleteUnusedTakesAction, RenameTakeAction):
- Right-click a clip with more than one take: `Delete "<active take>"`
  and "Delete Unused Takes". Deletion is named after the take that's
  PLAYING rather than being a generic entry, so you pick the victim by
  selecting it — one clear act instead of hunting a small trash icon in a
  list (which is where this started, and it was fiddly).
- Double-click a take in the dropdown to rename it in place.
- What happens to the selection on delete is the subtle part, and there's
  a test per case: deleting a take BELOW the active one shifts the
  selection down so you keep hearing the same take; deleting the ACTIVE
  take lands on whatever slid into its place (not silently back to take
  1); deleting the LAST take steps back one. The only take can't be
  deleted at all — the menu item isn't offered.
- Deleted takes' audio stays in the pool: undo has to put it back, and
  the other half of a split may still be playing it.

Fixes:
- A recording that stopped before the loop came round wasn't joining an
  existing take list — it landed as a separate overlapping clip. Trigger-
  on-wrap is right for the FIRST recording, but once takes exist there,
  a further run is plainly another take however short. The engine can't
  know that (it's document state), so the editor passes `force_takes`
  with the start-recording command and the run is cut and padded to the
  region even with zero wraps. This forced cycle_loop_len and `wrapped`
  apart on the MIDI side: the region length has to be known from the
  start, but the clip should only pin to full-region length AFTER a pass
  completes, or the bar jumps to full width the moment you hit record.
- Recording a second take left BOTH sounding. append_cycle_takes tore
  down the recording's backend clip by looking it up in
  clip_instance_to_backend_map — but on the audio path the recording
  instance isn't in that map yet; it's only added during promotion, which
  the append path skips. The event already carries the engine's clip id,
  so it's handed over explicitly now.
- The take badge is hidden when there's only one take — no choice to make.
2026-07-14 12:58:33 -04:00
Skyler Lehmkuhl c62164c365 Cycle recording: MIDI separate-takes mode, and append to existing folders
Completes the cycle-recording spec. Three related pieces:

MIDI separate takes (Preferences > Audio > "Cycle MIDI recording"):
- Each pass becomes its own MIDI clip, folded into a take folder — the same
  shape audio always gets — instead of merging into one clip. Merge stays
  the default.
- Notes are bucketed by the pass they were played in. The pass counter bumps
  BETWEEN close_active_notes and the re-note_on at a wrap, so a key held
  across the boundary has its sounding half filed under the pass that's
  ending and its re-opened half under the pass that's beginning. Put the bump
  on either side of that pair and the whole note lands in one pass; there's a
  test named for exactly that.
- A silent INTERIOR pass still yields an empty take, so take N is always pass
  N — otherwise the numbering silently shifts and "take 3" stops meaning "the
  third time round". A TRAILING empty pass is dropped: that's what hitting
  stop shortly after a wrap gives you, a stop artifact rather than a take you
  played. (Audio already behaved this way via its short-final-take rule.)
- Still triggers on the wrap: stop inside the first pass and it's an ordinary
  single recording, whatever the preference says.

Append to an existing take folder (AppendTakesAction):
- Cycle-recording over a region that already holds a take folder now ADDS to
  that folder rather than dropping a second clip on top of it, which stranded
  the new takes in an overlapping clip you couldn't audition against the old.
- "Same region" means same start AND same loop length: resize the cycle region
  and you get a fresh folder, rather than takes of a different length appended
  to an existing one, which would break the uniform-take invariant that
  comping-via-split depends on.
- The recording's own clip/instance are throwaway scaffolding here (the takes
  already live in the backend pools), so they're discarded; the single
  AppendTakesAction is the whole undoable step.

Don't play the region you're recording over:
- While recording into a MIDI track, every clip on that track is silenced
  EXCEPT the one being recorded into. A take folder already sitting in the
  cycle region was otherwise playing its active take underneath you on every
  pass, fighting the part you were trying to record.
- The recording clip itself is exempt, because in merge mode that's precisely
  what you want to hear: the overdub you've been building up. Other tracks are
  untouched.
2026-07-14 12:10:08 -04:00