diff --git a/.DS_Store b/.DS_Store index db5724e..11c6074 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml deleted file mode 100644 index 9827103..0000000 --- a/.github/workflows/ios.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: iOS starter workflow - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - name: Build and Test default scheme using any available iPhone simulator - runs-on: macos-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set Default Scheme - run: | - scheme_list=$(xcodebuild -list -json | tr -d "\n") - default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") - echo $default | cat >default - echo Using default scheme: $default - - name: Build - env: - scheme: ${{ 'default' }} - platform: ${{ 'iOS Simulator' }} - run: | - # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) - device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"` - if [ $scheme = default ]; then scheme=$(cat default); fi - if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi - file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` - xcodebuild build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" - - name: Test - env: - scheme: ${{ 'default' }} - platform: ${{ 'iOS Simulator' }} - run: | - # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) - device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}' | sed -e "s/ Simulator$//"` - if [ $scheme = default ]; then scheme=$(cat default); fi - if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi - file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` - xcodebuild test-without-building -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" diff --git a/.gitignore b/.gitignore index 48474a2..4129f2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,96 @@ +# macOS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Xcode +*.xcscmblueprint +*.xccheckout +DerivedData/ +.build/ +build/ +*.xcuserstate +xcuserdata/ +*.xcworkspace/xcuserdata/ +*.xcodeproj/project.xcworkspace/xcuserdata/ +*.xcodeproj/xcuserdata/ + +# Python Virtual Environments +venv/ +.venv/ +env/ +.env/ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +pip-log.txt +pip-delete-this-directory.txt + +# Server Dependencies +basic-pitch-server/venv/ +basic-pitch-server/__pycache__/ +**/node_modules/ +**/package-lock.json +**/yarn.lock + +# Documentation Build Files +docs/_build/ +docs/venv/ +**/sphinx/ + +# Reference Repositories (Large external repos) references/relevant_repos/aubio/ references/relevant_repos/Audio-Chord-Recognition/ references/relevant_repos/chord-detection/ references/relevant_repos/Chord-Recognition/ references/relevant_repos/ChordRecGen/ references/relevant_repos/dechorder/ +references/relevant_repos/omnizart/ +references/relevant_repos/librosa/ +references/relevant_repos/basic-pitch/ +references/relevant_repos/NMFtoolbox/ +references/relevant_repos/sphinx_rtd_theme/ +references/relevant_repos/rt-cqt/ +references/relevant_repos/df0/ + +# Swift Libraries (External dependencies) swift_libs/AudioKit/ swift_libs/Tonic/ -references/relevant_repos/omnizart/ + +# Audio Files (test recordings) +chord_recordings/ +*.mp3 +*.wav +*.m4a +*.aac +*.flac +*.ogg + +# ML Models (large binary files) +*.mlpackage/ +*.mlmodel +*.h5 +*.pb +*.onnx + +# PDFs +*.pdf + +# Temporary Files +temp/ +tmp/ +*.tmp +*.swp +*.swo +*~ + +# IDE Files +.vscode/ +.idea/ +*.sublime-* diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a2f1143 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements-minimal.txt \ No newline at end of file diff --git a/Amadeus-Fresh/.DS_Store b/Amadeus-Fresh/.DS_Store new file mode 100644 index 0000000..e443531 Binary files /dev/null and b/Amadeus-Fresh/.DS_Store differ diff --git a/Amadeus-Fresh/amadeus/.DS_Store b/Amadeus-Fresh/amadeus/.DS_Store new file mode 100644 index 0000000..c9f447c Binary files /dev/null and b/Amadeus-Fresh/amadeus/.DS_Store differ diff --git a/Amadeus-Fresh/amadeus/amadeus/.DS_Store b/Amadeus-Fresh/amadeus/amadeus/.DS_Store new file mode 100644 index 0000000..9ef5d30 Binary files /dev/null and b/Amadeus-Fresh/amadeus/amadeus/.DS_Store differ diff --git a/Amadeus-iOS/.DS_Store b/Amadeus-iOS/.DS_Store new file mode 100644 index 0000000..f06b517 Binary files /dev/null and b/Amadeus-iOS/.DS_Store differ diff --git a/Amadeus-iOS/Amadeus/.DS_Store b/Amadeus-iOS/Amadeus/.DS_Store new file mode 100644 index 0000000..65150af Binary files /dev/null and b/Amadeus-iOS/Amadeus/.DS_Store differ diff --git a/Amadeus/Package.resolved b/Amadeus/Package.resolved new file mode 100644 index 0000000..0358b56 --- /dev/null +++ b/Amadeus/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "audiokit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AudioKit/AudioKit.git", + "state" : { + "revision" : "5b3fd238ef8ee95c9842ca4ea83ca58ee151e630", + "version" : "5.6.5" + } + }, + { + "identity" : "tonic", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AudioKit/Tonic.git", + "state" : { + "revision" : "ed84c6add1c34895c8548632d50a19e9125b28d8", + "version" : "1.4.0" + } + } + ], + "version" : 2 +} diff --git a/Amadeus/Package.swift b/Amadeus/Package.swift new file mode 100644 index 0000000..a191329 --- /dev/null +++ b/Amadeus/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "Amadeus", + platforms: [ + .iOS(.v17) + ], + products: [ + .library( + name: "Amadeus", + targets: ["Amadeus"]) + ], + dependencies: [ + .package(url: "https://github.com/AudioKit/AudioKit.git", from: "5.6.0"), + .package(url: "https://github.com/AudioKit/Tonic.git", from: "1.0.6") + ], + targets: [ + .target( + name: "Amadeus", + dependencies: ["AudioKit", "Tonic"]) + ] +) \ No newline at end of file diff --git a/Amadeus/amadeus/amadeus.xcodeproj/project.pbxproj b/Amadeus/amadeus/amadeus.xcodeproj/project.pbxproj new file mode 100644 index 0000000..13089f3 --- /dev/null +++ b/Amadeus/amadeus/amadeus.xcodeproj/project.pbxproj @@ -0,0 +1,643 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 82E95AA32ECFFC4700520824 /* AudioKit in Frameworks */ = {isa = PBXBuildFile; productRef = 82E95AA22ECFFC4700520824 /* AudioKit */; }; + 82E95AA52ECFFC8700520824 /* Tonic in Frameworks */ = {isa = PBXBuildFile; productRef = 82E95AA42ECFFC8700520824 /* Tonic */; }; + 82E95AB62ED001DD00520824 /* The_Pogues-A Pair_of_Brown_Eyes.wav in Resources */ = {isa = PBXBuildFile; fileRef = 82E95AB52ED001DD00520824 /* The_Pogues-A Pair_of_Brown_Eyes.wav */; }; + 82E95AB72ED001DD00520824 /* Nada-Nahuel_Pennisi_Luis_Salinas.wav in Resources */ = {isa = PBXBuildFile; fileRef = 82E95AB42ED001DD00520824 /* Nada-Nahuel_Pennisi_Luis_Salinas.wav */; }; + 82E95AB82ED001DD00520824 /* 3-Part_Inventions_ (Sinfonias)_No.6_in_E_Major_BWV 792.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 82E95AB02ED001DD00520824 /* 3-Part_Inventions_ (Sinfonias)_No.6_in_E_Major_BWV 792.mp3 */; }; + 82E95AB92ED001DD00520824 /* Edie_Brickell-Good_Times.wav in Resources */ = {isa = PBXBuildFile; fileRef = 82E95AB12ED001DD00520824 /* Edie_Brickell-Good_Times.wav */; }; + 82E95ABA2ED001DD00520824 /* Flim.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 82E95AB22ED001DD00520824 /* Flim.mp3 */; }; + 82E95ABB2ED001DD00520824 /* Les_Trois_Valses_distinguées_du_précieux_dégoûté_II. Son binocle.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 82E95AB32ED001DD00520824 /* Les_Trois_Valses_distinguées_du_précieux_dégoûté_II. Son binocle.mp3 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 82491A782EBF029A001A2D13 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 82491A622EBF0299001A2D13 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 82491A692EBF0299001A2D13; + remoteInfo = amadeus; + }; + 82491A822EBF029A001A2D13 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 82491A622EBF0299001A2D13 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 82491A692EBF0299001A2D13; + remoteInfo = amadeus; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 82491A6A2EBF0299001A2D13 /* amadeus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = amadeus.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 82491A772EBF029A001A2D13 /* amadeusTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = amadeusTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 82491A812EBF029A001A2D13 /* amadeusUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = amadeusUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 82E95AB02ED001DD00520824 /* 3-Part_Inventions_ (Sinfonias)_No.6_in_E_Major_BWV 792.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "3-Part_Inventions_ (Sinfonias)_No.6_in_E_Major_BWV 792.mp3"; sourceTree = ""; }; + 82E95AB12ED001DD00520824 /* Edie_Brickell-Good_Times.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "Edie_Brickell-Good_Times.wav"; sourceTree = ""; }; + 82E95AB22ED001DD00520824 /* Flim.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Flim.mp3; sourceTree = ""; }; + 82E95AB32ED001DD00520824 /* Les_Trois_Valses_distinguées_du_précieux_dégoûté_II. Son binocle.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "Les_Trois_Valses_distinguées_du_précieux_dégoûté_II. Son binocle.mp3"; sourceTree = ""; }; + 82E95AB42ED001DD00520824 /* Nada-Nahuel_Pennisi_Luis_Salinas.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "Nada-Nahuel_Pennisi_Luis_Salinas.wav"; sourceTree = ""; }; + 82E95AB52ED001DD00520824 /* The_Pogues-A Pair_of_Brown_Eyes.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "The_Pogues-A Pair_of_Brown_Eyes.wav"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 828DB2212ED797F300F6485D /* Exceptions for "amadeus" folder in "amadeus" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 82491A692EBF0299001A2D13 /* amadeus */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 82491A6C2EBF0299001A2D13 /* amadeus */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 828DB2212ED797F300F6485D /* Exceptions for "amadeus" folder in "amadeus" target */, + ); + path = amadeus; + sourceTree = ""; + }; + 82491A7A2EBF029A001A2D13 /* amadeusTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = amadeusTests; + sourceTree = ""; + }; + 82491A842EBF029A001A2D13 /* amadeusUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = amadeusUITests; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 82491A672EBF0299001A2D13 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 82E95AA52ECFFC8700520824 /* Tonic in Frameworks */, + 82E95AA32ECFFC4700520824 /* AudioKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 82491A742EBF029A001A2D13 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 82491A7E2EBF029A001A2D13 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 82491A612EBF0299001A2D13 = { + isa = PBXGroup; + children = ( + 82E95AB02ED001DD00520824 /* 3-Part_Inventions_ (Sinfonias)_No.6_in_E_Major_BWV 792.mp3 */, + 82E95AB12ED001DD00520824 /* Edie_Brickell-Good_Times.wav */, + 82E95AB22ED001DD00520824 /* Flim.mp3 */, + 82E95AB32ED001DD00520824 /* Les_Trois_Valses_distinguées_du_précieux_dégoûté_II. Son binocle.mp3 */, + 82E95AB42ED001DD00520824 /* Nada-Nahuel_Pennisi_Luis_Salinas.wav */, + 82E95AB52ED001DD00520824 /* The_Pogues-A Pair_of_Brown_Eyes.wav */, + 82491A6C2EBF0299001A2D13 /* amadeus */, + 82491A7A2EBF029A001A2D13 /* amadeusTests */, + 82491A842EBF029A001A2D13 /* amadeusUITests */, + 82491A6B2EBF0299001A2D13 /* Products */, + ); + sourceTree = ""; + }; + 82491A6B2EBF0299001A2D13 /* Products */ = { + isa = PBXGroup; + children = ( + 82491A6A2EBF0299001A2D13 /* amadeus.app */, + 82491A772EBF029A001A2D13 /* amadeusTests.xctest */, + 82491A812EBF029A001A2D13 /* amadeusUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 82491A692EBF0299001A2D13 /* amadeus */ = { + isa = PBXNativeTarget; + buildConfigurationList = 82491A8B2EBF029A001A2D13 /* Build configuration list for PBXNativeTarget "amadeus" */; + buildPhases = ( + 82491A662EBF0299001A2D13 /* Sources */, + 82491A672EBF0299001A2D13 /* Frameworks */, + 82491A682EBF0299001A2D13 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 82491A6C2EBF0299001A2D13 /* amadeus */, + ); + name = amadeus; + packageProductDependencies = ( + 82E95AA22ECFFC4700520824 /* AudioKit */, + 82E95AA42ECFFC8700520824 /* Tonic */, + ); + productName = amadeus; + productReference = 82491A6A2EBF0299001A2D13 /* amadeus.app */; + productType = "com.apple.product-type.application"; + }; + 82491A762EBF029A001A2D13 /* amadeusTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 82491A8E2EBF029A001A2D13 /* Build configuration list for PBXNativeTarget "amadeusTests" */; + buildPhases = ( + 82491A732EBF029A001A2D13 /* Sources */, + 82491A742EBF029A001A2D13 /* Frameworks */, + 82491A752EBF029A001A2D13 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 82491A792EBF029A001A2D13 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 82491A7A2EBF029A001A2D13 /* amadeusTests */, + ); + name = amadeusTests; + packageProductDependencies = ( + ); + productName = amadeusTests; + productReference = 82491A772EBF029A001A2D13 /* amadeusTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 82491A802EBF029A001A2D13 /* amadeusUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 82491A912EBF029A001A2D13 /* Build configuration list for PBXNativeTarget "amadeusUITests" */; + buildPhases = ( + 82491A7D2EBF029A001A2D13 /* Sources */, + 82491A7E2EBF029A001A2D13 /* Frameworks */, + 82491A7F2EBF029A001A2D13 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 82491A832EBF029A001A2D13 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 82491A842EBF029A001A2D13 /* amadeusUITests */, + ); + name = amadeusUITests; + packageProductDependencies = ( + ); + productName = amadeusUITests; + productReference = 82491A812EBF029A001A2D13 /* amadeusUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 82491A622EBF0299001A2D13 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 82491A692EBF0299001A2D13 = { + CreatedOnToolsVersion = 16.4; + }; + 82491A762EBF029A001A2D13 = { + CreatedOnToolsVersion = 16.4; + TestTargetID = 82491A692EBF0299001A2D13; + }; + 82491A802EBF029A001A2D13 = { + CreatedOnToolsVersion = 16.4; + TestTargetID = 82491A692EBF0299001A2D13; + }; + }; + }; + buildConfigurationList = 82491A652EBF0299001A2D13 /* Build configuration list for PBXProject "amadeus" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 82491A612EBF0299001A2D13; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 82491A942EBF0382001A2D13 /* XCLocalSwiftPackageReference "../../swift_libs/Tonic" */, + 82491A952EBF038B001A2D13 /* XCLocalSwiftPackageReference "../../swift_libs/AudioKit" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 82491A6B2EBF0299001A2D13 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 82491A692EBF0299001A2D13 /* amadeus */, + 82491A762EBF029A001A2D13 /* amadeusTests */, + 82491A802EBF029A001A2D13 /* amadeusUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 82491A682EBF0299001A2D13 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 82E95AB62ED001DD00520824 /* The_Pogues-A Pair_of_Brown_Eyes.wav in Resources */, + 82E95AB72ED001DD00520824 /* Nada-Nahuel_Pennisi_Luis_Salinas.wav in Resources */, + 82E95AB82ED001DD00520824 /* 3-Part_Inventions_ (Sinfonias)_No.6_in_E_Major_BWV 792.mp3 in Resources */, + 82E95AB92ED001DD00520824 /* Edie_Brickell-Good_Times.wav in Resources */, + 82E95ABA2ED001DD00520824 /* Flim.mp3 in Resources */, + 82E95ABB2ED001DD00520824 /* Les_Trois_Valses_distinguées_du_précieux_dégoûté_II. Son binocle.mp3 in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 82491A752EBF029A001A2D13 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 82491A7F2EBF029A001A2D13 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 82491A662EBF0299001A2D13 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 82491A732EBF029A001A2D13 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 82491A7D2EBF029A001A2D13 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 82491A792EBF029A001A2D13 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 82491A692EBF0299001A2D13 /* amadeus */; + targetProxy = 82491A782EBF029A001A2D13 /* PBXContainerItemProxy */; + }; + 82491A832EBF029A001A2D13 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 82491A692EBF0299001A2D13 /* amadeus */; + targetProxy = 82491A822EBF029A001A2D13 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 82491A892EBF029A001A2D13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = XBUUBY6Z76; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 82491A8A2EBF029A001A2D13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = XBUUBY6Z76; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 82491A8C2EBF029A001A2D13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XBUUBY6Z76; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = amadeus/Info.plist; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Amadeus needs access to devices on your local network to communicate with the Basic Pitch analysis server for chord recognition."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "gaucho-dsp.amadeus"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 82491A8D2EBF029A001A2D13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XBUUBY6Z76; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = amadeus/Info.plist; + INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Amadeus needs access to devices on your local network to communicate with the Basic Pitch analysis server for chord recognition."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "gaucho-dsp.amadeus"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 82491A8F2EBF029A001A2D13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XBUUBY6Z76; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "gaucho-dsp.amadeusTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/amadeus.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/amadeus"; + }; + name = Debug; + }; + 82491A902EBF029A001A2D13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XBUUBY6Z76; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "gaucho-dsp.amadeusTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/amadeus.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/amadeus"; + }; + name = Release; + }; + 82491A922EBF029A001A2D13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XBUUBY6Z76; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "gaucho-dsp.amadeusUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = amadeus; + }; + name = Debug; + }; + 82491A932EBF029A001A2D13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XBUUBY6Z76; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "gaucho-dsp.amadeusUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = amadeus; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 82491A652EBF0299001A2D13 /* Build configuration list for PBXProject "amadeus" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 82491A892EBF029A001A2D13 /* Debug */, + 82491A8A2EBF029A001A2D13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 82491A8B2EBF029A001A2D13 /* Build configuration list for PBXNativeTarget "amadeus" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 82491A8C2EBF029A001A2D13 /* Debug */, + 82491A8D2EBF029A001A2D13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 82491A8E2EBF029A001A2D13 /* Build configuration list for PBXNativeTarget "amadeusTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 82491A8F2EBF029A001A2D13 /* Debug */, + 82491A902EBF029A001A2D13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 82491A912EBF029A001A2D13 /* Build configuration list for PBXNativeTarget "amadeusUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 82491A922EBF029A001A2D13 /* Debug */, + 82491A932EBF029A001A2D13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 82491A942EBF0382001A2D13 /* XCLocalSwiftPackageReference "../../swift_libs/Tonic" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../swift_libs/Tonic; + }; + 82491A952EBF038B001A2D13 /* XCLocalSwiftPackageReference "../../swift_libs/AudioKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../swift_libs/AudioKit; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 82E95AA22ECFFC4700520824 /* AudioKit */ = { + isa = XCSwiftPackageProductDependency; + package = 82491A952EBF038B001A2D13 /* XCLocalSwiftPackageReference "../../swift_libs/AudioKit" */; + productName = AudioKit; + }; + 82E95AA42ECFFC8700520824 /* Tonic */ = { + isa = XCSwiftPackageProductDependency; + package = 82491A942EBF0382001A2D13 /* XCLocalSwiftPackageReference "../../swift_libs/Tonic" */; + productName = Tonic; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 82491A622EBF0299001A2D13 /* Project object */; +} diff --git a/Amadeus/amadeus/amadeus.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Amadeus/amadeus/amadeus.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Amadeus/amadeus/amadeus.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Amadeus/amadeus/amadeus/Assets.xcassets/AccentColor.colorset/Contents.json b/Amadeus/amadeus/amadeus/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Amadeus/amadeus/amadeus/Assets.xcassets/AppIcon.appiconset/Contents.json b/Amadeus/amadeus/amadeus/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3682511 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "amadeuslogo.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Amadeus/amadeus/amadeus/Assets.xcassets/AppIcon.appiconset/amadeuslogo.png b/Amadeus/amadeus/amadeus/Assets.xcassets/AppIcon.appiconset/amadeuslogo.png new file mode 100644 index 0000000..ed3e49a Binary files /dev/null and b/Amadeus/amadeus/amadeus/Assets.xcassets/AppIcon.appiconset/amadeuslogo.png differ diff --git a/Amadeus/amadeus/amadeus/Assets.xcassets/Contents.json b/Amadeus/amadeus/amadeus/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Amadeus/amadeus/amadeus/Assets.xcassets/amadeuslogo.imageset/Contents.json b/Amadeus/amadeus/amadeus/Assets.xcassets/amadeuslogo.imageset/Contents.json new file mode 100644 index 0000000..5110110 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Assets.xcassets/amadeuslogo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "amadeuslogo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "amadeuslogo.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "amadeuslogo.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/Amadeus/amadeus/amadeus/Assets.xcassets/amadeuslogo.imageset/amadeuslogo.png b/Amadeus/amadeus/amadeus/Assets.xcassets/amadeuslogo.imageset/amadeuslogo.png new file mode 100644 index 0000000..ed3e49a Binary files /dev/null and b/Amadeus/amadeus/amadeus/Assets.xcassets/amadeuslogo.imageset/amadeuslogo.png differ diff --git a/Amadeus/amadeus/amadeus/Info.plist b/Amadeus/amadeus/amadeus/Info.plist new file mode 100644 index 0000000..ebf8211 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Info.plist @@ -0,0 +1,13 @@ + + + + + NSBonjourServices + + + _http._tcp + + NSMicrophoneUsageDescription + Amadeus needs microphone access to record audio for chord analysis. Record songs and discover their chord progressions in real-time. + + diff --git a/Amadeus/amadeus/amadeus/MainTabView.swift b/Amadeus/amadeus/amadeus/MainTabView.swift new file mode 100644 index 0000000..5320e41 --- /dev/null +++ b/Amadeus/amadeus/amadeus/MainTabView.swift @@ -0,0 +1,46 @@ +import SwiftUI + +// main navigation structure with tab-based interface +struct MainTabView: View { + // shared audio manager instance for all tabs + @StateObject private var audioManager = AudioManager() + // currently selected tab index + @State private var selectedTab = 0 + + var body: some View { + TabView(selection: $selectedTab) { + // analyse tab + AnalyseView(audioManager: audioManager) + .tabItem { + Label("Analyse", systemImage: "waveform") + } + .tag(0) + + // library tab + LibraryView() + .tabItem { + Label("Library", systemImage: "books.vertical") + } + .tag(1) + + // live tab + LiveView() + .tabItem { + Label("Live", systemImage: "mic.circle") + } + .tag(2) + + // profile tab + ProfileView() + .tabItem { + Label("Profile", systemImage: "person.circle") + } + .tag(3) + } + .accentColor(.blue) + } +} + +#Preview { + MainTabView() +} \ No newline at end of file diff --git a/Amadeus/amadeus/amadeus/Models/AnalysisManager.swift b/Amadeus/amadeus/amadeus/Models/AnalysisManager.swift new file mode 100644 index 0000000..fb793f2 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Models/AnalysisManager.swift @@ -0,0 +1,107 @@ +import Foundation +import AVFoundation +import AudioKit + +// manages audio analysis for chord detection and key estimation +class AnalysisManager: ObservableObject { +//analysis state tracking + @Published var isAnalyzing = false +//progress of current analysis from 0 to 1 + @Published var analysisProgress: Float = 0 +//human-readable status message + @Published var analysisStatus = "" +//detected chord segments with timing information + @Published var chordDetections: [ChordDetection] = [] +//estimated key of the user loaded file + @Published var estimatedKey = "—" +//total duration of analysed audio in seconds + @Published var duration: Double = 0 + +// create fresh pipeline each time to read current settings + private func createPipeline() -> ChordDetectionPipeline { + print("AnalysisManager creating fresh pipeline...") + return ChordDetectionPipeline() + } + +// analyse audio file for chord detection and key estimation + func analyzeAudioFile(_ url: URL) async { + await MainActor.run { + self.isAnalyzing = true + self.analysisStatus = "Loading audio file..." + self.analysisProgress = 0 + } + + do { + let pipeline = createPipeline() + let result = try await pipeline.analyzeFile(url) { progress in + Task { @MainActor in + self.analysisProgress = progress + +// update status based on progress with smooth transitions + switch progress { + case 0..<0.1: + self.analysisStatus = "Loading audio..." + case 0.1..<0.2: + self.analysisStatus = "Preparing audio data..." + case 0.2..<0.3: + self.analysisStatus = "Extracting audio features..." + case 0.3..<0.7: + self.analysisStatus = "Analyzing pitch and harmony..." + case 0.7..<0.9: + self.analysisStatus = "Detecting chord progressions..." + case 0.9..<1.0: + self.analysisStatus = "Estimating key signature..." + default: + self.analysisStatus = "Finalizing analysis..." + } + } + } + + await MainActor.run { + self.chordDetections = result.detections + self.estimatedKey = result.estimatedKey + self.duration = result.duration + self.isAnalyzing = false + self.analysisStatus = "Analysis complete" + +// increment stats + let songsAnalysed = UserDefaults.standard.integer(forKey: "songsAnalysed") + UserDefaults.standard.set(songsAnalysed + 1, forKey: "songsAnalysed") + + let uniqueChords = Set(result.detections.map { $0.chordName }).count + let currentChords = UserDefaults.standard.integer(forKey: "chordsLearned") + UserDefaults.standard.set(max(currentChords, uniqueChords), forKey: "chordsLearned") + + print("Found \(result.detections.count) chord segments") + for detection in result.detections.prefix(5) { + print(" \(detection.startTime)s - \(detection.endTime)s: \(detection.chordName) (\(detection.confidence))") + } + } + + } catch { + await MainActor.run { + self.analysisStatus = "Analysis failed: \(error.localizedDescription)" + self.isAnalyzing = false + } + } + } + +// get the chord playing at a specific time + func getChordAt(time: Double) -> String? { + for detection in chordDetections { + if time >= detection.startTime && time < detection.endTime { + return detection.chordName + } + } + return nil + } + +// reset all analysis data + func reset() { + chordDetections = [] + estimatedKey = "—" + duration = 0 + analysisProgress = 0 + analysisStatus = "" + } +} diff --git a/Amadeus/amadeus/amadeus/Models/AudioManager.swift b/Amadeus/amadeus/amadeus/Models/AudioManager.swift new file mode 100644 index 0000000..edc0857 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Models/AudioManager.swift @@ -0,0 +1,326 @@ +import Foundation +import AudioKit +import AVFoundation + +// manages audio playback, processing and analysis for the application +class AudioManager: ObservableObject { +// audiokit engine for audio processing + private var engine: AudioEngine +// audio player instance for file playback + private var player: AudioPlayer? +// variable speed processor for tempo adjustment + private var variSpeed: VariSpeed? +// pitch shifting processor for transposition + private var timePitch: TimePitch? + +// playback state tracking + @Published var isPlaying = false +// indicates whether an audio file is currently loaded + @Published var isFileLoaded = false +// status message for user feedback + @Published var statusMessage = "No file loaded" +// current chord being played at the current playback position + @Published var currentChord = "—" +// original key of the loaded audio file + @Published var originalKey = "C major" +// current key after pitch transposition + @Published var currentKey = "C major" +// current playback position in seconds + @Published var currentTime: Double = 0 +// total duration of the loaded audio file + @Published var duration: Double = 0 +// shows loading indicator during analysis + @Published var showAnalysisLoading = false +// shows completion message after analysis + @Published var showAnalysisComplete = false +// playback speed multiplier with automatic update + @Published var playbackSpeed: Float = 1.0 { + didSet { updateSpeed() } + } +// pitch transposition in semitones with automatic update + @Published var pitchShift: Int = 0 { + didSet { updatePitch() } + } + +// manages chord and key analysis + let analysisManager = AnalysisManager() +// timer for tracking playback position + private var playbackTimer: Timer? + +// initialise audio manager with fresh engine + init() { + engine = AudioEngine() + } + +// load and prepare an audio file for playback and analysis + func loadFile(_ url: URL) { + print("Loading audio file: \(url.lastPathComponent)") + + do { +// stop current engine and reset completely + engine.stop() + +// reset audio session to ensure proper volume levels + do { + let audioSession = AVAudioSession.sharedInstance() + try audioSession.setCategory(.playback, mode: .default, options: []) + try audioSession.setActive(true) + } catch { + print("AudioSession setup warning: \(error)") + } + +// files from documentpicker are already copied to temp directory +// bundle resources don't need security scope + let isTempFile = url.path.contains(FileManager.default.temporaryDirectory.path) + let isBundleFile = url.path.contains(Bundle.main.bundlePath) + + print(" • File path: \(url.path)") + print(" • Is temp file: \(isTempFile)") + print(" • Is bundle file: \(isBundleFile)") + +// validate file exists and is readable + guard FileManager.default.fileExists(atPath: url.path) else { + statusMessage = "File not found" + print("File not found: \(url.path)") + return + } + +// try to create avaudiofile with better error handling + let audioFile: AVAudioFile + do { + audioFile = try AVAudioFile(forReading: url) + print("Audio file opened successfully") + print(" • Format: \(audioFile.fileFormat)") + print(" • Duration: \(Double(audioFile.length) / audioFile.fileFormat.sampleRate) seconds") + } catch let avError as NSError { + let errorMessage = "Cannot read audio file (Error \(avError.code))" + statusMessage = errorMessage + print("AVAudioFile error: \(avError.localizedDescription)") + print(" Domain: \(avError.domain), Code: \(avError.code)") + return + } + +// validate audio format + guard audioFile.length > 0 else { + statusMessage = "Audio file is empty" + print("Audio file is empty") + return + } + +// create fresh audio chain + player = AudioPlayer(file: audioFile) + variSpeed = VariSpeed(player!) + timePitch = TimePitch(variSpeed!) + + engine.output = timePitch + +// start engine + try engine.start() + print("AudioKit engine started") + +// get duration + duration = Double(audioFile.length) / audioFile.fileFormat.sampleRate + + isFileLoaded = true + statusMessage = "File loaded: \(url.lastPathComponent)" + +// reset controls when loading new file + pitchShift = 0 + playbackSpeed = 1.0 + +// show loading animation and start analysis + showAnalysisLoading = true + + Task { + await analysisManager.analyzeAudioFile(url) + await MainActor.run { + self.originalKey = analysisManager.estimatedKey + self.currentKey = self.transposeKey(analysisManager.estimatedKey, semitones: self.pitchShift) + +// hide loading and show completion + self.showAnalysisLoading = false + self.showAnalysisComplete = true + +// auto-hide completion after a moment + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + self.showAnalysisComplete = false + } + + print("Analysis complete. Ready for playback.") + } + } + +// start playback time tracking + startPlaybackTimer() + + } catch { + let errorMessage = "Error loading audio: \(error.localizedDescription)" + statusMessage = errorMessage + print("Audio loading failed: \(error)") + } + } + +// start audio playback + func play() { + player?.play() + isPlaying = true + } + +// pause audio playback + func pause() { + player?.pause() + isPlaying = false + } + +// stop audio playback and reset position + func stop() { + player?.stop() + isPlaying = false + currentTime = 0 + currentChord = "—" + } + + //stop and reset audio engine completely + func stopEngine() { + stop() + engine.stop() + } + +// seek to specific position in the audio file + func seek(to time: Double) { + guard let player = player, duration > 0 else { return } + + let clampedTime = max(0, min(duration, time)) + print("SEEK: \(currentTime) -> \(clampedTime)") + +// completely stop timer + playbackTimer?.invalidate() + playbackTimer = nil + +// do the actual seek + player.stop() + player.seek(time: clampedTime) + +// force update current time immediately + currentTime = clampedTime + +// restart playback if it was playing + if isPlaying { + player.play() + } + +// always restart the timer + startPlaybackTimer() + } + +// update playback speed with pitch compensation + private func updateSpeed() { +// varispeed changes both speed and pitch together +// we need to compensate with opposite pitch shift + variSpeed?.rate = AUValue(playbackSpeed) + +// compensate pitch change from speed +// when speed = 0.5 (octave down), we need +1200 cents (octave up) +// when speed = 2.0 (octave up), we need -1200 cents (octave down) + let pitchCompensation = -1200.0 * log2(playbackSpeed) + timePitch?.pitch = AUValue(pitchCompensation + Float(pitchShift * 100)) + } + +// update pitch transposition + private func updatePitch() { +// apply pitch shift while maintaining speed compensation + let pitchCompensation = -1200.0 * log2(playbackSpeed) + timePitch?.pitch = AUValue(pitchCompensation + Float(pitchShift * 100)) + +// update the transposed key + currentKey = transposeKey(originalKey, semitones: pitchShift) + } + +// start timer for tracking playback position and updating current chord + private func startPlaybackTimer() { + playbackTimer?.invalidate() + playbackTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in + if self.isPlaying, let player = self.player { + self.currentTime = player.currentTime + +// update current chord from analysis and apply transposition + if let chord = self.analysisManager.getChordAt(time: self.currentTime) { + if self.pitchShift != 0 { + self.currentChord = self.transposeChord(chord, semitones: self.pitchShift) + } else { + self.currentChord = chord + } + } + +// loop at end + if self.currentTime >= self.duration { + self.stop() + } + } + } + } + +// transpose a key by given semitones + private func transposeKey(_ key: String, semitones: Int) -> String { + guard semitones != 0 else { return key } + + let notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + let altNotes = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"] + +// parse the key (e.g., "c major" or "a minor") + let components = key.split(separator: " ") + guard components.count >= 1 else { return key } + + let rootNote = String(components[0]) + let quality = components.count > 1 ? String(components[1]) : "" + +// find the index of the root note + let currentIndex = notes.firstIndex(of: rootNote) ?? altNotes.firstIndex(of: rootNote) ?? 0 + +// transpose + let newIndex = (currentIndex + semitones + 12) % 12 + let newRoot = notes[newIndex] + + return quality.isEmpty ? newRoot : "\(newRoot) \(quality)" + } + +// transpose a chord symbol by given semitones + private func transposeChord(_ chord: String, semitones: Int) -> String { + guard semitones != 0 else { return chord } + + let notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + let altNotes = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"] + +// handle common chord formats: c, cm, c7, cmaj7, etc. + var rootNote = "" + var suffix = "" + +// try to extract root note (may be 1 or 2 characters) + if chord.count >= 2 && (chord.dropFirst().first == "#" || chord.dropFirst().first == "b") { + rootNote = String(chord.prefix(2)) + suffix = String(chord.dropFirst(2)) + } else if chord.count >= 1 { + rootNote = String(chord.prefix(1)) + suffix = String(chord.dropFirst(1)) + } else { + return chord + } + +// find the index of the root note + let currentIndex = notes.firstIndex(of: rootNote) ?? altNotes.firstIndex(of: rootNote) ?? 0 + +// transpose + let newIndex = (currentIndex + semitones + 12) % 12 + let newRoot = notes[newIndex] + + return "\(newRoot)\(suffix)" + } + +// clean up resources when audio manager is deallocated + deinit { + engine.stop() + playbackTimer?.invalidate() + } +} + + diff --git a/Amadeus/amadeus/amadeus/Models/AudioRecorder.swift b/Amadeus/amadeus/amadeus/Models/AudioRecorder.swift new file mode 100644 index 0000000..4accef4 --- /dev/null +++ b/Amadeus/amadeus/amadeus/Models/AudioRecorder.swift @@ -0,0 +1,172 @@ +import Foundation +import AVFoundation + +// handles audio recording functionality +class AudioRecorder: NSObject, ObservableObject { + private var audioRecorder: AVAudioRecorder? + private var audioSession: AVAudioSession? + + @Published var isRecording = false + @Published var recordingURL: URL? + @Published var recordingTime: TimeInterval = 0 + @Published var hasRecording = false + + private var recordingTimer: Timer? + + override init() { + super.init() + setupAudioSession() + } + + private func setupAudioSession() { + audioSession = AVAudioSession.sharedInstance() + + do { + try audioSession?.setCategory(.playAndRecord, mode: .default) + try audioSession?.setActive(true) + + // request recording permission + if #available(iOS 17.0, *) { + AVAudioApplication.requestRecordPermission { allowed in + if !allowed { + print("Recording permission denied") + } + } + } else { + audioSession?.requestRecordPermission { allowed in + if !allowed { + print("Recording permission denied") + } + } + } + } catch { + print("Failed to set up audio session: \(error)") + } + } + + // start recording audio + func startRecording() { + // create unique file name in temp directory + let tempDir = FileManager.default.temporaryDirectory + let fileName = "recording_\(Date().timeIntervalSince1970).wav" + let fileURL = tempDir.appendingPathComponent(fileName) + + // configure recording settings for wav format + let settings: [String: Any] = [ + AVFormatIDKey: Int(kAudioFormatLinearPCM), + AVSampleRateKey: 44100.0, + AVNumberOfChannelsKey: 1, + AVLinearPCMBitDepthKey: 16, + AVLinearPCMIsFloatKey: false, + AVLinearPCMIsBigEndianKey: false, + AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue + ] + + do { + audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings) + audioRecorder?.delegate = self + audioRecorder?.prepareToRecord() + + // start recording + audioRecorder?.record() + isRecording = true + hasRecording = false + recordingURL = fileURL + recordingTime = 0 + + // start timer to track recording duration + recordingTimer?.invalidate() + recordingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in + self?.recordingTime = self?.audioRecorder?.currentTime ?? 0 + + // limit recording to 30 seconds for performance + if (self?.recordingTime ?? 0) >= 30 { + self?.stopRecording() + } + } + + print("Started recording to: \(fileURL.lastPathComponent)") + + } catch { + print("Failed to start recording: \(error)") + } + } + + // stop recording and save file + func stopRecording() { + audioRecorder?.stop() + isRecording = false + hasRecording = true + recordingTimer?.invalidate() + recordingTimer = nil + + if let url = recordingURL { + print("Stopped recording. File saved to: \(url.lastPathComponent)") + print(" Duration: \(recordingTime) seconds") + } + } + + //delete the recorded file + func deleteRecording() { + guard let url = recordingURL else { return } + + //stop recording if still active + if isRecording { + stopRecording() + } + + do { + try FileManager.default.removeItem(at: url) + recordingURL = nil + hasRecording = false + recordingTime = 0 + audioRecorder = nil + print("recording deleted") + } catch { + print("failed to delete recording: \(error)") + } + } + + //reset state for new recording + func resetForNewRecording() { + if isRecording { + stopRecording() + } + + //clean up previous recording + if let url = recordingURL { + try? FileManager.default.removeItem(at: url) + } + + recordingURL = nil + hasRecording = false + recordingTime = 0 + audioRecorder = nil + + //reset audio session + setupAudioSession() + } + + // get url of recorded file + func getRecordingURL() -> URL? { + return recordingURL + } +} + +// MARK: - avaudiorecorderdelegate +extension AudioRecorder: AVAudioRecorderDelegate { + func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { + if flag { + print("Recording finished successfully") + } else { + print("Recording failed") + hasRecording = false + } + } + + func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) { + if let error = error { + print("Recording encode error: \(error)") + } + } +} \ No newline at end of file diff --git a/Amadeus/amadeus/amadeus/Models/BasicPitchAnalyzer.swift b/Amadeus/amadeus/amadeus/Models/BasicPitchAnalyzer.swift new file mode 100644 index 0000000..22b094a --- /dev/null +++ b/Amadeus/amadeus/amadeus/Models/BasicPitchAnalyzer.swift @@ -0,0 +1,413 @@ +import Foundation +import AVFoundation +import CoreML +import Accelerate + +// MARK: - Basic Pitch Analyzer + +class BasicPitchAnalyzer: ChordAnalyzer { + + private var model: nmp? + private let chordAssembler = ChordAssembler() + + enum BasicPitchError: Error { + case modelLoadFailed + case invalidAudioFormat + case predictionFailed + case modelNotFound + } + + init() { + loadModel() + } + + private func loadModel() { + print("Attempting to load Basic Pitch model...") + do { + let configuration = MLModelConfiguration() + configuration.computeUnits = .all + model = try nmp(configuration: configuration) + print("Basic Pitch model loaded successfully") + print(" Model: \(model.debugDescription)") + } catch { + print("Failed to load Basic Pitch model: \(error)") + print(" Error details: \(error.localizedDescription)") + } + } + + func analyze(audioBuffer: AVAudioPCMBuffer, sampleRate: Float) async -> [ChordDetection] { + print("BasicPitchAnalyzer.analyze() called") + print(" • Buffer: \(audioBuffer.frameLength) frames @ \(sampleRate) Hz") + + // Check if model is available + guard model != nil else { + print("Basic Pitch model not available, falling back to simulation") + print(" Model is nil - check if nmp.mlpackage is in the project") + return await SimulatedChordAnalyzer().analyze(audioBuffer: audioBuffer, sampleRate: sampleRate) + } + + print("Model is loaded, attempting analysis...") + + do { + // Step 1: Convert audio to raw sample chunks for CoreML model + print(" Step 1: Converting audio to 43,844-sample chunks...") + let modelInputs = try BasicPitchPreprocessor.audioBufferToModelInputs(audioBuffer) + print(" Created \(modelInputs.count) audio chunks of 43,844 samples each") + + var allNoteEvents: [NoteEvent] = [] + let numChunks = modelInputs.count + let chunkDurationSeconds = Double(BasicPitchPreprocessor.chunkSampleCount) / BasicPitchPreprocessor.targetSampleRate + + for chunkIndex in 0.. 5 { + print(" Extracted \(adjustedNotes.count) notes from chunk \(chunkIndex + 1)") + } + } + + print(" Total: \(allNoteEvents.count) notes from \(numChunks) chunks") + + // Step 3: Convert note events to chord detections + print(" Step 3: Assembling chords...") + let chordDetections = chordAssembler.assembleChords(from: allNoteEvents) + print(" Assembled \(chordDetections.count) chord segments") + + print("✅ Basic Pitch analysis complete: \(allNoteEvents.count) notes → \(chordDetections.count) chords") + + // Print first few chords for debugging + for (index, chord) in chordDetections.prefix(5).enumerated() { + print(" Chord \(index + 1): \(chord.chordName) at \(String(format: "%.2f", chord.startTime))s") + } + + return chordDetections + + } catch { + print(" Basic Pitch analysis failed at some step") + print(" Error type: \(type(of: error))") + print(" Error: \(error)") + print(" Falling back to simulation") + return await SimulatedChordAnalyzer().analyze(audioBuffer: audioBuffer, sampleRate: sampleRate) + } + } + + // MARK: - Audio Preprocessing (now handled by BasicPitchPreprocessor) + + // MARK: - CoreML Inference + + private func runBasicPitchInference(_ audioInput: MLMultiArray) async throws -> BasicPitchOutput { + guard let model = model else { + throw BasicPitchError.modelNotFound + } + + // Create input for the auto-generated nmp model + let input = nmpInput(input_2: audioInput) + + // Run prediction using the auto-generated nmp class + let output = try await Task { + try model.prediction(input: input) + }.value + + // Extract outputs from the nmpOutput + return try extractModelOutputs(output) + } + + private func extractModelOutputs(_ output: nmpOutput) throws -> BasicPitchOutput { + // Extract outputs using MLFeatureProvider interface since property names may vary + + var onsetArray: MLMultiArray? + var frameArray: MLMultiArray? + var contourArray: MLMultiArray? + + // Basic Pitch typically has: onset (Identity_1), frame (Identity_2), contour (Identity) + // Let's map them correctly based on common patterns + for name in output.featureNames { + if let array = output.featureValue(for: name)?.multiArrayValue { + // Map based on common Basic Pitch naming patterns + if name.contains("1") || name.contains("onset") { + onsetArray = array + } else if name.contains("2") || name.contains("frame") { + frameArray = array + } else { + contourArray = array + } + } + } + + guard let onsets = onsetArray else { + print(" No outputs found in model response!") + print(" Available outputs: \(output.featureNames)") + throw BasicPitchError.predictionFailed + } + + // If we only have one output, duplicate it for frame (temporary workaround) + if frameArray == nil { + print(" ⚠️ Only one output found, using it for both onset and frame") + frameArray = onsets + } + + guard let frames = frameArray else { + throw BasicPitchError.predictionFailed + } + + let onsetProbs = convertMLArrayTo2D(onsets) + let frameProbs = convertMLArrayTo2D(frames) + let contourProbs = contourArray.map { convertMLArrayTo2D($0) } + + // TEMP DEBUG: Verify dimensions and pitch mapping + if !onsetProbs.isEmpty { + print("DEBUG: timeFrames = \(onsetProbs.count), pitches = \(onsetProbs[0].count)") + for p in 0.. [[Float]] { + let shape = array.shape.map { $0.intValue } + + switch shape.count { + case 3: + // Expect [batch, time, pitch] = [1, 172, 88] + let batch = shape[0] + let time = shape[1] + let pitches = shape[2] + + precondition(batch == 1, "Only batch size 1 is supported") + + let totalCount = batch * time * pitches + let ptr = array.dataPointer.bindMemory(to: Float.self, + capacity: totalCount) + + var result = Array( + repeating: Array(repeating: Float(0), count: pitches), + count: time + ) + + // Row-major layout: index = b*(time*pitches) + t*pitches + p + for t in 0..