Skip to content

Commit

Permalink
Waveform rendering!
Browse files Browse the repository at this point in the history
 - Refactored LoopingSampleProvider
 - Started stubbing the MediaCacheManager
 - Added new waveform renderer view model
 - Added peak file reader/writer (it's REALLY fast!)
 - Added new waveform control
 - Updated views to support the waveform control
 - Added waveform popup window to appreciate the pretty waveforms up close ;-)
 - Other small changes
  • Loading branch information
space928 committed Mar 28, 2024
1 parent 7143bba commit b78c98b
Show file tree
Hide file tree
Showing 12 changed files with 1,036 additions and 59 deletions.
2 changes: 1 addition & 1 deletion QPlayer/QPlayer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<Title>QPlayer</Title>
<Version>1.4.1-beta</Version>
<Version>1.5.1-beta</Version>
<Authors>Thomas Mathieson</Authors>
<Company>Thomas Mathieson</Company>
<Copyright>©️ Thomas Mathieson 2024</Copyright>
Expand Down
55 changes: 0 additions & 55 deletions QPlayer/ViewModels/AudioPlaybackManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,59 +242,4 @@ public void StopAllSounds()
activeChannels.Clear();
}
}

public class LoopingSampleProvider<T> : WaveStream, ISampleProvider where T : WaveStream, ISampleProvider
{
private readonly T input;
private readonly bool infinite;
private readonly int loops;
private long playedLoops = 0;

public LoopingSampleProvider(T input, bool infinite = true, int loops = 1)
{
this.input = input;
this.infinite = infinite;
this.loops = loops;
}

public override WaveFormat WaveFormat => input.WaveFormat;

public override long Length => infinite ? long.MaxValue/32 : input.Length * loops;

public override long Position
{
get => input.Position;
set
{
input.Position = value % input.Length;
playedLoops = value / input.Length;
}
}

public int Read(float[] buffer, int offset, int count)
{
throw new NotImplementedException();
}

public override int Read(byte[] buffer, int offset, int count)
{
int samplesRead = 0;
int readOffset = offset;
while (samplesRead < count)
{
int read = input.Read(buffer, readOffset, count - samplesRead);
readOffset += read;
if (read == 0)
{
input.Position = 0;
readOffset = 0;
playedLoops++;

if (!infinite && playedLoops == loops)
break;
}
}
return samplesRead;
}
}
}
76 changes: 76 additions & 0 deletions QPlayer/ViewModels/LoopingSampleProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using NAudio.Wave;
using System;

namespace QPlayer.ViewModels
{
public class LoopingSampleProvider<T> : WaveStream, ISampleProvider where T : WaveStream, ISampleProvider
{
private readonly T input;
private readonly bool infinite;
private readonly int loops;
private long playedLoops = 0;

public LoopingSampleProvider(T input, bool infinite = true, int loops = 1)
{
this.input = input;
this.infinite = infinite;
this.loops = loops;
}

public override WaveFormat WaveFormat => input.WaveFormat;

public override long Length => infinite ? long.MaxValue/32 : input.Length * loops;

public override long Position
{
get => input.Position;
set
{
input.Position = value % input.Length;
playedLoops = value / input.Length;
}
}

public int Read(float[] buffer, int offset, int count)
{
int samplesRead = 0;
int readOffset = offset;
while (samplesRead < count)
{
int read = input.Read(buffer, readOffset, count - samplesRead);
readOffset += read;
if (read == 0)
{
input.Position = 0;
readOffset = 0;
playedLoops++;

if (!infinite && playedLoops == loops)
break;
}
}
return samplesRead;
}

public override int Read(byte[] buffer, int offset, int count)
{
int samplesRead = 0;
int readOffset = offset;
while (samplesRead < count)
{
int read = input.Read(buffer, readOffset, count - samplesRead);
readOffset += read;
if (read == 0)
{
input.Position = 0;
readOffset = 0;
playedLoops++;

if (!infinite && playedLoops == loops)
break;
}
}
return samplesRead;
}
}
}
35 changes: 35 additions & 0 deletions QPlayer/ViewModels/MediaCacheManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QPlayer.ViewModels
{
internal class MediaCacheManager
{
private ConcurrentDictionary<string, CachedMediaStream> cacheDict;

public MediaCacheManager()
{
cacheDict = new();
}
}

internal class CachedMediaStream : MemoryStream, IDisposable
{
protected int references = 0;

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}

void IDisposable.Dispose()
{

}
}
}
20 changes: 17 additions & 3 deletions QPlayer/ViewModels/SoundCueViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Media;
using Cue = QPlayer.Models.Cue;

namespace QPlayer.ViewModels
Expand All @@ -18,12 +20,12 @@ public class SoundCueViewModel : CueViewModel, IConvertibleModel<Cue, CueViewMod
[Reactive] public TimeSpan StartTime { get; set; }
[Reactive] public TimeSpan PlaybackDuration { get; set; } = TimeSpan.Zero;
[Reactive] public override TimeSpan Duration => PlaybackDuration == TimeSpan.Zero ? (audioFile?.TotalTime ?? TimeSpan.Zero) - StartTime : PlaybackDuration;
[Reactive] public override TimeSpan PlaybackTime
{
[Reactive] public override TimeSpan PlaybackTime
{
get => IsAudioFileValid ? audioFile.CurrentTime - StartTime : TimeSpan.Zero;
set
{
if(IsAudioFileValid)
if (IsAudioFileValid)
audioFile.CurrentTime = value + StartTime;
}
}
Expand All @@ -33,10 +35,13 @@ [Reactive] public override TimeSpan PlaybackTime
[Reactive] public FadeType FadeType { get; set; }
[Reactive] public RelayCommand OpenMediaFileCommand { get; private set; }

[Reactive] public WaveFormRenderer WaveForm => waveFormRenderer;

private AudioFileReader? audioFile;
private FadingSampleProvider? fadeInOutProvider;
private readonly Timer audioProgressUpdater;
private readonly Timer fadeOutTimer;
private readonly WaveFormRenderer waveFormRenderer;

public SoundCueViewModel(MainViewModel mainViewModel) : base(mainViewModel)
{
Expand Down Expand Up @@ -67,6 +72,7 @@ public SoundCueViewModel(MainViewModel mainViewModel) : base(mainViewModel)
break;
}
};
waveFormRenderer = new WaveFormRenderer();
}

/// <summary>
Expand Down Expand Up @@ -294,6 +300,14 @@ private void LoadAudioFile()
OnPropertyChanged(nameof(PlaybackTimeStringShort));
// audioFile.Volume = Volume;
fadeInOutProvider = new(audioFile, true);

Task.Run(async () =>
{
return await PeakFileWriter.LoadOrGeneratePeakFile(path);
}).ContinueWith(x =>
{
waveFormRenderer.PeakFile = x.Result;
});
} catch (Exception ex)
{
MainViewModel.Log($"Error while loading audio file ({path}): \n" + ex, MainViewModel.LogLevel.Error);
Expand Down
5 changes: 5 additions & 0 deletions QPlayer/ViewModels/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ public void DeleteCueExecute()

public static void Log(object message, LogLevel level = LogLevel.Info, [CallerMemberName] string caller = "")
{
#if !DEBUG
if (level <= LogLevel.Debug)
return;
#endif

lock (logListLock)
{
string msg = $"[{level}] [{DateTime.Now}] [{caller}] {message}";
Expand Down
Loading

0 comments on commit b78c98b

Please sign in to comment.