Skip to content

Commit

Permalink
support change music play speed
Browse files Browse the repository at this point in the history
  • Loading branch information
MikiraSora committed Nov 1, 2024
1 parent 2213d6c commit 598c7ce
Show file tree
Hide file tree
Showing 16 changed files with 636 additions and 17 deletions.
6 changes: 6 additions & 0 deletions OngekiFumenEditor/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
<setting name="EnableSoundMultiPlay" serializeAs="String">
<value>True</value>
</setting>
<setting name="EnableVarspeed" serializeAs="String">
<value>False</value>
</setting>
<setting name="VarspeedReadDurationMs" serializeAs="String">
<value>50</value>
</setting>
</OngekiFumenEditor.Properties.AudioSetting>
<OngekiFumenEditor.Properties.OptionGeneratorToolsSetting>
<setting name="LastLoadedGameFolder" serializeAs="String">
Expand Down
Binary file added OngekiFumenEditor/Costura64/SoundTouch.dll
Binary file not shown.
8 changes: 3 additions & 5 deletions OngekiFumenEditor/FodyWeavers.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura IgnoreSatelliteAssemblies='false' CreateTemporaryAssemblies='true'>
<ExcludeAssemblies>

</ExcludeAssemblies>
</Costura>
<Costura IgnoreSatelliteAssemblies='false' CreateTemporaryAssemblies='true'>

</Costura>
</Weavers>
1 change: 1 addition & 0 deletions OngekiFumenEditor/Kernel/Audio/IAudioManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public partial interface IAudioManager : IDisposable
{
float SoundVolume { get; set; }
float MusicVolume { get; set; }
float MusicSpeed { get; set; }

Task<ISoundPlayer> LoadSoundAsync(string filePath);
Task<IAudioPlayer> LoadAudioAsync(string filePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NAudio.Wave.SampleProviders;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.Music;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.SoundTouch;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.Utils;
using OngekiFumenEditor.Kernel.Scheduler;
using OngekiFumenEditor.Utils;
Expand Down Expand Up @@ -112,34 +113,39 @@ public async Task Load(string audio_file, int targetSampleRate)
}
}

public void Seek(TimeSpan time, bool pause)
public void Seek(TimeSpan seekTime, bool pause)
{
time = MathUtils.Max(TimeSpan.FromMilliseconds(0), MathUtils.Min(time, Duration));
seekTime = MathUtils.Max(TimeSpan.FromMilliseconds(0), MathUtils.Min(seekTime, Duration));

audioFileReader.Seek((long)(audioFileReader.WaveFormat.AverageBytesPerSecond * time.TotalSeconds), SeekOrigin.Begin);
baseOffset = time;
audioFileReader.Seek((long)(audioFileReader.WaveFormat.AverageBytesPerSecond * seekTime.TotalSeconds), SeekOrigin.Begin);
//more accurate
baseOffset = audioFileReader.CurrentTime;

finishProvider.StartListen();

UpdatePropsManually();
if (!pause)
Play();
UpdatePropsManually();
}

public async void Play()
{
IsPlaying = true;
sw.Restart();
musicMixer.AddMixerInput(finishProvider);
UpdatePropsManually();
manager.Reposition();

await IoC.Get<ISchedulerManager>().AddScheduler(this);
}

private TimeSpan GetTime()
{
if (!IsPlaying)
return pauseTime;
var offset = TimeSpan.FromTicks(sw.ElapsedTicks);
var actualTime = offset + baseOffset;
var offset = TimeSpan.FromTicks(sw.ElapsedTicks) * manager.MusicSpeed;
var adjustedTime = offset + baseOffset - TimeSpan.FromMilliseconds(manager.SpeedCostDelayMs);
var actualTime = MathUtils.Max(TimeSpan.Zero, adjustedTime);
return actualTime;
}

Expand All @@ -149,6 +155,7 @@ public async void Stop()
musicMixer.RemoveMixerInput(finishProvider);
await IoC.Get<ISchedulerManager>().RemoveScheduler(this);
Seek(TimeSpan.FromMilliseconds(0), true);
UpdatePropsManually();
}

public async void Pause()
Expand Down
41 changes: 40 additions & 1 deletion OngekiFumenEditor/Kernel/Audio/NAudioImpl/NAudioManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using NAudio.Wave.SampleProviders;
using OngekiFumenEditor.Kernel.Audio.DefaultImp.Music;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.Sound;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.SoundTouch;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.Utils;
using OngekiFumenEditor.Properties;
using OngekiFumenEditor.Utils;
Expand All @@ -24,6 +25,7 @@ public class NAudioManager : PropertyChangedBase, IAudioManager
private HashSet<WeakReference<IAudioPlayer>> ownAudioPlayerRefs = new();
private bool enableSoundMultiPlay;
private int targetSampleRate;
private bool enableVarspeed;
private readonly IWavePlayer audioOutputDevice;

private readonly MixingSampleProvider masterMixer;
Expand All @@ -36,6 +38,11 @@ public class NAudioManager : PropertyChangedBase, IAudioManager
private readonly VolumeSampleProvider soundVolumeWrapper;
private readonly VolumeSampleProvider musicVolumeWrapper;

private VarispeedSampleProvider speedProvider;

public bool EnableVarspeed => speedProvider is not null;
public int SpeedCostDelayMs { get; init; }

public float SoundVolume
{
get => soundVolumeWrapper.Volume;
Expand All @@ -62,6 +69,21 @@ public float MusicVolume
}
}

public float MusicSpeed
{
get => EnableVarspeed ? speedProvider.PlaybackRate : 1;
set
{
if (EnableVarspeed)
{
//we can able to change speed when all player is not playing
if (!ownAudioPlayerRefs.Any(x => x.TryGetTarget(out var player) ? player.IsPlaying : false))
speedProvider.PlaybackRate = value;
}
NotifyOfPropertyChange(() => MusicSpeed);
}
}

public IEnumerable<(string fileExt, string extDesc)> SupportAudioFileExtensionList { get; } = new[] {
(".mp3","Audio File"),
(".wav","Audio File"),
Expand All @@ -73,10 +95,14 @@ public NAudioManager()
var audioOutputType = (AudioOutputType)AudioSetting.Default.AudioOutputType;
enableSoundMultiPlay = AudioSetting.Default.EnableSoundMultiPlay;
targetSampleRate = AudioSetting.Default.AudioSampleRate;
enableVarspeed = AudioSetting.Default.EnableVarspeed;
SpeedCostDelayMs = AudioSetting.Default.VarspeedReadDurationMs;

Log.LogDebug($"targetSampleRate: {targetSampleRate}");
Log.LogDebug($"audioOutputType: {audioOutputType}");
Log.LogDebug($"enableSoundMultiPlay: {enableSoundMultiPlay}");
Log.LogDebug($"enableVarspeed: {enableVarspeed}");
Log.LogDebug($"SpeedCostDelayMs: {SpeedCostDelayMs}");

try
{
Expand Down Expand Up @@ -112,7 +138,15 @@ public NAudioManager()
//setup music
musicMixer = new MixingSampleProvider(format);
musicMixer.ReadFully = true;
musicVolumeWrapper = new VolumeSampleProvider(musicMixer);
if (enableVarspeed)
{
speedProvider = new VarispeedSampleProvider(musicMixer, SpeedCostDelayMs, new SoundTouchProfile(true, false));
musicVolumeWrapper = new VolumeSampleProvider(speedProvider);
}
else
{
musicVolumeWrapper = new VolumeSampleProvider(musicMixer);
}
masterMixer.AddMixerInput(musicVolumeWrapper);
MusicVolume = AudioSetting.Default.MusicVolume;

Expand Down Expand Up @@ -252,5 +286,10 @@ public void StopLoopSound(ILoopHandle h)
//Log.LogDebug($"handle hashcode = {handle.GetHashCode()}");
RemoveSoundMixerInput(handle.Provider, true);
}

public void Reposition()
{
speedProvider?.Reposition();
}
}
}
139 changes: 139 additions & 0 deletions OngekiFumenEditor/Kernel/Audio/NAudioImpl/SoundTouch/SoundTouch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace OngekiFumenEditor.Kernel.Audio.NAudioImpl.SoundTouch
{
class SoundTouch : IDisposable
{
private IntPtr handle;
private string versionString;
public SoundTouch()
{
handle = SoundTouchInterop64.soundtouch_createInstance();
}

public string VersionString
{
get
{
if (versionString == null)
{
var s = new StringBuilder(100);
SoundTouchInterop64.soundtouch_getVersionString2(s, s.Capacity);
versionString = s.ToString();
}
return versionString;
}
}

public void SetPitchOctaves(float pitchOctaves)
{
SoundTouchInterop64.soundtouch_setPitchOctaves(handle, pitchOctaves);
}

public void SetSampleRate(int sampleRate)
{
SoundTouchInterop64.soundtouch_setSampleRate(handle, (uint)sampleRate);
}

public void SetChannels(int channels)
{
SoundTouchInterop64.soundtouch_setChannels(handle, (uint)channels);
}

private void DestroyInstance()
{
if (handle != IntPtr.Zero)
{
SoundTouchInterop64.soundtouch_destroyInstance(handle);
handle = IntPtr.Zero;
}
}

public void Dispose()
{
DestroyInstance();
GC.SuppressFinalize(this);
}

~SoundTouch()
{
DestroyInstance();
}

public void PutSamples(float[] samples, int numSamples)
{
SoundTouchInterop64.soundtouch_putSamples(handle, samples, numSamples);
}

public int ReceiveSamples(float[] outBuffer, int maxSamples)
{
return (int)SoundTouchInterop64.soundtouch_receiveSamples(handle, outBuffer, (uint)maxSamples);
}

public bool IsEmpty
{
get
{
return SoundTouchInterop64.soundtouch_isEmpty(handle) != 0;
}
}

public int NumberOfSamplesAvailable
{
get
{
return (int)SoundTouchInterop64.soundtouch_numSamples(handle);
}
}

public int NumberOfUnprocessedSamples
{
get
{
return SoundTouchInterop64.soundtouch_numUnprocessedSamples(handle);
}
}

public void Flush()
{
SoundTouchInterop64.soundtouch_flush(handle);
}

public void Clear()
{
SoundTouchInterop64.soundtouch_clear(handle);
}

public void SetRate(float newRate)
{
SoundTouchInterop64.soundtouch_setRate(handle, newRate);
}

public void SetTempo(float newTempo)
{
SoundTouchInterop64.soundtouch_setTempo(handle, newTempo);
}

public int GetUseAntiAliasing()
{
return SoundTouchInterop64.soundtouch_getSetting(handle, SoundTouchSettings.UseAaFilter);
}

public void SetUseAntiAliasing(bool useAntiAliasing)
{
SoundTouchInterop64.soundtouch_setSetting(handle, SoundTouchSettings.UseAaFilter, useAntiAliasing ? 1 : 0);
}

public void SetUseQuickSeek(bool useQuickSeek)
{
SoundTouchInterop64.soundtouch_setSetting(handle, SoundTouchSettings.UseQuickSeek, useQuickSeek ? 1 : 0);
}

public int GetUseQuickSeek()
{
return SoundTouchInterop64.soundtouch_getSetting(handle, SoundTouchSettings.UseQuickSeek);
}
}
}
Loading

0 comments on commit 598c7ce

Please sign in to comment.