Skip to content

Latest commit

 

History

History
374 lines (351 loc) · 20.4 KB

File metadata and controls

374 lines (351 loc) · 20.4 KB

Changelog

All notable changes to ToooT are documented here. The format follows Keep a Changelog and the project adheres to Semantic Versioning.

Added

  • Session-view live-cell render pathSongSnapshot.sessionLive carries a render-thread-safe [SessionLiveSnapshot.Cell] derived from SessionGrid.livePlayback + per-column liveSinceBeat. The render path treats live cells as priority-1 overrides over arrangement clips and the order list (matches the Ableton "session overrides arrangement" rule). Pattern cells substitute the row; .audio and .midi cells loop with per-loop trigger / event dispatch via shared dispatch helper. Quantized launches now actually advance: Timeline.publishSnapshot calls SessionGrid.advanceLaunches(nowBeat:) every UI tick. Plus PlaybackState.arrangement and PlaybackState.sessionGrid were added so the live engine consumes them — pre-fix only the test harness did.
  • Take comp regions + render path playbackTakeLane.regions: [CompRegion] for region-based active-take selection. TakeLane.cacheInBank(_:) allocates each take's interleaved PCM into the unified sample bank; the render path triggers a voice from the cached bank region whenever the playhead crosses a region's startBeat. RecordingTake gained bankOffset/bankLength (runtime-only — never persisted). TakeLaneSnapshot is the RT-safe handoff.
  • Modulation routing DAG — new ToooT_Core/Modulation.swift with ModSource, ModSlot, ModSnapshot. Sources: LFO, sidechain peak, envelope, MIDI CC. Destinations: channel volume / pan / send, bus volume, master volume. RenderResources.modBuffer is a flat 128-slot Float* allocated at engine init. evaluateModulation runs at every row tick after automation, additively writing source × amount into the same destination fields automation already touches. Bipolar slots span [-amount, +amount]; unipolar map sin LFOs to [0, amount]. Atomic swap pattern + dealloc queue mirrors AutomationSnapshot.
  • MaskingAnalyzer (offline frequency-masking)ToooT_Plugins/ MaskingAnalyzer.swift is an actor that consumes per-channel frozen PCM and produces ranked [MaskingSuggestion]. Hann-windowed FFT via vDSP.FFT, 10-band log-spaced binning matching LinearPhaseEQ, pair-wise mean-energy comparison with configurable threshold + cap. Confidence score 0…1. Pure offline — never touches RT thread.
  • StemSeparator scaffoldToooT_Plugins/StemSeparator.swift is the CoreML / Demucs integration scaffold. Streaming AVAudioFile I/O, 7.8 s window with 1 s overlap-add crossfade, per-stem WAV writers, cooperative cancellation, MainActor progress callback. Model is loaded from the bundle (Demucs_htdemucs.mlpackage); init throws Error.modelMissing with a clear searched-path if absent.
  • MIDIGenerator scaffoldToooT_Plugins/MIDIGenerator.swift is the same shape: actor, model probe, autoregressive token sampler (softmax with temperature), Magenta "performance" vocabulary decoder that emits [ClipMIDIEvent] ready for a .midi arrangement clip.
  • Take-comping UIToooT_UI/CompingView.swift renders stacked take rows with painted comp regions; horizontal drag paints a region active for the dragged span on whichever take row was clicked.
  • AUv3 native-UI embeddingToooT_UI/AUv3PluginViewWindow.swift ships AUv3ViewCache (caches NSViewController per plugin instance to skip XPC handshake on reopen), AUv3HostedView (NSViewControllerRepresentable), AUv3PluginWindow (sheet host with generic-parameter-list fallback when the plugin doesn't ship a custom UI). Uses the documented requestViewController path so v3 plugins remain out-of-process.
  • CollabSession foundationToooT_IO/CollabSession.swift is the Network.framework P2P transport: NWBrowser Bonjour discovery on _tooot._tcp.local., NWListener for structural sync (length-prefixed blob channel — Automerge applies once dep is vendored), UDP multicast for live ParamPacket parameter drags. Fixed-size 36-byte packet wire format encoded with one withUnsafeBytes, no per-call allocations.
  • DAW-Project XML exporterToooT_IO/DAWProjectExporter.swift serialises Arrangement (with tracks, pattern/audio/MIDI clips, fades, gains, MIDI notes) to the Bitwig-led MIT-licensed open interchange format. Replaces AAF / OMF as the open path for handing sessions to other DAWs.
  • Audio + MIDI clip render-path consumptionArrangement now drives audio for every ClipKind, not just .pattern. Audio clips trigger a one-shot voice into the unified bank region at clip onset and silence the channel at clip end (Arrangement.audioClipsStarting/Ending(forChannel:atBeat:)). MIDI clips carry an embedded [ClipMIDIEvent] array (relative beat offsets, noteOn/noteOff, MIDI note + velocity + instrument) which the render path dispatches at row resolution via the existing voice trigger machinery (Arrangement.midiEvents(forChannel:atBeat:)). Pattern-clip override semantics unchanged. UAT 62 verifies onset, end, noteOn, noteOff for both kinds end-to-end.
  • RTVoiceScheduler — opt-in worker pool for realtime voice processing — spawns N user-interactive Threads coordinated via per-worker DispatchSemaphores plus a single done-semaphore. When EngineSharedState.realtimeMulticoreEnabled = 1, the realtime render block partitions active voices across the pool, each writing into its thread-local scratch slot; voice processing + PDC + sidechain peak run in parallel, then the audio thread does a serial vDSP_vsma reduce into the master + bus accumulators. Defaults to off; flip the flag to opt in. UAT 63 covers the scheduler primitive (full dispatch coverage, slot ownership, prompt teardown, post-stop fallback).

Changed

  • UATRunner — section reorder + timeouts. §15 (60-second openmpt A/B fidelity test) now runs before the summary so its failures count toward the pass/fail tally, and the missing-reference-WAV path falls through instead of exit(0). §26 (AUv3 plugin discovery + hosting) runs last, bounded by a 15 s discovery timeout and a 30 s hosting timeout — so a cold AVAudioUnitComponentManager cache prints a skip-with-warning rather than gating the rest of the suite (was the v2.0.1 carry-over TODO).

2.0.1 — 2026-04-26

Added

  • Spatial Audio editor — proper top-down view — replaces the previous Metal mesh-shader pipeline (which silently failed on base M1 hardware and rendered as a black screen everywhere else due to clearColor.alpha = 0). New MetalSpatialView is pure SwiftUI Canvas: listener glyph with nose-pointing-front, concentric distance rings (1 / 2 / 4 / 8 m), cardinal Front/Back/L/R labels, 16 colored channel dots distributed at 2 m default radius (so first-launch users see content immediately instead of every dot piled at origin), drag-to-reposition, hover readout: "Ch N · x ?.? m z ?.? m · vol ??%". Works on every macOS-supported GPU.
  • Three-pane Automation editor — replaces the single-line legacy view. Sidebar lists every available target (master volume, tempo BPM, per-channel volume + pan for the first 8 channels, per-bus volume — plugin params auto-appendable) with a status dot per active lane and point counts. Gridded canvas with bar lines (every 4th brighter at bar boundaries), value markers at 25 / 50 / 75 %, edge labels, curve fills, selected-point ring, empty-state hint. Inspector strip below shows TIME / VALUE / INDEX of the selected point + Linear / S-Curve picker + Delete. Header has Clear-lane and +Point-at-midpoint shortcuts. Click empty space to insert; drag to move; click a point to select.
  • Generative panel rebuild — was 4 buttons on a 1100-character line. Now a proper editor: 8 generators (Markov Melody / Euclidean Beat / L-System Arp / Harmony Layer / Bassline / Drum Pattern / Chord Progression / Variation), style picker (Techno / DnB / Ambient / Hip-Hop / Jazz / Breakbeat), 12 chromatic keys, 8 scales, intensity
    • complexity sliders with % readouts, target channel picker, variation count stepper. Generators actually use the controls — Bassline picks degrees from selected scale + key, Drum Pattern has style-specific layouts, Chord Prog plays I–V–vi–IV in the selected key. Status messages report what was generated ("Bassline in C minor", "Techno drum pattern").
  • UX polish — welcome screen rewrite (drop "Mac Tracker for the Post-Human Era", add ⌘N / ⌘O / ⌘K / Space / ⌘. / ⌘Z keyboard shortcut card, drag-and-drop hint), HUD status with translucent material card + smooth slide-in/out animation, transport tooltips (Space / ⌘. / BPM / TPR), showStatus lifetime bumped 3 s → 5 s so users actually see messages.
  • Master linear-phase EQ in the master chainLinearPhaseEQ's 4096-point overlap-save FFT convolution is now instantiated by AudioHost and wired into RenderBlockWrapper.masterEQBlock. 10 log-spaced bands (31 Hz – 16 kHz). AudioHost.setMasterEQBand(_:dB:)
    • EngineSharedState.isMasterEQEnabled for runtime control. Master chain order: EQ → stereo widen → reverb → safety limiter.
  • AUv3 plugin parameter automationAudioHost.pluginParamRegistry walks each loaded plugin's parameterTree and registers parameters under target IDs plugin.<channel>.<slot>.<address> (channel inserts), plugin.<channel>.inst.<address> (channel instruments), plugin.bus.<bus>.<slot>.<address> (bus inserts). Timeline.syncEngineToUI evaluates plugin-targeted automation lanes at UI tick rate and writes through AUParameter.setValue. Native engine params stay on the audio thread; plugin params live at UI rate where setValue is documented thread-safe.
  • Tempo automation — new target ID tempo.bpm is honored by the audio thread's applyTarget. Lanes write directly to state.pointee.bpm at row boundaries, clamped to [20, 999].
  • Markers + time signaturesMarker + TimeSignatureChange + TimingMap ship in ToooT_Core. PlaybackState.timingMap carries named cue points and a list of meter changes; seekToMarker(named:) jumps the playhead. Persisted in the .mad TOOO chunk via exportAsPluginStateData. Mid-song meter consumption by the render path is a follow-up.
  • Recording take lanesRecordingTake + TakeLane per channel, RecordingMode { replace, overdub, loop }. commitTakeFromRecording finalises the in-progress capture into the configured channel's stack honoring the mode. Replace clears prior takes; overdub / loop append latest-active. Codable for .mad persistence.
  • Arrangement engine consumptionSongSnapshot.arrangement ships as an optional reference. When non-nil, processTickSequencer's per-channel event lookup overrides the order-list path: a pattern clip on a track at channelIndex N routes the engine to play that pattern's events for channel N. Pattern-grid playback is the fallback when no clip is active. Honors track mute / solo. UAT 61 verifies. Pressing play on a populated ArrangementView now actually produces audible output. Audio (.audio) and MIDI (.midi) clip kinds are still pending.
  • Cold-launch profiling instrumentationAudioHost.SetupTimings exposes wall-clock per phase (engineBoot / internalDSPBoot / outputUnitBoot). UAT 57 measures and asserts < 250 ms steady-state. AUv3HostManager no longer auto-scans on init; Timeline kicks off discoverPluginsAsync() on a detached utility Task, removing the 1–3 s AVAudioUnitComponentManager scan from cold launch's main thread.
  • AppIntents (Shortcuts.app)OpenToooTProjectIntent (file → open in app), OpenLastAutosaveIntent, NewToooTProjectIntent, plus ToooTShortcutsProvider so the intents surface in Spotlight and the Shortcuts gallery.
  • Crash-recovery prompt on launchTrackerAppView checks AudioHost.recentAutosaves(maxAgeSeconds: 86_400) on first appear; if anything is found, CrashRecoveryPromptView is shown as a sheet with restore-latest / dismiss actions. Engine work was already shipped in 2.0.0; this wires the UI half.
  • Render-path automation evaluatorAutomationSnapshot is published atomically to AudioRenderNode and consumed at every row boundary. Supported target IDs: ch.<N>.{volume,pan,send.<bus>}, bus.<B>.volume, master.volume. Lock-free read/write mirrors the song-snapshot pattern. Timeline.publishSnapshot republishes whenever PlaybackState changes (UI Bezier lanes are converted to the Core lane format inline).
  • MAD metadata + thumbnail extractorsMADMetadataReader.read returns title, format, pattern/channel/instrument counts, and instrument names from .mad and classic .mod/.xm/.it/.s3m files. MADThumbnail.renderPNG renders a tile-style preview of the first pattern via CoreGraphics + ImageIO. Pure Foundation — drops into a Quick Look .appex or Spotlight .mdimporter extension target unmodified. Recipes in docs/MAD_QUICKLOOK_SPOTLIGHT.md.
  • Cold-launch os_signpost instrumentationAudioHost.setup wraps an outer interval plus inner intervals on EngineBoot, InternalDSPBoot, OutputUnitBoot. AUv3Host.scanLog covers plugin discovery. Subsystem com.apple.ProjectToooT / category ColdLaunch for filtering in Instruments.app.
  • exportAudio switched to multi-core pathrenderOfflineConcurrent parallelizes voice processing across cores using a per-voice scratch pool. Bit-exact-modulo-fp-reorder parity with the serial path, verified by UAT 53.
  • UAT 53 rewritten as a real serial-vs-concurrent parity test (16 simultaneous voices on saw samples; max diff < 1e-6). Replaces the previous silent-input no-op.
  • UAT 55 — automation snapshot lifecycle stress: 500 atomic swaps with a concurrent reader thread driving renderOffline calls; asserts every retired snapshot is released after processDeallocations drains.

Fixed

  • Scratch-slot collision in renderOfflineConcurrent — per-voice scratch slots were allocated as idx % voiceThreadSlots with voiceThreadSlots = 8. concurrentPerform doesn't pin iterations to threads, so any two voices with the same idx % 8 could collide on the same threadScratchL/R/Mono buffers from different threads. Symptom: max abs diff of 0.054 between serial and concurrent render on a 16-voice test. Fix: voiceThreadSlots = kMaxChannels and slot = idx (no modulo). Memory cost ~20 MB allocated once at engine init. Material because v2.0.1's exportAudio switched to the concurrent path — anyone bouncing through it would have heard wrong output.
  • Undersized snapshot deallocation queuesautomationDealloc (capacity 64) and song deallocationQueue (capacity 128) silently dropped entries when push returned false, leaking the retained snapshot at each dropped pointer. Bumped both to 2048, comfortably absorbing realistic main-thread bursts.

Changed

  • renderResources.voiceThreadSlots is now kMaxChannels (was 8) — sized for one slot per voice, no modulo aliasing.
  • UI Bezier automation types renamedAutomationLane / AutomationPoint in ToooT_UI/Helpers.swift are now BezierAutomationLane / BezierAutomationPoint to disambiguate from ToooT_Core.AutomationLane (render-path representation). Timeline.publishSnapshot continues to convert between them.
  • Homebrew caskscripts/homebrew-tooot.rb sha256 filled in for the v2.0.0 DMG.

Documentation

  • docs/FEATURE_ROADMAP.md — accuracy pass against actual code. Flipped 8 stale items to ✅ SHIPPED (#7 Automation, #10 Crash recovery, #11 Piano Roll CC, #12 MPE, #17 Scene automation, #23 Video sync, #25 Keyboard shortcuts, #26 Undo browser); 4 to PARTIAL (#2, #8, #16, #30, #39). Refreshed pie chart counts and rewrote next-session priorities — the original top-five all shipped.
  • docs/MAD_QUICKLOOK_SPOTLIGHT.md — recipes for wrapping MADMetadataReader and MADThumbnail in Quick Look .appex and Spotlight .mdimporter extension bundles via Xcode (SPM doesn't produce these bundle types directly).
  • README.md — added AppIntents, multi-core export, render-path automation, MAD QL/Spotlight primitives, and macOS Native Integration sections; updated test count (100+ → 230+); removed broken CI badge; linked the new docs.

Changed

  • Synthesis tier names — pretentious sci-fi labels replaced with descriptive musical ones, with inline descriptions per choice:
    • CarbonStudio (clean, precise, tracker-classic)
    • BiologicalOrganic (humanized timing, natural vibrato)
    • XenomorphGenerative (stochastic / glitchy / experimental) PlaybackState fields renamed in lockstep: carbonCorruptionstudioCorruption, carbonGlitchRatestudioGlitchRate, bioArrhythmiaRateorganicTimingDrift, bioBreathinessorganicBreathiness, xenoFractalDimgenerativeFractalDim, xenoVoidThresholdgenerativeVoidThreshold.

Internal

  • Cleaned all build warnings to zero: redundant public on KeyBindings extension static, unnecessary nonisolated(unsafe) on Sendable OSLog constants (AudioHost, AUv3Host), var oklet, unused songLength, discardable result _ =, CLAPHost.process wrapped in nested withUnsafeMutablePointer to satisfy [#TemporaryPointers].
  • Cleanup: gitignore release artifacts (dist/, *.dmg, *.dSYM, *.zip); fix var fadeCliplet fadeClip warning in UATRunner.

2.0.0 — 2026-04-17

First major release. ~15 000 lines of new, tested code.

Plugin hosting

  • CLAP host (BSD-3, MIT-compatible) — real-time-safe, modern ABI. u-he / FabFilter / Arturia / Surge XT supported.
  • VST3 host — direct Steinberg SDK integration (no JUCE dependency).
  • AUv3 host — per-channel insert chains (4 inserts + 1 instrument per channel) and bus inserts on the 4 aux buses.

Mastering

  • ITU-R BS.1770-4 LUFS metering (momentary / short-term / gated integrated).
  • True-peak detection + look-ahead limiter AUv3.
  • LUFS-normalized export to Spotify (−14) / Apple Music (−16) / YouTube (−14) / EBU R128 (−23) / Amazon Music.
  • TPDF dither on bit-depth reduction.
  • Multiband compressor (3-band Linkwitz-Riley) + linear-phase FFT EQ (4096-point) bundled.

Composition

  • Arrangement timeline view (Pro Tools / Logic paradigm).
  • Session grid view (Ableton Live paradigm) with quantized clip launch.
  • Piano Roll with CC lanes (velocity / mod / expression / pan / pitch bend).
  • MIDI 2.0 MPE — per-note pitch bend + pressure with voice tracking.
  • Arpeggiator — 6 modes with hold, octaves, gate probability.
  • Music theory — 16 scales, 14 chord qualities, frequency-domain quantization.
  • Scenes — capture / recall full mixer state.
  • Automation lanes with 5 curve types + read/write/touch/latch/trim modes.

Workflow

  • ⌘K Command Palette with fuzzy match over every shipped command.
  • Keyboard shortcut customization — ToooT / Pro Tools / Logic Pro presets.
  • Undo history browser — 50-level stack, jump-to-step.
  • Project auto-save every 60 s with crash-recovery prompt on relaunch.
  • JavaScript scripting via JavaScriptCore.
  • Text-to-speech via AVSpeechSynthesizer + /usr/bin/say.
  • Starter content — 32 in-code synthesized instruments.
  • Template projects — Blank / Drum Starter / Ambient Pad / Techno Basic.
  • MIDI panic (⌘.) — transport stop + all-notes-off.

Engine + performance

  • Variable sample rate — 44.1 / 48 / 88.2 / 96 / 192 kHz end-to-end.
  • Multi-core offline render — DispatchQueue.concurrentPerform with per-thread scratch pool.
  • Zero-allocation audio thread (Swift 6 strict concurrency).
  • GPU DSP — 5-kernel Metal compute library.
  • SOLA time-stretch + pitch-shift.
  • Track freeze / stems / mastered export paths wired end-to-end.

Quality

  • UAT: 232 assertions / 0 failures.
  • StressRunner: 11 integration scenarios / 0 failures.
  • FuzzRunner for parser crash safety.

Installation

brew tap mstits/tooot
brew install --cask tooot

Or download the DMG and drag to Applications. macOS 14+, Apple Silicon.