From cba0f095059c9c2c938bb6bf8cb656cde3131d19 Mon Sep 17 00:00:00 2001 From: David Url Date: Fri, 27 Jan 2023 15:49:18 +0100 Subject: [PATCH] CATTY-357 Move loudness recorder to AudioEngine --- .../AudioEngine/AudioEngine.swift | 7 + .../AudioEngine/AudioManager+Loudness.swift | 166 ++++++++---------- .../PlayerEngine/Formula/FormulaManager.swift | 4 +- .../Stage/StagePresenterViewController.m | 8 - 4 files changed, 83 insertions(+), 102 deletions(-) diff --git a/src/Catty/PlayerEngine/AudioEngine/AudioEngine.swift b/src/Catty/PlayerEngine/AudioEngine/AudioEngine.swift index 957018aab2..94f486b69b 100644 --- a/src/Catty/PlayerEngine/AudioEngine/AudioEngine.swift +++ b/src/Catty/PlayerEngine/AudioEngine/AudioEngine.swift @@ -32,6 +32,13 @@ import Foundation var audioEngineHelper = AudioEngineHelper() var speechSynth = SpeechSynthesizer() + var loudnessEngine = AVAudioEngine() + var loudnessTimer: Timer! + var dbSamples: [Double] = [] + var isRecording = false + var inputNode: AVAudioInputNode! + var recordingFormat: AVAudioFormat! + var tempo = Int() var subtrees = [String: AudioSubtree]() diff --git a/src/Catty/PlayerEngine/AudioEngine/AudioManager+Loudness.swift b/src/Catty/PlayerEngine/AudioEngine/AudioManager+Loudness.swift index c63d0e01fd..1db66710a6 100644 --- a/src/Catty/PlayerEngine/AudioEngine/AudioManager+Loudness.swift +++ b/src/Catty/PlayerEngine/AudioEngine/AudioManager+Loudness.swift @@ -20,98 +20,80 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ -extension AudioManager: AudioManagerProtocol { - private var noiseRecogniserTimeIntervalInSeconds: Double { 0.05 } - private var noiseRecorderChannel: Int { 0 } - - func startLoudnessRecorder() { - if self.recorder == nil { - self.initRecorder() - } - - self.loudnessTimer = Timer.scheduledTimer(timeInterval: noiseRecogniserTimeIntervalInSeconds, - target: self, - selector: #selector(self.projectTimerCallback), - userInfo: nil, - repeats: true) - - self.recorder.isMeteringEnabled = true - self.recorder.record() - } - - func stopLoudnessRecorder() { - if self.recorder != nil { - self.recorder.stop() - self.recorder = nil - } - - if self.loudnessTimer != nil { - self.loudnessTimer.invalidate() - self.loudnessTimer = nil - } - } - - func pauseLoudnessRecorder() { - self.recorder?.pause() - } - - func resumeLoudnessRecorder() { - self.recorder?.record() - } - - func loudness() -> Double? { - if self.loudnessInDecibels == nil || self.recorder == nil { - return nil // no sound - } - return self.loudnessInDecibels as? Double - } - - func initRecorder() { - let url = URL(fileURLWithPath: "/dev/null") - - let settings = [ - AVFormatIDKey: Int(kAudioFormatAppleLossless), - AVSampleRateKey: 44100.0, - AVNumberOfChannelsKey: 0, - AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue - ] as [String: Any] - - try? self.recorder = AVAudioRecorder(url: url, settings: settings) - } - - @objc func projectTimerCallback() { - guard let recorder = self.recorder else { return } - recorder.updateMeters() - - self.loudnessInDecibels = recorder.averagePower(forChannel: noiseRecorderChannel) as NSNumber - } - - func loudnessAvailable() -> Bool { - var isGranted = false - let dispatchGroup = DispatchGroup() - - switch AVAudioSession.sharedInstance().recordPermission { - case AVAudioSession.RecordPermission.denied: - isGranted = false - case AVAudioSession.RecordPermission.undetermined: - dispatchGroup.enter() - AVAudioSession.sharedInstance().requestRecordPermission({ (granted: Bool) in - isGranted = granted - dispatchGroup.leave() - }) - dispatchGroup.wait() - case AVAudioSession.RecordPermission.granted: - isGranted = true - @unknown default: - print("ERROR: case not handled by switch statement") - } - - if isGranted && self.recorder == nil { - self.initRecorder() - return self.recorder.prepareToRecord() - } - - return isGranted +import AudioKit +import Foundation + +extension AudioEngine: AudioManagerProtocol { + func startLoudnessRecorder() { + if !self.isRecording { + self.inputNode = self.loudnessEngine.inputNode + self.recordingFormat = self.inputNode.outputFormat(forBus: 0) + self.inputNode.installTap(onBus: 0, bufferSize: 2048, format: self.recordingFormat) { buffer, _ in + let db = self.decibelFullScale(from: buffer) + if !db.isNaN { self.dbSamples.append(db) } + } + } + self.loudnessEngine.prepare() + try? self.loudnessEngine.start() + self.isRecording = true + + } + + func stopLoudnessRecorder() { + if self.isRecording { + self.inputNode.removeTap(onBus: 0) + self.loudnessEngine.stop() + self.dbSamples.removeAll() // dot return sound if stop was called + self.isRecording = false + } + } + + func pauseLoudnessRecorder() { + if self.isRecording { + self.loudnessEngine.pause() + self.isRecording = false + } + } + + func resumeLoudnessRecorder() { + if !self.isRecording { + try? self.loudnessEngine.start() + self.isRecording = true + } + } + + func loudness() -> Double? { + if self.dbSamples.isEmpty { return nil } + return (self.dbSamples.reduce(0, +) / Double(self.dbSamples.count)) + } + + private func decibelFullScale(from buffer: AVAudioPCMBuffer) -> Double { + let arraySize = Int(buffer.frameLength) + let samples = Array(UnsafeBufferPointer(start: buffer.floatChannelData![0], count: arraySize)) + let dBFS = 20 * log10(abs(samples.max()!)) + return Double(dBFS) } + func loudnessAvailable() -> Bool { + var isGranted = false + let dispatchGroup = DispatchGroup() + + switch AVAudioSession.sharedInstance().recordPermission { + case AVAudioSession.RecordPermission.denied: + isGranted = false + case AVAudioSession.RecordPermission.undetermined: + dispatchGroup.enter() + AVAudioSession.sharedInstance().requestRecordPermission({ (granted: Bool) in + isGranted = granted + dispatchGroup.leave() + }) + dispatchGroup.wait() + case AVAudioSession.RecordPermission.granted: + isGranted = true + @unknown default: + print("ERROR: case not handled by switch statement") + } + + return isGranted + } } diff --git a/src/Catty/PlayerEngine/Formula/FormulaManager.swift b/src/Catty/PlayerEngine/Formula/FormulaManager.swift index 2b0a8df3f5..b2f87a094c 100644 --- a/src/Catty/PlayerEngine/Formula/FormulaManager.swift +++ b/src/Catty/PlayerEngine/Formula/FormulaManager.swift @@ -41,7 +41,7 @@ import CoreMotion let motionManager = CMMotionManager() let locationManager = CLLocationManager() let visualDetectionManager = VisualDetectionManager() - let audioManager = AudioManager.shared()! + let audioManager = AudioEngine() let touchManager = TouchManager() let bluetoothService = BluetoothService.sharedInstance() @@ -81,7 +81,7 @@ import CoreMotion motionManager: CMMotionManager(), locationManager: CLLocationManager(), visualDetectionManager: VisualDetectionManager(), - audioManager: AudioManager(), + audioManager: AudioEngine(), touchManager: TouchManager(), bluetoothService: BluetoothService.sharedInstance()) } diff --git a/src/Catty/ViewController/Stage/StagePresenterViewController.m b/src/Catty/ViewController/Stage/StagePresenterViewController.m index 7fff4ddb2d..15b3ea3fc5 100644 --- a/src/Catty/ViewController/Stage/StagePresenterViewController.m +++ b/src/Catty/ViewController/Stage/StagePresenterViewController.m @@ -192,14 +192,6 @@ - (void)freeRessources { self.project = nil; self.stage = nil; - - // Delete sound rec for loudness sensor - NSError *error; - NSFileManager *fileMgr = [NSFileManager defaultManager]; - NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString *soundfile = [documentsPath stringByAppendingPathComponent:@"loudness_handler.m4a"]; - if ([fileMgr removeItemAtPath:soundfile error:&error] != YES) - NSDebug(@"No Sound file available or unable to delete file: %@", [error localizedDescription]); } #pragma mark View Setup