MixBoard iOS is an iOS/iPadOS music player and mix preparation tool — a companion to the existing MixBoard macOS desktop app. The user listens to their own MP3/FLAC/OGG/Opus files on their phone, builds playlists ("mixes"), and syncs the playlist metadata back to the Mac for export to Adobe Audition or other DAWs.
/Users/vdrobkov/Misc/Documents/Copilot/MixBoard//Users/vdrobkov/Misc/Documents/Copilot/Mixboard iOS/project.yml → generates MixBoardiOS.xcodeproj)xcodegen generate after adding/removing files or changing project settingsxcodebuild -scheme MixBoardiOS -destination 'generic/platform=iOS'xcodebuild -scheme MixBoardiOS -destination 'platform=iOS Simulator,name=iPhone 17 Pro'xcodebuild -scheme MixBoardiOSTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' testSources/MixBoardApp.swift
PlayerViewModel, PlaylistViewModel, LibraryManager, AppTheme, SyncManager@Environment / @EnvironmentObject to the view hierarchyModelContainer for SwiftData (Track, CuePoint, Playlist, PlaylistEntry, PlaylistFolder)dbResetV6) that nukes SQLite files on first launch after code changes — increment the version key when schema changesSources/Models/)Track.swift — @Model (SwiftData)
id: UUID, title, artist, album, genre, filePath (relative to Documents), fileName (just the filename for matching)duration, bpm, musicalKey, sampleRate, bitDepth, channels, fileFormat, fileSizeBytesyear: Int? — release year extracted from the Date metadata tagdateAdded, lastPlayed, playCount, rating, color, noteswaveformData: Data? — cached waveformisAnalyzed: Bool — whether BPM/key detection rancuePoints: [CuePoint] — cascade delete relationshipfileURL — computed property that resolves Documents/ + filePath, uses .standardizedFileURL to handle /private/var vs /var iOS path discrepancyfilePath stores the path relative to the app's Documents directory (e.g., 12.1 Child Udigrudi/12.1.2 Flaviola/01.mp3). The scan uses filePath (not fileName) for duplicate detection, so same-named files in different folders are imported separately.Playlist.swift — @Model
id, name, notes, dateCreated, dateModified, targetBPM, color (hex)groupTemplate: String — per-playlist grouping template (e.g., {Album} ({Date}))entries: [PlaylistEntry] — cascade deletefolder: PlaylistFolder?totalDuration traverses .track relationships which can crash on invalidated SwiftData models. The PlaylistRowView removed the duration display to avoid this.PlaylistEntry.swift — @Model
position, track: Track?, crossfadeDuration, startOffset, endOffset, gainAdjustment, notesCuePoint.swift — @Model, Comparable
timestamp, endTimestamp?, color, type: CuePointType (enum: Marker, Intro, Outro, Drop, etc.)PlaylistFolder.swift — @Model
nullify delete ruleAppTheme.swift — ObservableObject
AppState.swift — UserDefaults persistence for last playlist/track/playback position
GroupTemplateResolver.swift
{Artist}, {Album}, {Genre}, {Date}, {Folder}, {Format}, {BPM}, {Key} placeholdersSources/Services/)AudioEngine.swift — @Observable, @MainActor
AVAudioEngine + AVAudioPlayerNode + 3-band EQAVAudioFile + scheduleSegmentAVAudioPCMBuffer via stb_vorbis, played with scheduleBufferlibopusfile (compiled from source), same buffer path.playback category (background audio)playbackGeneration counter prevents stale completion handlers from triggering auto-advancenil format (auto-negotiate) — critical fix, using fixed output format broke playback for FLAC/OGGMetadataService.swift
AVURLAsset for standard formatsOGGDecoder.fileInfo) and Opus (via opusfile C library — reads Vorbis Comment tags: TITLE, ARTIST, ALBUM, GENRE, DATE)commonKeyCreationDate, TDRC, TYER, DATE tagsOGGDecoder.swift
stb_vorbis.c (public domain, single-file C decoder in Sources/OGG/)Sources/OGG/MixBoard-Bridging-Header.hOpusDecoder.swift
libopusfile (compiled static libraries in Sources/OpusLib/lib/)op_open_file → op_read_float → deinterleave to AVAudioPCMBufferlibogg.a, libopus.a, libopusfile.a — compiled for iOS ARM64 (iphoneos)Sources/OpusLib/include/ogg/ and Sources/OpusLib/include/opus//tmp/opus-build/install-sim/ if needed.LibraryManager.swift — ObservableObject, @MainActor
filePath (not fileName) — critical for same-named files in different foldersfileURL.standardizedFileURL.path minus docsDir.path — handles /private/var vs /varfixBadPathsIfNeeded(): One-time migration for tracks with broken paths from earlier bugscanMusicDirectory(): Called on library view appear (once) and manually from SettingsDocuments/Music/os.Logger with .notice level for debuggingSyncManager.swift — ObservableObject, @MainActor
Documents/Sync/mixboard-playlists.jsonSyncPayload → SyncPlaylist → SyncEntry (matched by filename between devices)ArtworkService.swift — actor
URL (captured before async) to avoid SwiftData model invalidationWaveformGenerator.swift, BPMDetector.swift, KeyDetector.swift
MediaKeyHandler.swift
MPRemoteCommandCenter for lock screen / Control Center controlsMPNowPlayingInfoCenter updatesSources/ViewModels/)PlayerViewModel.swift — @Observable, @MainActor
AudioEngine, syncs state at 30fps via timeros.Logger debug output for playback: logs file path, exists check, success/failurePlaylistViewModel.swift — @Observable, @MainActor
mixTargets: [Playlist?] array, indices 0-2)quickAddToMix(slot:track:context:) — adds track to specific mix slotisDuplicate uses fetchCount query (not relationship traversal) — critical to avoid SwiftData model invalidation crashesaddTracks sorts by filePath with .numeric option for correct folder orderingmixTarget0ID, mixTarget1ID, mixTarget2ID)Sources/Views/)ContentView.swift — Root view
MiniPlayerView)PlaylistListView toolbarPlaylistListView.swift — Main screen
PlaylistDetailView.swift
groupTemplateflatEntryList (no grouping) or groupedEntryList (by template)GroupTemplateEditorSheet for editing the grouping templateLibraryView.swift
FolderBrowserView directlyhasScanned flag)FolderBrowserView.swift — Drill-down folder navigation
allTracksRecursive cached in @State on appear (not computed in body — performance critical)NowPlayingView.swift — Full-screen player
.fullScreenCoverMiniPlayerView.swift — Bottom bar
.buttonStyle(.plain) with 44pt touch targetsTrackRow.swift — Reusable track row
SettingsView.swift
AddToPlaylistSheet.swift, AddGroupToPlaylistSheet.swift, GroupTemplateEditorSheet.swift, EntryNotesSheet.swift
Tests/ModelTests.swift)SwiftData @Model objects can become invalidated when:
.track?.property) on invalidated entriesMitigations applied:
isDuplicate uses fetchCount query instead of relationship traversalPlaylistRowView removed formattedTotalDuration (traverses .track)ArtworkService receives URL (captured before async), not Track modelallTracksRecursive cached in @State, not computed in body/var/mobile/... to /private/var/mobile/... for file URLs.standardizedFileURL before computing relative pathsTrack.fileURL uses standardizedFileURL for resolutionInfo.plist must have UIBackgroundModes: ["audio"] as an array (not string)Info.plist file referenced in project.yml info: section.playback in AudioEngine.init()stb_vorbis.c in Sources/OGG/ — single-file C decoder for OGG Vorbislibogg.a, libopus.a, libopusfile.a in Sources/OpusLib/lib/ — compiled for iOS ARM64Sources/OpusLib/include/Sources/OGG/MixBoard-Bridging-Header.h/tmp/opus-build/build_ios.sh (may need recreation)mixboard-playlists.json to Documents/Sync/SyncImporter.swift and SyncWatcher.swift (partially integrated)Sources/
├── MixBoardApp.swift # App entry point
├── Models/
│ ├── Track.swift # Core track model
│ ├── Playlist.swift # Playlist + PlaylistEntry models
│ ├── PlaylistFolder.swift # Folder grouping
│ ├── CuePoint.swift # Cue markers
│ ├── AppState.swift # UserDefaults persistence
│ ├── AppTheme.swift # 7 skins
│ └── GroupTemplateResolver.swift # Playlist grouping templates
├── ViewModels/
│ ├── PlayerViewModel.swift # Playback state
│ └── PlaylistViewModel.swift # Playlist CRUD, 3 mix targets
├── Views/
│ ├── ContentView.swift # Root view
│ ├── PlaylistListView.swift # Main screen (playlists)
│ ├── PlaylistDetailView.swift # Playlist detail + grouping
│ ├── LibraryView.swift # Library browser (5 modes)
│ ├── FolderBrowserView.swift # Drill-down folder navigation
│ ├── NowPlayingView.swift # Full-screen player
│ ├── MiniPlayerView.swift # Bottom player bar
│ ├── TrackRow.swift # Track row + 3 mix buttons
│ ├── WaveformView.swift # Canvas waveform
│ ├── SettingsView.swift # Settings + mix targets + icon picker
│ ├── AddToPlaylistSheet.swift # Single track → playlist
│ ├── AddGroupToPlaylistSheet.swift # Multiple tracks → playlist
│ └── GroupTemplateEditorSheet.swift # Grouping template editor
├── Services/
│ ├── AudioEngine.swift # AVAudioEngine playback
│ ├── MetadataService.swift # Tag reading (all formats)
│ ├── LibraryManager.swift # File scanning & import
│ ├── SyncManager.swift # JSON sync export/import
│ ├── ArtworkService.swift # Album art (embedded + folder)
│ ├── OGGDecoder.swift # OGG Vorbis via stb_vorbis
│ ├── OpusDecoder.swift # Opus via libopusfile
│ ├── WaveformGenerator.swift # Accelerate-based waveform
│ ├── BPMDetector.swift # Spectral flux BPM detection
│ ├── KeyDetector.swift # Chromagram key detection
│ └── MediaKeyHandler.swift # Lock screen controls
├── OGG/
│ ├── stb_vorbis.c # C decoder (public domain)
│ ├── stb_vorbis_wrapper.h # Header wrapper
│ └── MixBoard-Bridging-Header.h # Swift-C bridge
├── OpusLib/
│ ├── include/ogg/*.h # libogg headers
│ ├── include/opus/*.h # libopus + opusfile headers
│ └── lib/*.a # Static libraries (iOS ARM64)
└── Resources/
└── Assets.xcassets/ # App icons (10 color variants)
Tests/
└── ModelTests.swift # 49 unit tests
project.yml # XcodeGen project definition
Info.plist # Background audio mode
MixBoardiOS.entitlements # App group entitlement
Package.swift # SPM (for library builds)
Sources/ subdirectoryxcodegen generate to add it to the Xcode projectproject.ymlxcodegen generate@Model classesdbResetV* key in MixBoardApp.init() to force a clean databaseCmd+R in Xcode (must have team set)loadAndPlay: log linesscanMusicDirectory: shows how many files were found on disk