diff options
-rw-r--r-- | PrySDR.xcodeproj/project.pbxproj | 377 | ||||
-rw-r--r-- | Radio/Utils/WaterfallFile/NaiveFFT512.swift | 24 | ||||
-rw-r--r-- | WaterfallFile_UI/ContentView.swift | 1 | ||||
-rw-r--r-- | WaterfallFile_UI/FileSpectrum.swift | 2 | ||||
-rw-r--r-- | Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json | 11 | ||||
-rw-r--r-- | Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json | 58 | ||||
-rw-r--r-- | Waterfall_UI/Assets.xcassets/Contents.json | 6 | ||||
-rw-r--r-- | Waterfall_UI/ContentView.swift | 194 | ||||
-rw-r--r-- | Waterfall_UI/Item.swift | 18 | ||||
-rw-r--r-- | Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json | 6 | ||||
-rw-r--r-- | Waterfall_UI/SDRSpectrum.swift | 137 | ||||
-rw-r--r-- | Waterfall_UI/Waterfall_UI.entitlements | 12 | ||||
-rw-r--r-- | Waterfall_UI/Waterfall_UIApp.swift | 46 | ||||
-rw-r--r-- | Waterfall_UITests/Waterfall_UITests.swift | 17 | ||||
-rw-r--r-- | Waterfall_UIUITests/Waterfall_UIUITests.swift | 43 | ||||
-rw-r--r-- | Waterfall_UIUITests/Waterfall_UIUITestsLaunchTests.swift | 33 |
16 files changed, 984 insertions, 1 deletions
diff --git a/PrySDR.xcodeproj/project.pbxproj b/PrySDR.xcodeproj/project.pbxproj index 72c61bf..817e90c 100644 --- a/PrySDR.xcodeproj/project.pbxproj +++ b/PrySDR.xcodeproj/project.pbxproj @@ -68,6 +68,9 @@ 8D9A33502D0B0DD8009A4186 /* libbladerf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D87709D2CD6B4BB0082EC54 /* libbladerf.a */; }; 8D9A33522D0B0DE5009A4186 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 8D9A33512D0B0DE5009A4186 /* ArgumentParser */; }; 8D9A38852D19D427009A4186 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 8D9A38842D19D427009A4186 /* ArgumentParser */; }; + 8DBA9FA22D37C5C8008ECB92 /* libr820.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D5A5DB42CD4B70D0096CBD7 /* libr820.a */; }; + 8DBA9FA32D37C5CD008ECB92 /* libusb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D5A5DDA2CD4B9100096CBD7 /* libusb.a */; }; + 8DBA9FA52D37C5D1008ECB92 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 8DBA9FA42D37C5D1008ECB92 /* ArgumentParser */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -239,6 +242,20 @@ remoteGlobalIDString = 8D9A38762D196510009A4186; remoteInfo = WaterfallFile; }; + 8DBA9F822D2C979A008ECB92 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8DD98C392CC592540062D678 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DBA9F6E2D2C9796008ECB92; + remoteInfo = Waterfall_UI; + }; + 8DBA9F8C2D2C979A008ECB92 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8DD98C392CC592540062D678 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DBA9F6E2D2C9796008ECB92; + remoteInfo = Waterfall_UI; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -355,6 +372,9 @@ 8D9A38582D194A41009A4186 /* WaterfallFile_UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WaterfallFile_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8D9A38622D194A41009A4186 /* WaterfallFile_UIUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WaterfallFile_UIUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8D9A38772D196510009A4186 /* waterfall_file */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = waterfall_file; sourceTree = BUILT_PRODUCTS_DIR; }; + 8DBA9F6F2D2C9796008ECB92 /* Waterfall_UI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Waterfall_UI.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8DBA9F812D2C979A008ECB92 /* Waterfall_UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Waterfall_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8DBA9F8B2D2C979A008ECB92 /* Waterfall_UIUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Waterfall_UIUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8DD98C412CC592540062D678 /* prysdr */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = prysdr; sourceTree = BUILT_PRODUCTS_DIR; }; 8DD98C722CC632040062D678 /* MatrixXT.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatrixXT.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -814,6 +834,7 @@ membershipExceptions = ( FileReader.swift, PathUtils.swift, + Version.swift, ); target = 8D9A38762D196510009A4186 /* WaterfallFile */; }; @@ -833,6 +854,22 @@ ); target = 8D9A38452D194A3D009A4186 /* WaterfallFile_UI */; }; + 8DBA9F9E2D313168008ECB92 /* Exceptions for "Utils" folder in "Waterfall_UI" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Version.swift, + ); + target = 8DBA9F6E2D2C9796008ECB92 /* Waterfall_UI */; + }; + 8DBA9FA72D37CD92008ECB92 /* Exceptions for "Radio" folder in "Waterfall_UI" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + HW/RtlSdr/r820/r820.swift, + Utils/WaterfallFile/NaiveFFT512.swift, + Utils/WaterfallFile/SimpleImage.swift, + ); + target = 8DBA9F6E2D2C9796008ECB92 /* Waterfall_UI */; + }; 8DD98C7C2CC6320C0062D678 /* Exceptions for "LA" folder in "PrySDR" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -876,6 +913,21 @@ path = WaterfallFile_UIUITests; sourceTree = "<group>"; }; + 8DBA9F702D2C9796008ECB92 /* Waterfall_UI */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Waterfall_UI; + sourceTree = "<group>"; + }; + 8DBA9F842D2C979A008ECB92 /* Waterfall_UITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Waterfall_UITests; + sourceTree = "<group>"; + }; + 8DBA9F8E2D2C979A008ECB92 /* Waterfall_UIUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Waterfall_UIUITests; + sourceTree = "<group>"; + }; 8DD98C432CC592540062D678 /* PrySDR */ = { isa = PBXFileSystemSynchronizedRootGroup; path = PrySDR; @@ -909,6 +961,7 @@ 8D9A334E2D0B0D92009A4186 /* Exceptions for "Radio" folder in "BladeRFIQ" target */, 8DBA9F602D2928DE008ECB92 /* Exceptions for "Radio" folder in "WaterfallFile_UI" target */, 8D9A38812D196520009A4186 /* Exceptions for "Radio" folder in "WaterfallFile" target */, + 8DBA9FA72D37CD92008ECB92 /* Exceptions for "Radio" folder in "Waterfall_UI" target */, ); path = Radio; sourceTree = "<group>"; @@ -969,6 +1022,7 @@ 8D9A37F12D180D4C009A4186 /* Exceptions for "Utils" folder in "BladeRFIQ" target */, 8DBA9F652D2B5158008ECB92 /* Exceptions for "Utils" folder in "WaterfallFile_UI" target */, 8D9A38832D19D277009A4186 /* Exceptions for "Utils" folder in "WaterfallFile" target */, + 8DBA9F9E2D313168008ECB92 /* Exceptions for "Utils" folder in "Waterfall_UI" target */, ); path = Utils; sourceTree = "<group>"; @@ -1121,6 +1175,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8DBA9F6C2D2C9796008ECB92 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DBA9FA52D37C5D1008ECB92 /* ArgumentParser in Frameworks */, + 8DBA9FA32D37C5CD008ECB92 /* libusb.a in Frameworks */, + 8DBA9FA22D37C5C8008ECB92 /* libr820.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DBA9F7E2D2C979A008ECB92 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DBA9F882D2C979A008ECB92 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DD98C3E2CC592540062D678 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1171,6 +1249,9 @@ 8D9A38472D194A3D009A4186 /* WaterfallFile_UI */, 8D9A385B2D194A41009A4186 /* WaterfallFile_UITests */, 8D9A38652D194A41009A4186 /* WaterfallFile_UIUITests */, + 8DBA9F702D2C9796008ECB92 /* Waterfall_UI */, + 8DBA9F842D2C979A008ECB92 /* Waterfall_UITests */, + 8DBA9F8E2D2C979A008ECB92 /* Waterfall_UIUITests */, 8DD98C422CC592540062D678 /* Products */, 8DD98C642CC599CA0062D678 /* Series */, 8DD98C7F2CC7852E0062D678 /* Images */, @@ -1200,6 +1281,9 @@ 8D9A38582D194A41009A4186 /* WaterfallFile_UITests.xctest */, 8D9A38622D194A41009A4186 /* WaterfallFile_UIUITests.xctest */, 8D9A38772D196510009A4186 /* waterfall_file */, + 8DBA9F6F2D2C9796008ECB92 /* Waterfall_UI.app */, + 8DBA9F812D2C979A008ECB92 /* Waterfall_UITests.xctest */, + 8DBA9F8B2D2C979A008ECB92 /* Waterfall_UIUITests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -1607,6 +1691,75 @@ productReference = 8D9A38772D196510009A4186 /* waterfall_file */; productType = "com.apple.product-type.tool"; }; + 8DBA9F6E2D2C9796008ECB92 /* Waterfall_UI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8DBA9F932D2C979A008ECB92 /* Build configuration list for PBXNativeTarget "Waterfall_UI" */; + buildPhases = ( + 8DBA9F6B2D2C9796008ECB92 /* Sources */, + 8DBA9F6C2D2C9796008ECB92 /* Frameworks */, + 8DBA9F6D2D2C9796008ECB92 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 8DBA9F702D2C9796008ECB92 /* Waterfall_UI */, + ); + name = Waterfall_UI; + packageProductDependencies = ( + 8DBA9FA42D37C5D1008ECB92 /* ArgumentParser */, + ); + productName = Waterfall_UI; + productReference = 8DBA9F6F2D2C9796008ECB92 /* Waterfall_UI.app */; + productType = "com.apple.product-type.application"; + }; + 8DBA9F802D2C979A008ECB92 /* Waterfall_UITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8DBA9F962D2C979A008ECB92 /* Build configuration list for PBXNativeTarget "Waterfall_UITests" */; + buildPhases = ( + 8DBA9F7D2D2C979A008ECB92 /* Sources */, + 8DBA9F7E2D2C979A008ECB92 /* Frameworks */, + 8DBA9F7F2D2C979A008ECB92 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8DBA9F832D2C979A008ECB92 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 8DBA9F842D2C979A008ECB92 /* Waterfall_UITests */, + ); + name = Waterfall_UITests; + packageProductDependencies = ( + ); + productName = Waterfall_UITests; + productReference = 8DBA9F812D2C979A008ECB92 /* Waterfall_UITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 8DBA9F8A2D2C979A008ECB92 /* Waterfall_UIUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8DBA9F992D2C979A008ECB92 /* Build configuration list for PBXNativeTarget "Waterfall_UIUITests" */; + buildPhases = ( + 8DBA9F872D2C979A008ECB92 /* Sources */, + 8DBA9F882D2C979A008ECB92 /* Frameworks */, + 8DBA9F892D2C979A008ECB92 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8DBA9F8D2D2C979A008ECB92 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 8DBA9F8E2D2C979A008ECB92 /* Waterfall_UIUITests */, + ); + name = Waterfall_UIUITests; + packageProductDependencies = ( + ); + productName = Waterfall_UIUITests; + productReference = 8DBA9F8B2D2C979A008ECB92 /* Waterfall_UIUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 8DD98C402CC592540062D678 /* PrySDR */ = { isa = PBXNativeTarget; buildConfigurationList = 8DD98C482CC592540062D678 /* Build configuration list for PBXNativeTarget "PrySDR" */; @@ -1728,6 +1881,17 @@ 8D9A38762D196510009A4186 = { CreatedOnToolsVersion = 16.2; }; + 8DBA9F6E2D2C9796008ECB92 = { + CreatedOnToolsVersion = 16.2; + }; + 8DBA9F802D2C979A008ECB92 = { + CreatedOnToolsVersion = 16.2; + TestTargetID = 8DBA9F6E2D2C9796008ECB92; + }; + 8DBA9F8A2D2C979A008ECB92 = { + CreatedOnToolsVersion = 16.2; + TestTargetID = 8DBA9F6E2D2C9796008ECB92; + }; 8DD98C402CC592540062D678 = { CreatedOnToolsVersion = 16.0; }; @@ -1773,6 +1937,9 @@ 8D9A38572D194A41009A4186 /* WaterfallFile_UITests */, 8D9A38612D194A41009A4186 /* WaterfallFile_UIUITests */, 8D9A38762D196510009A4186 /* WaterfallFile */, + 8DBA9F6E2D2C9796008ECB92 /* Waterfall_UI */, + 8DBA9F802D2C979A008ECB92 /* Waterfall_UITests */, + 8DBA9F8A2D2C979A008ECB92 /* Waterfall_UIUITests */, ); }; /* End PBXProject section */ @@ -1799,6 +1966,27 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8DBA9F6D2D2C9796008ECB92 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DBA9F7F2D2C979A008ECB92 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DBA9F892D2C979A008ECB92 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DD98C702CC632040062D678 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1928,6 +2116,27 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8DBA9F6B2D2C9796008ECB92 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DBA9F7D2D2C979A008ECB92 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DBA9F872D2C979A008ECB92 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DD98C3D2CC592540062D678 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2065,6 +2274,16 @@ target = 8D9A38762D196510009A4186 /* WaterfallFile */; targetProxy = 8D9A38882D19D4E1009A4186 /* PBXContainerItemProxy */; }; + 8DBA9F832D2C979A008ECB92 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DBA9F6E2D2C9796008ECB92 /* Waterfall_UI */; + targetProxy = 8DBA9F822D2C979A008ECB92 /* PBXContainerItemProxy */; + }; + 8DBA9F8D2D2C979A008ECB92 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DBA9F6E2D2C9796008ECB92 /* Waterfall_UI */; + targetProxy = 8DBA9F8C2D2C979A008ECB92 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -2585,6 +2804,132 @@ }; name = Release; }; + 8DBA9F942D2C979A008ECB92 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Waterfall_UI/Waterfall_UI.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Waterfall_UI/Preview Content\""; + DEVELOPMENT_TEAM = 53B26AJZ4Z; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "RadioTeam.Waterfall-UI"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 8DBA9F952D2C979A008ECB92 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Waterfall_UI/Waterfall_UI.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Waterfall_UI/Preview Content\""; + DEVELOPMENT_TEAM = 53B26AJZ4Z; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "RadioTeam.Waterfall-UI"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 8DBA9F972D2C979A008ECB92 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 53B26AJZ4Z; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "RadioTeam.Waterfall-UITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Waterfall_UI.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Waterfall_UI"; + }; + name = Debug; + }; + 8DBA9F982D2C979A008ECB92 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 53B26AJZ4Z; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "RadioTeam.Waterfall-UITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Waterfall_UI.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Waterfall_UI"; + }; + name = Release; + }; + 8DBA9F9A2D2C979A008ECB92 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 53B26AJZ4Z; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "RadioTeam.Waterfall-UIUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = Waterfall_UI; + }; + name = Debug; + }; + 8DBA9F9B2D2C979A008ECB92 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 53B26AJZ4Z; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "RadioTeam.Waterfall-UIUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = Waterfall_UI; + }; + name = Release; + }; 8DD98C462CC592540062D678 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2926,6 +3271,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 8DBA9F932D2C979A008ECB92 /* Build configuration list for PBXNativeTarget "Waterfall_UI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8DBA9F942D2C979A008ECB92 /* Debug */, + 8DBA9F952D2C979A008ECB92 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8DBA9F962D2C979A008ECB92 /* Build configuration list for PBXNativeTarget "Waterfall_UITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8DBA9F972D2C979A008ECB92 /* Debug */, + 8DBA9F982D2C979A008ECB92 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8DBA9F992D2C979A008ECB92 /* Build configuration list for PBXNativeTarget "Waterfall_UIUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8DBA9F9A2D2C979A008ECB92 /* Debug */, + 8DBA9F9B2D2C979A008ECB92 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8DD98C3C2CC592540062D678 /* Build configuration list for PBXProject "PrySDR" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2997,6 +3369,11 @@ package = 8D0349232CF70E180026DA77 /* XCRemoteSwiftPackageReference "swift-argument-parser" */; productName = ArgumentParser; }; + 8DBA9FA42D37C5D1008ECB92 /* ArgumentParser */ = { + isa = XCSwiftPackageProductDependency; + package = 8D0349232CF70E180026DA77 /* XCRemoteSwiftPackageReference "swift-argument-parser" */; + productName = ArgumentParser; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 8DD98C392CC592540062D678 /* Project object */; diff --git a/Radio/Utils/WaterfallFile/NaiveFFT512.swift b/Radio/Utils/WaterfallFile/NaiveFFT512.swift index b4dca7e..0e6373a 100644 --- a/Radio/Utils/WaterfallFile/NaiveFFT512.swift +++ b/Radio/Utils/WaterfallFile/NaiveFFT512.swift @@ -46,4 +46,28 @@ class NaiveFFT512 { return transform_result } + func computeLine1024(_ processingArray: [Int8]) -> [Float] { + + var dataFloat = [Float](repeating: 0.0, count: NaiveFFT512.sampleCount*2) + + if processingArray.count != NaiveFFT512.sampleCount*2 { + print("Not supporting arrays not equail to \(NaiveFFT512.sampleCount)") + return [] + } + + vDSP.convertElements(of: processingArray, to: &dataFloat) + //dataFloat = vDSP.add(127.0, dataFloat) + //print(dataFloat) + + //move from -127.0 to 128.0 range -1.0...1.0 + //var adjusted = vDSP.divide(dataFloat, Float(sampleCount)) + var adjusted = dataFloat + //print(adjusted) + + var transform_result = forwardDCT.transform(adjusted) + transform_result = vDSP.absolute(transform_result) + + return transform_result + } + } diff --git a/WaterfallFile_UI/ContentView.swift b/WaterfallFile_UI/ContentView.swift index 92445d8..d6809b3 100644 --- a/WaterfallFile_UI/ContentView.swift +++ b/WaterfallFile_UI/ContentView.swift @@ -51,6 +51,7 @@ struct ContentView: View { scale: 1, orientation: .left) //.resizable() + .rotationEffect(Angle(degrees: 90)) //Image(fileSpectrum.makeSpectrogramImage(), scale: 1, orientation:.left, label: "Image") diff --git a/WaterfallFile_UI/FileSpectrum.swift b/WaterfallFile_UI/FileSpectrum.swift index ce64836..f816f37 100644 --- a/WaterfallFile_UI/FileSpectrum.swift +++ b/WaterfallFile_UI/FileSpectrum.swift @@ -96,7 +96,7 @@ class FileSpectrum: NSObject, ObservableObject { sessionQueue.async { print("lets start the task for spectrum analysis") for i in 0..<512 { - print("Process the task \(i)") + print("Process the task line \(i)") self.simpleImage.drawPixel(i, i, PixelData(a: 255, r: 255, g: 0, b: 0)) DispatchQueue.main.async { self.dataFileProcessor() diff --git a/Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json b/Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Waterfall_UI/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json b/Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/Waterfall_UI/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/Assets.xcassets/Contents.json b/Waterfall_UI/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Waterfall_UI/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/ContentView.swift b/Waterfall_UI/ContentView.swift new file mode 100644 index 0000000..6c09fec --- /dev/null +++ b/Waterfall_UI/ContentView.swift @@ -0,0 +1,194 @@ +// +// ContentView.swift +// Waterfall_UI +// +// Created by Jacky Jack on 06/01/2025. +// + +import SwiftUI +import SwiftData + +private struct DeviceChoose: Identifiable { + let deviceName: String + var id: String { deviceName } +} + +private let deviceList: [DeviceChoose] = [ + DeviceChoose(deviceName:"RtlSDR SN:00000000"), + DeviceChoose(deviceName:"BladeRF SN:00000000"), + DeviceChoose(deviceName:"AirSpy SN:00000000"), + DeviceChoose(deviceName:"AirSpyHF SN:00000000") +] + +private struct SampleRateChoose: Identifiable { + let sampleRate: Int + var id: String { String(sampleRate) } +} + +private let sampleRateList: [SampleRateChoose] = [ + SampleRateChoose(sampleRate:1000), + SampleRateChoose(sampleRate:100000) +] + +private struct FFTOptions: Identifiable { + let binsize: Int + var id: String { String(binsize) } +} + +private let fftBinsList: [FFTOptions] = [ + FFTOptions(binsize: 512), + FFTOptions(binsize: 1024), + FFTOptions(binsize: 2048), + FFTOptions(binsize: 4096), +] + +struct ContentView: View { + @Environment(\.modelContext) private var modelContext + + @EnvironmentObject var sdrSpectrum: SDRSpectrum + + @Query private var items: [Item] + + @State private var selectedDevice: String = deviceList[0].deviceName + @State private var selectedSampleRate: String = String(sampleRateList[0].sampleRate) + @State private var setFrequency: Int = 100000000 + @State private var toggleAGC: Bool = false + @State private var selectorFFTBin: String = String(fftBinsList[0].binsize) + @State private var statusMessage: String = "" + + + var body: some View { + /* + NavigationSplitView { + List { + ForEach(items) { item in + NavigationLink { + Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") + } label: { + Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) + } + } + .onDelete(perform: deleteItems) + } + .navigationSplitViewColumnWidth(min: 180, ideal: 200) + .toolbar { + ToolbarItem { + Button(action: addItem) { + Label("Add Item", systemImage: "plus") + } + } + } + } detail: { + Text("Select an item") + } + */ + HStack { + VStack { + Text("Hardware")//.border(.red) + Picker("Device", selection:$selectedDevice) { + ForEach(deviceList) { device in + Text("\(device.deviceName)") + } + } + .onChange(of: selectedDevice) { + statusMessage = "selected \(selectedDevice)" + } + Picker("SampleRate", selection:$selectedSampleRate) { + ForEach(sampleRateList) { samplerate in + Text("\(samplerate.sampleRate)") + } + }.onChange(of:selectedSampleRate) { + statusMessage = "selected \(selectedSampleRate)" + } + Divider() + HStack { + Text("Control") + Button("Start") { + print("Start") + statusMessage = "Running" + } + Button("Stop") { + print("Stop") + statusMessage = "Idle" + } + } + Divider() + Text("Config") + HStack { + Text("Frequency: ") + TextField("Frquency: ", value: $setFrequency, format: .number) + .onChange(of: setFrequency) { + statusMessage = "frequency \(setFrequency)" + } + .onSubmit { + statusMessage = "frequency submit" + } + Text("Hz") + } + Toggle("AGC",isOn: $toggleAGC) + .onChange(of: toggleAGC) { + if (self.toggleAGC) { + statusMessage = "AGC on" + } else { + statusMessage = "AGC off" + } + } + Divider() + Text("FFT View") + Picker("FFT bins", selection:$selectorFFTBin) { + ForEach(fftBinsList) { binssize in + Text("\(binssize.binsize)") + } + } + .onChange(of: selectorFFTBin) { + statusMessage = "set fft bins \(selectorFFTBin)" + } + Spacer() + Divider()//.border(.red) + + HStack { + Text("Version: \(software_version)")//.border(.red) + Spacer() + } + } + Divider() + VStack { + //Text("Right")//.border(.red) + Divider() + Image(decorative: sdrSpectrum.outputImage, + scale: 1, + orientation: .left) + .resizable() + .scaledToFit() + .rotationEffect(Angle(degrees: 90)) + Spacer() + Divider()//.border(.red) + + HStack { + Spacer() + Text("Status message: \(statusMessage)")//.border(.red) + } + } + } + } + + private func addItem() { + withAnimation { + let newItem = Item(timestamp: Date()) + modelContext.insert(newItem) + } + } + + private func deleteItems(offsets: IndexSet) { + withAnimation { + for index in offsets { + modelContext.delete(items[index]) + } + } + } +} + +#Preview { + ContentView() + .modelContainer(for: Item.self, inMemory: true) +} diff --git a/Waterfall_UI/Item.swift b/Waterfall_UI/Item.swift new file mode 100644 index 0000000..0f2a8ae --- /dev/null +++ b/Waterfall_UI/Item.swift @@ -0,0 +1,18 @@ +// +// Item.swift +// Waterfall_UI +// +// Created by Jacky Jack on 06/01/2025. +// + +import Foundation +import SwiftData + +@Model +final class Item { + var timestamp: Date + + init(timestamp: Date) { + self.timestamp = timestamp + } +} diff --git a/Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json b/Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Waterfall_UI/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Waterfall_UI/SDRSpectrum.swift b/Waterfall_UI/SDRSpectrum.swift new file mode 100644 index 0000000..c83e802 --- /dev/null +++ b/Waterfall_UI/SDRSpectrum.swift @@ -0,0 +1,137 @@ +// +// SDRSpectrum.swift +// PrySDR +// +// Created by Jacky Jack on 10/01/2025. +// + +// +// FileSpectrum.swift +// PrySDR +// +// Created by Jacky Jack on 01/01/2025. +// + +import Accelerate +import AppKit +import Combine +import CoreImage + +import libr820 + +class SDRSpectrum: NSObject, ObservableObject { + + static let default_width = 512 + static let default_height = 512 + static let defaultSleepTime:UInt32 = 1 + var offset:Int=0 + var allData:[Int8]? = nil + var line_counter:Int = 0 + + //SDR default configs + let device_idx: UInt32 = 0 + let samplerate: Int = 2048000 + let gain: Int = 0 + let frequency: Int = 99500000 + let nsamples: Int = default_width + var device: R820Tuner? + + //fft drawing related + let fft512 = NaiveFFT512() + + let sessionQueue = DispatchQueue(label: "sessionQueue", + attributes: [], + autoreleaseFrequency: .workItem) + + @Published var outputImage = emptyCGImage + + let simpleImage = SimpleImage(width: default_width, height: default_height) + + /// A 1x1 Core Graphics image. + static var emptyCGImage: CGImage = { + let buffer = vImage.PixelBuffer( + pixelValues: [0], + size: .init(width: 1, height: 1), + pixelFormat: vImage.Planar8.self) + + let fmt = vImage_CGImageFormat( + bitsPerComponent: 8, + bitsPerPixel: 8 , + colorSpace: CGColorSpaceCreateDeviceGray(), + bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue), + renderingIntent: .defaultIntent) + + return buffer.makeCGImage(cgImageFormat: fmt!)! + }() + + func makeSpectrogramImage() -> NSImage { + + return NSImage(cgImage: SDRSpectrum.emptyCGImage, size: .zero) + } + + //just get data time to time from databuffer and pretend to be realtime data + func dataFileProcessor() { + + + } + + func startRunning() { + let buf_ptr = UnsafeMutableRawPointer.allocate(byteCount: nsamples, alignment: 1) + var nbytes:Int32 = 0 + //var total_bytes:Int32 = 0 + + + //prepare sdr + print("Prepare SDR") + let count = getDeviceCount() + print("Found \(count) devices") + self.device = R820Tuner() + sleep(1) + if (self.device != nil) { + self.device!.open(index:0) + + //prepare dongle to receive some data + let _ = self.device!.setSampleRate(samplerate: UInt32(samplerate)) + let _ = self.device!.setCenterFreq(freq: UInt32(frequency)) + let _ = self.device!.setAgcMode(on: 1) + let _ = self.device!.resetBuffer() + } + //run the task + sessionQueue.async { + print("lets start the task for spectrum analysis") + for i in 0..<512 { + + if let dev = self.device { + let ret = dev.readSync(buf: buf_ptr, len: Int32(self.nsamples), n_read: &nbytes) + if ret<0 { + print("data read sync returned <0 = \(ret)") + sleep(1); + continue; + } + } + print("Got \(nbytes) bytes") + + /* + let dataU8 = buf_ptr.bindMemory(to: UInt8.self, capacity: self.nsamples) + let bufferU8 = UnsafeBufferPointer(start: dataU8, count: self.nsamples) + let convertedData = Data(bufferU8) + */ + let dataI8 = buf_ptr.bindMemory(to: Int8.self, capacity: self.nsamples) + let bufferI8 = UnsafeBufferPointer(start: dataI8, count: self.nsamples) + let safeI8 = Array(bufferI8) + + let transform_result = self.fft512.computeLine(safeI8) + print(transform_result) + print("FFT result \(transform_result.count) points") + //let drawSlice = transform_result[0...SDRSpectrum.default_width-1] + //print("FFT result \(drawSlice.count) points") + self.simpleImage.drawPalletLine(line: i, pixelLine: transform_result) + self.outputImage = self.simpleImage.toCGImage()! + + print("Process the task line \(i)") + sleep(1) + } + print("SDR loop done run") + } + } +} diff --git a/Waterfall_UI/Waterfall_UI.entitlements b/Waterfall_UI/Waterfall_UI.entitlements new file mode 100644 index 0000000..9c8ecca --- /dev/null +++ b/Waterfall_UI/Waterfall_UI.entitlements @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.app-sandbox</key> + <true/> + <key>com.apple.security.device.usb</key> + <true/> + <key>com.apple.security.files.user-selected.read-only</key> + <true/> +</dict> +</plist> diff --git a/Waterfall_UI/Waterfall_UIApp.swift b/Waterfall_UI/Waterfall_UIApp.swift new file mode 100644 index 0000000..6d9a13b --- /dev/null +++ b/Waterfall_UI/Waterfall_UIApp.swift @@ -0,0 +1,46 @@ +// +// Waterfall_UIApp.swift +// Waterfall_UI +// +// Created by Jacky Jack on 06/01/2025. +// + +import SwiftUI +import SwiftData + + +@main +struct Waterfall_UIApp: App { + + let sdrSpectrum = SDRSpectrum() + + @Environment(\.scenePhase) private var scenePhase + + var sharedModelContainer: ModelContainer = { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + do { + return try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + }() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(sdrSpectrum) + .onChange(of: scenePhase) { phase in + if phase == .active { + Task(priority: .userInitiated) { + sdrSpectrum.startRunning() + } + } + } + } + .modelContainer(sharedModelContainer) + } +} diff --git a/Waterfall_UITests/Waterfall_UITests.swift b/Waterfall_UITests/Waterfall_UITests.swift new file mode 100644 index 0000000..6f9a614 --- /dev/null +++ b/Waterfall_UITests/Waterfall_UITests.swift @@ -0,0 +1,17 @@ +// +// Waterfall_UITests.swift +// Waterfall_UITests +// +// Created by Jacky Jack on 06/01/2025. +// + +import Testing +@testable import Waterfall_UI + +struct Waterfall_UITests { + + @Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + } + +} diff --git a/Waterfall_UIUITests/Waterfall_UIUITests.swift b/Waterfall_UIUITests/Waterfall_UIUITests.swift new file mode 100644 index 0000000..4b8348c --- /dev/null +++ b/Waterfall_UIUITests/Waterfall_UIUITests.swift @@ -0,0 +1,43 @@ +// +// Waterfall_UIUITests.swift +// Waterfall_UIUITests +// +// Created by Jacky Jack on 06/01/2025. +// + +import XCTest + +final class Waterfall_UIUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + @MainActor + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + @MainActor + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Waterfall_UIUITests/Waterfall_UIUITestsLaunchTests.swift b/Waterfall_UIUITests/Waterfall_UIUITestsLaunchTests.swift new file mode 100644 index 0000000..d4d2fb8 --- /dev/null +++ b/Waterfall_UIUITests/Waterfall_UIUITestsLaunchTests.swift @@ -0,0 +1,33 @@ +// +// Waterfall_UIUITestsLaunchTests.swift +// Waterfall_UIUITests +// +// Created by Jacky Jack on 06/01/2025. +// + +import XCTest + +final class Waterfall_UIUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} |