Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CATTY-357 Move loudness recorder to AudioEngine #1790

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Catty/PlayerEngine/AudioEngine/AudioEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]()
Expand Down
166 changes: 74 additions & 92 deletions src/Catty/PlayerEngine/AudioEngine/AudioManager+Loudness.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
4 changes: 2 additions & 2 deletions src/Catty/PlayerEngine/Formula/FormulaManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -81,7 +81,7 @@ import CoreMotion
motionManager: CMMotionManager(),
locationManager: CLLocationManager(),
visualDetectionManager: VisualDetectionManager(),
audioManager: AudioManager(),
audioManager: AudioEngine(),
touchManager: TouchManager(),
bluetoothService: BluetoothService.sharedInstance())
}
Expand Down
8 changes: 0 additions & 8 deletions src/Catty/ViewController/Stage/StagePresenterViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down