diff --git a/software/source/clients/ios/README.md b/software/source/clients/ios/README.md new file mode 100644 index 0000000..2eaeac1 --- /dev/null +++ b/software/source/clients/ios/README.md @@ -0,0 +1,29 @@ +# iOS/iPadOS Native Client + +This repository contains the source code for the 01 iOS/iPadOS Native app. It is a work in progress and currently has a dedicated development button. + +Feel free to improve this and make a pull request! + +To run it on your own, you can either install the app directly through the current TestFlight [here](https://testflight.apple.com/join/v8SyuzMT), or build from the source code files in Xcode on your Mac. + +## Instructions + +Follow the **[software setup steps](https://github.com/OpenInterpreter/01?tab=readme-ov-file#software)** in the main repo's README first before you read this + +In Xcode, open the 'zerooone-app' project file in the project folder, change the Signing Team and Bundle Identifier, and build. + +## Using the App + +To use the app there are four features: + +### 1. The speak "Button" +Made to emulate the button on the hardware models of 01, the big, yellow circle in the middle of the screen is what you hold when you want to speak to the model, and let go when you're finished speaking. + +### 2. The settings button +Tapping the settings button will allow you to input your websocket address so that the app can properly connect to your computer. If you're not sure how to obtain this, read the **'How to Install'** section below! + +### 3. The reconnect button +The arrow will be RED when the websocket connection is not live, and GREEN when it is. If you're making some changes you can easily reconnect by simply tapping the arrow button (or you can just start holding the speak button, too!). + +### 4. The terminal button +The terminal button allows you to see all response text coming in from the server side of the 01. You can toggle it by tapping on the button, and each toggle clears the on-device cache of text. diff --git a/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.pbxproj b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.pbxproj new file mode 100644 index 0000000..37a8592 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.pbxproj @@ -0,0 +1,415 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 750E4C0B2BEDD11C00AEE3B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750E4C0A2BEDD11C00AEE3B1 /* AppDelegate.swift */; }; + 750E4C0D2BEDD11C00AEE3B1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750E4C0C2BEDD11C00AEE3B1 /* SceneDelegate.swift */; }; + 750E4C0F2BEDD11C00AEE3B1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750E4C0E2BEDD11C00AEE3B1 /* ViewController.swift */; }; + 750E4C122BEDD11C00AEE3B1 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 750E4C112BEDD11C00AEE3B1 /* Base */; }; + 750E4C142BEDD11D00AEE3B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 750E4C132BEDD11D00AEE3B1 /* Assets.xcassets */; }; + 750E4C172BEDD11D00AEE3B1 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 750E4C162BEDD11D00AEE3B1 /* Base */; }; + 750E4C202BEDD16E00AEE3B1 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 750E4C1F2BEDD16E00AEE3B1 /* Starscream */; }; + 755DC3B22BEE60A7002B66DF /* AudioRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755DC3B12BEE60A7002B66DF /* AudioRecording.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 750E4C072BEDD11C00AEE3B1 /* zeroone-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "zeroone-app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 750E4C0A2BEDD11C00AEE3B1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 750E4C0C2BEDD11C00AEE3B1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 750E4C0E2BEDD11C00AEE3B1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 750E4C112BEDD11C00AEE3B1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 750E4C132BEDD11D00AEE3B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 750E4C162BEDD11D00AEE3B1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 750E4C182BEDD11D00AEE3B1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 755DC3B12BEE60A7002B66DF /* AudioRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecording.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 750E4C042BEDD11C00AEE3B1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 750E4C202BEDD16E00AEE3B1 /* Starscream in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 750E4BFE2BEDD11C00AEE3B1 = { + isa = PBXGroup; + children = ( + 750E4C092BEDD11C00AEE3B1 /* zeroone-app */, + 750E4C082BEDD11C00AEE3B1 /* Products */, + ); + sourceTree = ""; + }; + 750E4C082BEDD11C00AEE3B1 /* Products */ = { + isa = PBXGroup; + children = ( + 750E4C072BEDD11C00AEE3B1 /* zeroone-app.app */, + ); + name = Products; + sourceTree = ""; + }; + 750E4C092BEDD11C00AEE3B1 /* zeroone-app */ = { + isa = PBXGroup; + children = ( + 750E4C0A2BEDD11C00AEE3B1 /* AppDelegate.swift */, + 750E4C0C2BEDD11C00AEE3B1 /* SceneDelegate.swift */, + 750E4C0E2BEDD11C00AEE3B1 /* ViewController.swift */, + 750E4C102BEDD11C00AEE3B1 /* Main.storyboard */, + 750E4C132BEDD11D00AEE3B1 /* Assets.xcassets */, + 750E4C152BEDD11D00AEE3B1 /* LaunchScreen.storyboard */, + 750E4C182BEDD11D00AEE3B1 /* Info.plist */, + 755DC3B12BEE60A7002B66DF /* AudioRecording.swift */, + ); + path = "zeroone-app"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 750E4C062BEDD11C00AEE3B1 /* zeroone-app */ = { + isa = PBXNativeTarget; + buildConfigurationList = 750E4C1B2BEDD11D00AEE3B1 /* Build configuration list for PBXNativeTarget "zeroone-app" */; + buildPhases = ( + 750E4C032BEDD11C00AEE3B1 /* Sources */, + 750E4C042BEDD11C00AEE3B1 /* Frameworks */, + 750E4C052BEDD11C00AEE3B1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "zeroone-app"; + packageProductDependencies = ( + 750E4C1F2BEDD16E00AEE3B1 /* Starscream */, + ); + productName = "zeroone-app"; + productReference = 750E4C072BEDD11C00AEE3B1 /* zeroone-app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 750E4BFF2BEDD11C00AEE3B1 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + 750E4C062BEDD11C00AEE3B1 = { + CreatedOnToolsVersion = 15.3; + }; + }; + }; + buildConfigurationList = 750E4C022BEDD11C00AEE3B1 /* Build configuration list for PBXProject "zeroone-app" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 750E4BFE2BEDD11C00AEE3B1; + packageReferences = ( + 750E4C1E2BEDD16D00AEE3B1 /* XCRemoteSwiftPackageReference "Starscream" */, + ); + productRefGroup = 750E4C082BEDD11C00AEE3B1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 750E4C062BEDD11C00AEE3B1 /* zeroone-app */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 750E4C052BEDD11C00AEE3B1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 750E4C142BEDD11D00AEE3B1 /* Assets.xcassets in Resources */, + 750E4C172BEDD11D00AEE3B1 /* Base in Resources */, + 750E4C122BEDD11C00AEE3B1 /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 750E4C032BEDD11C00AEE3B1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 750E4C0F2BEDD11C00AEE3B1 /* ViewController.swift in Sources */, + 750E4C0B2BEDD11C00AEE3B1 /* AppDelegate.swift in Sources */, + 755DC3B22BEE60A7002B66DF /* AudioRecording.swift in Sources */, + 750E4C0D2BEDD11C00AEE3B1 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 750E4C102BEDD11C00AEE3B1 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 750E4C112BEDD11C00AEE3B1 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 750E4C152BEDD11D00AEE3B1 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 750E4C162BEDD11D00AEE3B1 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 750E4C192BEDD11D00AEE3B1 /* 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; + 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 = 15.0; + 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; + }; + 750E4C1A2BEDD11D00AEE3B1 /* 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"; + 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 = 15.0; + 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; + }; + 750E4C1C2BEDD11D00AEE3B1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_TEAM = W5NGQJV8X2; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "zeroone-app/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = 01ForiOS; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Audio data from microphone is needed to send commands."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 15; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ontheroofstudios.zeroone-app"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 750E4C1D2BEDD11D00AEE3B1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_TEAM = W5NGQJV8X2; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "zeroone-app/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = 01ForiOS; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Audio data from microphone is needed to send commands."; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 15; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.ontheroofstudios.zeroone-app"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 750E4C022BEDD11C00AEE3B1 /* Build configuration list for PBXProject "zeroone-app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 750E4C192BEDD11D00AEE3B1 /* Debug */, + 750E4C1A2BEDD11D00AEE3B1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 750E4C1B2BEDD11D00AEE3B1 /* Build configuration list for PBXNativeTarget "zeroone-app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 750E4C1C2BEDD11D00AEE3B1 /* Debug */, + 750E4C1D2BEDD11D00AEE3B1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 750E4C1E2BEDD16D00AEE3B1 /* XCRemoteSwiftPackageReference "Starscream" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/daltoniam/Starscream"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.8; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 750E4C1F2BEDD16E00AEE3B1 /* Starscream */ = { + isa = XCSwiftPackageProductDependency; + package = 750E4C1E2BEDD16D00AEE3B1 /* XCRemoteSwiftPackageReference "Starscream" */; + productName = Starscream; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 750E4BFF2BEDD11C00AEE3B1 /* Project object */; +} diff --git a/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcuserdata/eladdekel.xcuserdatad/UserInterfaceState.xcuserstate b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcuserdata/eladdekel.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..9ad6955 Binary files /dev/null and b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcuserdata/eladdekel.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcuserdata/eladdekel.xcuserdatad/WorkspaceSettings.xcsettings b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcuserdata/eladdekel.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..bbfef02 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app.xcodeproj/project.xcworkspace/xcuserdata/eladdekel.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,14 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + ShowSharedSchemesAutomaticallyEnabled + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/AppDelegate.swift b/software/source/clients/ios/zeroone-app/zeroone-app/AppDelegate.swift new file mode 100644 index 0000000..de09029 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// zeroone-app +// +// Created by Elad Dekel on 2024-05-09. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AccentColor.colorset/Contents.json b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AppIcon.appiconset/Contents.json b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..4e17533 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "O.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AppIcon.appiconset/O.png b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AppIcon.appiconset/O.png new file mode 100644 index 0000000..0e7193d Binary files /dev/null and b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/AppIcon.appiconset/O.png differ diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/Contents.json b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/vector.imageset/Contents.json b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/vector.imageset/Contents.json new file mode 100644 index 0000000..4311484 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/vector.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "vector.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/vector.imageset/vector.svg b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/vector.imageset/vector.svg new file mode 100644 index 0000000..49c449b --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Assets.xcassets/vector.imageset/vector.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/AudioRecording.swift b/software/source/clients/ios/zeroone-app/zeroone-app/AudioRecording.swift new file mode 100644 index 0000000..46cde0e --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/AudioRecording.swift @@ -0,0 +1,67 @@ +// +// AudioRecording.swift +// zeroone-app +// +// Created by Elad Dekel on 2024-05-10. +// + +import Foundation +import AVFoundation + +class AudioRecording: NSObject, AVAudioRecorderDelegate { + + var recorder: AVAudioRecorder! + var session: AVAudioSession! + + var isRecording = false + + func startRecording() { + session = AVAudioSession() + let audio = getDocumentsDirectory().appendingPathComponent("tempvoice.wav") // indicates where the audio data will be recording to + let s: [String: Any] = [ + AVFormatIDKey: kAudioFormatLinearPCM, + AVSampleRateKey: 16000.0, + AVNumberOfChannelsKey: 1, + AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue + ] + do { + recorder = try AVAudioRecorder(url: audio, settings: s) + try session.setActive(true) + try session.setCategory(.playAndRecord, mode: .default) + recorder!.delegate = self + recorder!.record() + isRecording = true + + } catch { + print("Error recording") + print(error.localizedDescription) + } + + } + + func getDocumentsDirectory() -> URL { // big thanks to twostraws for this helper function (hackingwithswift.com) + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } + + + func stopRecording() -> Data? { + if isRecording && recorder != nil { + recorder!.stop() + let audio = getDocumentsDirectory().appendingPathComponent("tempvoice.wav") + recorder = nil + do { + let data = try Data(contentsOf: audio) // sends raw audio data + try FileManager.default.removeItem(at: audio) // deletes the file + return data + } catch { + print(error.localizedDescription) + return nil + } + } else { + print("not recording") + return nil + } + } + +} diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Base.lproj/LaunchScreen.storyboard b/software/source/clients/ios/zeroone-app/zeroone-app/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Base.lproj/Main.storyboard b/software/source/clients/ios/zeroone-app/zeroone-app/Base.lproj/Main.storyboard new file mode 100644 index 0000000..d9cd8b2 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Base.lproj/Main.storyboard @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/Info.plist b/software/source/clients/ios/zeroone-app/zeroone-app/Info.plist new file mode 100644 index 0000000..aef04f9 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/Info.plist @@ -0,0 +1,27 @@ + + + + + NSSpeechRecognitionUsageDescription + Your audio is used the convert your voice requests into text requests. + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/SceneDelegate.swift b/software/source/clients/ios/zeroone-app/zeroone-app/SceneDelegate.swift new file mode 100644 index 0000000..a67a1a3 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// zeroone-app +// +// Created by Elad Dekel on 2024-05-09. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/software/source/clients/ios/zeroone-app/zeroone-app/ViewController.swift b/software/source/clients/ios/zeroone-app/zeroone-app/ViewController.swift new file mode 100644 index 0000000..0b39175 --- /dev/null +++ b/software/source/clients/ios/zeroone-app/zeroone-app/ViewController.swift @@ -0,0 +1,366 @@ +// +// ViewController.swift +// zeroone-app +// +// Created by Elad Dekel on 2024-05-09. +// + +import UIKit +import Starscream +import AVFoundation + + +class ViewController: UIViewController, WebSocketDelegate { + + @IBOutlet weak var terminalFeed: UITextView! + @IBOutlet weak var terminalButton: UIImageView! + @IBOutlet weak var reconnectIcon: UIImageView! + @IBOutlet weak var circle: UIImageView! + @IBOutlet weak var settingsGear: UIImageView! + @IBOutlet weak var infoText: UILabel! + + var audioRecordingInstance: AudioRecording? + private var audioData = Data() + private var audioPlayer: AVAudioPlayer? + var address: String? + var isConnected = false + var recordingPermission = false + var terminal = false + var socket: WebSocket? + + + override func viewDidLoad() { + super.viewDidLoad() + terminalFeed.layer.cornerRadius = 15 + infoText.text = "Hold to start once connected." + // Create a gesture recognizer that tracks when the "button" is held + let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(buttonPress(_:))) + pressGesture.minimumPressDuration = 0.01 + circle.addGestureRecognizer(pressGesture) + circle.isUserInteractionEnabled = true + circle.translatesAutoresizingMaskIntoConstraints = false + + // Create a geature recognizer for the settings button + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(settingsGear(_:))) + settingsGear.addGestureRecognizer(tapGesture) + settingsGear.isUserInteractionEnabled = true + + + let reconnectGesture = UITapGestureRecognizer(target: self, action: #selector(recconectIcon(_:))) + reconnectIcon.addGestureRecognizer(reconnectGesture) + reconnectIcon.isUserInteractionEnabled = true + + + let terminal = UITapGestureRecognizer(target: self, action: #selector(terminalIcon(_:))) + terminalButton.addGestureRecognizer(terminal) + terminalButton.isUserInteractionEnabled = true + + + } + + + + + + + func checkRecordingPerms() { + let sess = AVAudioSession.sharedInstance() + switch (sess.recordPermission) { + case.denied, .undetermined: + sess.requestRecordPermission { (granted) in + if granted { + self.recordingPermission = true + } else { + let alert = UIAlertController(title: "Recording Not Permitted", message: "You must allow audio recording in order to send commands. Close the app and re-open it to try again.", preferredStyle: .alert) + let action = UIAlertAction(title: "Understood", style: .default) + alert.addAction(action) + self.present(alert, animated: true) + } + } + case .granted: + recordingPermission = true + default: + break + } + } + + override func viewDidAppear(_ animated: Bool) { + if ((UserDefaults.standard.value(forKey: "IPINFO")) != nil) { + print("here") + address = UserDefaults.standard.string(forKey: "IPINFO") + establishConnection() + } else { + print("there") + setAddress() + } + checkRecordingPerms() + } + + func receieved(data: String) { + infoText.text = data + } + + + + + + func setAddress() { + let alert = UIAlertController(title: "Set the Address", message: "Input the address of the WebSocket (found in the terminal running 01 software)", preferredStyle: .alert) + alert.addTextField { (field) in + field.placeholder = "Enter Address Here" + } + let cancelButton = UIAlertAction(title: "Cancel", style: .cancel) + alert.addAction(cancelButton) + let submitButton = UIAlertAction(title: "Done", style: .default) { (_) in + if let field = alert.textFields?.first, let text = field.text { + UserDefaults.standard.setValue(text, forKey: "IPINFO") + self.address = text + self.establishConnection() + // HAVE THE TEXT FIELD + } + } + alert.addAction(submitButton) + + present(alert, animated: true) + } + + @objc func recconectIcon(_ sender: UIGestureRecognizer) { + infoText.text = "" + self.establishConnection() + } + + @objc func terminalIcon(_ sender: UIGestureRecognizer) { + if (terminal) { + UIView.animate(withDuration: 0.3) { + self.terminalFeed.text = "" + self.terminalFeed.alpha = 0 + let moveT = CGAffineTransform(translationX: 0, y: -190) + self.appendTranslation(transform: moveT) + self.terminalButton.image = UIImage(systemName: "apple.terminal") + } completion: { done in + self.terminal = false + } + } else { + UIView.animate(withDuration: 0.3) { + self.terminalFeed.alpha = 1 + let moveT = CGAffineTransform(translationX: 0, y: 190) + self.appendTranslation(transform: moveT) + self.terminalButton.image = UIImage(systemName: "apple.terminal.fill") + } completion: { done in + self.terminal = true + } + + } + } + + @objc func settingsGear(_ sender: UIGestureRecognizer) { + infoText.text = "" + setAddress() + } + + func appendTranslation(transform: CGAffineTransform) { + var currentTransform = self.circle.transform + currentTransform = currentTransform.concatenating(transform) + self.circle.transform = currentTransform + } + + @objc func buttonPress(_ sender: UILongPressGestureRecognizer) { + infoText.text = "" + let feedback = UIImpactFeedbackGenerator(style: .medium) + if sender.state == .began { + socket?.connect() + // check for recording permission, if exists + // it began, start recording! + if (isConnected && recordingPermission) { + audioRecordingInstance = AudioRecording() + audioRecordingInstance!.startRecording() + infoText.text = "" + UIView.animate(withDuration: 0.1) { + self.circle.tintColor = .green + let newT = CGAffineTransform(scaleX: 0.7, y: 0.7) + self.appendTranslation(transform: newT) + feedback.prepare() + feedback.impactOccurred() + } + } else { + let errorFeedback = UIImpactFeedbackGenerator(style: .heavy) + errorFeedback.prepare() + errorFeedback.impactOccurred() + if (isConnected && !recordingPermission) { + infoText.text = "Not recording permission. Please close and re-open the app." + } else { + infoText.text = "Not connected." + establishConnection() + } + UIView.animate(withDuration: 0.5) { + self.circle.tintColor = .red + } completion: { _ in + self.circle.tintColor = .systemYellow + } + + + } + } else if sender.state == .ended { + if (isConnected && recordingPermission) { + if (audioRecordingInstance != nil) { + let response = audioRecordingInstance!.stopRecording() + if (response != nil) { + sendAudio(audio: response!) + } + UIView.animate(withDuration: 0.1) { + self.circle.tintColor = .systemYellow + let newT = CGAffineTransform(scaleX: 1.4, y: 1.4) + self.appendTranslation(transform: newT) + feedback.prepare() + feedback.impactOccurred() + } + } + } + // stop recording and send the audio + } + } + + + + + + func establishConnection() { //connect to the web socket + if (address != nil) { + var request = URLRequest(url: URL(string: "http://\(address!)")!) + request.timeoutInterval = 5 + socket = WebSocket(request: request) + socket!.delegate = self + socket!.connect() + } else { + setAddress() + } + } + + func didReceive(event: Starscream.WebSocketEvent, client: any Starscream.WebSocketClient) { // deal with receiving data from websocket + switch event { + case .connected( _): + isConnected = true + reconnectIcon.tintColor = .green + case .disconnected(let reason, let code): + isConnected = false + reconnectIcon.tintColor = .red + case .text(let string): + if (terminal) { + terminalFeed.text = terminalFeed.text + "\n>> \(string)" + let range = NSMakeRange(terminalFeed.text.count - 1, 0) + terminalFeed.scrollRangeToVisible(range) + } + if (string.contains("audio") && string.contains("bytes.raw") && string.contains("start")) { + infoText.text = "Receiving response..." + // it started collecting data! + print("Audio is being receieved.") + } else if (string.contains("audio") && string.contains("bytes.raw") && string.contains("end")) { + infoText.text = "" + print("Audio is no longer being receieved.") + let wavHeader = createWAVHeader(audioDataSize: Int32(audioData.count - 44)) + // Combine header and data + var completeWAVData = Data() + completeWAVData.append(wavHeader) + completeWAVData.append(audioData.subdata(in: 44.. Data { + let headerSize: Int32 = 44 // Standard WAV header size + let chunkSize: Int32 = 36 + audioDataSize + let sampleRate: Int32 = 16000 // From i2s_config + let numChannels: Int16 = 1 // From i2s_config (mono) + let bitsPerSample: Int16 = 16 // From i2s_config + let byteRate: Int32 = sampleRate * Int32(numChannels) * Int32(bitsPerSample) / 8 + let blockAlign: Int16 = numChannels * bitsPerSample / 8 + + var headerData = Data() + + // RIFF Chunk + headerData.append(stringToData("RIFF")) // ChunkID + headerData.append(int32ToData(chunkSize)) // ChunkSize + headerData.append(stringToData("WAVE")) // Format + + // fmt Subchunk + headerData.append(stringToData("fmt ")) // Subchunk1ID + headerData.append(int32ToData(16)) // Subchunk1Size (16 for PCM) + headerData.append(int16ToData(1)) // AudioFormat (1 for PCM) + headerData.append(int16ToData(numChannels)) // NumChannels + headerData.append(int32ToData(sampleRate)) // SampleRate + headerData.append(int32ToData(byteRate)) // ByteRate + headerData.append(int16ToData(blockAlign)) // BlockAlign + headerData.append(int16ToData(bitsPerSample)) // BitsPerSample + + // data Subchunk + headerData.append(stringToData("data")) // Subchunk2ID + headerData.append(int32ToData(audioDataSize)) // Subchunk2Size + + return headerData + } + + func stringToData(_ string: String) -> Data { + return string.data(using: .utf8)! + } + + func int16ToData(_ value: Int16) -> Data { + var value = value.littleEndian + return Data(bytes: &value, count: MemoryLayout.size) + } + + func int32ToData(_ value: Int32) -> Data { + var value = value.littleEndian + return Data(bytes: &value, count: MemoryLayout.size) + } + + + func sendAudio(audio: Data) { + if (isConnected) { + socket!.write(string: "{\"role\": \"user\", \"type\": \"audio\", \"format\": \"bytes.raw\", \"start\": true}") + socket!.write(data: audio) + socket!.write(string: "{\"role\": \"user\", \"type\": \"audio\", \"format\": \"bytes.raw\", \"end\": true}") + } else { + print("Not connected!") + } + } + + + + + +} + +