Section 7/151 menit

7. Sound Analysis — Klasifikasi Audio

7. Sound Analysis — Klasifikasi Audio

Built-in Sound Classifier

swift
import SoundAnalysis

class SoundAnalyzer: NSObject {

    private var analyzer: SNAudioStreamAnalyzer?
    private let analysisQueue = DispatchQueue(label: "com.company.sound.analysis")
    private let audioEngine = AVAudioEngine()

    var onSoundDetected: (String, Double) -> Void = { _, _ in }

    func startAnalysis() throws {
        // Built-in classifier — deteksi 300+ jenis suara
        let request = try SNClassifySoundRequest(classifierIdentifier: .version1)
        request.windowDuration = CMTimeMakeWithSeconds(1.5, preferredTimescale: 44100)
        request.overlapFactor = 0.5  // 50% overlap antar window

        let inputFormat = audioEngine.inputNode.inputFormat(forBus: 0)
        analyzer = SNAudioStreamAnalyzer(format: inputFormat)

        try analyzer?.add(request, withObserver: self)

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

        try AVAudioSession.sharedInstance().setCategory(.record)
        try AVAudioSession.sharedInstance().setActive(true)
        audioEngine.prepare()
        try audioEngine.start()
    }

    func stopAnalysis() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
        analyzer?.removeAllRequests()
        try? AVAudioSession.sharedInstance().setActive(false)
    }
}

extension SoundAnalyzer: SNResultsObserving {

    func request(_ request: SNRequest, didProduce result: SNResult) {
        guard let classificationResult = result as? SNClassificationResult else { return }

        // Ambil klasifikasi dengan confidence tertinggi
        if let topResult = classificationResult.classifications.first,
           topResult.confidence > 0.7 {
            Task { @MainActor in
                self.onSoundDetected(topResult.identifier, topResult.confidence)
            }
        }
    }

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

Custom Sound Classifier

swift
// SoundAnalysis: gunakan model custom yang di-train dengan Create ML

class CustomSoundClassifier {

    private var analyzer: SNAudioFileAnalyzer?

    // Analisis file audio (bukan live stream)
    func classify(audioFile: URL) async throws -> [SoundClassification] {
        let request = try SNClassifySoundRequest(
            mlModel: try MachineSound(configuration: .init()).model
        )

        let analyzer = try SNAudioFileAnalyzer(url: audioFile)

        return try await withCheckedThrowingContinuation { continuation in
            var classifications: [SoundClassification] = []

            do {
                try analyzer.add(request, withObserver: AnyResultsObserver { result, error in
                    if let error { continuation.resume(throwing: error); return }
                    guard let result = result as? SNClassificationResult else { return }

                    for classification in result.classifications where classification.confidence > 0.5 {
                        classifications.append(SoundClassification(
                            label: classification.identifier,
                            confidence: classification.confidence,
                            timeRange: result.timeRange
                        ))
                    }
                })

                analyzer.analyze()
                continuation.resume(returning: classifications)
            } catch {
                continuation.resume(throwing: error)
            }
        }
    }
}

struct SoundClassification {
    let label: String
    let confidence: Double
    let timeRange: CMTimeRange
}