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