Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/TypeWhisper.Windows/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ protected override void OnStartup(StartupEventArgs e)
var audio = _serviceProvider.GetRequiredService<AudioRecordingService>();
var mic = settings.Current.SelectedMicrophoneDevice;
if (mic.HasValue) audio.SetMicrophoneDevice(mic);
audio.WarmUp();
if (!audio.WarmUp())
System.Diagnostics.Debug.WriteLine("No audio input device available at startup. Polling for device...");

// Start API server if enabled
if (settings.Current.ApiServerEnabled)
Expand Down
2 changes: 2 additions & 0 deletions src/TypeWhisper.Windows/Resources/Localization/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@
"Status.Done": "Fertig",
"Status.ActionFormat": "{0} wird ausgeführt...",
"Status.Cancelled": "Abgebrochen",
"Status.NoMicrophone": "Kein Mikrofon angeschlossen",
"Status.MicrophoneRestored": "Mikrofon verbunden",
"Status.ErrorFormat": "Fehler: {0}",
"Status.ModelErrorFormat": "Modell-Fehler: {0}",
"Status.NoModelLoaded": "Kein Modell geladen",
Expand Down
2 changes: 2 additions & 0 deletions src/TypeWhisper.Windows/Resources/Localization/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@
"Status.Done": "Done",
"Status.ActionFormat": "Running {0}...",
"Status.Cancelled": "Cancelled",
"Status.NoMicrophone": "No microphone connected",
"Status.MicrophoneRestored": "Microphone connected",
"Status.ErrorFormat": "Error: {0}",
"Status.ModelErrorFormat": "Model error: {0}",
"Status.NoModelLoaded": "No model loaded",
Expand Down
113 changes: 79 additions & 34 deletions src/TypeWhisper.Windows/Services/AudioRecordingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ public sealed class AudioRecordingService : IDisposable
public event EventHandler<SamplesAvailableEventArgs>? SamplesAvailable;
public event EventHandler? DevicesChanged;
public event EventHandler? DeviceLost;
public event EventHandler? DeviceAvailable;

public bool HasDevice => WaveInEvent.DeviceCount > 0;
public bool WhisperModeEnabled { get; set; }
public bool NormalizationEnabled { get; set; } = true;
public bool IsRecording => _isRecording;
Expand All @@ -54,25 +56,47 @@ public void SetMicrophoneDevice(int? deviceNumber)
_activeDeviceNumber = newDevice;
}

public void WarmUp()
public bool WarmUp()
{
if (_isWarmedUp || _disposed) return;
if (_isWarmedUp || _disposed) return _isWarmedUp;

if (WaveInEvent.DeviceCount == 0)
{
System.Diagnostics.Debug.WriteLine("WarmUp: No audio input devices available.");
StartDevicePolling();
return false;
}

_activeDeviceNumber = _configuredDeviceNumber ?? FindBestMicrophoneDevice();
if (_activeDeviceNumber < 0)
{
StartDevicePolling();
return false;
}

_waveIn = new WaveInEvent
try
{
DeviceNumber = _activeDeviceNumber,
WaveFormat = new WaveFormat(SampleRate, BitsPerSample, Channels),
BufferMilliseconds = 30
};
_waveIn = new WaveInEvent
{
DeviceNumber = _activeDeviceNumber,
WaveFormat = new WaveFormat(SampleRate, BitsPerSample, Channels),
BufferMilliseconds = 30
};

_waveIn.DataAvailable += OnDataAvailable;
_waveIn.RecordingStopped += OnRecordingStopped;
_waveIn.StartRecording();
_waveIn.DataAvailable += OnDataAvailable;
_waveIn.RecordingStopped += OnRecordingStopped;
_waveIn.StartRecording();

_isWarmedUp = true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"WarmUp failed: {ex.Message}");
DisposeWaveIn();
}

_isWarmedUp = true;
StartDevicePolling();
return _isWarmedUp;
}

public static IReadOnlyList<(int DeviceNumber, string Name)> GetAvailableDevices()
Expand All @@ -90,8 +114,8 @@ public void StartRecording()
{
if (_isRecording) return;

if (!_isWarmedUp)
WarmUp();
if (!_isWarmedUp && !WarmUp())
return;

if (_waveIn is null) return;

Expand Down Expand Up @@ -225,7 +249,7 @@ private static int FindBestMicrophoneDevice()
return i;
}

return 0;
return deviceCount > 0 ? 0 : -1;
}

private void StartDevicePolling()
Expand All @@ -243,18 +267,29 @@ private void CheckForDeviceChanges()
try
{
var currentCount = WaveInEvent.DeviceCount;
if (currentCount != _lastKnownDeviceCount)
if (currentCount == _lastKnownDeviceCount) return;

var previousCount = _lastKnownDeviceCount;
_lastKnownDeviceCount = currentCount;
DevicesChanged?.Invoke(this, EventArgs.Empty);

if (currentCount == 0 && _isWarmedUp)
{
DeviceLost?.Invoke(this, EventArgs.Empty);
DisposeWaveIn();
_configuredDeviceNumber = null;
}
else if (currentCount > 0 && previousCount == 0)
{
_lastKnownDeviceCount = currentCount;
DevicesChanged?.Invoke(this, EventArgs.Empty);

if (_isWarmedUp && _activeDeviceNumber >= currentCount)
{
DeviceLost?.Invoke(this, EventArgs.Empty);
DisposeWaveIn();
_configuredDeviceNumber = null;
WarmUp();
}
DeviceAvailable?.Invoke(this, EventArgs.Empty);
WarmUp();
}
else if (_isWarmedUp && _activeDeviceNumber >= currentCount)
{
DeviceLost?.Invoke(this, EventArgs.Empty);
DisposeWaveIn();
_configuredDeviceNumber = null;
WarmUp();
}
}
catch { }
Expand All @@ -263,18 +298,28 @@ private void CheckForDeviceChanges()
public void StartPreview(int? deviceNumber)
{
StopPreview();
if (_disposed) return;
if (_disposed || WaveInEvent.DeviceCount == 0) return;

var deviceIndex = deviceNumber ?? FindBestMicrophoneDevice();
_previewWaveIn = new WaveInEvent
if (deviceIndex < 0) return;

try
{
_previewWaveIn = new WaveInEvent
{
DeviceNumber = deviceIndex,
WaveFormat = new WaveFormat(SampleRate, BitsPerSample, Channels),
BufferMilliseconds = 50
};
_previewWaveIn.DataAvailable += OnPreviewDataAvailable;
_previewWaveIn.StartRecording();
_isPreviewing = true;
}
catch (Exception ex)
{
DeviceNumber = deviceIndex,
WaveFormat = new WaveFormat(SampleRate, BitsPerSample, Channels),
BufferMilliseconds = 50
};
_previewWaveIn.DataAvailable += OnPreviewDataAvailable;
_previewWaveIn.StartRecording();
_isPreviewing = true;
System.Diagnostics.Debug.WriteLine($"StartPreview failed: {ex.Message}");
StopPreview();
}
}

public void StopPreview()
Expand Down
32 changes: 32 additions & 0 deletions src/TypeWhisper.Windows/ViewModels/DictationViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,28 @@ public DictationViewModel(
_consumerTask = Task.Run(() => ProcessJobsAsync(_consumerCts.Token));

_audio.AudioLevelChanged += OnAudioLevelChanged;
_audio.DeviceLost += (_, _) => Application.Current?.Dispatcher.InvokeAsync(async () =>
{
if (_isRecording)
{
_isRecording = false;
_durationTimer?.Stop();
_audio.StopRecording();
_audioDucking.RestoreAudio();
_mediaPause.ResumeMedia();
State = DictationState.Idle;
IsOverlayVisible = false;
}
FeedbackText = Loc.Instance["Status.NoMicrophone"];
FeedbackIsError = true;
ShowFeedback = true;
});
_audio.DeviceAvailable += (_, _) => Application.Current?.Dispatcher.InvokeAsync(() =>
{
FeedbackText = Loc.Instance["Status.MicrophoneRestored"];
FeedbackIsError = false;
ShowFeedback = true;
});
_settings.SettingsChanged += _ =>
{
OnPropertyChanged(nameof(LeftWidget));
Expand Down Expand Up @@ -251,6 +273,16 @@ private async Task StartRecording()
return;
}

if (!_audio.HasDevice)
{
StatusText = Loc.Instance["Status.NoMicrophone"];
FeedbackText = StatusText;
FeedbackIsError = true;
ShowFeedback = true;
_isRecording = false;
return;
}

ActiveProcessName = _capturedProcessName;
ActiveProfileName = _activeProfile?.Name;

Expand Down
1 change: 1 addition & 0 deletions src/TypeWhisper.Windows/ViewModels/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ private void RefreshMicrophones()
public void StartMicrophonePreview()
{
_audio.PreviewLevelChanged -= OnPreviewLevelChanged;
if (!_audio.HasDevice) return;
_audio.StartPreview(SelectedMicrophoneDevice);
_audio.PreviewLevelChanged += OnPreviewLevelChanged;
}
Expand Down
1 change: 1 addition & 0 deletions src/TypeWhisper.Windows/ViewModels/WelcomeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ private void Skip()
private void StartMicTest()
{
_audio.AudioLevelChanged += OnMicLevel;
if (!_audio.HasDevice) return;
_audio.WarmUp();
_audio.StartRecording();
}
Expand Down