fix: skip silent recordings instead of transcribing them
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,20 +13,24 @@ public final class DictationController {
|
|||||||
private let cleaner: Cleaner
|
private let cleaner: Cleaner
|
||||||
private let inserter: TextInserting
|
private let inserter: TextInserting
|
||||||
private let minimumSamples: Int
|
private let minimumSamples: Int
|
||||||
|
private let silenceThreshold: Float
|
||||||
|
|
||||||
/// - Parameter minimumSamples: 16 kHz samples; default is 0.3 s.
|
/// - Parameter minimumSamples: 16 kHz samples; default is 0.3 s.
|
||||||
|
/// - Parameter silenceThreshold: RMS below which a recording counts as silence and is skipped.
|
||||||
public init(
|
public init(
|
||||||
recorder: AudioRecording,
|
recorder: AudioRecording,
|
||||||
transcriber: Transcriber,
|
transcriber: Transcriber,
|
||||||
cleaner: Cleaner,
|
cleaner: Cleaner,
|
||||||
inserter: TextInserting,
|
inserter: TextInserting,
|
||||||
minimumSamples: Int = 4_800
|
minimumSamples: Int = 4_800,
|
||||||
|
silenceThreshold: Float = 0.005
|
||||||
) {
|
) {
|
||||||
self.recorder = recorder
|
self.recorder = recorder
|
||||||
self.transcriber = transcriber
|
self.transcriber = transcriber
|
||||||
self.cleaner = cleaner
|
self.cleaner = cleaner
|
||||||
self.inserter = inserter
|
self.inserter = inserter
|
||||||
self.minimumSamples = minimumSamples
|
self.minimumSamples = minimumSamples
|
||||||
|
self.silenceThreshold = silenceThreshold
|
||||||
}
|
}
|
||||||
|
|
||||||
public func handle(_ event: HotkeyEvent) async {
|
public func handle(_ event: HotkeyEvent) async {
|
||||||
@@ -44,6 +48,11 @@ public final class DictationController {
|
|||||||
state = .idle
|
state = .idle
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let energy = samples.reduce(0) { $0 + $1 * $1 }
|
||||||
|
guard (energy / Float(samples.count)).squareRoot() >= silenceThreshold else {
|
||||||
|
state = .idle
|
||||||
|
return
|
||||||
|
}
|
||||||
await process(samples)
|
await process(samples)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -45,6 +45,23 @@ struct DictationControllerTests {
|
|||||||
#expect(inserter.inserted.isEmpty)
|
#expect(inserter.inserted.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func silentRecordingIsDiscarded() async {
|
||||||
|
recorder.samples = [Float](repeating: 0, count: 16_000)
|
||||||
|
let controller = makeController()
|
||||||
|
var states: [DictationState] = []
|
||||||
|
var errors: [String] = []
|
||||||
|
controller.onStateChange = { states.append($0) }
|
||||||
|
controller.onError = { errors.append($0) }
|
||||||
|
|
||||||
|
await controller.handle(.pressed)
|
||||||
|
await controller.handle(.released)
|
||||||
|
|
||||||
|
#expect(states == [.recording, .idle])
|
||||||
|
#expect(transcriber.callCount == 0)
|
||||||
|
#expect(inserter.inserted.isEmpty)
|
||||||
|
#expect(errors.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
@Test func emptyTranscriptionIsNotInserted() async {
|
@Test func emptyTranscriptionIsNotInserted() async {
|
||||||
recorder.samples = Self.oneSecond
|
recorder.samples = Self.oneSecond
|
||||||
transcriber.result = .success(" \n")
|
transcriber.result = .success(" \n")
|
||||||
|
|||||||
Reference in New Issue
Block a user