Skip to content

Commit 2ecd5d2

Browse files
Includes important bug fixes (part - 2)
1 parent 1984a0b commit 2ecd5d2

9 files changed

Lines changed: 209 additions & 60 deletions

File tree

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
using CSCore;
22
using CSCore.Streams.Effects;
3-
using VoiceRecorder.Filters.Interfaces;
43

54
namespace VoiceRecorder.Filters;
65

7-
internal sealed class ChorusFilter : IAudioFilter
6+
internal sealed class ChorusFilter : DmoFilterBase
87
{
9-
public IWaveSource ApplyFilter(IWaveSource source)
8+
protected override IWaveSource ApplyDmoEffect(IWaveSource source)
109
{
11-
ArgumentNullException.ThrowIfNull(source);
12-
13-
var chorusEffect = new DmoChorusEffect(source)
10+
return new DmoChorusEffect(source)
1411
{
1512
WetDryMix = 40f,
1613
Depth = 30f,
@@ -19,7 +16,5 @@ public IWaveSource ApplyFilter(IWaveSource source)
1916
Delay = 15f,
2017
Waveform = ChorusWaveform.WaveformSin
2118
};
22-
23-
return chorusEffect;
2419
}
2520
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
using CSCore;
22
using CSCore.Streams.Effects;
3-
using VoiceRecorder.Filters.Interfaces;
43

54
namespace VoiceRecorder.Filters;
65

7-
internal sealed class CompressorFilter : IAudioFilter
6+
internal sealed class CompressorFilter : DmoFilterBase
87
{
9-
public IWaveSource ApplyFilter(IWaveSource source)
8+
protected override IWaveSource ApplyDmoEffect(IWaveSource source)
109
{
11-
ArgumentNullException.ThrowIfNull(source);
12-
13-
var compressorEffect = new DmoCompressorEffect(source)
10+
return new DmoCompressorEffect(source)
1411
{
1512
Gain = 5f,
1613
Attack = 5f,
@@ -19,7 +16,5 @@ public IWaveSource ApplyFilter(IWaveSource source)
1916
Ratio = 3f,
2017
Predelay = 2f
2118
};
22-
23-
return compressorEffect;
2419
}
2520
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
using CSCore;
22
using CSCore.Streams.Effects;
3-
using VoiceRecorder.Filters.Interfaces;
43

54
namespace VoiceRecorder.Filters;
65

7-
internal sealed class DistortionFilter : IAudioFilter
6+
internal sealed class DistortionFilter : DmoFilterBase
87
{
9-
public IWaveSource ApplyFilter(IWaveSource source)
8+
protected override IWaveSource ApplyDmoEffect(IWaveSource source)
109
{
11-
ArgumentNullException.ThrowIfNull(source);
12-
13-
var distortionEffect = new DmoDistortionEffect(source)
10+
return new DmoDistortionEffect(source)
1411
{
1512
Gain = -15,
1613
PostEQCenterFrequency = 300f,
1714
PostEQBandwidth = 2000f
1815
};
19-
20-
return distortionEffect;
2116
}
2217
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Diagnostics;
2+
using CSCore;
3+
using VoiceRecorder.Filters.Interfaces;
4+
5+
namespace VoiceRecorder.Filters;
6+
7+
internal abstract class DmoFilterBase : IAudioFilter
8+
{
9+
public IWaveSource ApplyFilter(IWaveSource source)
10+
{
11+
ArgumentNullException.ThrowIfNull(source);
12+
13+
var format = source.WaveFormat;
14+
Debug.WriteLine($"{GetType().Name} input: {format.SampleRate}Hz, {format.BitsPerSample}bit, {format.Channels}ch");
15+
16+
IWaveSource compatibleSource = source;
17+
18+
if (format.BitsPerSample != 16 && format.BitsPerSample != 32)
19+
{
20+
compatibleSource = source.ToSampleSource().ToWaveSource(16);
21+
Debug.WriteLine($"{GetType().Name}: Converted to 16-bit for DMO compatibility");
22+
}
23+
24+
return ApplyDmoEffect(compatibleSource);
25+
}
26+
27+
protected abstract IWaveSource ApplyDmoEffect(IWaveSource source);
28+
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
using CSCore;
22
using CSCore.Streams.Effects;
3-
using VoiceRecorder.Filters.Interfaces;
43

54
namespace VoiceRecorder.Filters;
65

7-
internal sealed class EchoFilter : IAudioFilter
6+
internal sealed class EchoFilter : DmoFilterBase
87
{
9-
public IWaveSource ApplyFilter(IWaveSource source)
8+
protected override IWaveSource ApplyDmoEffect(IWaveSource source)
109
{
11-
ArgumentNullException.ThrowIfNull(source);
12-
13-
var echoEffect = new DmoEchoEffect(source)
10+
return new DmoEchoEffect(source)
1411
{
1512
WetDryMix = 40f,
1613
Feedback = 35f,
1714
LeftDelay = 300f,
1815
RightDelay = 300f
1916
};
20-
21-
return echoEffect;
2217
}
2318
}
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
using CSCore;
22
using CSCore.Streams.Effects;
3-
using VoiceRecorder.Filters.Interfaces;
43

54
namespace VoiceRecorder.Filters;
65

7-
internal sealed class FlangerFilter : IAudioFilter
6+
internal sealed class FlangerFilter : DmoFilterBase
87
{
9-
public IWaveSource ApplyFilter(IWaveSource source)
8+
protected override IWaveSource ApplyDmoEffect(IWaveSource source)
109
{
11-
ArgumentNullException.ThrowIfNull(source);
12-
13-
var flangerEffect = new DmoFlangerEffect(source)
10+
return new DmoFlangerEffect(source)
1411
{
1512
WetDryMix = 30f,
1613
Depth = 50f,
@@ -19,7 +16,5 @@ public IWaveSource ApplyFilter(IWaveSource source)
1916
Delay = 1.5f,
2017
Waveform = FlangerWaveform.Sin
2118
};
22-
23-
return flangerEffect;
2419
}
2520
}

VoiceRecorder/Services/AudioPlayer.cs

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ internal sealed class AudioPlayer : IAudioPlayer
2323
private readonly SemaphoreSlim _playbackLock = new(1, 1);
2424
private CancellationTokenSource? _playbackCts;
2525
private System.Threading.Timer? _progressTimer;
26+
private bool _isWasapiWarmedUp;
27+
private readonly object _warmupLock = new();
2628

2729
public float Volume
2830
{
@@ -75,6 +77,44 @@ public TimeSpan Duration
7577
public event EventHandler<PlaybackStatusEventArgs>? PlaybackStatusChanged;
7678
public event EventHandler<PlaybackProgressEventArgs>? PlaybackProgressChanged;
7779

80+
public AudioPlayer()
81+
{
82+
Task.Run(WarmUpWasapiAsync);
83+
}
84+
85+
private async Task WarmUpWasapiAsync()
86+
{
87+
try
88+
{
89+
lock (_warmupLock)
90+
{
91+
if (_isWasapiWarmedUp)
92+
{
93+
return;
94+
}
95+
}
96+
97+
using var tempOut = new WasapiOut();
98+
99+
await Task.Delay(50).ConfigureAwait(false);
100+
101+
lock (_warmupLock)
102+
{
103+
_isWasapiWarmedUp = true;
104+
}
105+
106+
Debug.WriteLine("WASAPI warmed up successfully");
107+
}
108+
catch (Exception ex)
109+
{
110+
Debug.WriteLine($"WASAPI warmup failed (non-critical): {ex.Message}");
111+
lock (_warmupLock)
112+
{
113+
_isWasapiWarmedUp = true;
114+
}
115+
}
116+
}
117+
78118
public async Task PlayFileAsync(string filePath, CancellationToken cancellationToken = default)
79119
{
80120
ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
@@ -89,16 +129,23 @@ public async Task PlayFileAsync(string filePath, CancellationToken cancellationT
89129
try
90130
{
91131
_currentFilePath = filePath;
132+
var token = _playbackCts.Token;
92133

93-
await Task.Run(() =>
134+
await Task.Run(async () =>
94135
{
95136
_waveSource = CodecFactory.Instance.GetCodec(_currentFilePath);
96-
_soundOut = new WasapiOut();
137+
138+
var wasapiOut = new WasapiOut { Latency = 100 };
139+
_soundOut = wasapiOut;
140+
97141
_soundOut.Stopped += OnPlaybackStopped;
98142
_soundOut.Initialize(_waveSource);
99143
_soundOut.Volume = _volume;
144+
145+
await Task.Delay(50, token).ConfigureAwait(false);
146+
100147
_soundOut.Play();
101-
}, _playbackCts.Token).ConfigureAwait(false);
148+
}, token).ConfigureAwait(false);
102149

103150
_playbackState = PlaybackState.Playing;
104151

@@ -276,14 +323,39 @@ await Task.Run(() =>
276323
if (_soundOut != null)
277324
{
278325
_soundOut.Stopped -= OnPlaybackStopped;
279-
_soundOut.Stop();
280-
_soundOut.Dispose();
326+
327+
try
328+
{
329+
_soundOut.Stop();
330+
}
331+
catch (Exception ex)
332+
{
333+
Debug.WriteLine($"Error stopping soundOut: {ex.Message}");
334+
}
335+
336+
try
337+
{
338+
_soundOut.Dispose();
339+
}
340+
catch (Exception ex)
341+
{
342+
Debug.WriteLine($"Error disposing soundOut: {ex.Message}");
343+
}
344+
281345
_soundOut = null;
282346
}
283347

284348
if (_waveSource != null)
285349
{
286-
_waveSource.Dispose();
350+
try
351+
{
352+
_waveSource.Dispose();
353+
}
354+
catch (Exception ex)
355+
{
356+
Debug.WriteLine($"Error disposing waveSource: {ex.Message}");
357+
}
358+
287359
_waveSource = null;
288360
}
289361
}, cancellationToken).ConfigureAwait(false);
@@ -308,7 +380,10 @@ private async Task StopFileInternalAsync(CancellationToken cancellationToken = d
308380
return;
309381
}
310382

311-
await ((_playbackCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false));
383+
if (_playbackCts != null)
384+
{
385+
await _playbackCts.CancelAsync().ConfigureAwait(false);
386+
}
312387

313388
await StopInternalAsync(null, cancellationToken).ConfigureAwait(false);
314389
}
@@ -331,9 +406,19 @@ public void Dispose()
331406
if (!_disposed)
332407
{
333408
StopProgressTimer();
334-
StopFileAsync().GetAwaiter().GetResult();
409+
410+
try
411+
{
412+
StopFileAsync().GetAwaiter().GetResult();
413+
}
414+
catch (Exception ex)
415+
{
416+
Debug.WriteLine($"Error during dispose: {ex.Message}");
417+
}
418+
335419
_playbackCts?.Dispose();
336420
_playbackLock.Dispose();
421+
337422
_disposed = true;
338423
}
339424
}

VoiceRecorder/Services/AudioRecorder.cs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,21 +178,41 @@ private IWaveSource BuildProcessingChain(SoundInSource source, IAudioFilter? fil
178178
{
179179
IWaveSource processedSource = source;
180180

181-
if (settings != null && settings.IsValid())
181+
if (filter != null)
182182
{
183-
processedSource = processedSource
184-
.ChangeSampleRate(settings.SampleRate)
185-
.ToSampleSource()
186-
.ToWaveSource(settings.BitsPerSample);
187-
188-
processedSource = settings.Channels == 1
189-
? processedSource.ToMono()
190-
: processedSource.ToStereo();
183+
try
184+
{
185+
var filterCompatibleSource = processedSource
186+
.ToSampleSource()
187+
.ToWaveSource(16)
188+
.ToStereo();
189+
190+
processedSource = filter.ApplyFilter(filterCompatibleSource);
191+
Debug.WriteLine($"Filter applied: {filter.GetType().Name}");
192+
}
193+
catch (Exception ex)
194+
{
195+
Debug.WriteLine($"Failed to apply filter: {ex.Message}. Continuing without filter.");
196+
}
191197
}
192198

193-
if (filter != null)
199+
if (settings != null && settings.IsValid())
194200
{
195-
processedSource = filter.ApplyFilter(processedSource);
201+
try
202+
{
203+
processedSource = processedSource.ChangeSampleRate(settings.SampleRate);
204+
processedSource = processedSource.ToSampleSource().ToWaveSource(settings.BitsPerSample);
205+
processedSource = settings.Channels == 1
206+
? processedSource.ToMono()
207+
: processedSource.ToStereo();
208+
209+
Debug.WriteLine($"Applied settings: {settings.SampleRate}Hz, {settings.BitsPerSample}bit, {settings.Channels}ch");
210+
}
211+
catch (Exception ex)
212+
{
213+
Debug.WriteLine($"Failed to apply settings: {ex.Message}");
214+
throw new AudioRecorderException($"Cannot apply audio settings: {ex.Message}", ex);
215+
}
196216
}
197217

198218
return processedSource;

0 commit comments

Comments
 (0)