Section 8/201 menit

8. Sound Analysis: Audio Intelligence

8. Sound Analysis: Audio Intelligence

swift
// Sound Analysis: klasifikasi suara real-time

import SoundAnalysis
import AVFoundation

final class SoundClassifier: NSObject {
    private var analyzer: SNAudioStreamAnalyzer?
    private let analysisQueue = DispatchQueue(label: "com.app.sound-analysis")

    var onSoundDetected: ((String, Double) -> Void)?

    func startAnalyzing() throws {
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .default)
        try audioSession.setActive(true)

        // Gunakan built-in sound classifier Apple (iOS 15+)
        let request = try SNClassifySoundRequest(classifierIdentifier: .version1)
        request.windowDuration = CMTimeMakeWithSeconds(1.5, preferredTimescale: 44100)
        request.overlapFactor = 0.5  // 50% overlap antar window

        let inputFormat = AVAudioFormat(
            standardFormatWithSampleRate: 44100,
            channels: 1
        )!

        analyzer = SNAudioStreamAnalyzer(format: inputFormat)
        try analyzer?.add(request, withObserver: self)

        // Setup AVAudioEngine untuk capture audio
        setupAudioEngine(format: inputFormat)
    }

    private func setupAudioEngine(format: AVAudioFormat) {
        let engine = AVAudioEngine()
        let inputNode = engine.inputNode
        let recordingFormat = inputNode.outputFormat(forBus: 0)

        inputNode.installTap(onBus: 0, bufferSize: 8192, format: recordingFormat) { [weak self] buffer, time in
            self?.analysisQueue.async {
                self?.analyzer?.analyze(buffer, atAudioFramePosition: time.sampleTime)
            }
        }

        try? engine.start()
    }
}

extension SoundClassifier: SNResultsObserving {
    func request(_ request: SNRequest, didProduce result: SNResult) {
        guard let classificationResult = result as? SNClassificationResult,
              let topClassification = classificationResult.classifications.first,
              topClassification.confidence > 0.7 else { return }

        DispatchQueue.main.async { [weak self] in
            self?.onSoundDetected?(topClassification.identifier, topClassification.confidence)
        }
    }

    func request(_ request: SNRequest, didFailWithError error: Error) {
        print("Sound analysis failed: \(error)")
    }
}