Section 6/151 menit

6. Speech Framework — Pengenalan Suara

6. Speech Framework — Pengenalan Suara

On-Device Speech Recognition

swift
import Speech
import AVFoundation

class SpeechRecognizer: NSObject, ObservableObject {

    @Published var transcript = ""
    @Published var isRecording = false
    @Published var error: Error?

    private var recognitionTask: SFSpeechRecognitionTask?
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private let audioEngine = AVAudioEngine()
    private var speechRecognizer: SFSpeechRecognizer?

    override init() {
        super.init()
        speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id-ID"))
    }

    func requestPermission() async -> Bool {
        await withCheckedContinuation { continuation in
            SFSpeechRecognizer.requestAuthorization { status in
                continuation.resume(returning: status == .authorized)
            }
        }
    }

    @MainActor
    func startRecording() throws {
        // Stop existing task
        recognitionTask?.cancel()
        recognitionTask = nil

        // Configure audio session
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)

        // Create recognition request
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let recognitionRequest else { throw SpeechError.requestCreationFailed }

        recognitionRequest.shouldReportPartialResults = true
        recognitionRequest.requiresOnDeviceRecognition = true  // iOS 13+ on-device

        // Custom vocabulary untuk domain-spesifik (iOS 16+)
        // recognitionRequest.customizedLanguageModel = customModel

        // Start recognition task
        guard let speechRecognizer, speechRecognizer.isAvailable else {
            throw SpeechError.recognizerUnavailable
        }

        recognitionTask = speechRecognizer.recognitionTask(
            with: recognitionRequest
        ) { [weak self] result, error in
            Task { @MainActor [weak self] in
                if let result {
                    self?.transcript = result.bestTranscription.formattedString
                }
                if error != nil || result?.isFinal == true {
                    self?.stopRecording()
                }
            }
        }

        // Tap audio input
        let inputNode = audioEngine.inputNode
        let recordingFormat = inputNode.outputFormat(forBus: 0)

        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
            self?.recognitionRequest?.append(buffer)
        }

        audioEngine.prepare()
        try audioEngine.start()
        isRecording = true
    }

    @MainActor
    func stopRecording() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
        recognitionRequest?.endAudio()
        recognitionTask?.cancel()
        recognitionTask = nil
        isRecording = false

        try? AVAudioSession.sharedInstance().setActive(false)
    }
}

Custom Language Model (iOS 17+)

swift
// Speech: custom language model untuk domain-spesifik (medical, legal, tech)

import Speech

class CustomLanguageModelBuilder {

    func buildMedicalLanguageModel() async throws -> SFCustomLanguagueModel {
        let configuration = SFCustomLanguageModel.Configuration()

        // Vocabulary domain-spesifik
        let medicalTerms: Set<String> = [
            "hipertensi", "aritmia", "echokardiografi",
            "angioplasty", "kateterisasi jantung",
            "troponin", "BNP", "creatinine"
        ]

        // Contoh frasa yang sering digunakan
        let samplePhrases: [String] = [
            "pasien mengalami nyeri dada sejak kemarin",
            "tekanan darah 140 per 90",
            "denyut jantung 72 per menit",
        ]

        return try await SFCustomLanguagueModel.prepare(
            assetPath: URL.documentsDirectory.appendingPathComponent("MedicalLM"),
            configuration: configuration,
            customVocabulary: medicalTerms,
            customPhrases: samplePhrases
        )
    }
}