Compare commits
5 Commits
84474731e7
...
b8ee014cc3
| Author | SHA1 | Date | |
|---|---|---|---|
| b8ee014cc3 | |||
| cc9fc95519 | |||
| 411f1c0952 | |||
| b40a06b01b | |||
| b6361ec080 |
@@ -8,7 +8,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
private BufferedWaveProvider? _bufferProvider;
|
private BufferedWaveProvider? _bufferProvider;
|
||||||
private int _sampleRate;
|
private int _sampleRate;
|
||||||
private string _deviceName = string.Empty;
|
private string _deviceName = string.Empty;
|
||||||
private float _lastSample;
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public Action<string>? Log { get; set; }
|
public Action<string>? Log { get; set; }
|
||||||
@@ -19,7 +18,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
|
|
||||||
_sampleRate = sampleRate;
|
_sampleRate = sampleRate;
|
||||||
_deviceName = deviceName ?? string.Empty;
|
_deviceName = deviceName ?? string.Empty;
|
||||||
_lastSample = 0f;
|
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
@@ -44,9 +42,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
if (_bufferProvider is null) return;
|
if (_bufferProvider is null) return;
|
||||||
|
|
||||||
if (samples.Length > 0)
|
|
||||||
_lastSample = samples[^1];
|
|
||||||
|
|
||||||
byte[] bytes = new byte[samples.Length * sizeof(float)];
|
byte[] bytes = new byte[samples.Length * sizeof(float)];
|
||||||
Buffer.BlockCopy(samples, 0, bytes, 0, bytes.Length);
|
Buffer.BlockCopy(samples, 0, bytes, 0, bytes.Length);
|
||||||
_bufferProvider.AddSamples(bytes, 0, bytes.Length);
|
_bufferProvider.AddSamples(bytes, 0, bytes.Length);
|
||||||
@@ -57,21 +52,8 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
if (_bufferProvider is null || _sampleRate == 0) return;
|
if (_bufferProvider is null || _sampleRate == 0) return;
|
||||||
|
|
||||||
// Fade out from last sample to zero over 5ms to avoid click
|
|
||||||
int fadeSamples = (int)(_sampleRate * 0.005);
|
|
||||||
float[] fade = new float[fadeSamples];
|
|
||||||
for (int i = 0; i < fadeSamples; i++)
|
|
||||||
{
|
|
||||||
float t = (float)i / fadeSamples;
|
|
||||||
fade[i] = _lastSample * (1f - t);
|
|
||||||
}
|
|
||||||
WriteSamples(fade);
|
|
||||||
|
|
||||||
// Then 500ms of silence to let NAudio drain
|
|
||||||
int padSamples = (int)(_sampleRate * 0.5);
|
int padSamples = (int)(_sampleRate * 0.5);
|
||||||
WriteSamples(new float[padSamples]);
|
WriteSamples(new float[padSamples]);
|
||||||
|
|
||||||
_lastSample = 0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStopped(object? sender, StoppedEventArgs e)
|
private void OnPlaybackStopped(object? sender, StoppedEventArgs e)
|
||||||
@@ -89,7 +71,6 @@ internal sealed class AudioOutput : IDisposable
|
|||||||
}
|
}
|
||||||
_bufferProvider?.ClearBuffer();
|
_bufferProvider?.ClearBuffer();
|
||||||
_bufferProvider = null;
|
_bufferProvider = null;
|
||||||
_lastSample = 0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int FindDevice(string? deviceName)
|
private static int FindDevice(string? deviceName)
|
||||||
|
|||||||
+38
-10
@@ -324,27 +324,55 @@ fn transcriber_loop(
|
|||||||
// Drain trailing audio from ALSA buffer (cpal stream still alive).
|
// Drain trailing audio from ALSA buffer (cpal stream still alive).
|
||||||
// Fixed 100ms window — cpal delivers every ~50ms (800 samples @ 16kHz),
|
// Fixed 100ms window — cpal delivers every ~50ms (800 samples @ 16kHz),
|
||||||
// so this captures 1-2 more callbacks worth of trailing audio.
|
// so this captures 1-2 more callbacks worth of trailing audio.
|
||||||
|
// Hold one chunk back so we can apply a fade-out to the very last one.
|
||||||
let drain_deadline = std::time::Instant::now() + Duration::from_millis(100);
|
let drain_deadline = std::time::Instant::now() + Duration::from_millis(100);
|
||||||
|
let mut held_chunk: Option<Vec<f32>> = None;
|
||||||
while std::time::Instant::now() < drain_deadline {
|
while std::time::Instant::now() < drain_deadline {
|
||||||
match rx.recv_timeout(drain_deadline - std::time::Instant::now()) {
|
match rx.recv_timeout(drain_deadline - std::time::Instant::now()) {
|
||||||
Ok(chunk) => {
|
Ok(chunk) => {
|
||||||
if let Some(ref mut r) = recorder {
|
// Feed previously held chunk to Moonshine + recorder (no fade)
|
||||||
r.add_audio(&chunk);
|
if let Some(prev) = held_chunk.take() {
|
||||||
}
|
if let Some(ref mut r) = recorder {
|
||||||
|
r.add_audio(&prev);
|
||||||
unsafe {
|
}
|
||||||
moonshine_transcribe_add_audio_to_stream(
|
unsafe {
|
||||||
handle, stream_handle,
|
moonshine_transcribe_add_audio_to_stream(
|
||||||
chunk.as_ptr(), chunk.len() as u64,
|
handle, stream_handle,
|
||||||
SAMPLE_RATE, 0,
|
prev.as_ptr(), prev.len() as u64,
|
||||||
);
|
SAMPLE_RATE, 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
held_chunk = Some(chunk);
|
||||||
}
|
}
|
||||||
Err(mpsc::RecvTimeoutError::Timeout) => break,
|
Err(mpsc::RecvTimeoutError::Timeout) => break,
|
||||||
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply 5ms fade-out to the last chunk, then feed to both Moonshine and recorder
|
||||||
|
if let Some(mut chunk) = held_chunk.take() {
|
||||||
|
let fade_samples = (SAMPLE_RATE as usize * 5) / 1000;
|
||||||
|
if chunk.len() > fade_samples {
|
||||||
|
let start = chunk.len() - fade_samples;
|
||||||
|
for i in 0..fade_samples {
|
||||||
|
let t = 1.0 - (i as f32 / fade_samples as f32);
|
||||||
|
chunk[start + i] *= t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut r) = recorder {
|
||||||
|
r.add_audio(&chunk);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
moonshine_transcribe_add_audio_to_stream(
|
||||||
|
handle, stream_handle,
|
||||||
|
chunk.as_ptr(), chunk.len() as u64,
|
||||||
|
SAMPLE_RATE, 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If aborted (new session took over), skip final flush entirely
|
// If aborted (new session took over), skip final flush entirely
|
||||||
if aborted.load(Ordering::SeqCst) {
|
if aborted.load(Ordering::SeqCst) {
|
||||||
unsafe { moonshine_stop_stream(handle, stream_handle) };
|
unsafe { moonshine_stop_stream(handle, stream_handle) };
|
||||||
|
|||||||
Reference in New Issue
Block a user