feat: add GatedInserter, SwappableTranscriber and LevelSource

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:32:04 +02:00
co-authored by Claude Sonnet 5.5
parent 2d257a3bb9
commit 6f491accf1
2 changed files with 137 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
import Foundation
/// Inserts text only while `shouldInsert()` says so (not when Sst itself is in front, nor for window recordings).
public struct GatedInserter: TextInserting {
private let inner: TextInserting
private let shouldInsert: () -> Bool
public init(inner: TextInserting, shouldInsert: @escaping () -> Bool) {
self.inner = inner
self.shouldInsert = shouldInsert
}
public func insert(_ text: String) async throws {
guard shouldInsert() else { return }
try await inner.insert(text)
}
}
/// 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)
}
}