From 71fd2c0509710c1112a89bee1656ed3ce7405aab Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Mon, 11 Mar 2019 16:54:07 +0100 Subject: [PATCH 01/19] Add CoreML Model + supporting code --- Baby Monitor.xcodeproj/project.pbxproj | 88 ++++++++---- .../Source Files/AppDependencies.swift | 8 +- .../Services/Crying/AudioRecordService.swift | 81 ----------- .../Crying/CryingDetectionService.swift | 48 ------- .../AudioMicrophoneService.swift | 127 ++++++++++++++++++ .../CryingDetectionService.swift | 68 ++++++++++ .../CryingEventService.swift | 21 +-- .../AudioSpectrogramLayer.swift | 97 +++++++++++++ .../Services/MachineLearning/MfccLayer.swift | 99 ++++++++++++++ .../MachineLearning/MfccMelFilterbank.swift | 110 +++++++++++++++ .../Services/MachineLearning/MfccOp.swift | 75 +++++++++++ .../MachineLearning/SpectrogramOp.swift | 124 +++++++++++++++++ .../MediaPlayer/MicrophoneFactory.swift | 91 +++++++++++++ .../Services/MediaPlayer/NodeCapture.swift | 111 +++++++++++++++ .../MediaPlayer/RecorderFactory.swift | 53 -------- PeerConnectionFactoryProtocol.swift | 4 +- audioprocessing.mlmodel | Bin 0 -> 557 bytes crydetection.mlmodel | Bin 0 -> 271989 bytes 18 files changed, 983 insertions(+), 222 deletions(-) delete mode 100644 Baby Monitor/Source Files/Services/Crying/AudioRecordService.swift delete mode 100644 Baby Monitor/Source Files/Services/Crying/CryingDetectionService.swift create mode 100644 Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift create mode 100644 Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift rename Baby Monitor/Source Files/Services/{Crying => CryingDetection}/CryingEventService.swift (76%) create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift create mode 100644 Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift create mode 100644 Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift delete mode 100644 Baby Monitor/Source Files/Services/MediaPlayer/RecorderFactory.swift create mode 100644 audioprocessing.mlmodel create mode 100644 crydetection.mlmodel diff --git a/Baby Monitor.xcodeproj/project.pbxproj b/Baby Monitor.xcodeproj/project.pbxproj index 6c9c79d..1881e70 100644 --- a/Baby Monitor.xcodeproj/project.pbxproj +++ b/Baby Monitor.xcodeproj/project.pbxproj @@ -142,9 +142,6 @@ 8A7A60CC21A40D2F00488ED4 /* CryingDetectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A60CB21A40D2F00488ED4 /* CryingDetectionServiceTests.swift */; }; 8A7A60CE21A40D3B00488ED4 /* MicrophoneTrackerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A60CD21A40D3B00488ED4 /* MicrophoneTrackerMock.swift */; }; 8A7A60D221A416E300488ED4 /* WebRtcClientManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A60D121A416E300488ED4 /* WebRtcClientManagerProtocol.swift */; }; - 8A7A611521A59E3B00488ED4 /* AudioRecordService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A611221A59E3A00488ED4 /* AudioRecordService.swift */; }; - 8A7A611621A59E3B00488ED4 /* CryingDetectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A611321A59E3B00488ED4 /* CryingDetectionService.swift */; }; - 8A7A611721A59E3B00488ED4 /* CryingEventService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A611421A59E3B00488ED4 /* CryingEventService.swift */; }; 8A7A611B21A59E5000488ED4 /* MemoryCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A611821A59E5000488ED4 /* MemoryCleaner.swift */; }; 8A7A611C21A59E5000488ED4 /* ActivityLogEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A611921A59E5000488ED4 /* ActivityLogEvent.swift */; }; 8A7A611D21A59E5000488ED4 /* ActivityLogEventsRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A611A21A59E5000488ED4 /* ActivityLogEventsRepositoryProtocol.swift */; }; @@ -154,7 +151,6 @@ 8A7A612521A59E7500488ED4 /* FileManager+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A612321A59E7500488ED4 /* FileManager+Size.swift */; }; 8A7A612721A59E8600488ED4 /* DirectoryDocumentsSavable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A612621A59E8600488ED4 /* DirectoryDocumentsSavable.swift */; }; 8A7A612A21A59E9A00488ED4 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A612921A59E9A00488ED4 /* AlertPresenter.swift */; }; - 8A7A612C21A59EB000488ED4 /* RecorderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A612B21A59EB000488ED4 /* RecorderFactory.swift */; }; 8A7A612E21A5A35300488ED4 /* WebRtcServerManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A612D21A5A35300488ED4 /* WebRtcServerManagerProtocol.swift */; }; 8A7A613E21A6AACD00488ED4 /* MessageServerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A613D21A6AACD00488ED4 /* MessageServerMock.swift */; }; 8A7A614921A6AC7900488ED4 /* CryingDetectionServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614421A6AC7800488ED4 /* CryingDetectionServiceMock.swift */; }; @@ -239,6 +235,18 @@ A1ED8F912212C745005762E8 /* TwoOptionsBaseOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED8F902212C745005762E8 /* TwoOptionsBaseOnboardingView.swift */; }; A1ED8F932212CA35005762E8 /* OldBaseOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED8F922212CA35005762E8 /* OldBaseOnboardingView.swift */; }; A1ED8F952212CB8D005762E8 /* OnboardingContinuableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED8F942212CB8D005762E8 /* OnboardingContinuableViewController.swift */; }; + A7D756082232DBC800F9893E /* CryingEventService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756052232DBC800F9893E /* CryingEventService.swift */; }; + A7D756092232DBC800F9893E /* CryingDetectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756062232DBC800F9893E /* CryingDetectionService.swift */; }; + A7D7561F2232DE8700F9893E /* MicrophoneFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7561E2232DE8700F9893E /* MicrophoneFactory.swift */; }; + A7D756212232DF0A00F9893E /* AudioMicrophoneService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756202232DF0A00F9893E /* AudioMicrophoneService.swift */; }; + A7D7562522344B0300F9893E /* NodeCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562422344B0300F9893E /* NodeCapture.swift */; }; + A7D756272236AFA000F9893E /* crydetection.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = A7D756262236AF9F00F9893E /* crydetection.mlmodel */; }; + A7D756292236AFB800F9893E /* audioprocessing.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = A7D756282236AFB700F9893E /* audioprocessing.mlmodel */; }; + A7D756302236B03D00F9893E /* MfccMelFilterbank.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562B2236B03D00F9893E /* MfccMelFilterbank.swift */; }; + A7D756312236B03D00F9893E /* MfccLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562C2236B03D00F9893E /* MfccLayer.swift */; }; + A7D756322236B03D00F9893E /* AudioSpectrogramLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */; }; + A7D756332236B03D00F9893E /* MfccOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562E2236B03D00F9893E /* MfccOp.swift */; }; + A7D756342236B03D00F9893E /* SpectrogramOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */; }; EC4C9947216B946B0093EDFC /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4C9946216B946B0093EDFC /* OnboardingCoordinator.swift */; }; EC82B55A213EA072005CA395 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC82B559213EA072005CA395 /* AppDelegate.swift */; }; EC82B561213EA074005CA395 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC82B560213EA074005CA395 /* Assets.xcassets */; }; @@ -454,9 +462,6 @@ 8A7A60CB21A40D2F00488ED4 /* CryingDetectionServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CryingDetectionServiceTests.swift; path = "Baby MonitorTests/Services/CryingDetectionServiceTests.swift"; sourceTree = SOURCE_ROOT; }; 8A7A60CD21A40D3B00488ED4 /* MicrophoneTrackerMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrophoneTrackerMock.swift; sourceTree = ""; }; 8A7A60D121A416E300488ED4 /* WebRtcClientManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRtcClientManagerProtocol.swift; sourceTree = ""; }; - 8A7A611221A59E3A00488ED4 /* AudioRecordService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AudioRecordService.swift; path = Crying/AudioRecordService.swift; sourceTree = ""; }; - 8A7A611321A59E3B00488ED4 /* CryingDetectionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CryingDetectionService.swift; path = Crying/CryingDetectionService.swift; sourceTree = ""; }; - 8A7A611421A59E3B00488ED4 /* CryingEventService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CryingEventService.swift; path = Crying/CryingEventService.swift; sourceTree = ""; }; 8A7A611821A59E5000488ED4 /* MemoryCleaner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCleaner.swift; sourceTree = ""; }; 8A7A611921A59E5000488ED4 /* ActivityLogEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityLogEvent.swift; sourceTree = ""; }; 8A7A611A21A59E5000488ED4 /* ActivityLogEventsRepositoryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityLogEventsRepositoryProtocol.swift; sourceTree = ""; }; @@ -466,7 +471,6 @@ 8A7A612321A59E7500488ED4 /* FileManager+Size.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FileManager+Size.swift"; sourceTree = ""; }; 8A7A612621A59E8600488ED4 /* DirectoryDocumentsSavable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryDocumentsSavable.swift; sourceTree = ""; }; 8A7A612921A59E9A00488ED4 /* AlertPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlertPresenter.swift; path = "Baby Monitor/Source Files/Services/Other/AlertPresenter.swift"; sourceTree = SOURCE_ROOT; }; - 8A7A612B21A59EB000488ED4 /* RecorderFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecorderFactory.swift; sourceTree = ""; }; 8A7A612D21A5A35300488ED4 /* WebRtcServerManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRtcServerManagerProtocol.swift; sourceTree = ""; }; 8A7A613D21A6AACD00488ED4 /* MessageServerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageServerMock.swift; sourceTree = ""; }; 8A7A614421A6AC7800488ED4 /* CryingDetectionServiceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryingDetectionServiceMock.swift; sourceTree = ""; }; @@ -550,6 +554,18 @@ A1ED8F902212C745005762E8 /* TwoOptionsBaseOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoOptionsBaseOnboardingView.swift; sourceTree = ""; }; A1ED8F922212CA35005762E8 /* OldBaseOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OldBaseOnboardingView.swift; sourceTree = ""; }; A1ED8F942212CB8D005762E8 /* OnboardingContinuableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingContinuableViewController.swift; sourceTree = ""; }; + A7D756052232DBC800F9893E /* CryingEventService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryingEventService.swift; sourceTree = ""; }; + A7D756062232DBC800F9893E /* CryingDetectionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryingDetectionService.swift; sourceTree = ""; }; + A7D7561E2232DE8700F9893E /* MicrophoneFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrophoneFactory.swift; sourceTree = ""; }; + A7D756202232DF0A00F9893E /* AudioMicrophoneService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMicrophoneService.swift; sourceTree = ""; }; + A7D7562422344B0300F9893E /* NodeCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeCapture.swift; sourceTree = ""; }; + A7D756262236AF9F00F9893E /* crydetection.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = crydetection.mlmodel; sourceTree = ""; }; + A7D756282236AFB700F9893E /* audioprocessing.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = audioprocessing.mlmodel; sourceTree = ""; }; + A7D7562B2236B03D00F9893E /* MfccMelFilterbank.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccMelFilterbank.swift; sourceTree = ""; }; + A7D7562C2236B03D00F9893E /* MfccLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccLayer.swift; sourceTree = ""; }; + A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSpectrogramLayer.swift; sourceTree = ""; }; + A7D7562E2236B03D00F9893E /* MfccOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccOp.swift; sourceTree = ""; }; + A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpectrogramOp.swift; sourceTree = ""; }; DC0A7360EB7E7A40F5162D1F /* Pods_Baby_MonitorTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Baby_MonitorTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC4C9946216B946B0093EDFC /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = ""; }; EC82B556213EA072005CA395 /* BabyMonitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BabyMonitor.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -760,9 +776,10 @@ 4E63DC0C21E39CFA00604167 /* Notifications */, 8A7A612821A59E9100488ED4 /* Other */, 8A7A608221A4024100488ED4 /* ErrorHandler */, - 8A7A608121A4020B00488ED4 /* CryingDetection */, 8AFAE5A9219AAA40007013BC /* Websocket */, 8A43DAFB217DD1C300E3004D /* Connection */, + A7D756042232DBC800F9893E /* CryingDetection */, + A7D7562A2236AFD800F9893E /* MachineLearning */, 4E39432F2175D48C00AD7582 /* NetService */, 8AEAFBAF217735B3003E756F /* Persistence */, 4E5731A12170BA9D00DEAF0B /* MediaPlayer */, @@ -773,8 +790,9 @@ 4E5731A12170BA9D00DEAF0B /* MediaPlayer */ = { isa = PBXGroup; children = ( - 8A7A612B21A59EB000488ED4 /* RecorderFactory.swift */, + A7D7561E2232DE8700F9893E /* MicrophoneFactory.swift */, 8AA7CBE1218C576D00FCF62A /* URLMediaPlayer.swift */, + A7D7562422344B0300F9893E /* NodeCapture.swift */, ); path = MediaPlayer; sourceTree = ""; @@ -1145,16 +1163,6 @@ path = "New Group"; sourceTree = ""; }; - 8A7A608121A4020B00488ED4 /* CryingDetection */ = { - isa = PBXGroup; - children = ( - 8A7A611221A59E3A00488ED4 /* AudioRecordService.swift */, - 8A7A611321A59E3B00488ED4 /* CryingDetectionService.swift */, - 8A7A611421A59E3B00488ED4 /* CryingEventService.swift */, - ); - name = CryingDetection; - sourceTree = ""; - }; 8A7A608221A4024100488ED4 /* ErrorHandler */ = { isa = PBXGroup; children = ( @@ -1360,6 +1368,28 @@ path = CustomAnimations; sourceTree = ""; }; + A7D756042232DBC800F9893E /* CryingDetection */ = { + isa = PBXGroup; + children = ( + A7D756202232DF0A00F9893E /* AudioMicrophoneService.swift */, + A7D756062232DBC800F9893E /* CryingDetectionService.swift */, + A7D756052232DBC800F9893E /* CryingEventService.swift */, + ); + path = CryingDetection; + sourceTree = ""; + }; + A7D7562A2236AFD800F9893E /* MachineLearning */ = { + isa = PBXGroup; + children = ( + A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */, + A7D7562C2236B03D00F9893E /* MfccLayer.swift */, + A7D7562B2236B03D00F9893E /* MfccMelFilterbank.swift */, + A7D7562E2236B03D00F9893E /* MfccOp.swift */, + A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */, + ); + path = MachineLearning; + sourceTree = ""; + }; EC4C9945216B944B0093EDFC /* Onboarding */ = { isa = PBXGroup; children = ( @@ -1389,6 +1419,8 @@ EC82B54D213EA072005CA395 = { isa = PBXGroup; children = ( + A7D756282236AFB700F9893E /* audioprocessing.mlmodel */, + A7D756262236AF9F00F9893E /* crydetection.mlmodel */, ECB146D9213EA44C00C504FD /* Configuration */, EC82B558213EA072005CA395 /* Baby Monitor */, EC82B56D213EA074005CA395 /* Baby MonitorTests */, @@ -1801,18 +1833,20 @@ files = ( 8A6098C421EBFDA700592B01 /* PeerConnectionFactoryProtocol.swift in Sources */, 4E7B091321E6148600EDDD11 /* URLSessionProtocol.swift in Sources */, + A7D756092232DBC800F9893E /* CryingDetectionService.swift in Sources */, A1ED8F912212C745005762E8 /* TwoOptionsBaseOnboardingView.swift in Sources */, 4E1D2C5F216751F400E92F29 /* CameraPreviewViewModel.swift in Sources */, A1EC9777221AA53900C27786 /* OnboardingAccessViewModel.swift in Sources */, 4E63DC1B21E39D4B00604167 /* Request.swift in Sources */, - 8A7A611721A59E3B00488ED4 /* CryingEventService.swift in Sources */, 8A045D5B21A3F685006A2E10 /* (null) in Sources */, + A7D756342236B03D00F9893E /* SpectrogramOp.swift in Sources */, A18681912209A1A60069E521 /* SendRecordingsIntroFeatureView.swift in Sources */, 8A7A611B21A59E5000488ED4 /* MemoryCleaner.swift in Sources */, 8A7A611C21A59E5000488ED4 /* ActivityLogEvent.swift in Sources */, 4E1D2C6521678D9100E92F29 /* TypedViewController.swift in Sources */, 4EA2802621E8C6B200262E45 /* DateFormatter+Helpers.swift in Sources */, A1640E2B22048EA100398ED2 /* BabyMonitorSwitch.swift in Sources */, + A7D7562522344B0300F9893E /* NodeCapture.swift in Sources */, 8AED318E219045AD00FEFE8A /* BabyMonitorCellDeletable.swift in Sources */, A18D3E7621FF6D3700C165C6 /* ActivityLogViewController.swift in Sources */, A1572D8B22005AF600AC57C4 /* ActivityLogCell.swift in Sources */, @@ -1850,8 +1884,8 @@ 8A50AA3A21E602F60058C63A /* SessionDescriptionProtocol.swift in Sources */, A1ED8F8D2212C6EA005762E8 /* ContinuableBaseOnboardingView.swift in Sources */, 4E57319C2170BA7400DEAF0B /* BabyMonitorCellSelectable.swift in Sources */, + A7D756292236AFB800F9893E /* audioprocessing.mlmodel in Sources */, 4E57319A2170BA7400DEAF0B /* HasNavigationController.swift in Sources */, - 8A7A611621A59E3B00488ED4 /* CryingDetectionService.swift in Sources */, 4E6EA7982161FEEF005575E0 /* RootCoordinator.swift in Sources */, 4E8E057421AE9E4A009ACE05 /* UIColor+BabyMonitor.swift in Sources */, 8A43DB02217DD4A500E3004D /* NetServiceConnectionChecker.swift in Sources */, @@ -1871,7 +1905,6 @@ 8A7A616D21A8162000488ED4 /* UIColor+Custom.swift in Sources */, A1ED8F952212CB8D005762E8 /* OnboardingContinuableViewController.swift in Sources */, EC8A8FF321AE8EE500B5FCCF /* BasePageViewController.swift in Sources */, - 8A7A611521A59E3B00488ED4 /* AudioRecordService.swift in Sources */, 4EB6DE6321B6687D004701EA /* EventMessage.swift in Sources */, 8A8EF944219C40630098A27B /* RTCSessionDescription+JSON.swift in Sources */, 8AFAE5C3219C1FE3007013BC /* SdpAnswerDecoder.swift in Sources */, @@ -1894,11 +1927,14 @@ EC8A8FF521AE8F5F00B5FCCF /* TypedPageViewController.swift in Sources */, 8A8B76DF21EDE3640063EF7E /* WebsocketMessageDecodable.swift in Sources */, 4E7A1A9221F12BD00015C3A3 /* ServerCoordinator.swift in Sources */, + A7D756082232DBC800F9893E /* CryingEventService.swift in Sources */, A1640E272203A48F00398ED2 /* ServerSettingsViewModel.swift in Sources */, ECB2428A21AD4F9200B4E409 /* IntroViewController.swift in Sources */, 4E1D2C67216791AD00E92F29 /* DashboardView.swift in Sources */, A1492A66221D814500AA7716 /* ClearableLazyItem.swift in Sources */, + A7D756322236B03D00F9893E /* AudioSpectrogramLayer.swift in Sources */, 8A7A612A21A59E9A00488ED4 /* AlertPresenter.swift in Sources */, + A7D756212232DF0A00F9893E /* AudioMicrophoneService.swift in Sources */, 4E9563392164F09C00289475 /* BabyNavigationItemView.swift in Sources */, 4E63DC1C21E39D4B00604167 /* FirebasePushNotificationsRequest.swift in Sources */, 4EB6DE6521B67C11004701EA /* EventMessageDecoder.swift in Sources */, @@ -1920,21 +1956,25 @@ 8A7A608821A403D500488ED4 /* MicrophoneTrackerProtocol.swift in Sources */, A1ED8F932212CA35005762E8 /* OldBaseOnboardingView.swift in Sources */, 4E1CF817217856C500F48706 /* ServerViewModel.swift in Sources */, + A7D756302236B03D00F9893E /* MfccMelFilterbank.swift in Sources */, 8AEAFBB62177371B003E756F /* RealmBaby.swift in Sources */, 4E2F413521B1441600513843 /* OldOnboardingContinuableViewController.swift in Sources */, 8A7A60D221A416E300488ED4 /* WebRtcClientManagerProtocol.swift in Sources */, 4E63DC1021E39CFA00604167 /* NotificationService.swift in Sources */, 8AEAFBB3217736A8003E756F /* RealmBabiesRepository.swift in Sources */, 4E7A1A9021F1284B0015C3A3 /* UserDefaults+AppMode.swift in Sources */, + A7D756332236B03D00F9893E /* MfccOp.swift in Sources */, 4E1D2C5D21673FF800E92F29 /* CameraPreviewViewController.swift in Sources */, 200681C121FB53AA00340596 /* SettingsCoordinator.swift in Sources */, 4E95631B21638D1500289475 /* Localizable.swift in Sources */, + A7D756272236AFA000F9893E /* crydetection.mlmodel in Sources */, 8A50AA4021E603080058C63A /* WebRtcStreamId.swift in Sources */, 8A7A612421A59E7500488ED4 /* FileManager+DocumentsDirectories.swift in Sources */, 8A7A612121A59E6500488ED4 /* Result.swift in Sources */, 4E57319F2170BA7400DEAF0B /* Coordinator.swift in Sources */, 8A50AA4221E60A190058C63A /* RTCPeerConnectionDelegateProxy.swift in Sources */, 8A50AA3821E602F60058C63A /* PeerConnectionProtocol.swift in Sources */, + A7D756312236B03D00F9893E /* MfccLayer.swift in Sources */, 4E9563362164E77100289475 /* UITableView+Dequeue.swift in Sources */, 4E36A3AE21B15F850058DAD2 /* OnboardingClientSetupViewController.swift in Sources */, 8A7A612721A59E8600488ED4 /* DirectoryDocumentsSavable.swift in Sources */, @@ -1954,10 +1994,10 @@ 8A8B76EB21EF48980063EF7E /* WebSocketWebRtcService.swift in Sources */, 8A43DAFD217DD1D000E3004D /* ConnectionChecker.swift in Sources */, 4E9563322164DDC100289475 /* Baby.swift in Sources */, + A7D7561F2232DE8700F9893E /* MicrophoneFactory.swift in Sources */, 4E8E057221AE7B6A009ACE05 /* SpecifyDeviceOnboardingViewModel.swift in Sources */, 8A42DB142179DCAC00BF5F1B /* URL+Prefix.swift in Sources */, 95368F552209BC6A006E263A /* SpecifyDeviceInfoOnboardingView.swift in Sources */, - 8A7A612C21A59EB000488ED4 /* RecorderFactory.swift in Sources */, 8A8EF94D219C52480098A27B /* AnyMessageDecoder.swift in Sources */, A1EC977D221ADC8100C27786 /* OnboardingSpinnerView.swift in Sources */, 8A43DB1F2181D1E700E3004D /* GeneralSection.swift in Sources */, diff --git a/Baby Monitor/Source Files/AppDependencies.swift b/Baby Monitor/Source Files/AppDependencies.swift index 3d4c1f7..6115b81 100644 --- a/Baby Monitor/Source Files/AppDependencies.swift +++ b/Baby Monitor/Source Files/AppDependencies.swift @@ -15,14 +15,14 @@ final class AppDependencies { private let bag = DisposeBag() /// Service for cleaning too many crying events private(set) lazy var memoryCleaner: MemoryCleanerProtocol = MemoryCleaner() - /// Service for recording audio - private(set) lazy var audioRecordService: AudioRecordServiceProtocol? = try? AudioRecordService(recorderFactory: AudioKitRecorderFactory.makeRecorderFactory) + /// Service for capturing/recording microphone audio + private(set) lazy var audioMicrophoneService: AudioMicrophoneServiceProtocol? = try? AudioMicrophoneService(microphoneFactory: AudioKitMicrophoneFactory.makeMicrophoneFactory) /// Service for detecting baby's cry - private(set) lazy var cryingDetectionService: CryingDetectionServiceProtocol = CryingDetectionService(microphoneTracker: AKMicrophoneTracker()) + private(set) lazy var cryingDetectionService: CryingDetectionServiceProtocol = CryingDetectionService(microphoneCapture: audioMicrophoneService) /// Service that takes care of appropriate controling: crying detection, audio recording and saving these events to realm database private(set) lazy var cryingEventService: CryingEventsServiceProtocol = CryingEventService( cryingDetectionService: cryingDetectionService, - audioRecordService: audioRecordService, + microphoneRecord: audioMicrophoneService, activityLogEventsRepository: databaseRepository, storageService: storageServerService) diff --git a/Baby Monitor/Source Files/Services/Crying/AudioRecordService.swift b/Baby Monitor/Source Files/Services/Crying/AudioRecordService.swift deleted file mode 100644 index 387f942..0000000 --- a/Baby Monitor/Source Files/Services/Crying/AudioRecordService.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// CryingRecordService.swift -// Baby Monitor -// - -import Foundation -import AudioKit -import RxSwift -import RxCocoa - -protocol ErrorProducable { - var errorObservable: Observable { get } -} - -protocol AudioRecordServiceProtocol { - var directoryDocumentsSavableObservable: Observable { get } - var isRecording: Bool { get } - - func stopRecording() - func startRecording() -} - -final class AudioRecordService: AudioRecordServiceProtocol, ErrorProducable { - - enum AudioError: Error { - case initializationFailure - case recordFailure - case saveFailure - } - - lazy var errorObservable = errorSubject.asObservable() - lazy var directoryDocumentsSavableObservable = directoryDocumentsSavableSubject.asObservable() - - private(set) var isRecording = false - private var recorder: RecorderProtocol? - private let errorSubject = PublishSubject() - private let directoryDocumentsSavableSubject = PublishSubject() - - init(recorderFactory: () -> RecorderProtocol?) throws { - recorder = recorderFactory() - if recorder == nil { - throw(AudioRecordService.AudioError.initializationFailure) - } - } - - func stopRecording() { - guard isRecording else { - return - } - recorder?.stop() - isRecording = false - guard let audioFile = recorder?.audioFile else { - errorSubject.onNext(AudioError.recordFailure) - return - } - directoryDocumentsSavableSubject.onNext(audioFile) - } - - func startRecording() { - guard !isRecording else { - return - } - do { - try recorder?.reset() - try recorder?.record() - isRecording = true - } catch { - errorSubject.onNext(AudioError.recordFailure) - } - } -} - -protocol RecorderProtocol: Any { - var audioFile: AKAudioFile? { get } - - func stop() - func record() throws - func reset() throws -} - -extension AKNodeRecorder: RecorderProtocol {} diff --git a/Baby Monitor/Source Files/Services/Crying/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/Crying/CryingDetectionService.swift deleted file mode 100644 index a260b68..0000000 --- a/Baby Monitor/Source Files/Services/Crying/CryingDetectionService.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// CryingDetectionService.swift -// Baby Monitor -// - -import Foundation -import AudioKit -import RxSwift -import RxCocoa - -protocol CryingDetectionServiceProtocol: Any { - - /// Observable that informs about detection of baby's cry - var cryingDetectionObservable: Observable { get } - - /// Starts crying detection - func startAnalysis() - /// Stops crying detection - func stopAnalysis() -} - -final class CryingDetectionService: CryingDetectionServiceProtocol { - - lazy var cryingDetectionObservable: Observable = Observable.timer(0, period: 0.2, scheduler: MainScheduler.asyncInstance) - .map { [unowned self] _ in self.microphoneTracker.frequency } - .filter { $0 > 1000 } - .buffer(timeSpan: 10, count: 10, scheduler: ConcurrentDispatchQueueScheduler(queue: bufferQueue)) - .map { $0.count > 9 } - .distinctUntilChanged() - .subscribeOn(MainScheduler.asyncInstance) - .share() - - private var isCryingEventDetected = false - private let microphoneTracker: MicrophoneTrackerProtocol - private let bufferQueue = DispatchQueue(label: "bufferQueue", attributes: .concurrent) - - init(microphoneTracker: MicrophoneTrackerProtocol) { - self.microphoneTracker = microphoneTracker - } - - func startAnalysis() { - microphoneTracker.start() - } - - func stopAnalysis() { - microphoneTracker.stop() - } -} diff --git a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift new file mode 100644 index 0000000..ffa077f --- /dev/null +++ b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift @@ -0,0 +1,127 @@ +// +// AudioMicrophoneService.swift +// Baby Monitor +// + + +import Foundation +import AudioKit +import RxSwift +import RxCocoa + +protocol ErrorProducable { + var errorObservable: Observable { get } +} + + +protocol AudioMicrophoneRecordServiceProtocol { + var directoryDocumentsSavableObservable: Observable { get } + var isRecording: Bool { get } + + func stopRecording() + func startRecording() +} + + +protocol AudioMicrophoneCaptureServiceProtocol { + var microphoneBufferReadableObservable: Observable { get } + var isCapturing: Bool { get } + + func stopCapturing() + func startCapturing() +} + +protocol AudioMicrophoneServiceProtocol: AudioMicrophoneRecordServiceProtocol, AudioMicrophoneCaptureServiceProtocol {} + +final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProducable { + + enum AudioError: Error { + case initializationFailure + case captureFailure + case recordFailure + case saveFailure + } + + lazy var errorObservable = errorSubject.asObservable() + lazy var microphoneBufferReadableObservable = microphoneBufferReadableSubject.asObservable() + lazy var directoryDocumentsSavableObservable = directoryDocumentsSavableSubject.asObservable() + + + + + private(set) var isCapturing = false + private(set) var isRecording = false + + private var capture: MicrophoneCaptureProtocol + private var record: MicrophoneRecordProtocol + + private let errorSubject = PublishSubject() + private let microphoneBufferReadableSubject = PublishSubject() + private let directoryDocumentsSavableSubject = PublishSubject() + + private let disposeBag = DisposeBag() + + + init(microphoneFactory: () throws -> AudioKitMicrophoneProtocol?) throws { + guard let audioKitMicrophone = try microphoneFactory() else { + throw(AudioMicrophoneService.AudioError.initializationFailure) + } + capture = audioKitMicrophone.capture + record = audioKitMicrophone.record + rxSetup() + } + + func stopCapturing() { + guard isCapturing else { + return + } + capture.stop() + isCapturing = false + } + + func startCapturing() { + guard !isCapturing else { + return + } + do { + try capture.start() + } catch { + return + } + isCapturing = true + } + + func stopRecording() { + guard isRecording else { + return + } + record.stop() + isRecording = false + guard let audioFile = record.audioFile else { + errorSubject.onNext(AudioError.recordFailure) + return + } + directoryDocumentsSavableSubject.onNext(audioFile) + } + + func startRecording() { + guard !isRecording else { + return + } + do { + try record.reset() + try record.record() + isRecording = true + } catch { + errorSubject.onNext(AudioError.recordFailure) + } + } + + private func rxSetup() { + capture.bufferReadable.subscribe(onNext: { [unowned self] bufferReadable in + self.microphoneBufferReadableSubject.onNext(bufferReadable) + }).disposed(by: disposeBag) + } + +} + diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift new file mode 100644 index 0000000..624dc0e --- /dev/null +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift @@ -0,0 +1,68 @@ +// +// CryingDetectionService.swift +// Baby Monitor +// + +import Foundation +import CoreML +import AudioKit +import RxSwift +import RxCocoa + +protocol CryingDetectionServiceProtocol: Any { + + /// Observable that informs about detection of baby's cry + var cryingDetectionObservable: Observable { get } + + /// Starts crying detection + func startAnalysis() + /// Stops crying detection + func stopAnalysis() +} + +final class CryingDetectionService: CryingDetectionServiceProtocol { + + lazy var cryingDetectionObservable = cryingDetectionSubject.asObservable() + + private let cryingDetectionSubject = PublishSubject() + + private var isCryingEventDetected = false + private let microphoneCapture: AudioMicrophoneCaptureServiceProtocol? + private let disposeBag = DisposeBag() + private let audioprocessingModel = audioprocessing() + private let crydetectionModel = crydetection() + + init(microphoneCapture: AudioMicrophoneCaptureServiceProtocol?) { + self.microphoneCapture = microphoneCapture + rxSetup() + } + + func startAnalysis() { + microphoneCapture?.startCapturing() + } + + func stopAnalysis() { + microphoneCapture?.stopCapturing() + } + + private func rxSetup() { + microphoneCapture?.microphoneBufferReadableObservable.subscribe(onNext: { [unowned self] bufferReadable in + do { + print("RUNNING ML MODEL") + let audioProcessingMultiArray = try MLMultiArray(dataPointer: bufferReadable.floatChannelData!.pointee, shape: [264600], dataType: .float32, strides: [1]) + + let input = audioprocessingInput(raw_audio__0: audioProcessingMultiArray) + let pred = try self.audioprocessingModel.prediction(input: input) + let crydetectionMultiArray = try MLMultiArray(shape: [1,1,1,598,64], dataType: .float32) + crydetectionMultiArray.dataPointer.copyMemory(from: pred.Mfcc__0.dataPointer, byteCount: 38272*4) + let input1 = crydetectionInput(Mfcc__0: crydetectionMultiArray) + let pred2 = try self.crydetectionModel.prediction(input: input1) + print(pred2.labels_softmax__0) + + self.cryingDetectionSubject.onNext(false) + } catch { + print("ERROR") + } + }).disposed(by: disposeBag) + } +} diff --git a/Baby Monitor/Source Files/Services/Crying/CryingEventService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift similarity index 76% rename from Baby Monitor/Source Files/Services/Crying/CryingEventService.swift rename to Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift index 3026d50..497f45b 100644 --- a/Baby Monitor/Source Files/Services/Crying/CryingEventService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift @@ -29,14 +29,14 @@ final class CryingEventService: CryingEventsServiceProtocol, ErrorProducable { private let cryingEventPublisher = PublishSubject() private let errorPublisher = PublishSubject() private let cryingDetectionService: CryingDetectionServiceProtocol - private let audioRecordService: AudioRecordServiceProtocol? + private let microphoneRecord: AudioMicrophoneRecordServiceProtocol? private let activityLogEventsRepository: ActivityLogEventsRepositoryProtocol private let storageService: StorageServerServiceProtocol private let disposeBag = DisposeBag() - init(cryingDetectionService: CryingDetectionServiceProtocol, audioRecordService: AudioRecordServiceProtocol?, activityLogEventsRepository: ActivityLogEventsRepositoryProtocol, storageService: StorageServerServiceProtocol) { + init(cryingDetectionService: CryingDetectionServiceProtocol, microphoneRecord: AudioMicrophoneRecordServiceProtocol?, activityLogEventsRepository: ActivityLogEventsRepositoryProtocol, storageService: StorageServerServiceProtocol) { self.cryingDetectionService = cryingDetectionService - self.audioRecordService = audioRecordService + self.microphoneRecord = microphoneRecord self.activityLogEventsRepository = activityLogEventsRepository self.storageService = storageService rxSetup() @@ -44,39 +44,40 @@ final class CryingEventService: CryingEventsServiceProtocol, ErrorProducable { func start() throws { cryingDetectionService.startAnalysis() - if audioRecordService == nil { + if microphoneRecord == nil { throw CryingEventServiceError.audioRecordServiceError } } func stop() { cryingDetectionService.stopAnalysis() - audioRecordService?.stopRecording() + //microphoneRecord?.stopRecording() } private func rxSetup() { cryingDetectionService.cryingDetectionObservable.subscribe(onNext: { [unowned self] isBabyCrying in if isBabyCrying { + print("BABY CRYING") let fileNameSuffix = DateFormatter.fullTimeFormatString(breakCharacter: "_") self.nextFileName = "crying_".appending(fileNameSuffix).appending(".caf") - self.audioRecordService?.startRecording() + //self.microphoneRecord?.startRecording() let cryingEventMessage = EventMessage.initWithCryingEvent(value: self.nextFileName) self.cryingEventPublisher.onNext(cryingEventMessage) } else { - guard self.audioRecordService?.isRecording ?? false else { + guard self.microphoneRecord?.isRecording ?? false else { return } - self.audioRecordService?.stopRecording() + //self.microphoneRecord?.stopRecording() } }).disposed(by: disposeBag) - audioRecordService?.directoryDocumentsSavableObservable.subscribe(onNext: { [unowned self] savableFile in + microphoneRecord?.directoryDocumentsSavableObservable.subscribe(onNext: { [unowned self] savableFile in savableFile.save(withName: self.nextFileName, completion: { [unowned self] result in switch result { case .success: self.storageService.uploadRecordingsToDatabaseIfAllowed() case .failure(let error): - self.errorPublisher.onNext(error ?? AudioRecordService.AudioError.saveFailure) + self.errorPublisher.onNext(error ?? AudioMicrophoneService.AudioError.saveFailure) } }) }).disposed(by: disposeBag) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift new file mode 100644 index 0000000..53db484 --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift @@ -0,0 +1,97 @@ +// +// AudioSpectrogram.swift +// audio_ops +// +// Created by Timo Rohner on 15/02/2019. +// Copyright © 2019 Timo Rohner. All rights reserved. +// + +import Foundation +import CoreML +import Accelerate + +enum AudioSpectrogramLayerError: Error { + case parameterError(String) + case opError(String) +} + +func nextPowerOfTwo(value: Int) -> Int { + return Int(pow(2, ceil(log2(Double(value))))) +} + + +@objc(AudioSpectrogramLayer) class AudioSpectrogramLayer: NSObject, MLCustomLayer { + let windowSize:Int + let stride:Int + let magnitudeSquared:Bool + let outputChannels:NSNumber + let spectogramOp:SpectrogramOp + + required init(parameters: [String : Any]) throws { + if let windowSize = parameters["window_size"] as? Int { + self.windowSize = windowSize + } else { + throw AudioSpectrogramLayerError.parameterError("window_size") + } + + if let stride = parameters["stride"] as? Int { + self.stride = stride + } else { + throw AudioSpectrogramLayerError.parameterError("stride") + } + + if let magnitudeSquared = parameters["magnitude_squared"] as? Bool { + self.magnitudeSquared = magnitudeSquared + } else { + throw AudioSpectrogramLayerError.parameterError("magnitude_squared") + } + + + outputChannels = NSNumber(integerLiteral: 1 + nextPowerOfTwo(value: self.windowSize) / 2) + + spectogramOp = SpectrogramOp(windowLength: windowSize, + stepLength: stride, + magnitudeSquared: magnitudeSquared) + super.init() + } + + func setWeightData(_ weights: [Data]) throws { + // No weight data for this layer + } + + func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws -> [[NSNumber]] { + // Input shape is [1,1,NUM_SAMPLES, 1,1] + // This gives us the output shape + // [1,1,1,N_TIME_BINS, N_FREQUENCY_BINS] + var outputShapesArray = [[NSNumber]]() + let inputLength = Int(truncating:inputShapes[0][2]) + let outputLength = NSNumber(integerLiteral: 1 + (inputLength - self.windowSize) / self.stride) + outputShapesArray.append([1,1,1,outputLength, outputChannels]) + print("AudioSpectrogramLayer: ",#function, inputShapes, " -> ", outputShapesArray) + return outputShapesArray + } + + func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws { + try spectogramOp.compute(input: UnsafeMutablePointer(OpaquePointer(inputs[0].dataPointer)), + inputLength: inputs[0].count, + output: UnsafeMutablePointer(OpaquePointer(outputs[0].dataPointer))) + } +} + +// Audio OPS +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/ops/audio_ops.cc + +// AUDIOSPECTROGRAM +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram_op.cc +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram.h +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram.cc + +// MFCC +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc.cc +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc.h +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_dct.cc +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_dct.h +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.cc +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.h +// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_op.cc + diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift new file mode 100644 index 0000000..645e29f --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift @@ -0,0 +1,99 @@ +// +// AudioSpectrogram.swift +// audio_ops +// +// Created by Timo Rohner on 15/02/2019. +// Copyright © 2019 Timo Rohner. All rights reserved. +// + +import Foundation +import CoreML +import Accelerate + +enum MfccLayerError: Error { + case parameterError(String) + case evaluationError(String) +} + + +@objc(MfccLayer) class MfccLayer: NSObject, MLCustomLayer { + let upperFrequencyLimit:Double + let lowerFrequencyLimit:Double + let filterbankChannelCount:Int + let dctCoefficientCount:Int + let sampleRate:Double + var mfccOp:MfccOp? + + required init(parameters: [String : Any]) throws { + print(#function, parameters) + if let sampleRate = parameters["sample_rate"] as? Double { + self.sampleRate = sampleRate + } else { + throw MfccLayerError.parameterError("sample_rate") + } + if let upperFrequencyLimit = parameters["upper_frequency_limit"] as? Double { + self.upperFrequencyLimit = upperFrequencyLimit + } else { + throw MfccLayerError.parameterError("upper_frequency_limit") + } + + if let lowerFrequencyLimit = parameters["lower_frequency_limit"] as? Double { + self.lowerFrequencyLimit = lowerFrequencyLimit + } else { + throw MfccLayerError.parameterError("lower_frequency_limit") + } + + if let filterbankChannelCount = parameters["filterbank_channel_count"] as? Int { + self.filterbankChannelCount = filterbankChannelCount + } else { + throw MfccLayerError.parameterError("filterbank_channel_count") + } + + if let dctCoefficientCount = parameters["dct_coefficient_count"] as? Int { + self.dctCoefficientCount = dctCoefficientCount + } else { + throw MfccLayerError.parameterError("dct_coefficient_count") + } + + mfccOp = MfccOp(upperFrequencyLimit: upperFrequencyLimit, + lowerFrequencyLimit: lowerFrequencyLimit, + filterbankChannelCount: filterbankChannelCount, + dctCoefficientCount: dctCoefficientCount) + try mfccOp!.initialize(inputLength: 1025, inputSampleRate: sampleRate) + + super.init() + } + + func setWeightData(_ weights: [Data]) throws { + print(#function, weights) + + } + + func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws -> [[NSNumber]] { + // Input shape is [1,1,1,N_TIME_BINS, N_FREQUENCY_BINS] + // This gives us the output shape + // [1,1,N_TIME_BINS, dct_coefficient_count] + var outputShapesArray = [[NSNumber]]() + + outputShapesArray.append([1,1, 1, inputShapes[0][3], NSNumber(value:dctCoefficientCount)]) + print("MfccLayer: ",#function, inputShapes, " -> ", outputShapesArray) + return outputShapesArray + } + + func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws { + let nSpectrogramSamples = Int(truncating: inputs[0].shape[3]) + let nSpectrogramChannels = Int(truncating: inputs[0].shape[4]) + + + let ptrSpectrogramInput = UnsafeMutablePointer(OpaquePointer(inputs[0].dataPointer)) + let ptrOutput = UnsafeMutablePointer(OpaquePointer(outputs[0].dataPointer)) + + for spectrogramIndex in 0.. Bool { + self.inputLength = inputLength + self.sampleRate = inputSampleRate + self.numChannels = outputChannelCount + + let melLow = freqToMel(freq: lowerFrequencyLimit) + let melHi = freqToMel(freq: upperFrequencyLimit) + let melSpan = melHi - melLow; + let melSpacing = melSpan / Double(numChannels + 1) + + centerFrequencies = [Double](repeating: 0.0, count: numChannels+1) + + for i in 0...numChannels { + centerFrequencies![i] = melLow + (melSpacing * Double(i + 1)); + } + + let hzPerSbin = 0.5 * sampleRate / Double(inputLength - 1) + startIndex = Int(1.5 + (lowerFrequencyLimit / hzPerSbin)) + endIndex = Int(upperFrequencyLimit / hzPerSbin) + + bandMapper = [Int](repeating:0, count: self.inputLength) + + var channel = 0 + + for i in 0.. endIndex)) { + bandMapper![i] = -2; + } else { + while ((centerFrequencies![channel] < melf) && + (channel < numChannels)) { + channel += 1 + } + bandMapper![i] = channel - 1; + } + } + + weights = [Double](repeating: 0.0, count: self.inputLength) + for i in 0.. endIndex)) { + weights![i] = 0.0 + } else { + if (channel >= 0) { + weights![i] = (centerFrequencies![channel + 1] - freqToMel(freq: Double(i) * hzPerSbin))/(centerFrequencies![channel + 1] - centerFrequencies![channel]) + } else { + weights![i] = (centerFrequencies![0] - freqToMel(freq: Double(i) * hzPerSbin)) / (centerFrequencies![0] - melLow); + } + } + } + + workData = [Float](repeating: 0.0, count: endIndex-startIndex+1) + + initialized = true; + return true; + } + + func compute(input: UnsafeMutablePointer, output: UnsafeMutablePointer) { + for i in startIndex...endIndex { + //let spec_val = sqrtf(input.advanced(by: i).pointee) + let specVal = input.advanced(by: i).pointee + let weighted = specVal * Float((weights![i])) + var channel = bandMapper![i] + if (channel >= 0) { + output.advanced(by: channel).pointee += weighted + } + channel += 1 + + if (channel < numChannels) { + output.advanced(by: channel).pointee += specVal - weighted; + } + } + } + + private func freqToMel(freq: Double) -> Double { + return 1127.0 * log(1.0 + (freq / 700.0)); + } +} diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift new file mode 100644 index 0000000..d40ca03 --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift @@ -0,0 +1,75 @@ +// +// MfccOp.swift +// audio_ops +// +// Created by Timo Rohner on 17/02/2019. +// Copyright © 2019 Timo Rohner. All rights reserved. +// + +import Foundation +import Accelerate + +enum MfccOpError :Error { + case stringerror(String) +} + +class MfccOp { + var initialized:Bool = false + let upperFrequencyLimit:Double + let lowerFrequencyLimit:Double + let filterbankChannelCount:Int + let dctCoefficientCount:Int + var inputLength:Int = 0 + var inputSampleRate:Double = 0 + var kFilterbankFloor:Float = 1e-12 + //var mfcc_dct:MfccDct? + var melFilterbank:MfccMelFilterbank? + var dctSetup:vDSP_DFT_Setup? + + + init(upperFrequencyLimit:Double, + lowerFrequencyLimit:Double, + filterbankChannelCount:Int, + dctCoefficientCount:Int) { + self.upperFrequencyLimit = upperFrequencyLimit + self.lowerFrequencyLimit = lowerFrequencyLimit + self.filterbankChannelCount = filterbankChannelCount + self.dctCoefficientCount = dctCoefficientCount + //mfcc_dct = MfccDct() + } + + func initialize(inputLength: Int, inputSampleRate: Double) throws { + dctSetup = vDSP_DCT_CreateSetup(nil, vDSP_Length(dctCoefficientCount), vDSP_DCT_Type.II) + self.inputLength = inputLength + self.inputSampleRate = inputSampleRate + + melFilterbank = MfccMelFilterbank() + if(!melFilterbank!.initialize(inputLength: inputLength, inputSampleRate: inputSampleRate, outputChannelCount: filterbankChannelCount, lowerFrequencyLimit: lowerFrequencyLimit, upperFrequencyLimit: upperFrequencyLimit)) { + throw MfccOpError.stringerror("err") + } + + //try mfcc_dct!.Initialize(input_length: filterbank_channel_count_, coefficient_count: dct_coefficient_count_) + + self.initialized = true + } + + func compute(input: UnsafeMutablePointer, inputLength: Int, output: UnsafeMutablePointer) throws { + if(!initialized) { + print("ERROR") + } + var workingData1 = [Float](repeating: 0.0, count: filterbankChannelCount) + var workingData2 = [Float](repeating: 0.0, count: filterbankChannelCount) + var workingData3 = [Float](repeating: 0.0, count: filterbankChannelCount) + melFilterbank!.compute(input: input, output: &workingData1) + vDSP_vthr(&workingData1, 1, &kFilterbankFloor, &workingData2, 1, vDSP_Length(filterbankChannelCount)) + + var nElements = Int32(filterbankChannelCount) + vvlogf(&workingData3, &workingData2, &nElements) + + // Execute DCT + var fnorm = sqrtf(2.0 / Float(64)) + vDSP_vsmul(&workingData3, 1, &fnorm, &workingData2, 1, vDSP_Length(64)) + vDSP_DCT_Execute(dctSetup!, &workingData2, output) + //mfcc_dct!.Compute(input: &working_data3, output: output) + } +} diff --git a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift new file mode 100644 index 0000000..c0822a2 --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift @@ -0,0 +1,124 @@ +// +// Spectrogram.swift +// audio_ops +// +// Created by Timo Rohner on 16/02/2019. +// Copyright © 2019 Timo Rohner. All rights reserved. +// + +import Foundation +import Accelerate + +// Input: [264600,1] +// => Bin into windows => [598, 1323] +// => +// +// +class SpectrogramOp: NSObject{ + var stepLength:Int = 0 + var windowLength:Int = 0 + var fftLength:Int = 0 + var outputFrequencyChannels:Int = 0 + var initialized:Bool = false + var scaleFactor : Float = 0.5 + + var window:[Float] + var inputReal:[Float] + var inputImg:[Float] + var fftReal:[Float] + var fftImg:[Float] + + var samplesToNextStep:Int = 0 + var fftSetup:vDSP_DFT_Setup? + var magnitudeSquared:Bool = true + + init(windowLength: Int, stepLength: Int, magnitudeSquared: Bool) { + self.windowLength = windowLength + self.stepLength = stepLength + self.magnitudeSquared = magnitudeSquared + + if (self.windowLength < 2) { + print("window_length too short. window_length has to be greater or equal to 2.") + } + + if (self.stepLength < 1) { + print("step_length must be strictly positive") + } + + fftLength = nextPowerOfTwo(value: self.windowLength) + window = [Float](repeating: 0.0, count: self.windowLength) + + inputReal = [Float](repeating: 0.0, count: fftLength/2+1) + inputImg = [Float](repeating: 0.0, count: fftLength/2+1) + + fftReal = [Float](repeating: 0.0, count: fftLength/2+1) + fftImg = [Float](repeating: 0.0, count: fftLength/2+1) + + vDSP_hann_window(&window, vDSP_Length(windowLength), Int32(vDSP_HANN_DENORM)) + + samplesToNextStep = windowLength + outputFrequencyChannels = 1 + fftLength / 2 + + // Initialize FFT + fftSetup = vDSP_DFT_zrop_CreateSetup(nil, vDSP_Length(fftLength), .FORWARD) + } + + deinit { + vDSP_DFT_DestroySetup(fftSetup) + } + + func compute(input: UnsafeMutablePointer, + inputLength: Int, + output: UnsafeMutablePointer) throws { + + let nTimebins = 1 + (inputLength - windowLength) / stepLength + var nSamples = windowLength + + for i in 0.. AudioKitMicrophoneProtocol? = { + AKSettings.bufferLength = .medium + AKSettings.sampleRate = 44100.0 + AKSettings.channelCount = 1 + + try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP) + AKSettings.defaultToSpeaker = true + + recorderBooster = AKBooster(microphone) + captureBooster = AKBooster(microphone) + + monoToStereo = AKStereoFieldLimiter(recorderBooster, amount: 1) + recorderMixer = AKMixer(monoToStereo) + recorder = try AKNodeRecorder(node: recorderMixer) + capture = try AudioKitNodeCapture(node: captureBooster) + recorderSilenceBooster = AKBooster(recorderMixer) + recorderSilenceBooster?.gain = 0.0 + captureSilenceBooster = AKBooster(captureBooster) + captureSilenceBooster?.gain = 0.0 + + guard let recorder = recorder else { + return nil + } + guard let capture = capture else { + return nil + } + guard let audioFile = recorder.audioFile else { + return nil + } + player = AKPlayer(audioFile: audioFile) + mainMixer = AKMixer(player, recorderSilenceBooster) + AudioKit.output = mainMixer + try AudioKit.start() + + return AudioKitMicrophone(record: recorder, capture: capture) + } +} + +protocol MicrophoneRecordProtocol: Any { + var audioFile: AKAudioFile? { get } + + func stop() + func record() throws + func reset() throws +} + +protocol MicrophoneCaptureProtocol: Any { + var bufferReadable: Observable { get } + var isCapturing: Bool { get } + + func stop() + func start() throws + func reset() throws +} + +extension AKNodeRecorder: MicrophoneRecordProtocol {} +extension AudioKitNodeCapture: MicrophoneCaptureProtocol {} diff --git a/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift b/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift new file mode 100644 index 0000000..ac9d2f2 --- /dev/null +++ b/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift @@ -0,0 +1,111 @@ +// +// NodeCapture.swift +// Baby Monitor +// + + +import Foundation +import AudioKit +import RxSwift +import RxCocoa + +class AudioKitNodeCapture: NSObject { + + enum AudioCaptureError: Error { + case initializationFailure + case captureFailure + } + + private let bufferReadableSubject = PublishSubject() + lazy var bufferReadable = bufferReadableSubject.asObservable() + + private(set) var isCapturing: Bool = false + private var node: AKNode? + private let bufferSize: UInt32 + private var internalAudioBuffer: AVAudioPCMBuffer? + private let bufferFormat: AVAudioFormat? + + + public init(node: AKNode? = AudioKit.output, bufferSize: UInt32 = 264600) throws { + self.node = node + self.bufferSize = bufferSize + + self.bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, + sampleRate: 44100.0, + channels: 1, + interleaved: false) + guard let bufferFormat = bufferFormat else { + throw AudioCaptureError.initializationFailure + } + self.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: bufferSize) + } + + /// Start Capturing + func start() throws { + guard !isCapturing else { + return + } + guard let node = node else { + return + } + + guard let internalAudioBuffer = internalAudioBuffer else { + return + } + + node.avAudioUnitOrNode.installTap( + onBus: 0, + bufferSize: AKSettings.bufferLength.samplesCount, + format: internalAudioBuffer.format) { [weak self] (buffer: AVAudioPCMBuffer!, _) -> Void in + + guard let strongSelf = self else { + AKLog("Error: self is nil") + return + } + + guard let internalAudioBuffer = strongSelf.internalAudioBuffer else { + AKLog("Error: internalAudioBuffer is nil") + return + } + + let samplesLeft = internalAudioBuffer.frameCapacity - internalAudioBuffer.frameLength + + if(buffer.frameLength < samplesLeft) { + internalAudioBuffer.copy(from: buffer) + } + else if(buffer.frameLength >= samplesLeft) { + internalAudioBuffer.copy(from: buffer, readOffset: 0, frames: samplesLeft) + print("Buffer is filled. Pushing observe event") + strongSelf.bufferReadableSubject.onNext(internalAudioBuffer.copy() as! AVAudioPCMBuffer) + guard let bufferFormat = strongSelf.bufferFormat else { + AKLog("Error: bufferFormat is nil") + return + } + strongSelf.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: strongSelf.bufferSize) + } + if(buffer.frameLength > samplesLeft) { + internalAudioBuffer.copy(from: buffer, readOffset: samplesLeft, frames: buffer.frameLength - samplesLeft) + } + } + isCapturing = true + + } + + /// Stop Capturing + func stop() { + guard isCapturing else { + return + } + node?.avAudioUnitOrNode.removeTap(onBus: 0) + } + + /// Reset the Buffer to clear previous recordings + func reset() throws { + stop() + guard let bufferFormat = bufferFormat else { + AKLog("Error: bufferFormat is nil") + return + } + internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: bufferSize) + } +} diff --git a/Baby Monitor/Source Files/Services/MediaPlayer/RecorderFactory.swift b/Baby Monitor/Source Files/Services/MediaPlayer/RecorderFactory.swift deleted file mode 100644 index 47b2972..0000000 --- a/Baby Monitor/Source Files/Services/MediaPlayer/RecorderFactory.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// RecorderFactory.swift -// Baby Monitor -// - -import Foundation -import AudioKit - -enum AudioKitRecorderFactory { - - private static var recorder: AKNodeRecorder? - private static var player: AKPlayer? - private static var micMixer: AKMixer? - private static var micBooster: AKBooster? - private static var moogLadder: AKMoogLadder? - private static var mainMixer: AKMixer? - private static let microphone = AKMicrophone() - - static var makeRecorderFactory: () -> RecorderProtocol? = { - // Session settings - AKSettings.bufferLength = .medium - do { - try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP) - } catch { - return nil - } - AKSettings.defaultToSpeaker = true - // Patching - let monoToStereo = AKStereoFieldLimiter(microphone, amount: 1) - micMixer = AKMixer(monoToStereo) - micBooster = AKBooster(micMixer) - // Will set the level of microphone monitoring - micBooster?.gain = 0 - do { - recorder = try AKNodeRecorder(node: micMixer) - } catch { - return nil - } - guard let audioFile = recorder?.audioFile else { - return nil - } - player = AKPlayer(audioFile: audioFile) - moogLadder = AKMoogLadder(player) - mainMixer = AKMixer(moogLadder, micBooster) - AudioKit.output = mainMixer - do { - try AudioKit.start() - } catch { - return nil - } - return recorder - } -} diff --git a/PeerConnectionFactoryProtocol.swift b/PeerConnectionFactoryProtocol.swift index bc55b57..b7857a7 100644 --- a/PeerConnectionFactoryProtocol.swift +++ b/PeerConnectionFactoryProtocol.swift @@ -27,8 +27,8 @@ extension RTCPeerConnectionFactory: PeerConnectionFactoryProtocol { let vTrack = videoTrack(withID: "ARDAMSv0", source: videoSource(with: videoCapturer, constraints: nil)) localStream.addVideoTrack(vTrack) - let aTrack = audioTrack(withID: "ARDAMSa0") - localStream.addAudioTrack(aTrack) + //let aTrack = audioTrack(withID: "ARDAMSa0") + //localStream.addAudioTrack(aTrack) return localStream diff --git a/audioprocessing.mlmodel b/audioprocessing.mlmodel new file mode 100644 index 0000000000000000000000000000000000000000..ecd1cb7bf59c6092cc3887709620a7bf2e2fa509 GIT binary patch literal 557 zcma)2Jxc>Y5Owd)k4!}3YA{40hozO3pjAZd6fypQW!;;Li+8){-X1|KOFIP{OHFHO zWgGkrqCdgPaKFG@&>}&>sfIUi-pqS54&8udm^Hk^cos;_*m}LZRs#3#woEs%+lI$`7HVcrYXBjsbQ1`|qIaz>mN2)+~SSl}&dVTT;Ihlj0)EXHKfoHep?CxZ#)v-Sm z^z8|^zR<>CnrIcIzSRQ^{qge$i7%CJQKj01k?z2a176{r{^FqilS#MnL0YAP_Wk}M zlv5rj5i-Lq(#bw~d2@2I0@XlRCNzej6cSa@XUbByPntPtKrNJ!MRUy6DHA7LDU3!K wt5D671Pu!fo@J=SIg4a0EiD~R6m|79kQtB$pZ`s2UN?Kr=D*@8ER9F<1^A4--v9sr literal 0 HcmV?d00001 diff --git a/crydetection.mlmodel b/crydetection.mlmodel new file mode 100644 index 0000000000000000000000000000000000000000..7dbc783311e090b5dd4250f4db87f4b615169dff GIT binary patch literal 271989 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 ZeZ=$jpjQZ3PJ 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%0D}3<1>TRJZS0yvJTXO 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 Date: Mon, 11 Mar 2019 18:23:01 +0100 Subject: [PATCH 02/19] Fix linting --- .../AudioMicrophoneService.swift | 8 -- .../CryingDetectionService.swift | 4 +- .../AudioSpectrogramLayer.swift | 29 +++--- .../Services/MachineLearning/MfccLayer.swift | 26 +++--- .../MachineLearning/MfccMelFilterbank.swift | 66 +++++++------- .../Services/MachineLearning/MfccOp.swift | 40 +++++---- .../MachineLearning/SpectrogramOp.swift | 88 +++++++++---------- .../Services/MediaPlayer/NodeCapture.swift | 17 ++-- 8 files changed, 129 insertions(+), 149 deletions(-) diff --git a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift index ffa077f..c947eab 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift @@ -3,7 +3,6 @@ // Baby Monitor // - import Foundation import AudioKit import RxSwift @@ -13,7 +12,6 @@ protocol ErrorProducable { var errorObservable: Observable { get } } - protocol AudioMicrophoneRecordServiceProtocol { var directoryDocumentsSavableObservable: Observable { get } var isRecording: Bool { get } @@ -22,7 +20,6 @@ protocol AudioMicrophoneRecordServiceProtocol { func startRecording() } - protocol AudioMicrophoneCaptureServiceProtocol { var microphoneBufferReadableObservable: Observable { get } var isCapturing: Bool { get } @@ -46,9 +43,6 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca lazy var microphoneBufferReadableObservable = microphoneBufferReadableSubject.asObservable() lazy var directoryDocumentsSavableObservable = directoryDocumentsSavableSubject.asObservable() - - - private(set) var isCapturing = false private(set) var isRecording = false @@ -61,7 +55,6 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca private let disposeBag = DisposeBag() - init(microphoneFactory: () throws -> AudioKitMicrophoneProtocol?) throws { guard let audioKitMicrophone = try microphoneFactory() else { throw(AudioMicrophoneService.AudioError.initializationFailure) @@ -124,4 +117,3 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca } } - diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift index 624dc0e..c87f280 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift @@ -53,8 +53,8 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { let input = audioprocessingInput(raw_audio__0: audioProcessingMultiArray) let pred = try self.audioprocessingModel.prediction(input: input) - let crydetectionMultiArray = try MLMultiArray(shape: [1,1,1,598,64], dataType: .float32) - crydetectionMultiArray.dataPointer.copyMemory(from: pred.Mfcc__0.dataPointer, byteCount: 38272*4) + let crydetectionMultiArray = try MLMultiArray(shape: [1, 1, 1, 598, 64], dataType: .float32) + crydetectionMultiArray.dataPointer.copyMemory(from: pred.Mfcc__0.dataPointer, byteCount: 38272 * 4) let input1 = crydetectionInput(Mfcc__0: crydetectionMultiArray) let pred2 = try self.crydetectionModel.prediction(input: input1) print(pred2.labels_softmax__0) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift index 53db484..ff414d5 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift @@ -19,15 +19,14 @@ func nextPowerOfTwo(value: Int) -> Int { return Int(pow(2, ceil(log2(Double(value))))) } - @objc(AudioSpectrogramLayer) class AudioSpectrogramLayer: NSObject, MLCustomLayer { - let windowSize:Int - let stride:Int - let magnitudeSquared:Bool - let outputChannels:NSNumber - let spectogramOp:SpectrogramOp + let windowSize: Int + let stride: Int + let magnitudeSquared: Bool + let outputChannels: NSNumber + let spectogramOp: SpectrogramOp - required init(parameters: [String : Any]) throws { + required init(parameters: [String: Any]) throws { if let windowSize = parameters["window_size"] as? Int { self.windowSize = windowSize } else { @@ -46,8 +45,7 @@ func nextPowerOfTwo(value: Int) -> Int { throw AudioSpectrogramLayerError.parameterError("magnitude_squared") } - - outputChannels = NSNumber(integerLiteral: 1 + nextPowerOfTwo(value: self.windowSize) / 2) + outputChannels = NSNumber(value: 1 + nextPowerOfTwo(value: self.windowSize) / 2) spectogramOp = SpectrogramOp(windowLength: windowSize, stepLength: stride, @@ -64,17 +62,17 @@ func nextPowerOfTwo(value: Int) -> Int { // This gives us the output shape // [1,1,1,N_TIME_BINS, N_FREQUENCY_BINS] var outputShapesArray = [[NSNumber]]() - let inputLength = Int(truncating:inputShapes[0][2]) - let outputLength = NSNumber(integerLiteral: 1 + (inputLength - self.windowSize) / self.stride) - outputShapesArray.append([1,1,1,outputLength, outputChannels]) - print("AudioSpectrogramLayer: ",#function, inputShapes, " -> ", outputShapesArray) + let inputLength = Int(truncating: inputShapes[0][2]) + let outputLength = NSNumber(value: 1 + (inputLength - self.windowSize) / self.stride) + outputShapesArray.append([1, 1, 1, outputLength, outputChannels]) + print("AudioSpectrogramLayer: ", #function, inputShapes, " -> ", outputShapesArray) return outputShapesArray } func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws { try spectogramOp.compute(input: UnsafeMutablePointer(OpaquePointer(inputs[0].dataPointer)), - inputLength: inputs[0].count, - output: UnsafeMutablePointer(OpaquePointer(outputs[0].dataPointer))) + inputLength: inputs[0].count, + output: UnsafeMutablePointer(OpaquePointer(outputs[0].dataPointer))) } } @@ -94,4 +92,3 @@ func nextPowerOfTwo(value: Int) -> Int { // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.cc // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.h // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_op.cc - diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift index 645e29f..9dbee8e 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift @@ -15,16 +15,15 @@ enum MfccLayerError: Error { case evaluationError(String) } - @objc(MfccLayer) class MfccLayer: NSObject, MLCustomLayer { - let upperFrequencyLimit:Double - let lowerFrequencyLimit:Double - let filterbankChannelCount:Int - let dctCoefficientCount:Int - let sampleRate:Double - var mfccOp:MfccOp? + let upperFrequencyLimit: Double + let lowerFrequencyLimit: Double + let filterbankChannelCount: Int + let dctCoefficientCount: Int + let sampleRate: Double + var mfccOp: MfccOp? - required init(parameters: [String : Any]) throws { + required init(parameters: [String: Any]) throws { print(#function, parameters) if let sampleRate = parameters["sample_rate"] as? Double { self.sampleRate = sampleRate @@ -56,9 +55,9 @@ enum MfccLayerError: Error { } mfccOp = MfccOp(upperFrequencyLimit: upperFrequencyLimit, - lowerFrequencyLimit: lowerFrequencyLimit, - filterbankChannelCount: filterbankChannelCount, - dctCoefficientCount: dctCoefficientCount) + lowerFrequencyLimit: lowerFrequencyLimit, + filterbankChannelCount: filterbankChannelCount, + dctCoefficientCount: dctCoefficientCount) try mfccOp!.initialize(inputLength: 1025, inputSampleRate: sampleRate) super.init() @@ -75,8 +74,8 @@ enum MfccLayerError: Error { // [1,1,N_TIME_BINS, dct_coefficient_count] var outputShapesArray = [[NSNumber]]() - outputShapesArray.append([1,1, 1, inputShapes[0][3], NSNumber(value:dctCoefficientCount)]) - print("MfccLayer: ",#function, inputShapes, " -> ", outputShapesArray) + outputShapesArray.append([1, 1, 1, inputShapes[0][3], NSNumber(value: dctCoefficientCount)]) + print("MfccLayer: ", #function, inputShapes, " -> ", outputShapesArray) return outputShapesArray } @@ -84,7 +83,6 @@ enum MfccLayerError: Error { let nSpectrogramSamples = Int(truncating: inputs[0].shape[3]) let nSpectrogramChannels = Int(truncating: inputs[0].shape[4]) - let ptrSpectrogramInput = UnsafeMutablePointer(OpaquePointer(inputs[0].dataPointer)) let ptrOutput = UnsafeMutablePointer(OpaquePointer(outputs[0].dataPointer)) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift index d45a8e5..1a29944 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift @@ -10,19 +10,18 @@ import Foundation import Accelerate class MfccMelFilterbank { - var initialized:Bool = false - var numChannels:Int - var sampleRate:Double - var inputLength:Int - var centerFrequencies:[Double]? - var bandMapper:[Int]? - var weights:[Double]? - var startIndex:Int - var endIndex:Int - var workData:[Float]? + var initialized: Bool = false + var numChannels: Int + var sampleRate: Double + var inputLength: Int + var centerFrequencies: [Double]? + var bandMapper: [Int]? + var weights: [Double]? + var startIndex: Int + var endIndex: Int + var workData: [Float]? - - init(){ + init() { numChannels = 0 sampleRate = 0.0 inputLength = 0 @@ -37,54 +36,53 @@ class MfccMelFilterbank { let melLow = freqToMel(freq: lowerFrequencyLimit) let melHi = freqToMel(freq: upperFrequencyLimit) - let melSpan = melHi - melLow; + let melSpan = melHi - melLow let melSpacing = melSpan / Double(numChannels + 1) - centerFrequencies = [Double](repeating: 0.0, count: numChannels+1) + centerFrequencies = [Double](repeating: 0.0, count: numChannels + 1) for i in 0...numChannels { - centerFrequencies![i] = melLow + (melSpacing * Double(i + 1)); + centerFrequencies![i] = melLow + (melSpacing * Double(i + 1)) } let hzPerSbin = 0.5 * sampleRate / Double(inputLength - 1) startIndex = Int(1.5 + (lowerFrequencyLimit / hzPerSbin)) endIndex = Int(upperFrequencyLimit / hzPerSbin) - bandMapper = [Int](repeating:0, count: self.inputLength) + bandMapper = [Int](repeating: 0, count: self.inputLength) var channel = 0 for i in 0.. endIndex)) { - bandMapper![i] = -2; + if (i < startIndex) || (i > endIndex) { + bandMapper![i] = -2 } else { - while ((centerFrequencies![channel] < melf) && - (channel < numChannels)) { + while (centerFrequencies![channel] < melf) && (channel < numChannels) { channel += 1 } - bandMapper![i] = channel - 1; + bandMapper![i] = channel - 1 } } weights = [Double](repeating: 0.0, count: self.inputLength) - for i in 0.. endIndex)) { + for i in 0.. endIndex) { weights![i] = 0.0 } else { - if (channel >= 0) { - weights![i] = (centerFrequencies![channel + 1] - freqToMel(freq: Double(i) * hzPerSbin))/(centerFrequencies![channel + 1] - centerFrequencies![channel]) + if channel >= 0 { + weights![i] = (centerFrequencies![channel + 1] - freqToMel(freq: Double(i) * hzPerSbin)) / (centerFrequencies![channel + 1] - centerFrequencies![channel]) } else { - weights![i] = (centerFrequencies![0] - freqToMel(freq: Double(i) * hzPerSbin)) / (centerFrequencies![0] - melLow); + weights![i] = (centerFrequencies![0] - freqToMel(freq: Double(i) * hzPerSbin)) / (centerFrequencies![0] - melLow) } } } - workData = [Float](repeating: 0.0, count: endIndex-startIndex+1) + workData = [Float](repeating: 0.0, count: endIndex - startIndex + 1) - initialized = true; - return true; + initialized = true + return true } func compute(input: UnsafeMutablePointer, output: UnsafeMutablePointer) { @@ -93,18 +91,18 @@ class MfccMelFilterbank { let specVal = input.advanced(by: i).pointee let weighted = specVal * Float((weights![i])) var channel = bandMapper![i] - if (channel >= 0) { + if channel >= 0 { output.advanced(by: channel).pointee += weighted } channel += 1 - if (channel < numChannels) { - output.advanced(by: channel).pointee += specVal - weighted; + if channel < numChannels { + output.advanced(by: channel).pointee += specVal - weighted } } } private func freqToMel(freq: Double) -> Double { - return 1127.0 * log(1.0 + (freq / 700.0)); + return 1127.0 * log(1.0 + (freq / 700.0)) } } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift index d40ca03..e8223ab 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift @@ -9,28 +9,28 @@ import Foundation import Accelerate -enum MfccOpError :Error { +enum MfccOpError: Error { case stringerror(String) } class MfccOp { - var initialized:Bool = false - let upperFrequencyLimit:Double - let lowerFrequencyLimit:Double - let filterbankChannelCount:Int - let dctCoefficientCount:Int - var inputLength:Int = 0 - var inputSampleRate:Double = 0 - var kFilterbankFloor:Float = 1e-12 - //var mfcc_dct:MfccDct? - var melFilterbank:MfccMelFilterbank? - var dctSetup:vDSP_DFT_Setup? + var initialized: Bool = false + let upperFrequencyLimit: Double + let lowerFrequencyLimit: Double + let filterbankChannelCount: Int + let dctCoefficientCount: Int + var inputLength: Int = 0 + var inputSampleRate: Double = 0 + var kFilterbankFloor: Float = 1e-12 + //var mfcc_dct:MfccDct? + var melFilterbank: MfccMelFilterbank? + var dctSetup: vDSP_DFT_Setup? - init(upperFrequencyLimit:Double, - lowerFrequencyLimit:Double, - filterbankChannelCount:Int, - dctCoefficientCount:Int) { + init(upperFrequencyLimit: Double, + lowerFrequencyLimit: Double, + filterbankChannelCount: Int, + dctCoefficientCount: Int) { self.upperFrequencyLimit = upperFrequencyLimit self.lowerFrequencyLimit = lowerFrequencyLimit self.filterbankChannelCount = filterbankChannelCount @@ -44,7 +44,11 @@ class MfccOp { self.inputSampleRate = inputSampleRate melFilterbank = MfccMelFilterbank() - if(!melFilterbank!.initialize(inputLength: inputLength, inputSampleRate: inputSampleRate, outputChannelCount: filterbankChannelCount, lowerFrequencyLimit: lowerFrequencyLimit, upperFrequencyLimit: upperFrequencyLimit)) { + if !melFilterbank!.initialize(inputLength: inputLength, + inputSampleRate: inputSampleRate, + outputChannelCount: filterbankChannelCount, + lowerFrequencyLimit: lowerFrequencyLimit, + upperFrequencyLimit: upperFrequencyLimit) { throw MfccOpError.stringerror("err") } @@ -54,7 +58,7 @@ class MfccOp { } func compute(input: UnsafeMutablePointer, inputLength: Int, output: UnsafeMutablePointer) throws { - if(!initialized) { + if !initialized { print("ERROR") } var workingData1 = [Float](repeating: 0.0, count: filterbankChannelCount) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift index c0822a2..972faec 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift @@ -14,45 +14,45 @@ import Accelerate // => // // -class SpectrogramOp: NSObject{ - var stepLength:Int = 0 - var windowLength:Int = 0 - var fftLength:Int = 0 - var outputFrequencyChannels:Int = 0 - var initialized:Bool = false - var scaleFactor : Float = 0.5 +class SpectrogramOp: NSObject { + var stepLength: Int = 0 + var windowLength: Int = 0 + var fftLength: Int = 0 + var outputFrequencyChannels: Int = 0 + var initialized: Bool = false + var scaleFactor: Float = 0.5 - var window:[Float] - var inputReal:[Float] - var inputImg:[Float] - var fftReal:[Float] - var fftImg:[Float] + var window: [Float] + var inputReal: [Float] + var inputImg: [Float] + var fftReal: [Float] + var fftImg: [Float] - var samplesToNextStep:Int = 0 - var fftSetup:vDSP_DFT_Setup? - var magnitudeSquared:Bool = true + var samplesToNextStep: Int = 0 + var fftSetup: vDSP_DFT_Setup? + var magnitudeSquared: Bool = true init(windowLength: Int, stepLength: Int, magnitudeSquared: Bool) { self.windowLength = windowLength self.stepLength = stepLength self.magnitudeSquared = magnitudeSquared - if (self.windowLength < 2) { + if self.windowLength < 2 { print("window_length too short. window_length has to be greater or equal to 2.") } - if (self.stepLength < 1) { + if self.stepLength < 1 { print("step_length must be strictly positive") } fftLength = nextPowerOfTwo(value: self.windowLength) window = [Float](repeating: 0.0, count: self.windowLength) - inputReal = [Float](repeating: 0.0, count: fftLength/2+1) - inputImg = [Float](repeating: 0.0, count: fftLength/2+1) + inputReal = [Float](repeating: 0.0, count: fftLength / 2 + 1) + inputImg = [Float](repeating: 0.0, count: fftLength / 2 + 1) - fftReal = [Float](repeating: 0.0, count: fftLength/2+1) - fftImg = [Float](repeating: 0.0, count: fftLength/2+1) + fftReal = [Float](repeating: 0.0, count: fftLength / 2 + 1) + fftImg = [Float](repeating: 0.0, count: fftLength / 2 + 1) vDSP_hann_window(&window, vDSP_Length(windowLength), Int32(vDSP_HANN_DENORM)) @@ -71,54 +71,48 @@ class SpectrogramOp: NSObject{ inputLength: Int, output: UnsafeMutablePointer) throws { - let nTimebins = 1 + (inputLength - windowLength) / stepLength + let nTimebins = 1 + (inputLength - windowLength) / stepLength var nSamples = windowLength for i in 0.. Void in + format: internalAudioBuffer.format) { [weak self] (buffer: AVAudioPCMBuffer, _) -> Void in guard let strongSelf = self else { AKLog("Error: self is nil") @@ -70,10 +68,9 @@ class AudioKitNodeCapture: NSObject { let samplesLeft = internalAudioBuffer.frameCapacity - internalAudioBuffer.frameLength - if(buffer.frameLength < samplesLeft) { + if buffer.frameLength < samplesLeft { internalAudioBuffer.copy(from: buffer) - } - else if(buffer.frameLength >= samplesLeft) { + } else if buffer.frameLength >= samplesLeft { internalAudioBuffer.copy(from: buffer, readOffset: 0, frames: samplesLeft) print("Buffer is filled. Pushing observe event") strongSelf.bufferReadableSubject.onNext(internalAudioBuffer.copy() as! AVAudioPCMBuffer) @@ -83,12 +80,12 @@ class AudioKitNodeCapture: NSObject { } strongSelf.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: strongSelf.bufferSize) } - if(buffer.frameLength > samplesLeft) { + + if buffer.frameLength > samplesLeft { internalAudioBuffer.copy(from: buffer, readOffset: samplesLeft, frames: buffer.frameLength - samplesLeft) } } isCapturing = true - } /// Stop Capturing From 106c972ab16e53cc04fa646d9769dbcfc644f566 Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Tue, 12 Mar 2019 14:52:26 +0100 Subject: [PATCH 03/19] Extracted Machine Learning Math helper functions into seperate MathUtils structure --- .../Services/MachineLearning/MathUtils.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift b/Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift new file mode 100644 index 0000000..89284fb --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift @@ -0,0 +1,20 @@ +// +// MathUtils.swift +// Baby Monitor +// + +import Foundation + +enum MathUtils { + enum MathUtilsError: Error { + case parameterError(String) + } + + static var nextPowerOfTwo: (Int) -> Int = {(value: Int) -> Int in + return Int(pow(2, ceil(log2(Double(value))))) + } + + static var freqToMel: (Double) -> Double = {(freq: Double) -> Double in + return 1127.0 * log(1.0 + (freq / 700.0)) + } +} From 2d92f08d7b62294db7b9aca2b992826a20b93169 Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Tue, 12 Mar 2019 14:53:02 +0100 Subject: [PATCH 04/19] Added non Accelerate vDSP implementation for mfccDct Op --- .../Services/MachineLearning/MfccDct.swift | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift new file mode 100644 index 0000000..da4e7c9 --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift @@ -0,0 +1,63 @@ +// +// MfccDct.swift +// audio_ops +// +// Created by Timo Rohner on 17/02/2019. +// Copyright © 2019 Timo Rohner. All rights reserved. +// + +import Foundation + +class MfccDct { + enum MfccDctError: Error { + case parameterError(String) + } + + var initialized: Bool + var coefficientCount: Int + var inputLength: Int + var cosines: [[Double]] + + init() { + initialized = false + coefficientCount = 0 + inputLength = 0 + cosines = [] + } + + func initialize(inputLength: Int, coefficientCount: Int) throws { + guard coefficientCount >= 0 else { + throw MfccDctError.parameterError("coefficient_count must be strictly positive") + } + guard inputLength > 0 else { + throw MfccDctError.parameterError("input_length must be strictly positive") + } + guard coefficientCount > inputLength else { + throw MfccDctError.parameterError("coefficient_count must be less than or equal to input_length") + } + + self.inputLength = inputLength + self.coefficientCount = coefficientCount + self.cosines = [[Double]](repeating: [Double](repeating: 0.0, count: inputLength), count: coefficientCount) + + let fnorm = sqrt(2.0 / Double(inputLength)) + let arg = Double.pi / Double(inputLength) + + for i in 0.., output: UnsafeMutablePointer) { + for i in 0.. Date: Wed, 13 Mar 2019 14:33:22 +0100 Subject: [PATCH 05/19] Added automatic detection for whether to use Accelerate DCT or slower implementation based on parameters --- .../Services/MachineLearning/MfccOp.swift | 87 +++++++++++++++---- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift index e8223ab..190c996 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift @@ -9,11 +9,13 @@ import Foundation import Accelerate -enum MfccOpError: Error { - case stringerror(String) -} - class MfccOp { + enum MfccOpError: Error { + case evaluationError(String) + case filterbankError(String) + case mfccDctError(String) + case accelerateDctError(String) + } var initialized: Bool = false let upperFrequencyLimit: Double @@ -23,9 +25,11 @@ class MfccOp { var inputLength: Int = 0 var inputSampleRate: Double = 0 var kFilterbankFloor: Float = 1e-12 - //var mfcc_dct:MfccDct? var melFilterbank: MfccMelFilterbank? var dctSetup: vDSP_DFT_Setup? + var mfccDct: MfccDct? + + private var useAccelerate: Bool = false init(upperFrequencyLimit: Double, lowerFrequencyLimit: Double, @@ -35,25 +39,57 @@ class MfccOp { self.lowerFrequencyLimit = lowerFrequencyLimit self.filterbankChannelCount = filterbankChannelCount self.dctCoefficientCount = dctCoefficientCount - //mfcc_dct = MfccDct() + + // Figure out whether we can use the fast vDSP DCT implementation or whether we have to use our own slower implementation + // For vDSP to be available we need dctCoefficientCount to be equal to f * 2^n, where f is 1, 3, 5, or 15 and n >= 4 + // First we check whether dctCoefficientCount is a multiple of at least 2^4 via a bitwise and + if (dctCoefficientCount == filterbankChannelCount) && (dctCoefficientCount & 15) == 0 { + // It is indeed. We bitshift to remove 2^4 and then bitshift until we get an odd number. + var shiftedDctCoefficientCount = dctCoefficientCount >> 4 + while (shiftedDctCoefficientCount & 1) == 0 { + shiftedDctCoefficientCount = shiftedDctCoefficientCount >> 1 + } + switch shiftedDctCoefficientCount { + case 1, 3, 5, 15: useAccelerate = true + default: useAccelerate = false + } + } + + if useAccelerate { + dctSetup = vDSP_DCT_CreateSetup(nil, vDSP_Length(dctCoefficientCount), vDSP_DCT_Type.II) + } else { + mfccDct = MfccDct() + } + melFilterbank = MfccMelFilterbank() + } func initialize(inputLength: Int, inputSampleRate: Double) throws { - dctSetup = vDSP_DCT_CreateSetup(nil, vDSP_Length(dctCoefficientCount), vDSP_DCT_Type.II) self.inputLength = inputLength self.inputSampleRate = inputSampleRate - melFilterbank = MfccMelFilterbank() - if !melFilterbank!.initialize(inputLength: inputLength, - inputSampleRate: inputSampleRate, - outputChannelCount: filterbankChannelCount, - lowerFrequencyLimit: lowerFrequencyLimit, - upperFrequencyLimit: upperFrequencyLimit) { - throw MfccOpError.stringerror("err") + guard let melFilterbank = melFilterbank else { + throw MfccOpError.filterbankError("melFilterbank unavailable") } - //try mfcc_dct!.Initialize(input_length: filterbank_channel_count_, coefficient_count: dct_coefficient_count_) + try melFilterbank.initialize(inputLength: inputLength, + inputSampleRate: inputSampleRate, + outputChannelCount: filterbankChannelCount, + lowerFrequencyLimit: lowerFrequencyLimit, + upperFrequencyLimit: upperFrequencyLimit) + + if !useAccelerate { + guard let mfccDct = mfccDct else { + throw MfccOpError.mfccDctError("mfccDCT unavailable") + } + do { + try mfccDct.initialize(inputLength: filterbankChannelCount, + coefficientCount: dctCoefficientCount) + } catch { + throw MfccOpError.mfccDctError("mfccDct initialize failed") + } + } self.initialized = true } @@ -61,9 +97,11 @@ class MfccOp { if !initialized { print("ERROR") } + var workingData1 = [Float](repeating: 0.0, count: filterbankChannelCount) var workingData2 = [Float](repeating: 0.0, count: filterbankChannelCount) var workingData3 = [Float](repeating: 0.0, count: filterbankChannelCount) + melFilterbank!.compute(input: input, output: &workingData1) vDSP_vthr(&workingData1, 1, &kFilterbankFloor, &workingData2, 1, vDSP_Length(filterbankChannelCount)) @@ -71,9 +109,20 @@ class MfccOp { vvlogf(&workingData3, &workingData2, &nElements) // Execute DCT - var fnorm = sqrtf(2.0 / Float(64)) - vDSP_vsmul(&workingData3, 1, &fnorm, &workingData2, 1, vDSP_Length(64)) - vDSP_DCT_Execute(dctSetup!, &workingData2, output) - //mfcc_dct!.Compute(input: &working_data3, output: output) + if useAccelerate { + guard let dctSetup = dctSetup else { + throw MfccOpError.accelerateDctError("vDSP_DCT_Execute not available") + } + + var fnorm = sqrtf(2.0 / Float(filterbankChannelCount)) + vDSP_vsmul(&workingData3, 1, &fnorm, &workingData2, 1, vDSP_Length(filterbankChannelCount)) + vDSP_DCT_Execute(dctSetup, &workingData2, output) + } else { + guard let mfccDct = mfccDct else { + throw MfccOpError.mfccDctError("mfccDCT Op not available") + } + + mfccDct.compute(input: &workingData3, output: output) + } } } From 8997e042eeb76a128845d7368ba52413ef07cebb Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Wed, 13 Mar 2019 14:34:02 +0100 Subject: [PATCH 06/19] Fixing linting errors, adding better Exception handling --- Baby Monitor.xcodeproj/project.pbxproj | 8 +++ .../CryingDetectionService.swift | 25 +++++---- .../AudioSpectrogramLayer.swift | 41 +++++++-------- .../Services/MachineLearning/MfccDct.swift | 4 +- .../Services/MachineLearning/MfccLayer.swift | 51 +++++++++---------- .../MachineLearning/MfccMelFilterbank.swift | 27 +++++----- .../MachineLearning/SpectrogramOp.swift | 16 ++++-- 7 files changed, 88 insertions(+), 84 deletions(-) diff --git a/Baby Monitor.xcodeproj/project.pbxproj b/Baby Monitor.xcodeproj/project.pbxproj index 1881e70..ee6efca 100644 --- a/Baby Monitor.xcodeproj/project.pbxproj +++ b/Baby Monitor.xcodeproj/project.pbxproj @@ -247,6 +247,8 @@ A7D756322236B03D00F9893E /* AudioSpectrogramLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */; }; A7D756332236B03D00F9893E /* MfccOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562E2236B03D00F9893E /* MfccOp.swift */; }; A7D756342236B03D00F9893E /* SpectrogramOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */; }; + A7D756362237C31400F9893E /* MathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756352237C31400F9893E /* MathUtils.swift */; }; + A7D756382237CAE900F9893E /* MfccDct.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756372237CAE900F9893E /* MfccDct.swift */; }; EC4C9947216B946B0093EDFC /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4C9946216B946B0093EDFC /* OnboardingCoordinator.swift */; }; EC82B55A213EA072005CA395 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC82B559213EA072005CA395 /* AppDelegate.swift */; }; EC82B561213EA074005CA395 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC82B560213EA074005CA395 /* Assets.xcassets */; }; @@ -566,6 +568,8 @@ A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSpectrogramLayer.swift; sourceTree = ""; }; A7D7562E2236B03D00F9893E /* MfccOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccOp.swift; sourceTree = ""; }; A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpectrogramOp.swift; sourceTree = ""; }; + A7D756352237C31400F9893E /* MathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathUtils.swift; sourceTree = ""; }; + A7D756372237CAE900F9893E /* MfccDct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccDct.swift; sourceTree = ""; }; DC0A7360EB7E7A40F5162D1F /* Pods_Baby_MonitorTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Baby_MonitorTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC4C9946216B946B0093EDFC /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = ""; }; EC82B556213EA072005CA395 /* BabyMonitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BabyMonitor.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1381,11 +1385,13 @@ A7D7562A2236AFD800F9893E /* MachineLearning */ = { isa = PBXGroup; children = ( + A7D756372237CAE900F9893E /* MfccDct.swift */, A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */, A7D7562C2236B03D00F9893E /* MfccLayer.swift */, A7D7562B2236B03D00F9893E /* MfccMelFilterbank.swift */, A7D7562E2236B03D00F9893E /* MfccOp.swift */, A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */, + A7D756352237C31400F9893E /* MathUtils.swift */, ); path = MachineLearning; sourceTree = ""; @@ -1915,6 +1921,7 @@ 4E63DC1121E39CFA00604167 /* CacheService.swift in Sources */, 8A6098C221EBFD0D00592B01 /* RTCSessionDescriptionDelegateProxy.swift in Sources */, 4E3943332175E50200AD7582 /* NetServiceServer.swift in Sources */, + A7D756382237CAE900F9893E /* MfccDct.swift in Sources */, A18681932209A2350069E521 /* RecordingsIntroFeatureViewController.swift in Sources */, A18D3E6E21FF071200C165C6 /* CALayerBasicAnimation.swift in Sources */, 4E3943312175D4AD00AD7582 /* NetServiceClient.swift in Sources */, @@ -1969,6 +1976,7 @@ 4E95631B21638D1500289475 /* Localizable.swift in Sources */, A7D756272236AFA000F9893E /* crydetection.mlmodel in Sources */, 8A50AA4021E603080058C63A /* WebRtcStreamId.swift in Sources */, + A7D756362237C31400F9893E /* MathUtils.swift in Sources */, 8A7A612421A59E7500488ED4 /* FileManager+DocumentsDirectories.swift in Sources */, 8A7A612121A59E6500488ED4 /* Result.swift in Sources */, 4E57319F2170BA7400DEAF0B /* Coordinator.swift in Sources */, diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift index c87f280..651cfdf 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift @@ -48,18 +48,21 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { private func rxSetup() { microphoneCapture?.microphoneBufferReadableObservable.subscribe(onNext: { [unowned self] bufferReadable in do { - print("RUNNING ML MODEL") - let audioProcessingMultiArray = try MLMultiArray(dataPointer: bufferReadable.floatChannelData!.pointee, shape: [264600], dataType: .float32, strides: [1]) + print("RUNNING ML MODEL") + let audioProcessingMultiArray = try MLMultiArray(dataPointer: bufferReadable.floatChannelData!.pointee, + shape: [264600], + dataType: .float32, + strides: [1]) - let input = audioprocessingInput(raw_audio__0: audioProcessingMultiArray) - let pred = try self.audioprocessingModel.prediction(input: input) - let crydetectionMultiArray = try MLMultiArray(shape: [1, 1, 1, 598, 64], dataType: .float32) - crydetectionMultiArray.dataPointer.copyMemory(from: pred.Mfcc__0.dataPointer, byteCount: 38272 * 4) - let input1 = crydetectionInput(Mfcc__0: crydetectionMultiArray) - let pred2 = try self.crydetectionModel.prediction(input: input1) - print(pred2.labels_softmax__0) - - self.cryingDetectionSubject.onNext(false) + let input = audioprocessingInput(raw_audio__0: audioProcessingMultiArray) + let pred = try self.audioprocessingModel.prediction(input: input) + let crydetectionMultiArray = try MLMultiArray(shape: [1, 1, 1, 598, 64], dataType: .float32) + crydetectionMultiArray.dataPointer.copyMemory(from: pred.Mfcc__0.dataPointer, byteCount: 38272 * 4) + let input1 = crydetectionInput(Mfcc__0: crydetectionMultiArray) + let pred2 = try self.crydetectionModel.prediction(input: input1) + print(pred2.labels_softmax__0) + + self.cryingDetectionSubject.onNext(false) } catch { print("ERROR") } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift index ff414d5..e9b9ec8 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift @@ -10,16 +10,13 @@ import Foundation import CoreML import Accelerate -enum AudioSpectrogramLayerError: Error { - case parameterError(String) - case opError(String) -} - -func nextPowerOfTwo(value: Int) -> Int { - return Int(pow(2, ceil(log2(Double(value))))) -} - @objc(AudioSpectrogramLayer) class AudioSpectrogramLayer: NSObject, MLCustomLayer { + + enum AudioSpectrogramLayerError: Error { + case parameterError(String) + case evaluateError(String) + } + let windowSize: Int let stride: Int let magnitudeSquared: Bool @@ -27,29 +24,25 @@ func nextPowerOfTwo(value: Int) -> Int { let spectogramOp: SpectrogramOp required init(parameters: [String: Any]) throws { - if let windowSize = parameters["window_size"] as? Int { - self.windowSize = windowSize - } else { + guard let windowSize = parameters["window_size"] as? Int else { throw AudioSpectrogramLayerError.parameterError("window_size") } - - if let stride = parameters["stride"] as? Int { - self.stride = stride - } else { + self.windowSize = windowSize + + guard let stride = parameters["stride"] as? Int else { throw AudioSpectrogramLayerError.parameterError("stride") } + self.stride = stride - if let magnitudeSquared = parameters["magnitude_squared"] as? Bool { - self.magnitudeSquared = magnitudeSquared - } else { + guard let magnitudeSquared = parameters["magnitude_squared"] as? Bool else { throw AudioSpectrogramLayerError.parameterError("magnitude_squared") } + self.magnitudeSquared = magnitudeSquared - outputChannels = NSNumber(value: 1 + nextPowerOfTwo(value: self.windowSize) / 2) - - spectogramOp = SpectrogramOp(windowLength: windowSize, - stepLength: stride, - magnitudeSquared: magnitudeSquared) + outputChannels = NSNumber(value: 1 + MathUtils.nextPowerOfTwo(self.windowSize) / 2) + spectogramOp = try SpectrogramOp(windowLength: windowSize, + stepLength: stride, + magnitudeSquared: magnitudeSquared) super.init() } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift index da4e7c9..89a091c 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift @@ -43,8 +43,8 @@ class MfccDct { let fnorm = sqrt(2.0 / Double(inputLength)) let arg = Double.pi / Double(inputLength) - for i in 0.. [[NSNumber]] { // Input shape is [1,1,1,N_TIME_BINS, N_FREQUENCY_BINS] @@ -91,7 +88,5 @@ enum MfccLayerError: Error { let outputPointer = ptrOutput.advanced(by: spectrogramIndex * dctCoefficientCount) try mfccOp!.compute(input: inputPointer, inputLength: nSpectrogramChannels, output: outputPointer) } - } - } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift index 1a29944..d932dd2 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift @@ -10,6 +10,11 @@ import Foundation import Accelerate class MfccMelFilterbank { + + enum MfccMelFilterbankError: Error { + case parameterError(String) + } + var initialized: Bool = false var numChannels: Int var sampleRate: Double @@ -29,13 +34,13 @@ class MfccMelFilterbank { endIndex = 0 } - func initialize(inputLength: Int, inputSampleRate: Double, outputChannelCount: Int, lowerFrequencyLimit: Double, upperFrequencyLimit: Double) -> Bool { + func initialize(inputLength: Int, inputSampleRate: Double, outputChannelCount: Int, lowerFrequencyLimit: Double, upperFrequencyLimit: Double) throws { self.inputLength = inputLength self.sampleRate = inputSampleRate self.numChannels = outputChannelCount - let melLow = freqToMel(freq: lowerFrequencyLimit) - let melHi = freqToMel(freq: upperFrequencyLimit) + let melLow = MathUtils.freqToMel(lowerFrequencyLimit) + let melHi = MathUtils.freqToMel(upperFrequencyLimit) let melSpan = melHi - melLow let melSpacing = melSpan / Double(numChannels + 1) @@ -54,7 +59,7 @@ class MfccMelFilterbank { var channel = 0 for i in 0.. endIndex) { bandMapper![i] = -2 } else { @@ -71,24 +76,22 @@ class MfccMelFilterbank { if (i < startIndex) || (i > endIndex) { weights![i] = 0.0 } else { + let melFrequencyBin = MathUtils.freqToMel(Double(i) * hzPerSbin) if channel >= 0 { - weights![i] = (centerFrequencies![channel + 1] - freqToMel(freq: Double(i) * hzPerSbin)) / (centerFrequencies![channel + 1] - centerFrequencies![channel]) + weights![i] = (centerFrequencies![channel + 1] - melFrequencyBin) / (centerFrequencies![channel + 1] - centerFrequencies![channel]) } else { - weights![i] = (centerFrequencies![0] - freqToMel(freq: Double(i) * hzPerSbin)) / (centerFrequencies![0] - melLow) + weights![i] = (centerFrequencies![0] - melFrequencyBin) / (centerFrequencies![0] - melLow) } } } workData = [Float](repeating: 0.0, count: endIndex - startIndex + 1) - initialized = true - return true } func compute(input: UnsafeMutablePointer, output: UnsafeMutablePointer) { for i in startIndex...endIndex { - //let spec_val = sqrtf(input.advanced(by: i).pointee) - let specVal = input.advanced(by: i).pointee + let specVal = sqrtf(input.advanced(by: i).pointee) let weighted = specVal * Float((weights![i])) var channel = bandMapper![i] if channel >= 0 { @@ -101,8 +104,4 @@ class MfccMelFilterbank { } } } - - private func freqToMel(freq: Double) -> Double { - return 1127.0 * log(1.0 + (freq / 700.0)) - } } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift index 972faec..bce7bc3 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift @@ -15,6 +15,12 @@ import Accelerate // // class SpectrogramOp: NSObject { + + enum SpectrogramOpError: Error { + case parameterError(String) + case computeError(String) + } + var stepLength: Int = 0 var windowLength: Int = 0 var fftLength: Int = 0 @@ -32,20 +38,20 @@ class SpectrogramOp: NSObject { var fftSetup: vDSP_DFT_Setup? var magnitudeSquared: Bool = true - init(windowLength: Int, stepLength: Int, magnitudeSquared: Bool) { + init(windowLength: Int, stepLength: Int, magnitudeSquared: Bool) throws { self.windowLength = windowLength self.stepLength = stepLength self.magnitudeSquared = magnitudeSquared if self.windowLength < 2 { - print("window_length too short. window_length has to be greater or equal to 2.") + throw SpectrogramOpError.parameterError("windowLength has to be greater or equal to 2.") } if self.stepLength < 1 { - print("step_length must be strictly positive") + throw SpectrogramOpError.parameterError("stepLength must be strictly positive") } - fftLength = nextPowerOfTwo(value: self.windowLength) + fftLength = MathUtils.nextPowerOfTwo(self.windowLength) window = [Float](repeating: 0.0, count: self.windowLength) inputReal = [Float](repeating: 0.0, count: fftLength / 2 + 1) @@ -108,7 +114,7 @@ class SpectrogramOp: NSObject { var dspSplit = DSPSplitComplex(realp: &inputReal, imagp: &inputImg) // Compute output values and write to output buffer - if !magnitudeSquared { + if magnitudeSquared { vDSP_zvmags(&dspSplit, 1, outputPtr, 1, vDSP_Length(fftLength / 2 + 1)) } else { vDSP_zvabs(&dspSplit, 1, outputPtr, 1, vDSP_Length(fftLength / 2 + 1)) From 6bf2cdc859fc9c6d33e23565c2edcc8b82b82dbd Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Fri, 15 Mar 2019 13:10:34 +0100 Subject: [PATCH 07/19] Enable passing crying baby detection results instead of a constant no crying baby detected state --- .../CryingDetection/CryingDetectionService.swift | 5 +++-- .../Services/CryingDetection/CryingEventService.swift | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift index 651cfdf..78f8d2b 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift @@ -26,7 +26,6 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { private let cryingDetectionSubject = PublishSubject() - private var isCryingEventDetected = false private let microphoneCapture: AudioMicrophoneCaptureServiceProtocol? private let disposeBag = DisposeBag() private let audioprocessingModel = audioprocessing() @@ -60,9 +59,11 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { crydetectionMultiArray.dataPointer.copyMemory(from: pred.Mfcc__0.dataPointer, byteCount: 38272 * 4) let input1 = crydetectionInput(Mfcc__0: crydetectionMultiArray) let pred2 = try self.crydetectionModel.prediction(input: input1) + let babyCryingDetected: Bool = pred2.labels_softmax__0[0].compare(pred2.labels_softmax__0[1]) == .orderedAscending + self.cryingDetectionSubject.onNext(babyCryingDetected) + print(pred2.labels_softmax__0) - self.cryingDetectionSubject.onNext(false) } catch { print("ERROR") } diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift index 497f45b..58848c0 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift @@ -51,23 +51,25 @@ final class CryingEventService: CryingEventsServiceProtocol, ErrorProducable { func stop() { cryingDetectionService.stopAnalysis() - //microphoneRecord?.stopRecording() + guard self.microphoneRecord?.isRecording ?? false else { + return + } + self.microphoneRecord?.stopRecording() } private func rxSetup() { cryingDetectionService.cryingDetectionObservable.subscribe(onNext: { [unowned self] isBabyCrying in if isBabyCrying { - print("BABY CRYING") let fileNameSuffix = DateFormatter.fullTimeFormatString(breakCharacter: "_") self.nextFileName = "crying_".appending(fileNameSuffix).appending(".caf") - //self.microphoneRecord?.startRecording() + self.microphoneRecord?.startRecording() let cryingEventMessage = EventMessage.initWithCryingEvent(value: self.nextFileName) self.cryingEventPublisher.onNext(cryingEventMessage) } else { guard self.microphoneRecord?.isRecording ?? false else { return } - //self.microphoneRecord?.stopRecording() + self.microphoneRecord?.stopRecording() } }).disposed(by: disposeBag) From f6ebdfadd27464e0fe5814c9a0272fd01bc84eae Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Fri, 15 Mar 2019 18:26:02 +0100 Subject: [PATCH 08/19] Extract AudioMicrophoneRecord/CaptureServiceProtocol protocols to seperate source files --- Baby Monitor.xcodeproj/project.pbxproj | 8 ++++++++ .../AudioMicrophoneCaptureServiceProtocol.swift | 15 +++++++++++++++ .../AudioMicrophoneRecordServiceProtocol.swift | 14 ++++++++++++++ .../CryingDetection/AudioMicrophoneService.swift | 16 ---------------- 4 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 Baby Monitor/Source Files/Other Protocols/AudioMicrophoneCaptureServiceProtocol.swift create mode 100644 Baby Monitor/Source Files/Other Protocols/AudioMicrophoneRecordServiceProtocol.swift diff --git a/Baby Monitor.xcodeproj/project.pbxproj b/Baby Monitor.xcodeproj/project.pbxproj index ee6efca..7f2537a 100644 --- a/Baby Monitor.xcodeproj/project.pbxproj +++ b/Baby Monitor.xcodeproj/project.pbxproj @@ -235,6 +235,8 @@ A1ED8F912212C745005762E8 /* TwoOptionsBaseOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED8F902212C745005762E8 /* TwoOptionsBaseOnboardingView.swift */; }; A1ED8F932212CA35005762E8 /* OldBaseOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED8F922212CA35005762E8 /* OldBaseOnboardingView.swift */; }; A1ED8F952212CB8D005762E8 /* OnboardingContinuableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED8F942212CB8D005762E8 /* OnboardingContinuableViewController.swift */; }; + A7B48132223C16BB00FE4A74 /* AudioMicrophoneCaptureServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B48131223C16BB00FE4A74 /* AudioMicrophoneCaptureServiceProtocol.swift */; }; + A7B48134223C16D600FE4A74 /* AudioMicrophoneRecordServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B48133223C16D600FE4A74 /* AudioMicrophoneRecordServiceProtocol.swift */; }; A7D756082232DBC800F9893E /* CryingEventService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756052232DBC800F9893E /* CryingEventService.swift */; }; A7D756092232DBC800F9893E /* CryingDetectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756062232DBC800F9893E /* CryingDetectionService.swift */; }; A7D7561F2232DE8700F9893E /* MicrophoneFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7561E2232DE8700F9893E /* MicrophoneFactory.swift */; }; @@ -556,6 +558,8 @@ A1ED8F902212C745005762E8 /* TwoOptionsBaseOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoOptionsBaseOnboardingView.swift; sourceTree = ""; }; A1ED8F922212CA35005762E8 /* OldBaseOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OldBaseOnboardingView.swift; sourceTree = ""; }; A1ED8F942212CB8D005762E8 /* OnboardingContinuableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingContinuableViewController.swift; sourceTree = ""; }; + A7B48131223C16BB00FE4A74 /* AudioMicrophoneCaptureServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMicrophoneCaptureServiceProtocol.swift; sourceTree = ""; }; + A7B48133223C16D600FE4A74 /* AudioMicrophoneRecordServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMicrophoneRecordServiceProtocol.swift; sourceTree = ""; }; A7D756052232DBC800F9893E /* CryingEventService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryingEventService.swift; sourceTree = ""; }; A7D756062232DBC800F9893E /* CryingDetectionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryingDetectionService.swift; sourceTree = ""; }; A7D7561E2232DE8700F9893E /* MicrophoneFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrophoneFactory.swift; sourceTree = ""; }; @@ -767,6 +771,8 @@ 8AED318D219045AD00FEFE8A /* BabyMonitorCellDeletable.swift */, 4E7B091221E6148600EDDD11 /* URLSessionProtocol.swift */, 3A8DAC88222D403400427BBE /* ApplicationStateProvider.swift */, + A7B48131223C16BB00FE4A74 /* AudioMicrophoneCaptureServiceProtocol.swift */, + A7B48133223C16D600FE4A74 /* AudioMicrophoneRecordServiceProtocol.swift */, ); path = "Other Protocols"; sourceTree = ""; @@ -1932,6 +1938,7 @@ 4E1D2C692167926500E92F29 /* BaseView.swift in Sources */, 8A8B76ED21EF53A20063EF7E /* WebSocketEventMessageService.swift in Sources */, EC8A8FF521AE8F5F00B5FCCF /* TypedPageViewController.swift in Sources */, + A7B48132223C16BB00FE4A74 /* AudioMicrophoneCaptureServiceProtocol.swift in Sources */, 8A8B76DF21EDE3640063EF7E /* WebsocketMessageDecodable.swift in Sources */, 4E7A1A9221F12BD00015C3A3 /* ServerCoordinator.swift in Sources */, A7D756082232DBC800F9893E /* CryingEventService.swift in Sources */, @@ -1977,6 +1984,7 @@ A7D756272236AFA000F9893E /* crydetection.mlmodel in Sources */, 8A50AA4021E603080058C63A /* WebRtcStreamId.swift in Sources */, A7D756362237C31400F9893E /* MathUtils.swift in Sources */, + A7B48134223C16D600FE4A74 /* AudioMicrophoneRecordServiceProtocol.swift in Sources */, 8A7A612421A59E7500488ED4 /* FileManager+DocumentsDirectories.swift in Sources */, 8A7A612121A59E6500488ED4 /* Result.swift in Sources */, 4E57319F2170BA7400DEAF0B /* Coordinator.swift in Sources */, diff --git a/Baby Monitor/Source Files/Other Protocols/AudioMicrophoneCaptureServiceProtocol.swift b/Baby Monitor/Source Files/Other Protocols/AudioMicrophoneCaptureServiceProtocol.swift new file mode 100644 index 0000000..fbf2631 --- /dev/null +++ b/Baby Monitor/Source Files/Other Protocols/AudioMicrophoneCaptureServiceProtocol.swift @@ -0,0 +1,15 @@ +// +// AudioMicrophoneCaptureServiceProtocol.swift +// Baby Monitor +// + +import AVFoundation +import RxSwift + +protocol AudioMicrophoneCaptureServiceProtocol { + var microphoneBufferReadableObservable: Observable { get } + var isCapturing: Bool { get } + + func stopCapturing() + func startCapturing() +} diff --git a/Baby Monitor/Source Files/Other Protocols/AudioMicrophoneRecordServiceProtocol.swift b/Baby Monitor/Source Files/Other Protocols/AudioMicrophoneRecordServiceProtocol.swift new file mode 100644 index 0000000..8cd553e --- /dev/null +++ b/Baby Monitor/Source Files/Other Protocols/AudioMicrophoneRecordServiceProtocol.swift @@ -0,0 +1,14 @@ +// +// AudioMicrophoneRecordServiceProtocol.swift +// Baby Monitor +// + +import RxSwift + +protocol AudioMicrophoneRecordServiceProtocol { + var directoryDocumentsSavableObservable: Observable { get } + var isRecording: Bool { get } + + func stopRecording() + func startRecording() +} diff --git a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift index c947eab..72374b7 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift @@ -12,22 +12,6 @@ protocol ErrorProducable { var errorObservable: Observable { get } } -protocol AudioMicrophoneRecordServiceProtocol { - var directoryDocumentsSavableObservable: Observable { get } - var isRecording: Bool { get } - - func stopRecording() - func startRecording() -} - -protocol AudioMicrophoneCaptureServiceProtocol { - var microphoneBufferReadableObservable: Observable { get } - var isCapturing: Bool { get } - - func stopCapturing() - func startCapturing() -} - protocol AudioMicrophoneServiceProtocol: AudioMicrophoneRecordServiceProtocol, AudioMicrophoneCaptureServiceProtocol {} final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProducable { From 72b48b38445f01da33c8fc005ebfeb5f555c270c Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Fri, 15 Mar 2019 18:28:38 +0100 Subject: [PATCH 09/19] Removed redundant print calls and redundant empty lines --- .../Services/CryingDetection/CryingDetectionService.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift index 78f8d2b..9ddc41c 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift @@ -47,7 +47,6 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { private func rxSetup() { microphoneCapture?.microphoneBufferReadableObservable.subscribe(onNext: { [unowned self] bufferReadable in do { - print("RUNNING ML MODEL") let audioProcessingMultiArray = try MLMultiArray(dataPointer: bufferReadable.floatChannelData!.pointee, shape: [264600], dataType: .float32, @@ -61,9 +60,6 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { let pred2 = try self.crydetectionModel.prediction(input: input1) let babyCryingDetected: Bool = pred2.labels_softmax__0[0].compare(pred2.labels_softmax__0[1]) == .orderedAscending self.cryingDetectionSubject.onNext(babyCryingDetected) - - print(pred2.labels_softmax__0) - } catch { print("ERROR") } From 2fdfbe146cf9d570d1b7e261aa210d258f313d86 Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Mon, 25 Mar 2019 09:33:46 +0100 Subject: [PATCH 10/19] Rename microphoneRecord and microphoneCapture to microphoneRecordService and microphoneCaptureService to better reflect their true purpose --- .../CryingDetectionService.swift | 12 +++++------ .../CryingDetection/CryingEventService.swift | 20 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift index 9ddc41c..ae909a2 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingDetectionService.swift @@ -26,26 +26,26 @@ final class CryingDetectionService: CryingDetectionServiceProtocol { private let cryingDetectionSubject = PublishSubject() - private let microphoneCapture: AudioMicrophoneCaptureServiceProtocol? + private let microphoneCaptureService: AudioMicrophoneCaptureServiceProtocol? private let disposeBag = DisposeBag() private let audioprocessingModel = audioprocessing() private let crydetectionModel = crydetection() - init(microphoneCapture: AudioMicrophoneCaptureServiceProtocol?) { - self.microphoneCapture = microphoneCapture + init(microphoneCaptureService: AudioMicrophoneCaptureServiceProtocol?) { + self.microphoneCaptureService = microphoneCaptureService rxSetup() } func startAnalysis() { - microphoneCapture?.startCapturing() + microphoneCaptureService?.startCapturing() } func stopAnalysis() { - microphoneCapture?.stopCapturing() + microphoneCaptureService?.stopCapturing() } private func rxSetup() { - microphoneCapture?.microphoneBufferReadableObservable.subscribe(onNext: { [unowned self] bufferReadable in + microphoneCaptureService?.microphoneBufferReadableObservable.subscribe(onNext: { [unowned self] bufferReadable in do { let audioProcessingMultiArray = try MLMultiArray(dataPointer: bufferReadable.floatChannelData!.pointee, shape: [264600], diff --git a/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift b/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift index 58848c0..725ef0a 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/CryingEventService.swift @@ -29,14 +29,14 @@ final class CryingEventService: CryingEventsServiceProtocol, ErrorProducable { private let cryingEventPublisher = PublishSubject() private let errorPublisher = PublishSubject() private let cryingDetectionService: CryingDetectionServiceProtocol - private let microphoneRecord: AudioMicrophoneRecordServiceProtocol? + private let microphoneRecordService: AudioMicrophoneRecordServiceProtocol? private let activityLogEventsRepository: ActivityLogEventsRepositoryProtocol private let storageService: StorageServerServiceProtocol private let disposeBag = DisposeBag() - init(cryingDetectionService: CryingDetectionServiceProtocol, microphoneRecord: AudioMicrophoneRecordServiceProtocol?, activityLogEventsRepository: ActivityLogEventsRepositoryProtocol, storageService: StorageServerServiceProtocol) { + init(cryingDetectionService: CryingDetectionServiceProtocol, microphoneRecordService: AudioMicrophoneRecordServiceProtocol?, activityLogEventsRepository: ActivityLogEventsRepositoryProtocol, storageService: StorageServerServiceProtocol) { self.cryingDetectionService = cryingDetectionService - self.microphoneRecord = microphoneRecord + self.microphoneRecordService = microphoneRecordService self.activityLogEventsRepository = activityLogEventsRepository self.storageService = storageService rxSetup() @@ -44,17 +44,17 @@ final class CryingEventService: CryingEventsServiceProtocol, ErrorProducable { func start() throws { cryingDetectionService.startAnalysis() - if microphoneRecord == nil { + if microphoneRecordService == nil { throw CryingEventServiceError.audioRecordServiceError } } func stop() { cryingDetectionService.stopAnalysis() - guard self.microphoneRecord?.isRecording ?? false else { + guard self.microphoneRecordService?.isRecording ?? false else { return } - self.microphoneRecord?.stopRecording() + self.microphoneRecordService?.stopRecording() } private func rxSetup() { @@ -62,18 +62,18 @@ final class CryingEventService: CryingEventsServiceProtocol, ErrorProducable { if isBabyCrying { let fileNameSuffix = DateFormatter.fullTimeFormatString(breakCharacter: "_") self.nextFileName = "crying_".appending(fileNameSuffix).appending(".caf") - self.microphoneRecord?.startRecording() + self.microphoneRecordService?.startRecording() let cryingEventMessage = EventMessage.initWithCryingEvent(value: self.nextFileName) self.cryingEventPublisher.onNext(cryingEventMessage) } else { - guard self.microphoneRecord?.isRecording ?? false else { + guard self.microphoneRecordService?.isRecording ?? false else { return } - self.microphoneRecord?.stopRecording() + self.microphoneRecordService?.stopRecording() } }).disposed(by: disposeBag) - microphoneRecord?.directoryDocumentsSavableObservable.subscribe(onNext: { [unowned self] savableFile in + microphoneRecordService?.directoryDocumentsSavableObservable.subscribe(onNext: { [unowned self] savableFile in savableFile.save(withName: self.nextFileName, completion: { [unowned self] result in switch result { case .success: From 36d476cb194e62e57ddbf9c384620230fea08a6f Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Mon, 25 Mar 2019 09:37:36 +0100 Subject: [PATCH 11/19] Renamed capture and record variable to microphone{Capturer,Recorder} for more clarity on the purpose --- .../AudioMicrophoneService.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift index 72374b7..a996687 100644 --- a/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift +++ b/Baby Monitor/Source Files/Services/CryingDetection/AudioMicrophoneService.swift @@ -30,8 +30,8 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca private(set) var isCapturing = false private(set) var isRecording = false - private var capture: MicrophoneCaptureProtocol - private var record: MicrophoneRecordProtocol + private var microphoneCapturer: MicrophoneCaptureProtocol + private var microphoneRecorder: MicrophoneRecordProtocol private let errorSubject = PublishSubject() private let microphoneBufferReadableSubject = PublishSubject() @@ -43,8 +43,8 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca guard let audioKitMicrophone = try microphoneFactory() else { throw(AudioMicrophoneService.AudioError.initializationFailure) } - capture = audioKitMicrophone.capture - record = audioKitMicrophone.record + microphoneCapturer = audioKitMicrophone.capture + microphoneRecorder = audioKitMicrophone.record rxSetup() } @@ -52,7 +52,7 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca guard isCapturing else { return } - capture.stop() + microphoneCapturer.stop() isCapturing = false } @@ -61,7 +61,7 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca return } do { - try capture.start() + try microphoneCapturer.start() } catch { return } @@ -72,9 +72,9 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca guard isRecording else { return } - record.stop() + microphoneRecorder.stop() isRecording = false - guard let audioFile = record.audioFile else { + guard let audioFile = microphoneRecorder.audioFile else { errorSubject.onNext(AudioError.recordFailure) return } @@ -86,8 +86,8 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca return } do { - try record.reset() - try record.record() + try microphoneRecorder.reset() + try microphoneRecorder.record() isRecording = true } catch { errorSubject.onNext(AudioError.recordFailure) @@ -95,7 +95,7 @@ final class AudioMicrophoneService: AudioMicrophoneServiceProtocol, ErrorProduca } private func rxSetup() { - capture.bufferReadable.subscribe(onNext: { [unowned self] bufferReadable in + microphoneCapturer.bufferReadable.subscribe(onNext: { [unowned self] bufferReadable in self.microphoneBufferReadableSubject.onNext(bufferReadable) }).disposed(by: disposeBag) } From 5e82a923827b9fdf6477f9f21e9d41d33ff90f94 Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Mon, 25 Mar 2019 09:43:53 +0100 Subject: [PATCH 12/19] Update naming to reflect previous changes from AudioRecordService to AudioMicrophoneService --- Baby Monitor/Source Files/AppDependencies.swift | 4 ++-- .../Source Files/Modules/Server/ServerCoordinator.swift | 2 +- .../Source Files/Modules/Server/ServerViewModel.swift | 6 +++--- Baby Monitor/Source Files/Services/ServerService.swift | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Baby Monitor/Source Files/AppDependencies.swift b/Baby Monitor/Source Files/AppDependencies.swift index 6115b81..0051f36 100644 --- a/Baby Monitor/Source Files/AppDependencies.swift +++ b/Baby Monitor/Source Files/AppDependencies.swift @@ -18,11 +18,11 @@ final class AppDependencies { /// Service for capturing/recording microphone audio private(set) lazy var audioMicrophoneService: AudioMicrophoneServiceProtocol? = try? AudioMicrophoneService(microphoneFactory: AudioKitMicrophoneFactory.makeMicrophoneFactory) /// Service for detecting baby's cry - private(set) lazy var cryingDetectionService: CryingDetectionServiceProtocol = CryingDetectionService(microphoneCapture: audioMicrophoneService) + private(set) lazy var cryingDetectionService: CryingDetectionServiceProtocol = CryingDetectionService(microphoneCaptureService: audioMicrophoneService) /// Service that takes care of appropriate controling: crying detection, audio recording and saving these events to realm database private(set) lazy var cryingEventService: CryingEventsServiceProtocol = CryingEventService( cryingDetectionService: cryingDetectionService, - microphoneRecord: audioMicrophoneService, + microphoneRecordService: audioMicrophoneService, activityLogEventsRepository: databaseRepository, storageService: storageServerService) diff --git a/Baby Monitor/Source Files/Modules/Server/ServerCoordinator.swift b/Baby Monitor/Source Files/Modules/Server/ServerCoordinator.swift index ba65c00..d842a7f 100644 --- a/Baby Monitor/Source Files/Modules/Server/ServerCoordinator.swift +++ b/Baby Monitor/Source Files/Modules/Server/ServerCoordinator.swift @@ -27,7 +27,7 @@ final class ServerCoordinator: Coordinator { private func showServerView() { let viewModel = ServerViewModel(serverService: appDependencies.serverService) let serverViewController = ServerViewController(viewModel: viewModel) - viewModel.onAudioRecordServiceError = { [unowned self, weak serverViewController] in + viewModel.onAudioMicrophoneServiceError = { [unowned self, weak serverViewController] in guard !self.isAudioServiceErrorAlreadyShown, let serverViewController = serverViewController diff --git a/Baby Monitor/Source Files/Modules/Server/ServerViewModel.swift b/Baby Monitor/Source Files/Modules/Server/ServerViewModel.swift index 09f15c9..a9597da 100644 --- a/Baby Monitor/Source Files/Modules/Server/ServerViewModel.swift +++ b/Baby Monitor/Source Files/Modules/Server/ServerViewModel.swift @@ -11,7 +11,7 @@ final class ServerViewModel { var stream: Observable { return serverService.localStreamObservable } - var onAudioRecordServiceError: (() -> Void)? + var onAudioMicrophoneServiceError: (() -> Void)? var settingsTap: Observable? let bag = DisposeBag() @@ -32,8 +32,8 @@ final class ServerViewModel { } private func rxSetup() { - serverService.audioRecordServiceErrorObservable.subscribe(onNext: { [weak self] _ in - self?.onAudioRecordServiceError?() + serverService.audioMicrophoneServiceErrorObservable.subscribe(onNext: { [weak self] _ in + self?.onAudioMicrophoneServiceError?() }) .disposed(by: bag) } diff --git a/Baby Monitor/Source Files/Services/ServerService.swift b/Baby Monitor/Source Files/Services/ServerService.swift index d3de728..f50987c 100644 --- a/Baby Monitor/Source Files/Services/ServerService.swift +++ b/Baby Monitor/Source Files/Services/ServerService.swift @@ -8,7 +8,7 @@ import RxSwift protocol ServerServiceProtocol: AnyObject { var localStreamObservable: Observable { get } - var audioRecordServiceErrorObservable: Observable { get } + var audioMicrophoneServiceErrorObservable: Observable { get } func startStreaming() func stop() } @@ -18,7 +18,7 @@ final class ServerService: ServerServiceProtocol { var localStreamObservable: Observable { return webRtcServerManager.mediaStream } - lazy var audioRecordServiceErrorObservable = audioRecordServiceErrorPublisher.asObservable() + lazy var audioMicrophoneServiceErrorObservable = audioMicrophoneServiceErrorPublisher.asObservable() private var isCryingMessageReceivedFromClient = false private var timer: Timer? @@ -32,7 +32,7 @@ final class ServerService: ServerServiceProtocol { private let disposeBag = DisposeBag() private let decoders: [AnyMessageDecoder] private let notificationsService: NotificationServiceProtocol - private let audioRecordServiceErrorPublisher = PublishSubject() + private let audioMicrophoneServiceErrorPublisher = PublishSubject() private let babyMonitorEventMessagesDecoder: AnyMessageDecoder init(webRtcServerManager: WebRtcServerManagerProtocol, messageServer: MessageServerProtocol, netServiceServer: NetServiceServerProtocol, webRtcDecoders: [AnyMessageDecoder], cryingService: CryingEventsServiceProtocol, babyModelController: BabyModelControllerProtocol, cacheService: CacheServiceProtocol, notificationsService: NotificationServiceProtocol, babyMonitorEventMessagesDecoder: AnyMessageDecoder, parentResponseTime: TimeInterval = 5.0) { @@ -144,7 +144,7 @@ final class ServerService: ServerServiceProtocol { } catch { switch error { case CryingEventService.CryingEventServiceError.audioRecordServiceError: - audioRecordServiceErrorPublisher.onNext(()) + audioMicrophoneServiceErrorPublisher.onNext(()) default: break } From 34718cf3ede8c5f02bc59560f4cdb4bd8714d8c3 Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Mon, 25 Mar 2019 13:26:24 +0100 Subject: [PATCH 13/19] Changes addressing comments from CodeReview --- Baby Monitor.xcodeproj/project.pbxproj | 8 +++---- .../AudioSpectrogramLayer.swift | 24 +++++++------------ .../MachineLearning/MathUtilities.swift | 8 +++++++ .../Services/MachineLearning/MathUtils.swift | 20 ---------------- .../MachineLearning/MfccMelFilterbank.swift | 12 ++++++---- .../Services/MachineLearning/MfccOp.swift | 2 -- .../MachineLearning/SpectrogramOp.swift | 4 ++-- 7 files changed, 30 insertions(+), 48 deletions(-) create mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift delete mode 100644 Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift diff --git a/Baby Monitor.xcodeproj/project.pbxproj b/Baby Monitor.xcodeproj/project.pbxproj index 7f2537a..e4c284b 100644 --- a/Baby Monitor.xcodeproj/project.pbxproj +++ b/Baby Monitor.xcodeproj/project.pbxproj @@ -249,7 +249,7 @@ A7D756322236B03D00F9893E /* AudioSpectrogramLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */; }; A7D756332236B03D00F9893E /* MfccOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562E2236B03D00F9893E /* MfccOp.swift */; }; A7D756342236B03D00F9893E /* SpectrogramOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */; }; - A7D756362237C31400F9893E /* MathUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756352237C31400F9893E /* MathUtils.swift */; }; + A7D756362237C31400F9893E /* MathUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756352237C31400F9893E /* MathUtilities.swift */; }; A7D756382237CAE900F9893E /* MfccDct.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D756372237CAE900F9893E /* MfccDct.swift */; }; EC4C9947216B946B0093EDFC /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4C9946216B946B0093EDFC /* OnboardingCoordinator.swift */; }; EC82B55A213EA072005CA395 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC82B559213EA072005CA395 /* AppDelegate.swift */; }; @@ -572,7 +572,7 @@ A7D7562D2236B03D00F9893E /* AudioSpectrogramLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSpectrogramLayer.swift; sourceTree = ""; }; A7D7562E2236B03D00F9893E /* MfccOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccOp.swift; sourceTree = ""; }; A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpectrogramOp.swift; sourceTree = ""; }; - A7D756352237C31400F9893E /* MathUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathUtils.swift; sourceTree = ""; }; + A7D756352237C31400F9893E /* MathUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathUtilities.swift; sourceTree = ""; }; A7D756372237CAE900F9893E /* MfccDct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MfccDct.swift; sourceTree = ""; }; DC0A7360EB7E7A40F5162D1F /* Pods_Baby_MonitorTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Baby_MonitorTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC4C9946216B946B0093EDFC /* OnboardingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCoordinator.swift; sourceTree = ""; }; @@ -1397,7 +1397,7 @@ A7D7562B2236B03D00F9893E /* MfccMelFilterbank.swift */, A7D7562E2236B03D00F9893E /* MfccOp.swift */, A7D7562F2236B03D00F9893E /* SpectrogramOp.swift */, - A7D756352237C31400F9893E /* MathUtils.swift */, + A7D756352237C31400F9893E /* MathUtilities.swift */, ); path = MachineLearning; sourceTree = ""; @@ -1983,7 +1983,7 @@ 4E95631B21638D1500289475 /* Localizable.swift in Sources */, A7D756272236AFA000F9893E /* crydetection.mlmodel in Sources */, 8A50AA4021E603080058C63A /* WebRtcStreamId.swift in Sources */, - A7D756362237C31400F9893E /* MathUtils.swift in Sources */, + A7D756362237C31400F9893E /* MathUtilities.swift in Sources */, A7B48134223C16D600FE4A74 /* AudioMicrophoneRecordServiceProtocol.swift in Sources */, 8A7A612421A59E7500488ED4 /* FileManager+DocumentsDirectories.swift in Sources */, 8A7A612121A59E6500488ED4 /* Result.swift in Sources */, diff --git a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift index e9b9ec8..024b547 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift @@ -13,8 +13,7 @@ import Accelerate @objc(AudioSpectrogramLayer) class AudioSpectrogramLayer: NSObject, MLCustomLayer { enum AudioSpectrogramLayerError: Error { - case parameterError(String) - case evaluateError(String) + case parameterError } let windowSize: Int @@ -24,24 +23,18 @@ import Accelerate let spectogramOp: SpectrogramOp required init(parameters: [String: Any]) throws { - guard let windowSize = parameters["window_size"] as? Int else { - throw AudioSpectrogramLayerError.parameterError("window_size") + guard let windowSize = parameters["window_size"] as? Int, + let stride = parameters["stride"] as? Int, + let magnitudeSquared = parameters["magnitude_squared"] as? Bool else { + throw AudioSpectrogramLayerError.parameterError } self.windowSize = windowSize - - guard let stride = parameters["stride"] as? Int else { - throw AudioSpectrogramLayerError.parameterError("stride") - } self.stride = stride - - guard let magnitudeSquared = parameters["magnitude_squared"] as? Bool else { - throw AudioSpectrogramLayerError.parameterError("magnitude_squared") - } self.magnitudeSquared = magnitudeSquared - outputChannels = NSNumber(value: 1 + MathUtils.nextPowerOfTwo(self.windowSize) / 2) - spectogramOp = try SpectrogramOp(windowLength: windowSize, - stepLength: stride, + outputChannels = NSNumber(value: 1 + UInt(self.windowSize).nextPowerOfTwo / 2) + spectogramOp = try SpectrogramOp(windowLength: Int(windowSize), + stepLength: Int(stride), magnitudeSquared: magnitudeSquared) super.init() } @@ -58,7 +51,6 @@ import Accelerate let inputLength = Int(truncating: inputShapes[0][2]) let outputLength = NSNumber(value: 1 + (inputLength - self.windowSize) / self.stride) outputShapesArray.append([1, 1, 1, outputLength, outputChannels]) - print("AudioSpectrogramLayer: ", #function, inputShapes, " -> ", outputShapesArray) return outputShapesArray } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift b/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift new file mode 100644 index 0000000..5faa2f3 --- /dev/null +++ b/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift @@ -0,0 +1,8 @@ +// +// MathUtilities.swift +// Baby Monitor +// + +extension UInt { + var nextPowerOfTwo: UInt { return UInt(pow(2, ceil(log2(Double(self)))))} +} diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift b/Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift deleted file mode 100644 index 89284fb..0000000 --- a/Baby Monitor/Source Files/Services/MachineLearning/MathUtils.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// MathUtils.swift -// Baby Monitor -// - -import Foundation - -enum MathUtils { - enum MathUtilsError: Error { - case parameterError(String) - } - - static var nextPowerOfTwo: (Int) -> Int = {(value: Int) -> Int in - return Int(pow(2, ceil(log2(Double(value))))) - } - - static var freqToMel: (Double) -> Double = {(freq: Double) -> Double in - return 1127.0 * log(1.0 + (freq / 700.0)) - } -} diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift index d932dd2..688d233 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift @@ -39,8 +39,8 @@ class MfccMelFilterbank { self.sampleRate = inputSampleRate self.numChannels = outputChannelCount - let melLow = MathUtils.freqToMel(lowerFrequencyLimit) - let melHi = MathUtils.freqToMel(upperFrequencyLimit) + let melLow = MfccMelFilterbank.freqToMel(freq: lowerFrequencyLimit) + let melHi = MfccMelFilterbank.freqToMel(freq: upperFrequencyLimit) let melSpan = melHi - melLow let melSpacing = melSpan / Double(numChannels + 1) @@ -59,7 +59,7 @@ class MfccMelFilterbank { var channel = 0 for i in 0.. endIndex) { bandMapper![i] = -2 } else { @@ -76,7 +76,7 @@ class MfccMelFilterbank { if (i < startIndex) || (i > endIndex) { weights![i] = 0.0 } else { - let melFrequencyBin = MathUtils.freqToMel(Double(i) * hzPerSbin) + let melFrequencyBin = MfccMelFilterbank.freqToMel(freq: Double(i) * hzPerSbin) if channel >= 0 { weights![i] = (centerFrequencies![channel + 1] - melFrequencyBin) / (centerFrequencies![channel + 1] - centerFrequencies![channel]) } else { @@ -104,4 +104,8 @@ class MfccMelFilterbank { } } } + + static func freqToMel(freq: Double) -> Double { + return 1127.0 * log(1.0 + (freq / 700.0)) + } } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift index 190c996..334a362 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift @@ -61,7 +61,6 @@ class MfccOp { mfccDct = MfccDct() } melFilterbank = MfccMelFilterbank() - } func initialize(inputLength: Int, inputSampleRate: Double) throws { @@ -121,7 +120,6 @@ class MfccOp { guard let mfccDct = mfccDct else { throw MfccOpError.mfccDctError("mfccDCT Op not available") } - mfccDct.compute(input: &workingData3, output: output) } } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift index bce7bc3..0b5bf05 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift @@ -34,7 +34,7 @@ class SpectrogramOp: NSObject { var fftReal: [Float] var fftImg: [Float] - var samplesToNextStep: Int = 0 + var samplesToNextStep: Int var fftSetup: vDSP_DFT_Setup? var magnitudeSquared: Bool = true @@ -51,7 +51,7 @@ class SpectrogramOp: NSObject { throw SpectrogramOpError.parameterError("stepLength must be strictly positive") } - fftLength = MathUtils.nextPowerOfTwo(self.windowLength) + fftLength = Int(UInt(self.windowLength).nextPowerOfTwo) window = [Float](repeating: 0.0, count: self.windowLength) inputReal = [Float](repeating: 0.0, count: fftLength / 2 + 1) From e461fee697ddf677ece8261591278ba60fdca2ef Mon Sep 17 00:00:00 2001 From: Adrian Kashivskyy Date: Wed, 3 Apr 2019 21:46:40 +0200 Subject: [PATCH 14/19] Update tests to reflect changes in cry detection --- Baby Monitor.xcodeproj/project.pbxproj | 32 ++-- ...wift => AudioMicrophoneServiceTests.swift} | 15 +- .../Mocks/AudioKitMicrophoneMock.swift | 19 ++ ...swift => AudioMicrophoneServiceMock.swift} | 19 +- .../Mocks/MicrophoneCaptureMock.swift | 30 ++++ ...rMock.swift => MicrophoneRecordMock.swift} | 4 +- .../CryingDetectionServiceTests.swift | 168 +++++++++--------- .../Services/CryingEventServiceTests.swift | 16 +- 8 files changed, 189 insertions(+), 114 deletions(-) rename Baby MonitorTests/Media/{AudioRecordServiceTests.swift => AudioMicrophoneServiceTests.swift} (79%) create mode 100644 Baby MonitorTests/Mocks/AudioKitMicrophoneMock.swift rename Baby MonitorTests/Mocks/{AudioRecordServiceMock.swift => AudioMicrophoneServiceMock.swift} (52%) create mode 100644 Baby MonitorTests/Mocks/MicrophoneCaptureMock.swift rename Baby MonitorTests/Mocks/{RecorderMock.swift => MicrophoneRecordMock.swift} (84%) diff --git a/Baby Monitor.xcodeproj/project.pbxproj b/Baby Monitor.xcodeproj/project.pbxproj index 6ceab5e..e4684a7 100644 --- a/Baby Monitor.xcodeproj/project.pbxproj +++ b/Baby Monitor.xcodeproj/project.pbxproj @@ -19,6 +19,9 @@ 3A3C61AC223EC2140020FD3D /* StreamVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3C61AB223EC2140020FD3D /* StreamVideoView.swift */; }; 3A8DAC89222D403400427BBE /* ApplicationStateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8DAC88222D403400427BBE /* ApplicationStateProvider.swift */; }; 3A8DAC8B222D512400427BBE /* ApplicationStateProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8DAC8A222D512400427BBE /* ApplicationStateProviderMock.swift */; }; + 3ABC41B722553F7900645ADA /* AudioKitMicrophoneMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABC41B622553F7900645ADA /* AudioKitMicrophoneMock.swift */; }; + 3ABC41B922553FE700645ADA /* MicrophoneRecordMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABC41B822553FE700645ADA /* MicrophoneRecordMock.swift */; }; + 3ABC41BB2255407100645ADA /* MicrophoneCaptureMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABC41BA2255407100645ADA /* MicrophoneCaptureMock.swift */; }; 4E190CA321789E820004ED79 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E190CA221789E820004ED79 /* Constants.swift */; }; 4E1CF817217856C500F48706 /* ServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1CF816217856C500F48706 /* ServerViewModel.swift */; }; 4E1D2C5D21673FF800E92F29 /* CameraPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E1D2C5C21673FF800E92F29 /* CameraPreviewViewController.swift */; }; @@ -155,10 +158,9 @@ 8A7A612E21A5A35300488ED4 /* WebRtcServerManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A612D21A5A35300488ED4 /* WebRtcServerManagerProtocol.swift */; }; 8A7A613E21A6AACD00488ED4 /* MessageServerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A613D21A6AACD00488ED4 /* MessageServerMock.swift */; }; 8A7A614921A6AC7900488ED4 /* CryingDetectionServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614421A6AC7800488ED4 /* CryingDetectionServiceMock.swift */; }; - 8A7A614A21A6AC7900488ED4 /* AudioRecordServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614521A6AC7900488ED4 /* AudioRecordServiceMock.swift */; }; + 8A7A614A21A6AC7900488ED4 /* AudioMicrophoneServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614521A6AC7900488ED4 /* AudioMicrophoneServiceMock.swift */; }; 8A7A614B21A6AC7900488ED4 /* DocumentsSavableMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614621A6AC7900488ED4 /* DocumentsSavableMock.swift */; }; - 8A7A614C21A6AC7900488ED4 /* RecorderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614721A6AC7900488ED4 /* RecorderMock.swift */; }; - 8A7A614E21A6AC8800488ED4 /* AudioRecordServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614D21A6AC8800488ED4 /* AudioRecordServiceTests.swift */; }; + 8A7A614E21A6AC8800488ED4 /* AudioMicrophoneServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614D21A6AC8800488ED4 /* AudioMicrophoneServiceTests.swift */; }; 8A7A615021A6ACB000488ED4 /* CryingEventServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A614F21A6ACB000488ED4 /* CryingEventServiceTests.swift */; }; 8A7A615221A6B00F00488ED4 /* CryingEventsServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A615121A6B00F00488ED4 /* CryingEventsServiceMock.swift */; }; 8A7A616D21A8162000488ED4 /* UIColor+Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A616C21A8162000488ED4 /* UIColor+Custom.swift */; }; @@ -297,6 +299,9 @@ 3A3C61AB223EC2140020FD3D /* StreamVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamVideoView.swift; sourceTree = ""; }; 3A8DAC88222D403400427BBE /* ApplicationStateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStateProvider.swift; sourceTree = ""; }; 3A8DAC8A222D512400427BBE /* ApplicationStateProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStateProviderMock.swift; sourceTree = ""; }; + 3ABC41B622553F7900645ADA /* AudioKitMicrophoneMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioKitMicrophoneMock.swift; sourceTree = ""; }; + 3ABC41B822553FE700645ADA /* MicrophoneRecordMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrophoneRecordMock.swift; sourceTree = ""; }; + 3ABC41BA2255407100645ADA /* MicrophoneCaptureMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrophoneCaptureMock.swift; sourceTree = ""; }; 3D35897D6D02EF8BB7728EDE /* Pods-Baby MonitorTests.production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Baby MonitorTests.production.xcconfig"; path = "Target Support Files/Pods-Baby MonitorTests/Pods-Baby MonitorTests.production.xcconfig"; sourceTree = ""; }; 4E1134B321E498E5005E3583 /* BabyMonitor.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BabyMonitor.entitlements; sourceTree = ""; }; 4E190CA221789E820004ED79 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; @@ -480,10 +485,9 @@ 8A7A612D21A5A35300488ED4 /* WebRtcServerManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRtcServerManagerProtocol.swift; sourceTree = ""; }; 8A7A613D21A6AACD00488ED4 /* MessageServerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageServerMock.swift; sourceTree = ""; }; 8A7A614421A6AC7800488ED4 /* CryingDetectionServiceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryingDetectionServiceMock.swift; sourceTree = ""; }; - 8A7A614521A6AC7900488ED4 /* AudioRecordServiceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioRecordServiceMock.swift; sourceTree = ""; }; + 8A7A614521A6AC7900488ED4 /* AudioMicrophoneServiceMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioMicrophoneServiceMock.swift; sourceTree = ""; }; 8A7A614621A6AC7900488ED4 /* DocumentsSavableMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentsSavableMock.swift; sourceTree = ""; }; - 8A7A614721A6AC7900488ED4 /* RecorderMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecorderMock.swift; sourceTree = ""; }; - 8A7A614D21A6AC8800488ED4 /* AudioRecordServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioRecordServiceTests.swift; sourceTree = ""; }; + 8A7A614D21A6AC8800488ED4 /* AudioMicrophoneServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioMicrophoneServiceTests.swift; sourceTree = ""; }; 8A7A614F21A6ACB000488ED4 /* CryingEventServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CryingEventServiceTests.swift; path = "Baby MonitorTests/Services/CryingEventServiceTests.swift"; sourceTree = SOURCE_ROOT; }; 8A7A615121A6B00F00488ED4 /* CryingEventsServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryingEventsServiceMock.swift; sourceTree = ""; }; 8A7A616C21A8162000488ED4 /* UIColor+Custom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Custom.swift"; sourceTree = ""; }; @@ -1090,11 +1094,10 @@ 8A50AA9621E8DBA10058C63A /* VideoCapturerMock.swift */, 8A50AA9321E8DBA10058C63A /* WebRtcClientManagerMock.swift */, 8A50AA9421E8DBA10058C63A /* WebRtcServerManagerMock.swift */, - 8A7A614521A6AC7900488ED4 /* AudioRecordServiceMock.swift */, + 8A7A614521A6AC7900488ED4 /* AudioMicrophoneServiceMock.swift */, 8A7A614421A6AC7800488ED4 /* CryingDetectionServiceMock.swift */, A1C5E6E121FA2E7100D93203 /* DatabaseRepositoryMock.swift */, 8A7A614621A6AC7900488ED4 /* DocumentsSavableMock.swift */, - 8A7A614721A6AC7900488ED4 /* RecorderMock.swift */, 8A7A60CD21A40D3B00488ED4 /* MicrophoneTrackerMock.swift */, 8A42DB002179C0CE00BF5F1B /* NetServiceClientMock.swift */, 8A42DB0D2179D8FB00BF5F1B /* URLUserDefaultsMock.swift */, @@ -1113,6 +1116,9 @@ 4EB93D1F21ECA15F00E99BB3 /* StorageServerServiceMock.swift */, 8A8B76F121F1D8210063EF7E /* WebSocketEventMessageServiceMock.swift */, 3A8DAC8A222D512400427BBE /* ApplicationStateProviderMock.swift */, + 3ABC41B622553F7900645ADA /* AudioKitMicrophoneMock.swift */, + 3ABC41B822553FE700645ADA /* MicrophoneRecordMock.swift */, + 3ABC41BA2255407100645ADA /* MicrophoneCaptureMock.swift */, ); path = Mocks; sourceTree = ""; @@ -1248,7 +1254,7 @@ isa = PBXGroup; children = ( 8A7A614F21A6ACB000488ED4 /* CryingEventServiceTests.swift */, - 8A7A614D21A6AC8800488ED4 /* AudioRecordServiceTests.swift */, + 8A7A614D21A6AC8800488ED4 /* AudioMicrophoneServiceTests.swift */, 8A7A60CB21A40D2F00488ED4 /* CryingDetectionServiceTests.swift */, ); path = Media; @@ -2049,6 +2055,7 @@ 8A7A615221A6B00F00488ED4 /* CryingEventsServiceMock.swift in Sources */, 8A50AAA421E8DBA20058C63A /* PeerConnectionMock.swift in Sources */, 8A50AA9F21E8DBA20058C63A /* SdpOfferDecoderMock.swift in Sources */, + 3ABC41B722553F7900645ADA /* AudioKitMicrophoneMock.swift in Sources */, 8A50AA8721E8D8D60058C63A /* MessageServerTests.swift in Sources */, 8A42DB122179D9A300BF5F1B /* UserDefaultsURLConfiguration.swift in Sources */, 8AA7CBE4218C5A5100FCF62A /* URLMediaPlayerMock.swift in Sources */, @@ -2057,15 +2064,17 @@ 8A50AAA021E8DBA20058C63A /* VideoCapturerMock.swift in Sources */, 8A7A60CE21A40D3B00488ED4 /* MicrophoneTrackerMock.swift in Sources */, 8A6098C021EBFB1F00592B01 /* PeerConnectionFactoryMock.swift in Sources */, + 3ABC41BB2255407100645ADA /* MicrophoneCaptureMock.swift in Sources */, 8AFAE5BE219C0FF7007013BC /* MessageDecoderMock.swift in Sources */, 4E8E057721AEADC5009ACE05 /* SpecifyDeviceOnboardingViewModelTests.swift in Sources */, 8AFAE5B8219AFF6D007013BC /* WebSocketMock.swift in Sources */, 8A50AA8E21E8D8FA0058C63A /* WebRtcClientManagerTests.swift in Sources */, + 3ABC41B922553FE700645ADA /* MicrophoneRecordMock.swift in Sources */, 8AA7CBCC21871B3E00FCF62A /* ConnectionCheckerMock.swift in Sources */, 8A8EF955219D581D0098A27B /* MessageStreamMock.swift in Sources */, 8AA7CBC02187105600FCF62A /* BabyMonitorCellMock.swift in Sources */, 8AA7CBCA21871AE300FCF62A /* DashboardViewModelTests.swift in Sources */, - 8A7A614A21A6AC7900488ED4 /* AudioRecordServiceMock.swift in Sources */, + 8A7A614A21A6AC7900488ED4 /* AudioMicrophoneServiceMock.swift in Sources */, 8A42DB0E2179D8FB00BF5F1B /* URLUserDefaultsMock.swift in Sources */, 8A7A614921A6AC7900488ED4 /* CryingDetectionServiceMock.swift in Sources */, 8A8B76F221F1D8210063EF7E /* WebSocketEventMessageServiceMock.swift in Sources */, @@ -2078,7 +2087,7 @@ 8A7A613E21A6AACD00488ED4 /* MessageServerMock.swift in Sources */, 8A7A614B21A6AC7900488ED4 /* DocumentsSavableMock.swift in Sources */, 3A8DAC8B222D512400427BBE /* ApplicationStateProviderMock.swift in Sources */, - 8A7A614E21A6AC8800488ED4 /* AudioRecordServiceTests.swift in Sources */, + 8A7A614E21A6AC8800488ED4 /* AudioMicrophoneServiceTests.swift in Sources */, 4EB93D2021ECA15F00E99BB3 /* StorageServerServiceMock.swift in Sources */, 8A42DB172179DD9C00BF5F1B /* URL+PrefixTests.swift in Sources */, 8AEAFBC321773D3E003E756F /* RealmBabiesRepositoryTests.swift in Sources */, @@ -2087,7 +2096,6 @@ 8A50AA9E21E8DBA20058C63A /* WebRtcServerManagerMock.swift in Sources */, 8A7A60C721A40CF200488ED4 /* NetServiceConnectionCheckerTests.swift in Sources */, 8A8B76E121EDE4650063EF7E /* WebsocketMessageDecodableTests.swift in Sources */, - 8A7A614C21A6AC7900488ED4 /* RecorderMock.swift in Sources */, 8A42DB012179C0CE00BF5F1B /* NetServiceClientMock.swift in Sources */, 8A50AA9C21E8DBA20058C63A /* IceCandidateDecoderMock.swift in Sources */, 4E2D302A2180AC9200722477 /* Rx+Expectations.swift in Sources */, diff --git a/Baby MonitorTests/Media/AudioRecordServiceTests.swift b/Baby MonitorTests/Media/AudioMicrophoneServiceTests.swift similarity index 79% rename from Baby MonitorTests/Media/AudioRecordServiceTests.swift rename to Baby MonitorTests/Media/AudioMicrophoneServiceTests.swift index c075f89..c0529d5 100644 --- a/Baby MonitorTests/Media/AudioRecordServiceTests.swift +++ b/Baby MonitorTests/Media/AudioMicrophoneServiceTests.swift @@ -7,17 +7,20 @@ import XCTest import RxSwift @testable import BabyMonitor -class AudioRecordServiceTests: XCTestCase { +class AudioMicrophoneServiceTests: XCTestCase { //Given - let recorderMock = RecorderMock() - lazy var sut = try! AudioRecordService(recorderFactory: { - return recorderMock + lazy var recorderMock = MicrophoneRecordMock() + lazy var capturerMock = MicrophoneCaptureMock() + lazy var microphoneMock = AudioKitMicrophoneMock(record: recorderMock, capture: capturerMock) + + lazy var sut = try! AudioMicrophoneService(microphoneFactory: { + return microphoneMock }) override func setUp() { - sut = try! AudioRecordService(recorderFactory: { - return recorderMock + sut = try! AudioMicrophoneService(microphoneFactory: { + return microphoneMock }) } diff --git a/Baby MonitorTests/Mocks/AudioKitMicrophoneMock.swift b/Baby MonitorTests/Mocks/AudioKitMicrophoneMock.swift new file mode 100644 index 0000000..ecf98bd --- /dev/null +++ b/Baby MonitorTests/Mocks/AudioKitMicrophoneMock.swift @@ -0,0 +1,19 @@ +// +// AudioKitMicrophoneMock.swift +// Baby MonitorTests +// + +import Foundation +@testable import BabyMonitor + +final class AudioKitMicrophoneMock: AudioKitMicrophoneProtocol { + + init(record: MicrophoneRecordProtocol, capture: MicrophoneCaptureProtocol) { + self.record = record + self.capture = capture + } + + let record: MicrophoneRecordProtocol + let capture: MicrophoneCaptureProtocol + +} diff --git a/Baby MonitorTests/Mocks/AudioRecordServiceMock.swift b/Baby MonitorTests/Mocks/AudioMicrophoneServiceMock.swift similarity index 52% rename from Baby MonitorTests/Mocks/AudioRecordServiceMock.swift rename to Baby MonitorTests/Mocks/AudioMicrophoneServiceMock.swift index 506da32..45838f8 100644 --- a/Baby MonitorTests/Mocks/AudioRecordServiceMock.swift +++ b/Baby MonitorTests/Mocks/AudioMicrophoneServiceMock.swift @@ -1,13 +1,14 @@ // -// AudioRecordServiceMock.swift +// AudioMicrophoneServiceMock.swift // Baby MonitorTests // +import AudioKit import Foundation import RxSwift @testable import BabyMonitor -final class AudioRecordServiceMock: AudioRecordServiceProtocol { +final class AudioMicrophoneServiceMock: AudioMicrophoneServiceProtocol { lazy var directoryDocumentsSavableObservable: Observable = directoryDocumentSavablePublihser.asObservable() var directoryDocumentSavablePublihser = PublishSubject() @@ -22,4 +23,18 @@ final class AudioRecordServiceMock: AudioRecordServiceProtocol { func startRecording() { isRecording = true } + + lazy var microphoneBufferReadableObservable: Observable = microphoneBufferReadablePublisher.asObservable() + var microphoneBufferReadablePublisher = PublishSubject() + var isCapturing: Bool = false + + func stopCapturing() { + isCapturing = true + microphoneBufferReadablePublisher.onNext(AVAudioPCMBuffer()) + } + + func startCapturing() { + isCapturing = false + } + } diff --git a/Baby MonitorTests/Mocks/MicrophoneCaptureMock.swift b/Baby MonitorTests/Mocks/MicrophoneCaptureMock.swift new file mode 100644 index 0000000..b81a7d8 --- /dev/null +++ b/Baby MonitorTests/Mocks/MicrophoneCaptureMock.swift @@ -0,0 +1,30 @@ +// +// MicrophoneCaptureMock.swift +// Baby MonitorTests +// + +import Foundation +import AudioKit +import RxSwift +@testable import BabyMonitor + +final class MicrophoneCaptureMock: MicrophoneCaptureProtocol { + + let bufferPublisher = PublishSubject() + lazy var bufferReadable = bufferPublisher.asObservable() + var isCapturing = false + var isCaptureReset = false + + func stop() { + isCapturing = false + } + + func start() throws { + isCapturing = true + } + + func reset() throws { + isCaptureReset = true + } + +} diff --git a/Baby MonitorTests/Mocks/RecorderMock.swift b/Baby MonitorTests/Mocks/MicrophoneRecordMock.swift similarity index 84% rename from Baby MonitorTests/Mocks/RecorderMock.swift rename to Baby MonitorTests/Mocks/MicrophoneRecordMock.swift index 9e6f68f..287a94c 100644 --- a/Baby MonitorTests/Mocks/RecorderMock.swift +++ b/Baby MonitorTests/Mocks/MicrophoneRecordMock.swift @@ -1,5 +1,5 @@ // -// RecorderMock.swift +// MicrophoneRecordProtocol.swift // Baby MonitorTests // @@ -7,7 +7,7 @@ import Foundation import AudioKit @testable import BabyMonitor -final class RecorderMock: RecorderProtocol { +final class MicrophoneRecordMock: MicrophoneRecordProtocol { var audioFile: AKAudioFile? { return shouldReturnNilForAudioFile ? nil : try! AKAudioFile() diff --git a/Baby MonitorTests/Services/CryingDetectionServiceTests.swift b/Baby MonitorTests/Services/CryingDetectionServiceTests.swift index 0a6f6e6..602857a 100644 --- a/Baby MonitorTests/Services/CryingDetectionServiceTests.swift +++ b/Baby MonitorTests/Services/CryingDetectionServiceTests.swift @@ -10,89 +10,89 @@ import RxTest class CryingDetectionServiceTests: XCTestCase { - let microphoneTrackerMock = MicrophoneTrackerMock() - lazy var cryingDetectionService = CryingDetectionService(microphoneTracker: microphoneTrackerMock) + let audioMicrophoneServiceMock = AudioMicrophoneServiceMock() + lazy var cryingDetectionService = CryingDetectionService(microphoneCaptureService: audioMicrophoneServiceMock) - override func tearDown() { - microphoneTrackerMock.simulatedFrequencyLimit = 2000 - microphoneTrackerMock.simulatedFrequencyStartValue = 0 - microphoneTrackerMock.simulatedReturnedValues = [] - microphoneTrackerMock.stop() - } - - func testShouldDetectCrying() { - //Given - microphoneTrackerMock.simulatedFrequencyStartValue = 1100 - let bag = DisposeBag() - let exp = XCTestExpectation(description: "Should inform about crying detection") - - //When - microphoneTrackerMock.start() - cryingDetectionService.cryingDetectionObservable.subscribe(onNext: { _ in - exp.fulfill() - }) - .disposed(by: bag) - - //Then - wait(for: [exp], timeout: 10.0) - } - - func testShouldNotDetectCryingAfterTenSeconds() { - //Given - microphoneTrackerMock.simulatedReturnedValues = [1200, 1300, 1200, 1400, 1500] - microphoneTrackerMock.simulatedFrequencyLimit = 1000 - let bag = DisposeBag() - let exp = expectation(description: "Should not detect crying (i.e. should not fulfill)") - - //When - microphoneTrackerMock.start() - cryingDetectionService.cryingDetectionObservable.subscribe(onNext: { isBabyCrying in - if isBabyCrying { - exp.fulfill() - } - }).disposed(by: bag) - let result = XCTWaiter.wait(for: [exp], timeout: 10) - - //Then - XCTAssertTrue(result == .timedOut) - } - - func testShouldNotifyAboutCryingDetectionOnlyOnce() { - //Given - microphoneTrackerMock.simulatedFrequencyStartValue = 1100 - let bag = DisposeBag() - let scheduler = TestScheduler(initialClock: 0) - let observer = scheduler.createObserver(Bool.self) - let exp = expectation(description: "") - - //When - microphoneTrackerMock.start() - cryingDetectionService.cryingDetectionObservable.fulfill(expectation: exp, afterEventCount: 2, bag: bag) - cryingDetectionService.cryingDetectionObservable.subscribe(observer).disposed(by: bag) - - //Then - let result = XCTWaiter.wait(for: [exp], timeout: 5.0) - XCTAssertTrue(result == .timedOut) - XCTAssertTrue(observer.events.map { $0.value.element! } == [true]) - } - - func testShouldNotifyAboutCryingDetectionTwoTimes() { - //Given - for _ in 0...9 { microphoneTrackerMock.simulatedReturnedValues.append(1100) } - for _ in 0...49 { microphoneTrackerMock.simulatedReturnedValues.append(900) } - for _ in 0...9 { microphoneTrackerMock.simulatedReturnedValues.append(1100) } - let bag = DisposeBag() - let scheduler = TestScheduler(initialClock: 0) - let observer = scheduler.createObserver(Bool.self) - let exp = expectation(description: "") - - //When - microphoneTrackerMock.start() - cryingDetectionService.cryingDetectionObservable.fulfill(expectation: exp, afterEventCount: 3, bag: bag) - cryingDetectionService.cryingDetectionObservable.subscribe(observer).disposed(by: bag) - - //Then - wait(for: [exp], timeout: 20.0) - XCTAssertTrue(observer.events.map { $0.value.element! } == [true, false, true]) - } +// override func tearDown() { +// microphoneTrackerMock.simulatedFrequencyLimit = 2000 +// microphoneTrackerMock.simulatedFrequencyStartValue = 0 +// microphoneTrackerMock.simulatedReturnedValues = [] +// microphoneTrackerMock.stop() +// } + +// func testShouldDetectCrying() { +// //Given +// microphoneTrackerMock.simulatedFrequencyStartValue = 1100 +// let bag = DisposeBag() +// let exp = XCTestExpectation(description: "Should inform about crying detection") +// +// //When +// microphoneTrackerMock.start() +// cryingDetectionService.cryingDetectionObservable.subscribe(onNext: { _ in +// exp.fulfill() +// }) +// .disposed(by: bag) +// +// //Then +// wait(for: [exp], timeout: 10.0) +// } + +// func testShouldNotDetectCryingAfterTenSeconds() { +// //Given +// microphoneTrackerMock.simulatedReturnedValues = [1200, 1300, 1200, 1400, 1500] +// microphoneTrackerMock.simulatedFrequencyLimit = 1000 +// let bag = DisposeBag() +// let exp = expectation(description: "Should not detect crying (i.e. should not fulfill)") +// +// //When +// microphoneTrackerMock.start() +// cryingDetectionService.cryingDetectionObservable.subscribe(onNext: { isBabyCrying in +// if isBabyCrying { +// exp.fulfill() +// } +// }).disposed(by: bag) +// let result = XCTWaiter.wait(for: [exp], timeout: 10) +// +// //Then +// XCTAssertTrue(result == .timedOut) +// } + +// func testShouldNotifyAboutCryingDetectionOnlyOnce() { +// //Given +// microphoneTrackerMock.simulatedFrequencyStartValue = 1100 +// let bag = DisposeBag() +// let scheduler = TestScheduler(initialClock: 0) +// let observer = scheduler.createObserver(Bool.self) +// let exp = expectation(description: "") +// +// //When +// microphoneTrackerMock.start() +// cryingDetectionService.cryingDetectionObservable.fulfill(expectation: exp, afterEventCount: 2, bag: bag) +// cryingDetectionService.cryingDetectionObservable.subscribe(observer).disposed(by: bag) +// +// //Then +// let result = XCTWaiter.wait(for: [exp], timeout: 5.0) +// XCTAssertTrue(result == .timedOut) +// XCTAssertTrue(observer.events.map { $0.value.element! } == [true]) +// } + +// func testShouldNotifyAboutCryingDetectionTwoTimes() { +// //Given +// for _ in 0...9 { microphoneTrackerMock.simulatedReturnedValues.append(1100) } +// for _ in 0...49 { microphoneTrackerMock.simulatedReturnedValues.append(900) } +// for _ in 0...9 { microphoneTrackerMock.simulatedReturnedValues.append(1100) } +// let bag = DisposeBag() +// let scheduler = TestScheduler(initialClock: 0) +// let observer = scheduler.createObserver(Bool.self) +// let exp = expectation(description: "") +// +// //When +// microphoneTrackerMock.start() +// cryingDetectionService.cryingDetectionObservable.fulfill(expectation: exp, afterEventCount: 3, bag: bag) +// cryingDetectionService.cryingDetectionObservable.subscribe(observer).disposed(by: bag) +// +// //Then +// wait(for: [exp], timeout: 20.0) +// XCTAssertTrue(observer.events.map { $0.value.element! } == [true, false, true]) +// } } diff --git a/Baby MonitorTests/Services/CryingEventServiceTests.swift b/Baby MonitorTests/Services/CryingEventServiceTests.swift index 43367e4..4f44657 100644 --- a/Baby MonitorTests/Services/CryingEventServiceTests.swift +++ b/Baby MonitorTests/Services/CryingEventServiceTests.swift @@ -11,17 +11,17 @@ import RxTest class CryingEventServiceTests: XCTestCase { //Given - lazy var sut = CryingEventService(cryingDetectionService: cryingDetectionServiceMock, audioRecordService: audioRecordServiceMock, activityLogEventsRepository: cryingEventsRepositoryMock, storageService: storageServiceMock) + lazy var sut = CryingEventService(cryingDetectionService: cryingDetectionServiceMock, microphoneRecordService: audioMicrophoneServiceMock, activityLogEventsRepository: cryingEventsRepositoryMock, storageService: storageServiceMock) var cryingDetectionServiceMock = CryingDetectionServiceMock() - var audioRecordServiceMock = AudioRecordServiceMock() + var audioMicrophoneServiceMock = AudioMicrophoneServiceMock() var cryingEventsRepositoryMock = DatabaseRepositoryMock() var storageServiceMock = StorageServerServiceMock() override func setUp() { cryingDetectionServiceMock = CryingDetectionServiceMock() - audioRecordServiceMock = AudioRecordServiceMock() + audioMicrophoneServiceMock = AudioMicrophoneServiceMock() cryingEventsRepositoryMock = DatabaseRepositoryMock() - sut = CryingEventService(cryingDetectionService: cryingDetectionServiceMock, audioRecordService: audioRecordServiceMock, activityLogEventsRepository: cryingEventsRepositoryMock, storageService: storageServiceMock) + sut = CryingEventService(cryingDetectionService: cryingDetectionServiceMock, microphoneRecordService: audioMicrophoneServiceMock, activityLogEventsRepository: cryingEventsRepositoryMock, storageService: storageServiceMock) } func testShouldStartCryingDetectionAnalysis() { @@ -37,7 +37,7 @@ class CryingEventServiceTests: XCTestCase { try! sut.start() //Then - XCTAssertFalse(audioRecordServiceMock.isRecording) + XCTAssertFalse(audioMicrophoneServiceMock.isRecording) } func testShouldStartRecordingAudio() { @@ -46,13 +46,13 @@ class CryingEventServiceTests: XCTestCase { cryingDetectionServiceMock.notifyAboutCryingDetection(isBabyCrying: true) //Then - XCTAssertTrue(audioRecordServiceMock.isRecording) + XCTAssertTrue(audioMicrophoneServiceMock.isRecording) } func testShouldNotSaveCryingEventWithSuccessfullCryingAudioRecordSave() { //When try! sut.start() - audioRecordServiceMock.isSaveActionSuccess = true + audioMicrophoneServiceMock.isSaveActionSuccess = true cryingDetectionServiceMock.notifyAboutCryingDetection(isBabyCrying: true) cryingDetectionServiceMock.notifyAboutCryingDetection(isBabyCrying: false) @@ -63,7 +63,7 @@ class CryingEventServiceTests: XCTestCase { func testShouldNotSaveCryingEvent() { //When try! sut.start() - audioRecordServiceMock.isSaveActionSuccess = false + audioMicrophoneServiceMock.isSaveActionSuccess = false cryingDetectionServiceMock.notifyAboutCryingDetection(isBabyCrying: true) cryingDetectionServiceMock.notifyAboutCryingDetection(isBabyCrying: false) From ddeb454df47f604576623fd5029e52492790e59a Mon Sep 17 00:00:00 2001 From: Adrian Kashivskyy Date: Wed, 3 Apr 2019 21:46:55 +0200 Subject: [PATCH 15/19] Address minor code review suggestions --- .../Services/MachineLearning/MfccLayer.swift | 1 - .../Services/MediaPlayer/MicrophoneFactory.swift | 8 +------- .../Services/MediaPlayer/NodeCapture.swift | 13 ++++--------- PeerConnectionFactoryProtocol.swift | 4 ++-- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift index 2e73dda..41026a3 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccLayer.swift @@ -72,7 +72,6 @@ import Accelerate var outputShapesArray = [[NSNumber]]() outputShapesArray.append([1, 1, 1, inputShapes[0][3], NSNumber(value: dctCoefficientCount)]) - print("MfccLayer: ", #function, inputShapes, " -> ", outputShapesArray) return outputShapesArray } diff --git a/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift b/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift index 80d0d8f..f791d8d 100644 --- a/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift +++ b/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift @@ -52,13 +52,7 @@ enum AudioKitMicrophoneFactory { captureSilenceBooster = AKBooster(captureBooster) captureSilenceBooster?.gain = 0.0 - guard let recorder = recorder else { - return nil - } - guard let capture = capture else { - return nil - } - guard let audioFile = recorder.audioFile else { + guard let recorder = recorder, let capture = capture, let audioFile = recorder.audioFile else { return nil } player = AKPlayer(audioFile: audioFile) diff --git a/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift b/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift index 571e19a..9a6e4c3 100644 --- a/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift +++ b/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift @@ -56,12 +56,7 @@ final class AudioKitNodeCapture: NSObject { bufferSize: AKSettings.bufferLength.samplesCount, format: internalAudioBuffer.format) { [weak self] (buffer: AVAudioPCMBuffer, _) -> Void in - guard let strongSelf = self else { - AKLog("Error: self is nil") - return - } - - guard let internalAudioBuffer = strongSelf.internalAudioBuffer else { + guard let self = self, let internalAudioBuffer = self.internalAudioBuffer else { AKLog("Error: internalAudioBuffer is nil") return } @@ -73,12 +68,12 @@ final class AudioKitNodeCapture: NSObject { } else if buffer.frameLength >= samplesLeft { internalAudioBuffer.copy(from: buffer, readOffset: 0, frames: samplesLeft) print("Buffer is filled. Pushing observe event") - strongSelf.bufferReadableSubject.onNext(internalAudioBuffer.copy() as! AVAudioPCMBuffer) - guard let bufferFormat = strongSelf.bufferFormat else { + self.bufferReadableSubject.onNext(internalAudioBuffer.copy() as! AVAudioPCMBuffer) + guard let bufferFormat = self.bufferFormat else { AKLog("Error: bufferFormat is nil") return } - strongSelf.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: strongSelf.bufferSize) + self.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: self.bufferSize) } if buffer.frameLength > samplesLeft { diff --git a/PeerConnectionFactoryProtocol.swift b/PeerConnectionFactoryProtocol.swift index b7857a7..bc55b57 100644 --- a/PeerConnectionFactoryProtocol.swift +++ b/PeerConnectionFactoryProtocol.swift @@ -27,8 +27,8 @@ extension RTCPeerConnectionFactory: PeerConnectionFactoryProtocol { let vTrack = videoTrack(withID: "ARDAMSv0", source: videoSource(with: videoCapturer, constraints: nil)) localStream.addVideoTrack(vTrack) - //let aTrack = audioTrack(withID: "ARDAMSa0") - //localStream.addAudioTrack(aTrack) + let aTrack = audioTrack(withID: "ARDAMSa0") + localStream.addAudioTrack(aTrack) return localStream From 4522027c265ff5b5104b4a39bc879c33aaf79e75 Mon Sep 17 00:00:00 2001 From: Adrian Kashivskyy Date: Wed, 3 Apr 2019 21:47:25 +0200 Subject: [PATCH 16/19] Update dependencies for Xcode 10.2 --- Gemfile | 3 +- Gemfile.lock | 163 ++++++--------------------------------------------- Podfile | 6 +- Podfile.lock | 18 +++--- 4 files changed, 31 insertions(+), 159 deletions(-) diff --git a/Gemfile b/Gemfile index 21c4bd0..a7e7b83 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '>= 1.6.0.beta' +gem 'cocoapods' gem 'cocoapods-keys' -gem 'xcode-install' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index c2ebf75..2e9d86f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,21 +4,18 @@ GEM CFPropertyList (3.0.0) RubyInline (3.12.4) ZenTest (~> 4.3) - ZenTest (4.11.1) - activesupport (4.2.10) + ZenTest (4.11.2) + activesupport (4.2.11.1) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) atomos (0.1.3) - babosa (1.0.2) claide (1.0.2) - cocoapods (1.6.0.beta.2) + cocoapods (1.6.1) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) + cocoapods-core (= 1.6.1) cocoapods-deintegrate (>= 1.0.2, < 2.0) cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -28,186 +25,62 @@ GEM cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.2.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) + ruby-macho (~> 1.4) + xcodeproj (>= 1.8.1, < 2.0) + cocoapods-core (1.6.1) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) + cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.2.2) - cocoapods-keys (2.0.6) + cocoapods-keys (2.1.0) dotenv osx_keychain cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) + cocoapods-stats (1.1.0) cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.1.0) - colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) - concurrent-ruby (1.0.5) - declarative (0.0.10) - declarative-option (0.1.0) - domain_name (0.5.20180417) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.5.0) - emoji_regex (0.1.1) + concurrent-ruby (1.1.5) + dotenv (2.7.2) escape (0.0.4) - excon (0.62.0) - faraday (0.15.3) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) - http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.4) - fastlane (2.107.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) - excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.24.0) - highline (>= 1.7.2, < 2.0.0) - json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json - multi_xml (~> 0.5) - multipart-post (~> 2.0.0) - plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.2.2, < 2.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.6.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fourflusher (2.0.1) + fourflusher (2.2.0) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-api-client (0.23.9) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) - httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.9) - googleauth (0.6.7) - faraday (~> 0.12) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (~> 0.7) - highline (1.7.10) - http-cookie (1.0.3) - domain_name (~> 0.5) - httpclient (2.8.3) i18n (0.9.5) concurrent-ruby (~> 1.0) - json (2.1.0) - jwt (2.1.0) - memoist (0.16.0) - mime-types (3.2.2) - mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) - mini_magick (4.5.1) minitest (5.11.3) molinillo (0.6.6) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) nanaimo (0.2.6) nap (1.1.0) - naturally (2.2.0) netrc (0.11.0) - os (1.0.0) osx_keychain (1.0.2) RubyInline (~> 3) - plist (3.4.0) - public_suffix (2.0.5) - representable (3.0.4) - declarative (< 0.1.0) - declarative-option (< 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rouge (2.0.7) - ruby-macho (1.3.1) - rubyzip (1.2.2) - security (0.1.3) - signet (0.11.0) - addressable (~> 2.3) - faraday (~> 0.9) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.5) - CFPropertyList - naturally - slack-notifier (2.3.2) - terminal-notifier (1.8.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + ruby-macho (1.4.0) thread_safe (0.3.6) - tty-cursor (0.6.0) - tty-screen (0.6.5) - tty-spinner (0.8.0) - tty-cursor (>= 0.5.0) tzinfo (1.2.5) thread_safe (~> 0.1) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.5) - unicode-display_width (1.4.0) - word_wrap (1.0.0) - xcode-install (2.4.4) - claide (>= 0.9.1, < 1.1.0) - fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.7.0) + xcodeproj (1.8.2) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.2.6) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) - xcpretty (~> 0.2, >= 0.0.7) PLATFORMS ruby DEPENDENCIES - cocoapods (>= 1.6.0.beta) + cocoapods cocoapods-keys - xcode-install BUNDLED WITH - 1.16.2 + 1.17.2 diff --git a/Podfile b/Podfile index 4a679c6..13152f8 100644 --- a/Podfile +++ b/Podfile @@ -8,7 +8,7 @@ plugin 'cocoapods-keys', { platform :ios, '11.0' use_frameworks! inhibit_all_warnings! - + target 'Baby Monitor' do @@ -18,7 +18,7 @@ target 'Baby Monitor' do pod 'RxCocoa', '~> 4.0' pod 'RxDataSources', '~> 3.0' pod 'PocketSocket', '~> 1.0.1' - pod 'AudioKit', '4.5.2' + pod 'AudioKit', '~> 4.7' pod 'Firebase/Core' pod 'Firebase/Messaging' pod 'Firebase/Storage' @@ -39,6 +39,6 @@ post_install do |installer| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '4.2' end - + end end diff --git a/Podfile.lock b/Podfile.lock index b36edee..c10c055 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - AudioKit (4.5.2): - - AudioKit/Core (= 4.5.2) - - AudioKit/UI (= 4.5.2) - - AudioKit/Core (4.5.2) - - AudioKit/UI (4.5.2): + - AudioKit (4.7): + - AudioKit/Core (= 4.7) + - AudioKit/UI (= 4.7) + - AudioKit/Core (4.7) + - AudioKit/UI (4.7): - AudioKit/Core - Differentiator (3.1.0) - Firebase (5.15.0): @@ -111,7 +111,7 @@ PODS: - SwiftLint (0.27.0) DEPENDENCIES: - - AudioKit (= 4.5.2) + - AudioKit (~> 4.7) - Firebase - Firebase/Core - Firebase/Messaging @@ -159,7 +159,7 @@ EXTERNAL SOURCES: :path: Pods/CocoaPodsKeys SPEC CHECKSUMS: - AudioKit: af6770360f1d8b2aed16ce2802dafe69f0e592d0 + AudioKit: 8b3c883fb6023d1b30764e40372991c9c66aa022 Differentiator: be49ca3408f0ecfc761e4c7763d20c62be01b9ad Firebase: 8bb9268bff82374f2cbaaabb143e725743c316ae FirebaseAnalytics: c06f9d70577d79074214700a71fd5d39de5550fb @@ -186,6 +186,6 @@ SPEC CHECKSUMS: RxTest: 19d03286bdc0a3aaea5d61d4cde31fdf4bb8a5ba SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 -PODFILE CHECKSUM: fe9040a90a72316bdf40d1fc5761014a8455055a +PODFILE CHECKSUM: 32ca7fa472d38ae588fc7f259224c9c8e6e26b53 -COCOAPODS: 1.6.0.beta.2 +COCOAPODS: 1.6.1 From fbf872dc67d5907d024d7a41e2c0361d73b10af0 Mon Sep 17 00:00:00 2001 From: Adrian Kashivskyy Date: Wed, 3 Apr 2019 23:15:20 +0200 Subject: [PATCH 17/19] Revert upgrade to Xcode 10.2 --- PeerConnectionFactoryProtocol.swift | 4 ++-- Podfile | 2 +- Podfile.lock | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/PeerConnectionFactoryProtocol.swift b/PeerConnectionFactoryProtocol.swift index bc55b57..b44561d 100644 --- a/PeerConnectionFactoryProtocol.swift +++ b/PeerConnectionFactoryProtocol.swift @@ -27,8 +27,8 @@ extension RTCPeerConnectionFactory: PeerConnectionFactoryProtocol { let vTrack = videoTrack(withID: "ARDAMSv0", source: videoSource(with: videoCapturer, constraints: nil)) localStream.addVideoTrack(vTrack) - let aTrack = audioTrack(withID: "ARDAMSa0") - localStream.addAudioTrack(aTrack) +// let aTrack = audioTrack(withID: "ARDAMSa0") +// localStream.addAudioTrack(aTrack) return localStream diff --git a/Podfile b/Podfile index 13152f8..3c9f299 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ target 'Baby Monitor' do pod 'RxCocoa', '~> 4.0' pod 'RxDataSources', '~> 3.0' pod 'PocketSocket', '~> 1.0.1' - pod 'AudioKit', '~> 4.7' + pod 'AudioKit', '4.5.2' pod 'Firebase/Core' pod 'Firebase/Messaging' pod 'Firebase/Storage' diff --git a/Podfile.lock b/Podfile.lock index c10c055..2c3cd1b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - AudioKit (4.7): - - AudioKit/Core (= 4.7) - - AudioKit/UI (= 4.7) - - AudioKit/Core (4.7) - - AudioKit/UI (4.7): + - AudioKit (4.5.2): + - AudioKit/Core (= 4.5.2) + - AudioKit/UI (= 4.5.2) + - AudioKit/Core (4.5.2) + - AudioKit/UI (4.5.2): - AudioKit/Core - Differentiator (3.1.0) - Firebase (5.15.0): @@ -111,7 +111,7 @@ PODS: - SwiftLint (0.27.0) DEPENDENCIES: - - AudioKit (~> 4.7) + - AudioKit (= 4.5.2) - Firebase - Firebase/Core - Firebase/Messaging @@ -159,7 +159,7 @@ EXTERNAL SOURCES: :path: Pods/CocoaPodsKeys SPEC CHECKSUMS: - AudioKit: 8b3c883fb6023d1b30764e40372991c9c66aa022 + AudioKit: af6770360f1d8b2aed16ce2802dafe69f0e592d0 Differentiator: be49ca3408f0ecfc761e4c7763d20c62be01b9ad Firebase: 8bb9268bff82374f2cbaaabb143e725743c316ae FirebaseAnalytics: c06f9d70577d79074214700a71fd5d39de5550fb @@ -186,6 +186,6 @@ SPEC CHECKSUMS: RxTest: 19d03286bdc0a3aaea5d61d4cde31fdf4bb8a5ba SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 -PODFILE CHECKSUM: 32ca7fa472d38ae588fc7f259224c9c8e6e26b53 +PODFILE CHECKSUM: 4d4f90f5e94acae904cd5a4dab5b7e167ab3b631 COCOAPODS: 1.6.1 From 6d63886754067c0737e91d389424c7d42b1d5e1d Mon Sep 17 00:00:00 2001 From: Adrian Kashivskyy Date: Thu, 4 Apr 2019 18:48:07 +0200 Subject: [PATCH 18/19] Bump AudioKit and fix audio capturing --- .../MediaPlayer/MicrophoneFactory.swift | 57 ++++++--------- .../Services/MediaPlayer/NodeCapture.swift | 70 +++++-------------- PeerConnectionFactoryProtocol.swift | 4 +- Podfile | 2 +- Podfile.lock | 16 ++--- 5 files changed, 50 insertions(+), 99 deletions(-) diff --git a/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift b/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift index f791d8d..3b3669a 100644 --- a/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift +++ b/Baby Monitor/Source Files/Services/MediaPlayer/MicrophoneFactory.swift @@ -18,49 +18,34 @@ struct AudioKitMicrophone: AudioKitMicrophoneProtocol { } enum AudioKitMicrophoneFactory { - - private static var recorder: AKNodeRecorder? - private static var capture: AudioKitNodeCapture? - private static var recorderBooster: AKBooster? - private static var player: AKPlayer? - private static var captureBooster: AKBooster? - private static var recorderSilenceBooster: AKBooster? - private static var captureSilenceBooster: AKBooster? - private static var monoToStereo: AKStereoFieldLimiter? - private static var recorderMixer: AKMixer? - private static var captureMixer: AKMixer? - private static var mainMixer: AKMixer? - private static var microphone = AKMicrophone() - + static var makeMicrophoneFactory: () throws -> AudioKitMicrophoneProtocol? = { + AKSettings.bufferLength = .medium AKSettings.sampleRate = 44100.0 AKSettings.channelCount = 1 - - try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP) + AKSettings.audioInputEnabled = true AKSettings.defaultToSpeaker = true - - recorderBooster = AKBooster(microphone) - captureBooster = AKBooster(microphone) - - monoToStereo = AKStereoFieldLimiter(recorderBooster, amount: 1) - recorderMixer = AKMixer(monoToStereo) - recorder = try AKNodeRecorder(node: recorderMixer) - capture = try AudioKitNodeCapture(node: captureBooster) - recorderSilenceBooster = AKBooster(recorderMixer) - recorderSilenceBooster?.gain = 0.0 - captureSilenceBooster = AKBooster(captureBooster) - captureSilenceBooster?.gain = 0.0 - - guard let recorder = recorder, let capture = capture, let audioFile = recorder.audioFile else { - return nil - } - player = AKPlayer(audioFile: audioFile) - mainMixer = AKMixer(player, recorderSilenceBooster) - AudioKit.output = mainMixer + + try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP) + + let microphone = AKMicrophone() + + let recorderBooster = AKBooster(microphone) + let capturerBooster = AKBooster(microphone) + let recorderMixer = AKMixer(recorderBooster) + + let recorder = try AKNodeRecorder(node: recorderMixer) + let capturer = try AudioKitNodeCapture(node: capturerBooster) + + let outputMixer = AKMixer(recorderMixer) + outputMixer.volume = 0 + + AudioKit.output = outputMixer try AudioKit.start() - return AudioKitMicrophone(record: recorder, capture: capture) + return AudioKitMicrophone(record: recorder, capture: capturer) + } } diff --git a/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift b/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift index 9a6e4c3..8618a30 100644 --- a/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift +++ b/Baby Monitor/Source Files/Services/MediaPlayer/NodeCapture.swift @@ -21,64 +21,34 @@ final class AudioKitNodeCapture: NSObject { private(set) var isCapturing: Bool = false private var node: AKNode? private let bufferSize: UInt32 - private var internalAudioBuffer: AVAudioPCMBuffer? - private let bufferFormat: AVAudioFormat? + private var internalAudioBuffer: AVAudioPCMBuffer + private let bufferFormat: AVAudioFormat + private let bufferQueue = DispatchQueue(label: "co.netguru.netguru.babymonitor.AudioKitNodeCapture.bufferQueue", qos: .default) init(node: AKNode? = AudioKit.output, bufferSize: UInt32 = 264600) throws { self.node = node self.bufferSize = bufferSize - - self.bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, - sampleRate: 44100.0, - channels: 1, - interleaved: false) - guard let bufferFormat = bufferFormat else { - throw AudioCaptureError.initializationFailure - } - self.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: bufferSize) + self.bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100.0, channels: 1, interleaved: false)! + self.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: bufferSize)! } - + /// Start Capturing func start() throws { - guard !isCapturing else { - return - } - guard let node = node else { + guard !isCapturing, let node = node else { return } - - guard let internalAudioBuffer = internalAudioBuffer else { - return - } - - node.avAudioUnitOrNode.installTap( - onBus: 0, - bufferSize: AKSettings.bufferLength.samplesCount, - format: internalAudioBuffer.format) { [weak self] (buffer: AVAudioPCMBuffer, _) -> Void in - - guard let self = self, let internalAudioBuffer = self.internalAudioBuffer else { - AKLog("Error: internalAudioBuffer is nil") - return - } - - let samplesLeft = internalAudioBuffer.frameCapacity - internalAudioBuffer.frameLength - + node.avAudioUnitOrNode.installTap(onBus: 0, bufferSize: AKSettings.bufferLength.samplesCount, format: internalAudioBuffer.format) { [weak self] buffer, _ in + self?.bufferQueue.async { + guard let self = self else { return } + let samplesLeft = self.internalAudioBuffer.frameCapacity - self.internalAudioBuffer.frameLength if buffer.frameLength < samplesLeft { - internalAudioBuffer.copy(from: buffer) - } else if buffer.frameLength >= samplesLeft { - internalAudioBuffer.copy(from: buffer, readOffset: 0, frames: samplesLeft) - print("Buffer is filled. Pushing observe event") - self.bufferReadableSubject.onNext(internalAudioBuffer.copy() as! AVAudioPCMBuffer) - guard let bufferFormat = self.bufferFormat else { - AKLog("Error: bufferFormat is nil") - return - } - self.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: self.bufferSize) - } - - if buffer.frameLength > samplesLeft { - internalAudioBuffer.copy(from: buffer, readOffset: samplesLeft, frames: buffer.frameLength - samplesLeft) + self.internalAudioBuffer.copy(from: buffer) + } else { + self.bufferReadableSubject.onNext(self.internalAudioBuffer.copy() as! AVAudioPCMBuffer) + self.internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: self.bufferFormat, frameCapacity: self.bufferSize)! + self.internalAudioBuffer.copy(from: buffer) } + } } isCapturing = true } @@ -94,10 +64,6 @@ final class AudioKitNodeCapture: NSObject { /// Reset the Buffer to clear previous recordings func reset() throws { stop() - guard let bufferFormat = bufferFormat else { - AKLog("Error: bufferFormat is nil") - return - } - internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: bufferSize) + internalAudioBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: bufferSize)! } } diff --git a/PeerConnectionFactoryProtocol.swift b/PeerConnectionFactoryProtocol.swift index b44561d..bc55b57 100644 --- a/PeerConnectionFactoryProtocol.swift +++ b/PeerConnectionFactoryProtocol.swift @@ -27,8 +27,8 @@ extension RTCPeerConnectionFactory: PeerConnectionFactoryProtocol { let vTrack = videoTrack(withID: "ARDAMSv0", source: videoSource(with: videoCapturer, constraints: nil)) localStream.addVideoTrack(vTrack) -// let aTrack = audioTrack(withID: "ARDAMSa0") -// localStream.addAudioTrack(aTrack) + let aTrack = audioTrack(withID: "ARDAMSa0") + localStream.addAudioTrack(aTrack) return localStream diff --git a/Podfile b/Podfile index 3c9f299..7d53cc0 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ target 'Baby Monitor' do pod 'RxCocoa', '~> 4.0' pod 'RxDataSources', '~> 3.0' pod 'PocketSocket', '~> 1.0.1' - pod 'AudioKit', '4.5.2' + pod 'AudioKit', '~> 4.7.0' pod 'Firebase/Core' pod 'Firebase/Messaging' pod 'Firebase/Storage' diff --git a/Podfile.lock b/Podfile.lock index 2c3cd1b..f464e86 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - AudioKit (4.5.2): - - AudioKit/Core (= 4.5.2) - - AudioKit/UI (= 4.5.2) - - AudioKit/Core (4.5.2) - - AudioKit/UI (4.5.2): + - AudioKit (4.7): + - AudioKit/Core (= 4.7) + - AudioKit/UI (= 4.7) + - AudioKit/Core (4.7) + - AudioKit/UI (4.7): - AudioKit/Core - Differentiator (3.1.0) - Firebase (5.15.0): @@ -111,7 +111,7 @@ PODS: - SwiftLint (0.27.0) DEPENDENCIES: - - AudioKit (= 4.5.2) + - AudioKit (~> 4.7.0) - Firebase - Firebase/Core - Firebase/Messaging @@ -159,7 +159,7 @@ EXTERNAL SOURCES: :path: Pods/CocoaPodsKeys SPEC CHECKSUMS: - AudioKit: af6770360f1d8b2aed16ce2802dafe69f0e592d0 + AudioKit: 8b3c883fb6023d1b30764e40372991c9c66aa022 Differentiator: be49ca3408f0ecfc761e4c7763d20c62be01b9ad Firebase: 8bb9268bff82374f2cbaaabb143e725743c316ae FirebaseAnalytics: c06f9d70577d79074214700a71fd5d39de5550fb @@ -186,6 +186,6 @@ SPEC CHECKSUMS: RxTest: 19d03286bdc0a3aaea5d61d4cde31fdf4bb8a5ba SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 -PODFILE CHECKSUM: 4d4f90f5e94acae904cd5a4dab5b7e167ab3b631 +PODFILE CHECKSUM: ca468c0118816d13f546fe8a6efff3b209e114c3 COCOAPODS: 1.6.1 From 72fb7bdba35344f24de723ed987131f128fec4d9 Mon Sep 17 00:00:00 2001 From: Timo Rohner Date: Fri, 5 Apr 2019 14:29:06 +0200 Subject: [PATCH 19/19] Added Documentation --- .../AudioSpectrogramLayer.swift | 42 ++++++++++--------- .../MachineLearning/MathUtilities.swift | 3 ++ .../Services/MachineLearning/MfccDct.swift | 18 +++++++- .../Services/MachineLearning/MfccLayer.swift | 30 ++++++++++--- .../MachineLearning/MfccMelFilterbank.swift | 24 +++++++++++ .../Services/MachineLearning/MfccOp.swift | 35 +++++++++++++++- .../MachineLearning/SpectrogramOp.swift | 20 ++++++--- 7 files changed, 138 insertions(+), 34 deletions(-) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift index 024b547..fa81254 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/AudioSpectrogramLayer.swift @@ -10,6 +10,9 @@ import Foundation import CoreML import Accelerate +/** + This class adds functionality to Apple's coreML in the form of a Custom Layer that allows computing the Spectrogram of a half precision linear pcm audio signal + */ @objc(AudioSpectrogramLayer) class AudioSpectrogramLayer: NSObject, MLCustomLayer { enum AudioSpectrogramLayerError: Error { @@ -22,6 +25,10 @@ import Accelerate let outputChannels: NSNumber let spectogramOp: SpectrogramOp + /** + Initializes instance of AudioSpectrogramLayer with given parameters from the .mlmodel protobufs + - Parameter parameters: parameters as defined in the protobuf files of the coreml .mlmodel binary file + */ required init(parameters: [String: Any]) throws { guard let windowSize = parameters["window_size"] as? Int, let stride = parameters["stride"] as? Int, @@ -39,14 +46,21 @@ import Accelerate super.init() } + /** + Serves no purpose, since this layer has no associated weights. + */ func setWeightData(_ weights: [Data]) throws { // No weight data for this layer } + /** + Computes the predicted output shapes for a given input shapes according to the following formula: + [1, 1, NUM_SAMPLES, 1, 1] => [1, 1, 1, 1 + (NUM_SAMPLES - self.windowSize) / self.stride, self.outputChannels], + where NUM_SAMPLES is the number of samples of the audio signal and self.windowSize, self.stride, self.outputChannels are given by parameters during initialization of the AudioSpectrogramLayer instance. + - Parameter inputShapes: inputShapes for which to calculate output shapes + - Returns: outputShapes for given inputShapes + */ func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws -> [[NSNumber]] { - // Input shape is [1,1,NUM_SAMPLES, 1,1] - // This gives us the output shape - // [1,1,1,N_TIME_BINS, N_FREQUENCY_BINS] var outputShapesArray = [[NSNumber]]() let inputLength = Int(truncating: inputShapes[0][2]) let outputLength = NSNumber(value: 1 + (inputLength - self.windowSize) / self.stride) @@ -54,26 +68,14 @@ import Accelerate return outputShapesArray } + /** + Evaluate the layer for a given set of inputs and write result to a given set of outputs + - Parameter inputs: Array of MLMultiArray instances, each representing 1 input to be evaluated. + - Parameter outputs: Array of MLMultiArray instances, each representing 1 output into which the evaluation of the corresponding input is written. + */ func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws { try spectogramOp.compute(input: UnsafeMutablePointer(OpaquePointer(inputs[0].dataPointer)), inputLength: inputs[0].count, output: UnsafeMutablePointer(OpaquePointer(outputs[0].dataPointer))) } } - -// Audio OPS -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/ops/audio_ops.cc - -// AUDIOSPECTROGRAM -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram_op.cc -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram.h -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram.cc - -// MFCC -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc.cc -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc.h -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_dct.cc -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_dct.h -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.cc -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.h -// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_op.cc diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift b/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift index 5faa2f3..ebbd5c2 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MathUtilities.swift @@ -4,5 +4,8 @@ // extension UInt { + /** + Calculates the smallest number that is greater than the given UInt and a power of two + */ var nextPowerOfTwo: UInt { return UInt(pow(2, ceil(log2(Double(self)))))} } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift index 89a091c..5943ef2 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccDct.swift @@ -8,6 +8,10 @@ import Foundation +/** + This class provides computation functionality to calculate the Discrete Cosine Transform as part of determining the Mel-frequency-cepstrum coefficients for an audio signal and it mirrors the corresponding tensorflow kernel, whose implementation can be found here: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_dct.h + This class however implements a sub-optimal non-hardware accelerated method of computing the DCT and is only used when the hardware accelerated implementation cannot be used due to the specific configuration/parametrization used. + */ class MfccDct { enum MfccDctError: Error { case parameterError(String) @@ -24,7 +28,12 @@ class MfccDct { inputLength = 0 cosines = [] } - + + /** + Initializes instance of MfccDCT. + - Parameter inputLength: length of input on which we have to apply the DCT + - Parameter coefficientCount: number of coefficients used in the Discrete Cosine Transform + */ func initialize(inputLength: Int, coefficientCount: Int) throws { guard coefficientCount >= 0 else { throw MfccDctError.parameterError("coefficient_count must be strictly positive") @@ -50,7 +59,12 @@ class MfccDct { } initialized = true } - + + /** + Computes the Discrete Cosine transform for given input and writes it to output + - Parameter input: Pointer to input spectrogram data for which to calculate the MFCCs + - Parameter output: Pointer indicating where to write the evaluation via DCT of the input. Memory allocation is on the caller of this method. + */ func compute(input: UnsafeMutablePointer, output: UnsafeMutablePointer) { for i in 0.. [1, 1, 1, N_TIME_BINS, self.dctCoefficientCount], + where self.self.dctCoefficientCount is given by parameters during initialization of the MfccLayer instance. + - Parameter inputShapes: inputShapes for which to calculate output shapes + - Returns: outputShapes for given inputShapes + */ func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws -> [[NSNumber]] { - // Input shape is [1,1,1,N_TIME_BINS, N_FREQUENCY_BINS] - // This gives us the output shape - // [1,1,N_TIME_BINS, dct_coefficient_count] var outputShapesArray = [[NSNumber]]() - outputShapesArray.append([1, 1, 1, inputShapes[0][3], NSNumber(value: dctCoefficientCount)]) return outputShapesArray } + /** + Evaluate the layer for a given set of inputs and write result to a given set of outputs + - Parameter inputs: Array of MLMultiArray instances, each representing 1 input to be evaluated. + - Parameter outputs: Array of MLMultiArray instances, each representing 1 output into which the evaluation of the corresponding input is written. + */ func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws { let nSpectrogramSamples = Int(truncating: inputs[0].shape[3]) let nSpectrogramChannels = Int(truncating: inputs[0].shape[4]) diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift index 688d233..e7118ca 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccMelFilterbank.swift @@ -9,6 +9,9 @@ import Foundation import Accelerate +/** + This class provides computation functionality to calculate the Mel filterbank associated with a spectrogram generated from a linear raw pcm audio signal and it mirrors the corresponding tensorflow kernel, whose implementation can be found here: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc_mel_filterbank.h + */ class MfccMelFilterbank { enum MfccMelFilterbankError: Error { @@ -34,6 +37,14 @@ class MfccMelFilterbank { endIndex = 0 } + /** + Initializes needed variables to execute the filterbank computation. + - Parameter inputLength: Spectrogram input length + - Parameter inputSampleRate: Sample rate of the audio signal from which the Spectrogram was calculated + - Parameter outputChannelCount: How many channels we would like to generate + - Parameter lowerFrequencyLimit: lower frequency limit of our filterbank + - Parameter upperFrequencyLimit: upper frequency limit of our filterbank + */ func initialize(inputLength: Int, inputSampleRate: Double, outputChannelCount: Int, lowerFrequencyLimit: Double, upperFrequencyLimit: Double) throws { self.inputLength = inputLength self.sampleRate = inputSampleRate @@ -58,6 +69,7 @@ class MfccMelFilterbank { var channel = 0 + // Initialize freq to band mapping index matrix for i in 0.. endIndex) { @@ -70,6 +82,7 @@ class MfccMelFilterbank { } } + // Initialize weight matrix weights = [Double](repeating: 0.0, count: self.inputLength) for i in 0.., output: UnsafeMutablePointer) { for i in startIndex...endIndex { let specVal = sqrtf(input.advanced(by: i).pointee) @@ -105,6 +124,11 @@ class MfccMelFilterbank { } } + /** + Calculates the Mel value of a given frequency + - Parameter freq: Frequency for which to calculate the corresponding mel value + - Returns: Mel value corresponding to the frequency given by parameter freq + */ static func freqToMel(freq: Double) -> Double { return 1127.0 * log(1.0 + (freq / 700.0)) } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift index 334a362..54a0c67 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/MfccOp.swift @@ -9,6 +9,9 @@ import Foundation import Accelerate +/** + This class implements calculating the Mel-frequency cepstrum coefficients from a spectrogram. It mirrors the tensorflow implementation (written in C++) (see: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/mfcc.h) + */ class MfccOp { enum MfccOpError: Error { case evaluationError(String) @@ -31,6 +34,13 @@ class MfccOp { private var useAccelerate: Bool = false + /** + Creates an instance of MfccOp and determines whether for the given configuration the Accelerate framework can be used to provide hardware accelerated computation. + - Parameter upperFrequencyLimit: Upper Frequency limit used in the Mel frequency cepstrum computation + - Parameter lowerFrequencyLimit: Lower Frequency limit used in the Mel frequency cepstrum computation + - Parameter filterbankChannelCount: The number of filterbank Channels used in the Mel frequency cepstrum computation + - Parameter dctCoefficientCount: The number of DCT (Discrete Cosine Transform) coefficients that should be used in the Mel frequency cepstrum computation + */ init(upperFrequencyLimit: Double, lowerFrequencyLimit: Double, filterbankChannelCount: Int, @@ -55,14 +65,22 @@ class MfccOp { } } + // If we are able to make use of accelerate we set up the Discrete cosine transform using accelerate + // Otherwise we make use of the non hardware accelerated DCT implementation that can be found in MfccDct.swift if useAccelerate { dctSetup = vDSP_DCT_CreateSetup(nil, vDSP_Length(dctCoefficientCount), vDSP_DCT_Type.II) } else { mfccDct = MfccDct() } + // Initialize the filterbank melFilterbank = MfccMelFilterbank() } + /** + Initializes the operation by providing additional information needed for evaluation + - Parameter inputLength: InputLength of the Spectrogram + - Parameter inputSampleRate: The original sample rate of the audio that was passed to AudioSpectrogramLayer + */ func initialize(inputLength: Int, inputSampleRate: Double) throws { self.inputLength = inputLength self.inputSampleRate = inputSampleRate @@ -92,34 +110,47 @@ class MfccOp { self.initialized = true } + /** + Computes the mel-frequency cepstrum coefficients for given input and writes it to output + - Parameter input: Pointer to input spectrogram data for which to calculate the MFCCs + - Parameter inputLength: Number of floats present in memory at location indicated by input + - Parameter output: Pointer indicating where to write the computed MFCCs. Memory allocation is on the caller of this method. + */ func compute(input: UnsafeMutablePointer, inputLength: Int, output: UnsafeMutablePointer) throws { if !initialized { print("ERROR") } + // Create needed temporary memory allocations var workingData1 = [Float](repeating: 0.0, count: filterbankChannelCount) var workingData2 = [Float](repeating: 0.0, count: filterbankChannelCount) var workingData3 = [Float](repeating: 0.0, count: filterbankChannelCount) + // Compute filterbank melFilterbank!.compute(input: input, output: &workingData1) + // Make sure we don't have negative or 0 values, because we're about to take the logarithm vDSP_vthr(&workingData1, 1, &kFilterbankFloor, &workingData2, 1, vDSP_Length(filterbankChannelCount)) var nElements = Int32(filterbankChannelCount) + // Take logarithm of filterbank evaluation vvlogf(&workingData3, &workingData2, &nElements) - // Execute DCT + // Execute Discrete cosine transform using either accelerate or slower non-hardware accelerated implementation if useAccelerate { guard let dctSetup = dctSetup else { throw MfccOpError.accelerateDctError("vDSP_DCT_Execute not available") } - + var fnorm = sqrtf(2.0 / Float(filterbankChannelCount)) + // Multiply by fnorm to be in line with tensorflow implementation vDSP_vsmul(&workingData3, 1, &fnorm, &workingData2, 1, vDSP_Length(filterbankChannelCount)) + // Execute hardware accelerated (using Accelerate framework) DCT vDSP_DCT_Execute(dctSetup, &workingData2, output) } else { guard let mfccDct = mfccDct else { throw MfccOpError.mfccDctError("mfccDCT Op not available") } + // Execute non-hardware accelerated DCT mfccDct.compute(input: &workingData3, output: output) } } diff --git a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift index 0b5bf05..5ae9fa5 100644 --- a/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift +++ b/Baby Monitor/Source Files/Services/MachineLearning/SpectrogramOp.swift @@ -9,11 +9,9 @@ import Foundation import Accelerate -// Input: [264600,1] -// => Bin into windows => [598, 1323] -// => -// -// +/** + This class implements generating Spectrograms from linear pcm audio signals. It mirrors the tensorflow implementation (written in C++) (see: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/kernels/spectrogram.h) + */ class SpectrogramOp: NSObject { enum SpectrogramOpError: Error { @@ -38,6 +36,12 @@ class SpectrogramOp: NSObject { var fftSetup: vDSP_DFT_Setup? var magnitudeSquared: Bool = true + /** + Initializes a SpectrogramOp instance capable of computing a Spectrogram of a linear pcm audio signal. + - Parameter windowLength: Window length to be used during Spectrogram computation + - Parameter stepLength: Step length to be used during Spectrogram computation. A stepLength strictly smaller than windowLength results in window overlap. + - Parameter magnituteSquared: Whether to calculate final magnitutdes of Spectrogram as L2 norm squared or normal L2 norm + */ init(windowLength: Int, stepLength: Int, magnitudeSquared: Bool) throws { self.windowLength = windowLength self.stepLength = stepLength @@ -73,6 +77,12 @@ class SpectrogramOp: NSObject { vDSP_DFT_DestroySetup(fftSetup) } + /** + Computes the spectrogram based on configuration of this Op instance on a given linear pcm audio signal + - Parameter input: Pointer to Float (16bit/Half precision) values representing the raw linear pcm audio signal + - Parameter inputLength: Number of raw linear pcm audio signal samples + - Parameter output: Pointer to memory to be used to write the generated spectrogram. Has to be allocated by the caller of this method. + */ func compute(input: UnsafeMutablePointer, inputLength: Int, output: UnsafeMutablePointer) throws {