Skip to content

Commit a67280f

Browse files
authored
Merge pull request #107 from dan0v/master
Version 2.11.0
2 parents 6b116ab + 4990917 commit a67280f

File tree

14 files changed

+2734
-2586
lines changed

14 files changed

+2734
-2586
lines changed

AmplitudeSoundboard.csproj

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<RootNamespace>Amplitude</RootNamespace>
1818
<NeutralLanguage>en</NeutralLanguage>
1919
<Platforms>AnyCPU;x64</Platforms>
20-
<Version>2.10.5</Version>
20+
<Version>2.11.0</Version>
2121
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
2222
<PublishTrimmed>true</PublishTrimmed>
2323
<TrimMode>partial</TrimMode>
@@ -35,19 +35,20 @@
3535
<AvaloniaResource Include="Assets\**" />
3636
</ItemGroup>
3737
<ItemGroup>
38-
<PackageReference Include="Avalonia" Version="11.3.0" />
39-
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
40-
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.0" />
41-
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
42-
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.0" />
38+
<PackageReference Include="Avalonia" Version="11.3.2" />
39+
<PackageReference Include="Avalonia.Desktop" Version="11.3.2" />
40+
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.2" />
41+
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.2" />
42+
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.2" />
4343
<PackageReference Include="ManagedBass" Version="3.1.1" />
4444
<PackageReference Include="ManagedBass.Flac" Version="3.1.1" />
4545
<PackageReference Include="ManagedBass.Mix" Version="3.1.1" />
4646
<PackageReference Include="ManagedBass.Opus" Version="3.1.1" />
47-
<PackageReference Include="SharpHook" Version="6.1.0" />
47+
<PackageReference Include="SharpHook" Version="6.1.2" />
4848
<PackageReference Include="System.Net.Http" Version="4.3.4" />
4949
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
5050
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
51+
<PackageReference Include="System.Threading.Tasks" Version="4.3.0" />
5152
</ItemGroup>
5253
<ItemGroup>
5354
<Compile Update="Localization\Language.Designer.cs">

Converters/IntConverter.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
AmplitudeSoundboard
3+
Copyright (C) 2021-2025 dan0v
4+
https://git.dan0v.com/AmplitudeSoundboard
5+
6+
This file is part of AmplitudeSoundboard.
7+
8+
AmplitudeSoundboard is free software: you can redistribute it and/or modify
9+
it under the terms of the GNU General Public License as published by
10+
the Free Software Foundation, either version 3 of the License, or
11+
(at your option) any later version.
12+
13+
AmplitudeSoundboard is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
GNU General Public License for more details.
17+
18+
You should have received a copy of the GNU General Public License
19+
along with AmplitudeSoundboard. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
using Avalonia.Data.Converters;
23+
using System;
24+
using System.Globalization;
25+
26+
namespace Amplitude.Converters
27+
{
28+
public class IntConverter : IValueConverter
29+
{
30+
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
31+
{
32+
return "" + value;
33+
}
34+
35+
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
36+
{
37+
if (value is string value_ && int.TryParse(value_, out int res))
38+
{
39+
return res;
40+
}
41+
return 0;
42+
}
43+
}
44+
}

Helpers/ISoundEngine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public interface ISoundEngine : IDisposable
4747

4848
public List<string> OutputDeviceListWithGlobal { get; }
4949

50-
public void StopPlaying(int bassId);
50+
public void StopPlaying(int handle, double remainingMilis, int fadeOutMilis = 0);
5151

5252
public void RemoveFromQueue(SoundClip clip);
5353

Helpers/MSoundEngine.cs

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ You should have received a copy of the GNU General Public License
2121

2222
using Amplitude.Models;
2323
using AmplitudeSoundboard;
24+
using DynamicData;
2425
using ManagedBass;
2526
using ManagedBass.Mix;
2627
using System;
@@ -38,15 +39,16 @@ class MSoundEngine : ISoundEngine
3839
public static ISoundEngine Instance => _instance ??= new MSoundEngine();
3940

4041
private readonly object currentlyPlayingLock = new();
41-
4242
private ObservableCollection<PlayingClip> _currentlyPlaying = [];
4343
public ObservableCollection<PlayingClip> CurrentlyPlaying => _currentlyPlaying;
4444

4545
private readonly object queueLock = new();
46-
4746
private ObservableCollection<SoundClip> _queued = [];
4847
public ObservableCollection<SoundClip> Queued => _queued;
4948

49+
private readonly object streamsToFreeLock = new();
50+
private Collection<StreamToFree> _streamsToFree = [];
51+
5052

5153
private const long TIMER_MS = 200;
5254
private Timer timer = new(TIMER_MS)
@@ -56,30 +58,24 @@ class MSoundEngine : ISoundEngine
5658

5759
private void RefreshPlaybackProgressAndCheckQueue(object? sender, ElapsedEventArgs e)
5860
{
59-
lock (currentlyPlayingLock)
61+
foreach (var track in CurrentlyPlaying.ToArray())
6062
{
61-
List<PlayingClip> toRemove = [];
62-
foreach (var track in CurrentlyPlaying)
63-
{
64-
track.CurrentPos += TIMER_MS * 0.001d;
65-
if (track.ProgressPct == 1)
66-
{
67-
toRemove.Add(track);
68-
}
69-
}
63+
track.CurrentPos += TIMER_MS * 0.001d;
7064

71-
foreach (var track in toRemove)
65+
if (track.LoopClip)
7266
{
73-
if (track.LoopClip)
67+
if (track.ProgressPct == 1)
7468
{
7569
track.CurrentPos = 0;
7670
Bass.ChannelPlay(track.BassStreamId, true);
7771
}
78-
else
79-
{
80-
Bass.StreamFree(track.BassStreamId);
81-
CurrentlyPlaying.Remove(track);
82-
}
72+
}
73+
// might be off by 100ms, but oh well
74+
else if (track.CurrentPos + 0.1d >= track.Length - (track.FadeOutMilis * 0.001d))
75+
{
76+
var timeRemainingMilis = 1000 * (track.Length - track.CurrentPos - 0.1d);
77+
var fadeOutDuration = timeRemainingMilis < track.FadeOutMilis ? timeRemainingMilis : track.FadeOutMilis;
78+
StopPlaying(track.BassStreamId, track.RemainingMilis, track.FadeOutMilis);
8379
}
8480
}
8581
lock (queueLock)
@@ -91,11 +87,21 @@ private void RefreshPlaybackProgressAndCheckQueue(object? sender, ElapsedEventAr
9187
Queued.RemoveAt(0);
9288
}
9389
}
90+
lock (streamsToFreeLock)
91+
{
92+
var timeNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
93+
var streamsToFreeAndRemove = _streamsToFree.Where(it => timeNow >= it.freeAtUnixTime).ToArray();
94+
foreach (StreamToFree stream in streamsToFreeAndRemove)
95+
{
96+
StopPlaying(stream.bassStreamId, 0, 0);
97+
}
98+
_streamsToFree.RemoveMany(streamsToFreeAndRemove);
99+
}
94100
}
95101

96102
public const int SAMPLE_RATE = 44100;
97103

98-
private readonly object bass_lock = new object();
104+
private readonly object bass_lock = new();
99105

100106

101107
public List<string> OutputDeviceListWithoutGlobal
@@ -166,20 +172,14 @@ private void StopAndRemoveFromQueue(string id)
166172
{
167173
lock (queueLock)
168174
{
169-
var toRemove = Queued.Where(clip => clip.Id == id).ToList();
170-
foreach (var clip in toRemove)
175+
foreach (var clip in Queued.Where(clip => clip.Id == id).ToArray())
171176
{
172177
Queued.Remove(clip);
173178
}
174179
}
175-
lock (currentlyPlayingLock)
180+
foreach (var clip in CurrentlyPlaying.Where(clip => clip.SoundClipId == id).ToArray())
176181
{
177-
var toRemove = CurrentlyPlaying.Where(clip => clip.SoundClipId == id).ToList();
178-
foreach (var clip in toRemove)
179-
{
180-
Bass.StreamFree(clip.BassStreamId);
181-
CurrentlyPlaying.Remove(clip);
182-
}
182+
StopPlaying(clip.BassStreamId, clip.RemainingMilis ,clip.FadeOutMilis);
183183
}
184184
}
185185

@@ -218,11 +218,11 @@ public void Play(SoundClip source, bool fromQueue = false)
218218

219219
foreach (OutputSettings settings in source.OutputSettingsFromProfile)
220220
{
221-
Play(source.AudioFilePath, settings.Volume, source.Volume, settings.DeviceName, source.LoopClip, tempId, source.Name);
221+
Play(source.AudioFilePath, settings.Volume, source.Volume, settings.DeviceName, source.LoopClip, tempId, settings.FadeOutMilis, source.Name);
222222
}
223223
}
224224

225-
private void Play(string fileName, int volume, int volumeMultiplier, string playerDeviceName, bool loopClip, string soundClipId, string? name = null)
225+
private void Play(string fileName, int volume, int volumeMultiplier, string playerDeviceName, bool loopClip, string soundClipId, int fadeOutMilis, string? name = null)
226226
{
227227
double vol = (volume / 100.0) * (volumeMultiplier / 100.0);
228228

@@ -257,7 +257,15 @@ private void Play(string fileName, int volume, int volumeMultiplier, string play
257257
{
258258
var len = Bass.ChannelGetLength(stream, PositionFlags.Bytes);
259259
double length = Bass.ChannelBytes2Seconds(stream, len);
260-
PlayingClip track = new(string.IsNullOrEmpty(name) ? Path.GetFileNameWithoutExtension(fileName) ?? "" : name, soundClipId, playerDeviceName, stream, length, loopClip);
260+
PlayingClip track = new(
261+
string.IsNullOrEmpty(name) ? Path.GetFileNameWithoutExtension(fileName) ?? "" : name,
262+
soundClipId,
263+
playerDeviceName,
264+
stream,
265+
length,
266+
loopClip,
267+
fadeOutMilis
268+
);
261269

262270
lock (currentlyPlayingLock)
263271
{
@@ -310,27 +318,33 @@ public void Reset()
310318
{
311319
Queued.Clear();
312320
}
313-
lock (currentlyPlayingLock)
321+
foreach (var stream in CurrentlyPlaying.ToArray())
314322
{
315-
foreach (var stream in CurrentlyPlaying)
316-
{
317-
Bass.StreamFree(stream.BassStreamId);
318-
}
319-
320-
CurrentlyPlaying.Clear();
323+
var timeRemainingMilis = stream.RemainingMilis;
324+
var fadeOutDuration = timeRemainingMilis < stream.FadeOutMilis ? timeRemainingMilis : stream.FadeOutMilis;
325+
StopPlaying(stream.BassStreamId, timeRemainingMilis, (int)fadeOutDuration);
321326
}
322327
}
323328

324-
public void StopPlaying(int bassId)
329+
public void StopPlaying(int handle, double remainingMilis, int fadeOutMilis)
325330
{
326-
lock (currentlyPlayingLock)
331+
if (fadeOutMilis == 0)
327332
{
328-
PlayingClip? track = CurrentlyPlaying.FirstOrDefault(c => c.BassStreamId == bassId);
329-
if (track != null)
333+
Bass.StreamFree(handle);
334+
lock (currentlyPlayingLock)
330335
{
331-
CurrentlyPlaying.Remove(track);
336+
PlayingClip? track = CurrentlyPlaying.FirstOrDefault(c => c.BassStreamId == handle);
337+
if (track != null)
338+
{
339+
CurrentlyPlaying.Remove(track);
340+
}
332341
}
333-
Bass.StreamFree(bassId);
342+
}
343+
else
344+
{
345+
int remainingFadeOut = (int)(remainingMilis < fadeOutMilis ? remainingMilis : fadeOutMilis);
346+
Bass.ChannelSlideAttribute(handle, ChannelAttribute.Volume, 0, remainingFadeOut);
347+
_streamsToFree.Add(new StreamToFree(handle, DateTimeOffset.Now.ToUnixTimeMilliseconds() + remainingFadeOut));
334348
}
335349
}
336350

@@ -349,5 +363,17 @@ public void Dispose()
349363
Reset();
350364
Bass.Free();
351365
}
352-
}
366+
367+
private class StreamToFree
368+
{
369+
public int bassStreamId;
370+
public long freeAtUnixTime;
371+
372+
public StreamToFree(int bassStreamId, long freeAtUnixTime)
373+
{
374+
this.bassStreamId = bassStreamId;
375+
this.freeAtUnixTime = freeAtUnixTime;
376+
}
377+
}
378+
}
353379
}

0 commit comments

Comments
 (0)