diff --git a/OngekiFumenEditor/App.config b/OngekiFumenEditor/App.config
index b9a1ed79..168cc600 100644
--- a/OngekiFumenEditor/App.config
+++ b/OngekiFumenEditor/App.config
@@ -45,6 +45,12 @@
48000
+
+ 1
+
+
+ 1
+
diff --git a/OngekiFumenEditor/Kernel/Audio/IAudioManager.cs b/OngekiFumenEditor/Kernel/Audio/IAudioManager.cs
index 3f081b57..69afb484 100644
--- a/OngekiFumenEditor/Kernel/Audio/IAudioManager.cs
+++ b/OngekiFumenEditor/Kernel/Audio/IAudioManager.cs
@@ -7,8 +7,9 @@ namespace OngekiFumenEditor.Kernel.Audio
public partial interface IAudioManager : IDisposable
{
float SoundVolume { get; set; }
+ float MusicVolume { get; set; }
- Task LoadSoundAsync(string filePath);
+ Task LoadSoundAsync(string filePath);
Task LoadAudioAsync(string filePath);
IEnumerable<(string fileExt, string extDesc)> SupportAudioFileExtensionList { get; }
diff --git a/OngekiFumenEditor/Kernel/Audio/IAudioPlayer.cs b/OngekiFumenEditor/Kernel/Audio/IAudioPlayer.cs
index 1338471e..cb3023c7 100644
--- a/OngekiFumenEditor/Kernel/Audio/IAudioPlayer.cs
+++ b/OngekiFumenEditor/Kernel/Audio/IAudioPlayer.cs
@@ -16,15 +16,17 @@ public interface IAudioPlayer : IDisposable, INotifyPropertyChanged
///
float Speed { get; set; }
+ /* control by IAudioManager
///
/// 音量,0~1
///
float Volume { get; set; }
+ */
- ///
- /// 总长度,毫秒
- ///
- TimeSpan Duration { get; }
+ ///
+ /// 总长度,毫秒
+ ///
+ TimeSpan Duration { get; }
///
/// 是否正在播放
diff --git a/OngekiFumenEditor/Kernel/Audio/NAudioImpl/NAudioManager.cs b/OngekiFumenEditor/Kernel/Audio/NAudioImpl/NAudioManager.cs
index 87b30bf6..4d1f59c5 100644
--- a/OngekiFumenEditor/Kernel/Audio/NAudioImpl/NAudioManager.cs
+++ b/OngekiFumenEditor/Kernel/Audio/NAudioImpl/NAudioManager.cs
@@ -1,183 +1,211 @@
-using NAudio.CoreAudioApi;
+using Caliburn.Micro;
+using NAudio.CoreAudioApi;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using OngekiFumenEditor.Kernel.Audio.DefaultImp.Music;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.Sound;
using OngekiFumenEditor.Kernel.Audio.NAudioImpl.Utils;
+using OngekiFumenEditor.Properties;
using OngekiFumenEditor.Utils;
using System;
using System.Buffers;
using System.Collections.Generic;
+using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading.Tasks;
namespace OngekiFumenEditor.Kernel.Audio.NAudioImpl
{
- [Export(typeof(IAudioManager))]
- public class NAudioManager : IAudioManager
- {
- private HashSet> ownAudioPlayerRefs = new();
- private int targetSampleRate;
- private readonly IWavePlayer audioOutputDevice;
-
- private readonly MixingSampleProvider audioMixer;
- private readonly MixingSampleProvider soundMixer;
- private readonly MixingSampleProvider musicMixer;
-
- private readonly VolumeSampleProvider soundVolumeWrapper;
- private readonly VolumeSampleProvider musicVolumeWrapper;
-
- public float SoundVolume { get => soundVolumeWrapper.Volume; set => soundVolumeWrapper.Volume = value; }
- public float MusicVolume { get => musicVolumeWrapper.Volume; set => musicVolumeWrapper.Volume = value; }
-
- public IEnumerable<(string fileExt, string extDesc)> SupportAudioFileExtensionList { get; } = new[] {
- (".mp3","Audio File"),
- (".wav","Audio File"),
- (".acb","Criware Audio File"),
- };
-
- public NAudioManager()
- {
- var audioOutputType = (AudioOutputType)Properties.AudioSetting.Default.AudioOutputType;
- targetSampleRate = Properties.AudioSetting.Default.AudioSampleRate;
- Log.LogDebug($"targetSampleRate: {targetSampleRate}");
- Log.LogDebug($"audioOutputType: {audioOutputType}");
-
- try
- {
- audioOutputDevice = audioOutputType switch
- {
- AudioOutputType.Asio => new AsioOut() { AutoStop = false },
- AudioOutputType.Wasapi => new WasapiOut(AudioClientShareMode.Shared, 0),
- AudioOutputType.WaveOut or _ => new WaveOut() { DesiredLatency = 100 },
- };
- }
- catch (Exception e)
- {
- Log.LogError($"Can't create audio output device:{audioOutputType}", e);
- throw;
- }
- Log.LogDebug($"audioOutputDevice: {audioOutputDevice}");
-
- var format = WaveFormat.CreateIeeeFloatWaveFormat(targetSampleRate, 2);
-
- audioMixer = new MixingSampleProvider(format);
- audioMixer.ReadFully = true;
- audioOutputDevice.Init(audioMixer);
- audioOutputDevice.Play();
-
- //setup sound
- soundMixer = new MixingSampleProvider(format);
- soundMixer.ReadFully = true;
- soundVolumeWrapper = new VolumeSampleProvider(soundMixer);
- audioMixer.AddMixerInput(soundVolumeWrapper);
-
- //setup sound
- musicMixer = new MixingSampleProvider(format);
- musicMixer.ReadFully = true;
- musicVolumeWrapper = new VolumeSampleProvider(musicMixer);
- audioMixer.AddMixerInput(musicVolumeWrapper);
-
- Log.LogInfo($"Audio implement will use {GetType()}");
- }
-
- public void PlaySound(CachedSound sound, float volume, TimeSpan init)
- {
- ISampleProvider provider = new VolumeSampleProvider(new CachedSoundSampleProvider(sound))
- {
- Volume = volume
- };
- if (init.TotalMilliseconds != 0)
- {
- provider = new OffsetSampleProvider(provider)
- {
- SkipOver = init
- };
- }
-
- AddSoundMixerInput(provider);
- }
-
- public void AddSoundMixerInput(ISampleProvider input)
- {
- soundMixer.AddMixerInput(input);
- }
-
- public void RemoveSoundMixerInput(ISampleProvider input)
- {
- soundMixer.RemoveMixerInput(input);
- }
-
- public async Task LoadAudioAsync(string filePath)
- {
- if (string.IsNullOrWhiteSpace(filePath))
- return null;
-
- if (filePath.EndsWith(".acb"))
- {
- filePath = await AcbConverter.ConvertAcbFileToWavFile(filePath);
- if (filePath is null)
- return null;
- }
-
- var player = new DefaultMusicPlayer(musicMixer);
- ownAudioPlayerRefs.Add(new WeakReference(player));
- await player.Load(filePath, targetSampleRate);
- return player;
- }
-
- public async Task LoadSoundAsync(string filePath)
- {
- using var audioFileReader = new AudioFileReader(filePath);
- Log.LogInfo($"Load sound file: {filePath}");
-
- var provider = await AudioCompatibilizer.CheckCompatible(audioFileReader, targetSampleRate);
-
- return new NAudioSoundPlayer(new CachedSound(provider), this);
- }
-
- public void Dispose()
- {
- Log.LogDebug("call DefaultAudioManager.Dispose()");
- foreach (var weakRef in ownAudioPlayerRefs)
- {
- if (weakRef.TryGetTarget(out var player))
- player?.Dispose();
- }
- ownAudioPlayerRefs.Clear();
- audioOutputDevice?.Dispose();
- }
-
- public ILoopHandle PlayLoopSound(CachedSound sound, float volume, TimeSpan init)
- {
- ISampleProvider provider = new LoopableProvider(new CachedSoundSampleProvider(sound));
-
- if (init.TotalMilliseconds != 0)
- {
- provider = new OffsetSampleProvider(provider)
- {
- SkipOver = init
- };
- }
-
- var handle = new NAudioLoopHandle(new VolumeSampleProvider(provider));
- handle.Volume = volume;
-
- //add to mixer
- AddSoundMixerInput(handle.Provider);
-
- //Log.LogDebug($"handle hashcode = {handle.GetHashCode()}");
- return handle;
- }
-
- public void StopLoopSound(ILoopHandle h)
- {
- if (h is not NAudioLoopHandle handle)
- return;
-
- //Log.LogDebug($"handle hashcode = {handle.GetHashCode()}");
- RemoveSoundMixerInput(handle.Provider);
- }
- }
+ [Export(typeof(IAudioManager))]
+ public class NAudioManager : PropertyChangedBase, IAudioManager
+ {
+ private HashSet> ownAudioPlayerRefs = new();
+ private int targetSampleRate;
+ private readonly IWavePlayer audioOutputDevice;
+
+ private readonly MixingSampleProvider audioMixer;
+ private readonly MixingSampleProvider soundMixer;
+ private readonly MixingSampleProvider musicMixer;
+
+ private readonly VolumeSampleProvider soundVolumeWrapper;
+ private readonly VolumeSampleProvider musicVolumeWrapper;
+
+ public float SoundVolume
+ {
+ get => soundVolumeWrapper.Volume;
+ set
+ {
+ soundVolumeWrapper.Volume = value;
+
+ AudioSetting.Default.SoundVolume = value;
+ AudioSetting.Default.Save();
+ NotifyOfPropertyChange(() => SoundVolume);
+ }
+ }
+
+ public float MusicVolume
+ {
+ get => musicVolumeWrapper.Volume;
+ set
+ {
+ musicVolumeWrapper.Volume = value;
+
+ AudioSetting.Default.MusicVolume = value;
+ AudioSetting.Default.Save();
+ NotifyOfPropertyChange(() => MusicVolume);
+ }
+ }
+
+ public IEnumerable<(string fileExt, string extDesc)> SupportAudioFileExtensionList { get; } = new[] {
+ (".mp3","Audio File"),
+ (".wav","Audio File"),
+ (".acb","Criware Audio File"),
+ };
+
+ public NAudioManager()
+ {
+ var audioOutputType = (AudioOutputType)Properties.AudioSetting.Default.AudioOutputType;
+ targetSampleRate = Properties.AudioSetting.Default.AudioSampleRate;
+ Log.LogDebug($"targetSampleRate: {targetSampleRate}");
+ Log.LogDebug($"audioOutputType: {audioOutputType}");
+
+ try
+ {
+ audioOutputDevice = audioOutputType switch
+ {
+ AudioOutputType.Asio => new AsioOut() { AutoStop = false },
+ AudioOutputType.Wasapi => new WasapiOut(AudioClientShareMode.Shared, 0),
+ AudioOutputType.WaveOut or _ => new WaveOut() { DesiredLatency = 100 },
+ };
+ }
+ catch (Exception e)
+ {
+ Log.LogError($"Can't create audio output device:{audioOutputType}", e);
+ throw;
+ }
+ Log.LogDebug($"audioOutputDevice: {audioOutputDevice}");
+
+ var format = WaveFormat.CreateIeeeFloatWaveFormat(targetSampleRate, 2);
+
+ audioMixer = new MixingSampleProvider(format);
+ audioMixer.ReadFully = true;
+ audioOutputDevice.Init(audioMixer);
+ audioOutputDevice.Play();
+
+ //setup sound
+ soundMixer = new MixingSampleProvider(format);
+ soundMixer.ReadFully = true;
+ soundVolumeWrapper = new VolumeSampleProvider(soundMixer);
+ audioMixer.AddMixerInput(soundVolumeWrapper);
+ SoundVolume = AudioSetting.Default.SoundVolume;
+
+ //setup music
+ musicMixer = new MixingSampleProvider(format);
+ musicMixer.ReadFully = true;
+ musicVolumeWrapper = new VolumeSampleProvider(musicMixer);
+ audioMixer.AddMixerInput(musicVolumeWrapper);
+ MusicVolume = AudioSetting.Default.MusicVolume;
+
+ Log.LogInfo($"Audio implement will use {GetType()}");
+ }
+
+ public void PlaySound(CachedSound sound, float volume, TimeSpan init)
+ {
+ ISampleProvider provider = new VolumeSampleProvider(new CachedSoundSampleProvider(sound))
+ {
+ Volume = volume
+ };
+ if (init.TotalMilliseconds != 0)
+ {
+ provider = new OffsetSampleProvider(provider)
+ {
+ SkipOver = init
+ };
+ }
+
+ AddSoundMixerInput(provider);
+ }
+
+ public void AddSoundMixerInput(ISampleProvider input)
+ {
+ soundMixer.AddMixerInput(input);
+ }
+
+ public void RemoveSoundMixerInput(ISampleProvider input)
+ {
+ soundMixer.RemoveMixerInput(input);
+ }
+
+ public async Task LoadAudioAsync(string filePath)
+ {
+ if (string.IsNullOrWhiteSpace(filePath))
+ return null;
+
+ if (filePath.EndsWith(".acb"))
+ {
+ filePath = await AcbConverter.ConvertAcbFileToWavFile(filePath);
+ if (filePath is null)
+ return null;
+ }
+
+ var player = new DefaultMusicPlayer(musicMixer);
+ ownAudioPlayerRefs.Add(new WeakReference(player));
+ await player.Load(filePath, targetSampleRate);
+ return player;
+ }
+
+ public async Task LoadSoundAsync(string filePath)
+ {
+ using var audioFileReader = new AudioFileReader(filePath);
+ Log.LogInfo($"Load sound file: {filePath}");
+
+ var provider = await AudioCompatibilizer.CheckCompatible(audioFileReader, targetSampleRate);
+
+ return new NAudioSoundPlayer(new CachedSound(provider), this);
+ }
+
+ public void Dispose()
+ {
+ Log.LogDebug("call DefaultAudioManager.Dispose()");
+ foreach (var weakRef in ownAudioPlayerRefs)
+ {
+ if (weakRef.TryGetTarget(out var player))
+ player?.Dispose();
+ }
+ ownAudioPlayerRefs.Clear();
+ audioOutputDevice?.Dispose();
+ }
+
+ public ILoopHandle PlayLoopSound(CachedSound sound, float volume, TimeSpan init)
+ {
+ ISampleProvider provider = new LoopableProvider(new CachedSoundSampleProvider(sound));
+
+ if (init.TotalMilliseconds != 0)
+ {
+ provider = new OffsetSampleProvider(provider)
+ {
+ SkipOver = init
+ };
+ }
+
+ var handle = new NAudioLoopHandle(new VolumeSampleProvider(provider));
+ handle.Volume = volume;
+
+ //add to mixer
+ AddSoundMixerInput(handle.Provider);
+
+ //Log.LogDebug($"handle hashcode = {handle.GetHashCode()}");
+ return handle;
+ }
+
+ public void StopLoopSound(ILoopHandle h)
+ {
+ if (h is not NAudioLoopHandle handle)
+ return;
+
+ //Log.LogDebug($"handle hashcode = {handle.GetHashCode()}");
+ RemoveSoundMixerInput(handle.Provider);
+ }
+ }
}
diff --git a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs
index 7df4d38a..b0ab2eb0 100644
--- a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs
+++ b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs
@@ -113,7 +113,7 @@ public IFumenSoundPlayer FumenSoundPlayer
public bool[] SoundControls { get; set; } = new bool[Enum.GetValues().Length];
- public SoundVolumeProxy[] SoundVolumes { get; set; } = new SoundVolumeProxy[0];
+ public SoundVolumeProxy[] SoundVolumes { get; set; } = [];
public float SoundVolume
{
@@ -125,6 +125,16 @@ public float SoundVolume
}
}
+ public float MusicVolume
+ {
+ get => IoC.Get().MusicVolume;
+ set
+ {
+ IoC.Get().MusicVolume = value;
+ NotifyOfPropertyChange(() => MusicVolume);
+ }
+ }
+
public bool IsAudioButtonEnabled => AudioPlayer is not null;
public AudioPlayerToolViewerViewModel()
diff --git a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml
index 4d0deae2..b55d287a 100644
--- a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml
+++ b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml
@@ -286,7 +286,7 @@
LargeChange="0.2"
Maximum="1"
SmallChange="0.2"
- Value="{Binding AudioPlayer.Volume}">
+ Value="{Binding MusicVolume}">
48000
+
+ 1
+
+
+ 1
+
\ No newline at end of file
diff --git a/OngekiFumenEditor/Properties/Resources.zh-Hans.resx b/OngekiFumenEditor/Properties/Resources.zh-Hans.resx
index d60aa0e7..81a94e07 100644
--- a/OngekiFumenEditor/Properties/Resources.zh-Hans.resx
+++ b/OngekiFumenEditor/Properties/Resources.zh-Hans.resx
@@ -1665,4 +1665,10 @@
保存
+
+ 生成标准音击谱面
+
+
+ 无法标准化音击谱面
+
\ No newline at end of file