import Foundation /// Where one dictation's text goes. public enum InsertionRoute: Equatable, Sendable { /// Pasted into whichever app is in front. case system /// Typed into the Notitie editor inside Sst. case editor /// Dropped (Sst is in front but nothing there takes text, or the recording started in the window). case none } /// Sends each dictation to the system paster or the in-app editor, asked at insertion time rather than at creation. public struct RoutingInserter: TextInserting { private let system: TextInserting private let editor: TextInserting private let route: () async -> InsertionRoute public init(system: TextInserting, editor: TextInserting, route: @escaping () async -> InsertionRoute) { self.system = system self.editor = editor self.route = route } public func insert(_ text: String) async throws { switch await route() { case .system: try await system.insert(text) case .editor: try await editor.insert(text) case .none: break } } } /// Lets the app replace the speech model (for example after a model switch) without rebuilding the controller. public final class SwappableTranscriber: Transcriber, @unchecked Sendable { public enum SwapError: Error, LocalizedError { case noTranscriber public var errorDescription: String? { "Spraakmodel is nog niet geladen." } } private let lock = NSLock() private var current: Transcriber? public init(_ initial: Transcriber? = nil) { current = initial } public func swap(to transcriber: Transcriber?) { lock.withLock { current = transcriber } } public var hasTranscriber: Bool { lock.withLock { current != nil } } public func transcribe(_ samples: [Float]) async throws -> String { try await transcribe(samples, vocabulary: []) } public func transcribe(_ samples: [Float], vocabulary: [String]) async throws -> String { guard let transcriber = lock.withLock({ current }) else { throw SwapError.noTranscriber } return try await transcriber.transcribe(samples, vocabulary: vocabulary) } } /// The latest input level, written from the audio thread and read by the meter once per frame. public final class LevelSource: @unchecked Sendable { private let lock = NSLock() private var value = AudioLevel.silenceFloorDbfs public init() {} public var dbfs: Float { lock.withLock { value } } public func update(_ dbfs: Float) { lock.withLock { value = dbfs } } public func reset() { update(AudioLevel.silenceFloorDbfs) } }