iOS App Structure
The Amadeus iOS application follows a clean MVVM (Model-View-ViewModel) architecture with clear separation of concerns.
Project Organisation
Amadeus-Fresh/amadeus/
├── amadeusApp.swift # App entry point
├── MainTabView.swift # Root navigation
├── Models/ # Data models and business logic
│ ├── AudioManager.swift
│ ├── AnalysisManager.swift
│ ├── ChordDetectionPipeline.swift
│ ├── BasicPitchHTTPClient.swift
│ ├── ChordAssembler.swift
│ ├── ChordDictionary.swift
│ ├── ScaleDictionary.swift
│ └── ...
├── Views/ # SwiftUI views
│ ├── Tabs/ # Main tab views
│ │ ├── AnalyseView.swift
│ │ ├── LibraryView.swift
│ │ ├── LiveView.swift
│ │ └── ProfileView.swift
│ ├── PlaybackView.swift
│ ├── ChordTimelineView.swift
│ ├── RecordingView.swift
│ └── ...
├── Styles/ # UI styling
│ └── AppStyles.swift
└── Assets.xcassets/ # Images and colors
App Entry Point
amadeusApp.swift
The main application entry point configuring the SwiftUI app lifecycle:
@main
struct amadeusApp: App {
@StateObject private var audioManager = AudioManager()
@StateObject private var analysisManager = AnalysisManager()
var body: some Scene {
WindowGroup {
MainTabView()
.environmentObject(audioManager)
.environmentObject(analysisManager)
}
}
}
Core Managers
AudioManager
Handles all audio playback and session management:
Responsibilities:
Audio file loading and playback
Seek and scrub operations
Audio session configuration
Volume and rate control
Playback state management
Key Methods:
class AudioManager: ObservableObject {
@Published var isPlaying = false
@Published var currentTime: TimeInterval = 0
@Published var duration: TimeInterval = 0
func loadAudio(from url: URL)
func play()
func pause()
func seek(to time: TimeInterval)
func skip(by seconds: TimeInterval)
}
AnalysisManager
Coordinates the analysis workflow:
Responsibilities:
Triggering server analysis
Managing analysis state
Caching results
Error handling
Progress tracking
Key Properties:
class AnalysisManager: ObservableObject {
@Published var isAnalyzing = false
@Published var analysisProgress: Double = 0
@Published var currentAnalysis: AnalysisResult?
@Published var analysisError: Error?
func analyzeAudio(at url: URL) async
func cancelAnalysis()
}
ChordDetectionPipeline
Local chord processing and refinement:
Features:
Post-processing server results
Temporal smoothing
Confidence calculation
Key estimation refinement
View Components
Tab Views
- AnalyseView
Primary interface for audio analysis featuring:
File selection/recording
Analysis triggering
Results visualisation
Playback controls
- LibraryView
Music theory reference containing:
Chord dictionary
Scale explorer
Progression library
Circle of fifths
- LiveView
Real-time detection interface (future):
Microphone input
Live chord display
Tuner functionality
- ProfileView
User settings and preferences:
Account management
App settings
Export options
About section
Specialised Views
- ChordTimelineView
Interactive chord progression visualisation:
Horizontal timeline
Color-coded chords
Confidence indicators
Tap-to-seek interaction
- PlaybackView
Audio control interface:
Play/pause button
Progress slider
Time display
Skip controls
- RecordingView
Audio recording interface with 30-second limit:
Record button
Level meters
30-second duration limit
Save/discard options
Temporary storage before analysis
Data Models
Core Structures
ChordDetection
struct ChordDetection: Codable {
let chord: String
let startTime: TimeInterval
let endTime: TimeInterval
let confidence: Double
let pitchClasses: Set<Int>
}
AnalysisResult
struct AnalysisResult {
let chords: [ChordDetection]
let key: String
let mode: String
let tempo: Double?
let duration: TimeInterval
}
NoteEvent
struct NoteEvent {
let pitch: Int
let startTime: TimeInterval
let endTime: TimeInterval
let velocity: Float
let confidence: Float
}
Networking
BasicPitchHTTPClient
Handles server communication:
Features:
Multipart form data encoding
JSON response parsing
Error handling
Retry logic
Progress tracking
Key Methods:
class BasicPitchHTTPClient: ChordAnalyzer {
func analyzeAudio(
audioData: Data,
sampleRate: Double
) async throws -> AnalysisResult
private func encodeWAV(
from audioData: Data,
sampleRate: Double
) -> Data
}
State Management
The app uses SwiftUI’s state management system:
@StateObject: Manager lifecycle@EnvironmentObject: Dependency injection@Published: Observable properties@State: View-local state@Binding: Two-way data flow
Error Handling
Comprehensive error handling throughout:
enum AnalysisError: LocalizedError {
case networkError(Error)
case serverError(String)
case invalidAudioFormat
case fileTooLarge
case analysisTimeout
var errorDescription: String? {
switch self {
case .networkError(let error):
return "Network error: \(error.localizedDescription)"
case .serverError(let message):
return "Server error: \(message)"
case .invalidAudioFormat:
return "Unsupported audio format"
case .fileTooLarge:
return "Audio file is too large"
case .analysisTimeout:
return "Analysis timed out"
}
}
}