84 lines
2.6 KiB
Swift
84 lines
2.6 KiB
Swift
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: () -> InsertionRoute
|
|
|
|
public init(system: TextInserting, editor: TextInserting, route: @escaping () -> InsertionRoute) {
|
|
self.system = system
|
|
self.editor = editor
|
|
self.route = route
|
|
}
|
|
|
|
public func insert(_ text: String) async throws {
|
|
switch 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)
|
|
}
|
|
}
|