diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4669c32 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/SharpAL"] + path = external/SharpAL + url = https://github.com/OpenCGSS/SharpAL.git diff --git a/Apps/ScoreViewer/AudioManager.cs b/Apps/ScoreViewer/AudioManager.cs new file mode 100644 index 0000000..8b80835 --- /dev/null +++ b/Apps/ScoreViewer/AudioManager.cs @@ -0,0 +1,38 @@ +using DereTore.Common; +using NAudio.Wave; +using SharpAL; + +namespace DereTore.Apps.ScoreViewer { + public sealed class AudioManager : DisposableBase { + + public AudioManager() { + _audioDevice = new AudioDevice(); + _audioContext = new AudioContext(_audioDevice); + } + + public AudioDevice AudioDevice => _audioDevice; + + public AudioContext AudioContext => _audioContext; + + public static readonly WaveFormat StandardFormat = new WaveFormat(); + + public static bool NeedsConversion(WaveFormat test, WaveFormat standard) { + return test.SampleRate != standard.SampleRate || + test.BitsPerSample != standard.BitsPerSample || + test.Channels != standard.Channels || + test.Encoding != standard.Encoding; + } + + protected override void Dispose(bool disposing) { + _audioContext?.Dispose(); + _audioDevice?.Dispose(); + + _audioContext = null; + _audioDevice = null; + } + + private AudioDevice _audioDevice; + private AudioContext _audioContext; + + } +} diff --git a/Apps/ScoreViewer/DereTore.Apps.ScoreViewer.csproj b/Apps/ScoreViewer/DereTore.Apps.ScoreViewer.csproj index 15cd371..7c6e1b8 100644 --- a/Apps/ScoreViewer/DereTore.Apps.ScoreViewer.csproj +++ b/Apps/ScoreViewer/DereTore.Apps.ScoreViewer.csproj @@ -60,9 +60,11 @@ 6 + Component + @@ -160,6 +162,10 @@ {EECF4BAA-9C9E-4687-A616-0F5C65C5F14B} DereTore.Exchange.Archive.ACB + + {7e5e07e6-4300-438e-bd37-c9f6e5e14a41} + SharpAL + {3a0d1281-a503-4e5d-9765-d7bf56f89266} DereTore.Interop.OS diff --git a/Apps/ScoreViewer/Extensions/StreamExtensions.cs b/Apps/ScoreViewer/Extensions/StreamExtensions.cs new file mode 100644 index 0000000..a9d2e05 --- /dev/null +++ b/Apps/ScoreViewer/Extensions/StreamExtensions.cs @@ -0,0 +1,38 @@ +using System.IO; + +namespace DereTore.Apps.ScoreViewer.Extensions { + public static class StreamExtensions { + + public static byte[] ReadToEnd(this Stream stream) { + return ReadToEnd(stream, 102400); + } + + public static byte[] ReadToEnd(this Stream stream, int bufferSize) { + byte[] data; + var buffer = new byte[bufferSize]; + var length = stream.Length; + + using (var memoryStream = new MemoryStream()) { + var read = 1; + long totalRead = 0; + + while (read > 0) { + read = stream.Read(buffer, 0, bufferSize); + memoryStream.Write(buffer, 0, read); + + totalRead += read; + + // totalread >= length: for WaveOffsetStream + if (read < bufferSize || totalRead >= length) { + break; + } + } + + data = memoryStream.ToArray(); + } + + return data; + } + + } +} diff --git a/Apps/ScoreViewer/Forms/FViewer.EventHandlers.cs b/Apps/ScoreViewer/Forms/FViewer.EventHandlers.cs index 5950d7c..065e745 100644 --- a/Apps/ScoreViewer/Forms/FViewer.EventHandlers.cs +++ b/Apps/ScoreViewer/Forms/FViewer.EventHandlers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Windows.Forms; using DereTore.Apps.ScoreViewer.Controls; @@ -165,9 +165,9 @@ public void UpdateSfx(TimeSpan rawMusicTime) { foreach (var note in _score.Notes) { if (!(note.HitTiming < prev) && (note.HitTiming < now)) { if (note.IsFlick) { - _sfxManager.PlayWave(_currentFlickHcaFileName, TimeSpan.FromSeconds(note.HitTiming), PlayerSettings.SfxVolume); + _sfxManager.PlayWave(_currentFlickHcaFileName, PlayerSettings.SfxVolume); } else if (note.IsTap || note.IsHold || note.IsSlide) { - _sfxManager.PlayWave(_currentTapHcaFileName, TimeSpan.FromSeconds(note.HitTiming), PlayerSettings.SfxVolume); + _sfxManager.PlayWave(_currentTapHcaFileName, PlayerSettings.SfxVolume); } } } @@ -220,6 +220,8 @@ private void BtnScoreUnload_Click(object sender, EventArgs e) { _scorePlayer.Dispose(); _scorePlayer = null; } + _audioManager?.Dispose(); + _audioManager = null; _musicWaveStream?.Dispose(); _musicWaveStream = null; if (_audioFileStream != null) { @@ -256,10 +258,12 @@ private void BtnScoreLoad_Click(object sender, EventArgs e) { } else { throw new ArgumentOutOfRangeException(nameof(audioFileExtension), $"Unsupported audio format: '{audioFileExtension}'."); } - _scorePlayer = new ScorePlayer(); + _audioManager = new AudioManager(); + _scorePlayer = new ScorePlayer(_audioManager); _scorePlayer.PlaybackStopped += MusicPlayer_PlaybackStopped; - _scorePlayer.AddInputStream(_musicWaveStream, PlayerSettings.MusicVolume); - _sfxManager = new SfxManager(_scorePlayer); + _scorePlayer.LoadStream(_musicWaveStream); + //_scorePlayer.AddInputStream(_musicWaveStream, PlayerSettings.MusicVolume); + _sfxManager = new SfxManager(_audioManager); PreloadNoteSounds(); _sfxBufferTime = 0d; _scorePlayer.PositionChanged += MusicPlayer_PositionChanged; @@ -283,7 +287,7 @@ private void BtnScoreLoad_Click(object sender, EventArgs e) { timer.Start(); } - private void MusicPlayer_PlaybackStopped(object sender, NAudio.Wave.StoppedEventArgs e) { + private void MusicPlayer_PlaybackStopped(object sender, EventArgs e) { BtnStop_Click(this, EventArgs.Empty); } diff --git a/Apps/ScoreViewer/Forms/FViewer.cs b/Apps/ScoreViewer/Forms/FViewer.cs index ebd0392..a909fc0 100644 --- a/Apps/ScoreViewer/Forms/FViewer.cs +++ b/Apps/ScoreViewer/Forms/FViewer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Windows.Forms; @@ -90,10 +90,8 @@ private void PreloadNoteSounds() { for (var i = 0; i < sfxTypeCount; ++i) { var sfxDirName = string.Format(SoundEffectAudioDirectoryNameFormat, i.ToString("00")); foreach (var waveAudioName in new[] { TapHcaName, FlickHcaName }) { - var key = $"{sfxDirName}/{waveAudioName}"; - using (var dataStream = File.Open(key, FileMode.Open, FileAccess.Read)) { - _sfxManager.PreloadWave(dataStream, key); - } + var fileName = $"{sfxDirName}/{waveAudioName}"; + _sfxManager.PreloadWave(fileName); } } } @@ -182,6 +180,7 @@ private void SetControlsEnabled(ViewerState state) { private readonly Timer timer = new Timer(5); private uint _lastRedrawTime; + private AudioManager _audioManager; private ScorePlayer _scorePlayer; private LiveMusicWaveStream _musicWaveStream; private SfxManager _sfxManager; diff --git a/Apps/ScoreViewer/PlayerSettings.cs b/Apps/ScoreViewer/PlayerSettings.cs index 2e9d094..c8c7428 100644 --- a/Apps/ScoreViewer/PlayerSettings.cs +++ b/Apps/ScoreViewer/PlayerSettings.cs @@ -1,4 +1,4 @@ -using System; +using System; using DereTore.Common; namespace DereTore.Apps.ScoreViewer { @@ -27,10 +27,10 @@ public static float SfxVolume { } // Compensates for systematic offset of official scores, which turns out to be very close to zero - public static TimeSpan GlobalOffset { get; set; } = TimeSpan.Zero; + public static TimeSpan GlobalOffset { get; set; } = TimeSpan.FromSeconds(0.036); // Compensates for a future ~3ms (128 samples) HCA encoder delay on WAV music files - public static TimeSpan MusicFileOffset { get; set; } = TimeSpan.Zero; + public static TimeSpan MusicFileOffset { get; set; } = TimeSpan.FromSeconds(0.003); // Total offset public static TimeSpan SfxOffset { diff --git a/Apps/ScoreViewer/ScorePlayer.cs b/Apps/ScoreViewer/ScorePlayer.cs index 07651c8..409e602 100644 --- a/Apps/ScoreViewer/ScorePlayer.cs +++ b/Apps/ScoreViewer/ScorePlayer.cs @@ -1,34 +1,27 @@ -using System; -using System.Collections.Generic; +using System; +using System.Timers; +using DereTore.Apps.ScoreViewer.Extensions; using DereTore.Common; -using NAudio.CoreAudioApi; using NAudio.Wave; -using AudioOut = NAudio.Wave.WasapiOut; +using SharpAL; +using SharpAL.Extensions; +using SharpAL.OpenAL; namespace DereTore.Apps.ScoreViewer { public sealed class ScorePlayer : DisposableBase { - public ScorePlayer() { + public ScorePlayer(AudioManager audioManager) { + _audioManager = audioManager; _syncObject = new object(); - _waveStream = new WaveMixerStream32(); - _soundPlayer = new AudioOut(AudioClientShareMode.Shared, 60); - _soundPlayer.Init(_waveStream); - _channels = new Dictionary(); + _audioSource = new AudioSource(audioManager.AudioContext); + _audioBuffer = new AudioBuffer(audioManager.AudioContext); PlayerSettings.MusicVolumeChanged += OnMusicVolumeChanged; + _timer = new Timer(15); + _timer.Start(); + _timer.Elapsed += Timer_Tick; } - public event EventHandler PlaybackStopped { - add { - if (_soundPlayer != null) { - _soundPlayer.PlaybackStopped += value; - } - } - remove { - if (_soundPlayer != null) { - _soundPlayer.PlaybackStopped -= value; - } - } - } + public event EventHandler PlaybackStopped; public event EventHandler PositionChanged; @@ -40,12 +33,12 @@ public void Play() { Stop(); } } - _soundPlayer?.Play(); + _audioSource?.Play(); IsPlaying = true; } public void Stop() { - _soundPlayer?.Stop(); + _audioSource?.Stop(); IsPlaying = false; } @@ -53,32 +46,27 @@ public void Pause() { if (!IsPlaying || IsPaused) { return; } - _soundPlayer?.Pause(); + _audioSource?.Pause(); IsPaused = true; } public TimeSpan CurrentTime { - get { return _waveStream.CurrentTime; } + get { return _audioSource.CurrentTime; } set { + var cur = _audioSource.CurrentTime; + + if (cur == value) { + return; + } + lock (_syncObject) { - var waveStream = _waveStream; - waveStream.CurrentTime = value; - var position = waveStream.Position; - var blockAlign = waveStream.BlockAlign; - if (position % blockAlign != 0) { - position = (long)(Math.Round(position / (double)blockAlign) * blockAlign); - if (position < 0) { - position = 0; - } - waveStream.Position = position; - } + _audioSource.CurrentTime = value; } + PositionChanged?.Invoke(this, EventArgs.Empty); } } - public TimeSpan TotalTime => _waveStream.TotalTime; - public bool IsPlaying { get { lock (_syncObject) { @@ -105,63 +93,65 @@ private set { } } - public WaveChannel32 AddInputStream(WaveStream waveStream, float volume = 1f) { - lock (_syncObject) { - var rateConvertedStream = waveStream; - if (NeedSampleRateConversion(waveStream.WaveFormat)) { - rateConvertedStream = new ResamplerDmoStream(waveStream, _waveStream.WaveFormat); - } - var addedStream = new WaveChannel32(rateConvertedStream, volume, 0f); - if (_channels.Count == 0 && _musicChannel == null) { - // The first stream is always the music stream. - _musicChannel = addedStream; - } else { - if (!_channels.ContainsKey(waveStream)) { - _channels.Add(waveStream, addedStream); - } - } - _waveStream.AddInputStream(addedStream); - return addedStream; - } - } + public void LoadStream(WaveStream stream) { + WaveStream transformedStream; - public void RemoveInputStream(WaveStream waveStream) { - lock (_syncObject) { - if (_channels.ContainsKey(waveStream)) { - _channels.Remove(waveStream); - } - _waveStream.RemoveInputStream(waveStream); + if (AudioManager.NeedsConversion(stream.WaveFormat, AudioManager.StandardFormat)) { + transformedStream = new WaveFormatConversionStream(AudioManager.StandardFormat, stream); + } else { + transformedStream = stream; } + + var allData = transformedStream.ReadToEnd(); + _audioBuffer.BufferData(allData, stream.WaveFormat.SampleRate); + _audioSource.Bind(_audioBuffer); + + //if (transformedStream != stream) { + // transformedStream.Dispose(); + //} } protected override void Dispose(bool disposing) { PlayerSettings.MusicVolumeChanged -= OnMusicVolumeChanged; - _soundPlayer?.Stop(); - _soundPlayer?.Dispose(); - _waveStream?.Dispose(); - _soundPlayer = null; - _waveStream = null; - _soundPlayer = null; + _timer.Stop(); + + Stop(); + + _audioSource?.Bind(null); + _audioSource?.Dispose(); + _audioBuffer?.Dispose(); + + _audioSource = null; + _audioBuffer = null; + + _timer.Elapsed -= Timer_Tick; } - private bool NeedSampleRateConversion(WaveFormat waveFormat) { - if (_waveStream.InputCount == 0) { - return false; + private void Timer_Tick(object sender, ElapsedEventArgs e) { + var state = _audioSource.State; + + if ((_lastSourceState == ALSourceState.Paused || _lastSourceState == ALSourceState.Playing) && state == ALSourceState.Stopped) { + PlaybackStopped?.Invoke(this, EventArgs.Empty); } - return waveFormat.SampleRate != _waveStream.WaveFormat.SampleRate; + + _lastSourceState = state; } private void OnMusicVolumeChanged(object sender, EventArgs e) { - _musicChannel.Volume = PlayerSettings.MusicVolume; + _audioSource.Volume = PlayerSettings.MusicVolume; } - private WaveMixerStream32 _waveStream; - private AudioOut _soundPlayer; private bool _isPlaying; private bool _isPaused; + + private AudioSource _audioSource; + private AudioBuffer _audioBuffer; + private readonly AudioManager _audioManager; private readonly object _syncObject; - private readonly Dictionary _channels; - private WaveChannel32 _musicChannel; + + private ALSourceState _lastSourceState = ALSourceState.Initial; + + private Timer _timer; } } diff --git a/Apps/ScoreViewer/SfxManager.cs b/Apps/ScoreViewer/SfxManager.cs index 68a6c6c..eec81d9 100644 --- a/Apps/ScoreViewer/SfxManager.cs +++ b/Apps/ScoreViewer/SfxManager.cs @@ -1,190 +1,191 @@ -using System; +using System; using System.Collections.Generic; -using System.IO; +using DereTore.Apps.ScoreViewer.Extensions; using DereTore.Common; using NAudio.Wave; -using Timer = System.Timers.Timer; +using SharpAL; +using SharpAL.Extensions; +using SharpAL.OpenAL; namespace DereTore.Apps.ScoreViewer { public sealed class SfxManager : DisposableBase { - public WaveOffsetStream PreloadWave(string fileName) { - return PreloadWave(null, fileName); + public SfxManager(AudioManager audioManager) { + _syncObject = new object(); + _items = new List(30); + _fileNames = new List(30); + _audioManager = audioManager; } - public WaveOffsetStream PreloadWave(Stream dataStream, string fileName) { - int index; - var @out = GetFreeStream(fileName, TimeSpan.Zero, out index) - ?? (dataStream != null ? CreateStream(dataStream, fileName, TimeSpan.Zero, out index) : CreateStream(fileName, TimeSpan.Zero, out index)); - return @out; - } + public void PreloadWave(string fileName) { + if (_fileNames.Contains(fileName)) { + return; + } - public void PlayWave(string fileName, TimeSpan startTime, float volume = 1f) { - PlayWave(null, fileName, startTime, volume); + _fileNames.Add(fileName); + var sfx = LoadSfx(fileName); + _items.Add(sfx); } - public void PlayWave(Stream dataStream, string fileName, TimeSpan startTime, float volume) { - int index; - TimeSpan correctedStartTime = startTime + PlayerSettings.SfxOffset; - var @out = GetFreeStream(fileName, correctedStartTime, out index) - ?? (dataStream != null ? CreateStream(dataStream, fileName, correctedStartTime, out index) : CreateStreamForceUsingCache(fileName, correctedStartTime, out index)); - @out.Seek(0, SeekOrigin.Begin); - _playingList[index] = true; - _mixerInputWaveStreams[index] = _scorePlayer?.AddInputStream(@out, volume); + public void PlayWave(string fileName, float volume) { + var sfx = GetFreeSfx(fileName); + sfx.AudioSource.Volume = volume; + sfx.AudioSource.Play(); } public void StopAll() { - for (int i = 0; i < _waveOffsetStreams.Count; ++i) { - if (_playingList[i]) { - _scorePlayer.RemoveInputStream(_mixerInputWaveStreams[i]); - _mixerInputWaveStreams[i] = null; - _playingList[i] = false; + foreach (var item in _items) { + if (item.IsPlaying) { + item.AudioSource.Stop(); } } } - public void ClearCache() { - DisposeInternal(); - _soundStreams.Clear(); - _waveStreams.Clear(); - _fileNames.Clear(); - _waveOffsetStreams.Clear(); - _mixerInputWaveStreams.Clear(); - _playingList.Clear(); - } - public TimeSpan BufferSize { get; set; } = new TimeSpan(0, 0, 0, 0, 80); - public TimeSpan BufferOffset => BufferSize - PlayerSettings.SfxOffset; protected override void Dispose(bool disposing) { - if (disposing) { - _timer.Elapsed -= Timer_Tick; - _timer.Stop(); - _timer.Dispose(); - DisposeInternal(); - } - } - - private void DisposeInternal() { StopAll(); - foreach (var stream in _waveOffsetStreams) { - stream.Dispose(); - } - foreach (var hcaWaveProvider in _waveStreams) { - hcaWaveProvider.Dispose(); - } - foreach (var memoryStream in _soundStreams) { - memoryStream.Dispose(); + foreach (var item in _items) { + item.Dispose(); } } - private WaveOffsetStream GetFreeStream(string fileName, TimeSpan startTime, out int index) { - if (!_fileNames.Contains(fileName)) { - index = -1; - return null; + private SfxItem GetFreeSfx(string fileName) { + var requireFreshReload = !_fileNames.Contains(fileName); + + if (requireFreshReload) { + _fileNames.Add(fileName); } - for (var i = 0; i < _waveOffsetStreams.Count; ++i) { - if (_fileNames[i] == fileName && !_playingList[i]) { - index = i; - var waveOffsetStream = _waveOffsetStreams[i]; - waveOffsetStream.StartTime = startTime; - waveOffsetStream.CurrentTime = startTime; - return waveOffsetStream; + + SfxItem sfx; + + if (!requireFreshReload) { + SfxItem template = null; + + foreach (var item in _items) { + if (item.FileName != fileName) { + continue; + } + + template = item; + + if (!item.IsPlaying) { + return item; + } } - } - index = -1; - return null; - } - private WaveOffsetStream CreateStream(Stream dataStream, string fileName, TimeSpan startTime, out int index) { - var fileNames = _fileNames; - var soundStreams = _soundStreams; - MemoryStream templateMemory = null; - if (fileNames.Contains(fileName)) { - for (var i = 0; i < fileNames.Count; ++i) { - if (fileNames[i] == fileName) { - templateMemory = soundStreams[i]; - break; + if (template != null) { + sfx = template.Extend(); + + lock (_syncObject) { + _items.Add(sfx); } + + return sfx; } } - fileNames.Add(fileName); - MemoryStream memory; - if (templateMemory != null) { - memory = new MemoryStream(templateMemory.Capacity); - templateMemory.WriteTo(memory); - } else { - if (dataStream == null) { - throw new ArgumentNullException(nameof(dataStream), "When not using a cache, the data stream must not be null."); - } - memory = new MemoryStream((int)dataStream.Length); - dataStream.CopyTo(memory); + sfx = LoadSfx(fileName); + lock (_syncObject) { + _items.Add(sfx); } - memory.Seek(0, SeekOrigin.Begin); - memory.Capacity = (int)memory.Length; - soundStreams.Add(memory); - - // The SFX files were provided so just keep it the 44.1kHz/16bits/stereo. - var waveProvider = new RawSourceWaveStream(memory, DefaultWaveFormat); - _waveStreams.Add(waveProvider); - _playingList.Add(false); - var waveOffsetStream = new WaveOffsetStream(waveProvider, startTime, TimeSpan.Zero, waveProvider.TotalTime); - _waveOffsetStreams.Add(waveOffsetStream); - _mixerInputWaveStreams.Add(null); - index = _waveOffsetStreams.Count - 1; - return waveOffsetStream; + + return sfx; } - private WaveOffsetStream CreateStream(string fileName, TimeSpan startTime, out int index) { - using (var fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { - return CreateStream(fs, fileName, startTime, out index); + private SfxItem LoadSfx(string fileName) { + if (!_fileNames.Contains(fileName)) { + _fileNames.Add(fileName); } - } - private WaveOffsetStream CreateStreamForceUsingCache(string fileName, TimeSpan startTime, out int index) { - return CreateStream(null, fileName, startTime, out index); - } + var sfx = new SfxItem(); - private void Timer_Tick(object sender, EventArgs e) { - lock (_syncObject) { - for (var i = 0; i < _waveOffsetStreams.Count; i++) { - if (_playingList[i] && _waveOffsetStreams[i].Position >= _waveOffsetStreams[i].Length) { - _scorePlayer.RemoveInputStream(_mixerInputWaveStreams[i]); - _mixerInputWaveStreams[i] = null; - _playingList[i] = false; - } + byte[] data; + int sampleRate; + TimeSpan totalTime; + + using (var waveReader = new WaveFileReader(fileName)) { + sampleRate = waveReader.WaveFormat.SampleRate; + totalTime = waveReader.TotalTime; + + WaveStream waveStream; + + if (AudioManager.NeedsConversion(waveReader.WaveFormat, AudioManager.StandardFormat)) { + waveStream = new WaveFormatConversionStream(AudioManager.StandardFormat, waveReader); + } else { + waveStream = waveReader; + } + + using (var offsetStream = new WaveOffsetStream(waveStream)) { + offsetStream.StartTime = PlayerSettings.SfxOffset; + data = offsetStream.ReadToEnd(); } } + + sfx.AudioBuffer = new AudioBuffer(_audioManager.AudioContext); + sfx.AudioSource = new AudioSource(_audioManager.AudioContext); + sfx.AudioBuffer.BufferData(data, sampleRate); + sfx.AudioSource.Bind(sfx.AudioBuffer); + sfx.Data = data; + sfx.FileName = fileName; + sfx.SampleRate = sampleRate; + sfx.TotalTime = totalTime; + + return sfx; } - public SfxManager(ScorePlayer scorePlayer) { - _syncObject = new object(); - _soundStreams = new List(); - _fileNames = new List(); - _waveStreams = new List(); - _waveOffsetStreams = new List(); - _mixerInputWaveStreams = new List(); - _playingList = new List(); - _timer = new Timer(15); - _scorePlayer = scorePlayer; - _timer.Elapsed += Timer_Tick; - _timer.Start(); + private sealed class SfxItem : IDisposable { + + public string FileName; + + /// + /// Transformed data, 16-bit stereo 44.1 kHz. + /// + public byte[] Data; + + public int SampleRate; + + public AudioSource AudioSource; + + public AudioBuffer AudioBuffer; + + public TimeSpan TotalTime; + + public bool IsPlaying => AudioSource.State == ALSourceState.Playing; + + public SfxItem Extend() { + var sfx = new SfxItem { + FileName = FileName, + Data = Data, + SampleRate = SampleRate, + TotalTime = TotalTime + }; + + sfx.AudioSource = new AudioSource(AudioSource.Context); + sfx.AudioBuffer = new AudioBuffer(AudioBuffer.Context); + sfx.AudioBuffer.BufferData(sfx.Data, sfx.SampleRate); + sfx.AudioSource.Bind(sfx.AudioBuffer); + + return sfx; + } + + public void Dispose() { + AudioSource?.Bind(null); + AudioSource?.Dispose(); + AudioBuffer?.Dispose(); + AudioSource = null; + AudioBuffer = null; + } + } - private readonly ScorePlayer _scorePlayer; - private readonly List _soundStreams; - private readonly List _waveStreams; + private readonly AudioManager _audioManager; + private readonly List _items; private readonly List _fileNames; - private readonly List _waveOffsetStreams; - private readonly List _mixerInputWaveStreams; - private readonly List _playingList; private readonly object _syncObject; - private static readonly WaveFormat DefaultWaveFormat = new WaveFormat(); - private readonly Timer _timer; } } diff --git a/DereTore.sln b/DereTore.sln index dfcf2e4..c514876 100644 --- a/DereTore.sln +++ b/DereTore.sln @@ -1,12 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.8 +VisualStudioVersion = 15.0.27130.2024 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{9203B750-3E22-4048-A379-2147B3C9C7BF}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore + .gitmodules = .gitmodules appveyor.yml = appveyor.yml CONTRIBUTING.md = CONTRIBUTING.md CONTRIBUTORS.md = CONTRIBUTORS.md @@ -65,6 +66,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{6FE6FC74-E docs\TODO.md = docs\TODO.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "external", "external", "{F815AF74-5C82-416E-85C5-2C79C01521AA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpAL", "external\SharpAL\SharpAL\SharpAL.csproj", "{7E5E07E6-4300-438E-BD37-C9F6E5E14A41}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -255,6 +260,18 @@ Global {31E86FB4-68DE-4DD6-8F03-D8E2DAAE07FA}.Release|x64.Build.0 = Release|x64 {31E86FB4-68DE-4DD6-8F03-D8E2DAAE07FA}.Release|x86.ActiveCfg = Release|x86 {31E86FB4-68DE-4DD6-8F03-D8E2DAAE07FA}.Release|x86.Build.0 = Release|x86 + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Debug|x64.ActiveCfg = Debug|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Debug|x64.Build.0 = Debug|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Debug|x86.ActiveCfg = Debug|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Debug|x86.Build.0 = Debug|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Release|Any CPU.Build.0 = Release|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Release|x64.ActiveCfg = Release|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Release|x64.Build.0 = Release|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Release|x86.ActiveCfg = Release|Any CPU + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,6 +295,7 @@ Global {C6BC3FE0-8333-4DFF-8284-07431E151B44} = {73543281-7546-49F0-AD31-DF837F0FDED5} {1D5FB1F1-D4C9-4B4D-9225-73F4C459B875} = {73543281-7546-49F0-AD31-DF837F0FDED5} {31E86FB4-68DE-4DD6-8F03-D8E2DAAE07FA} = {73543281-7546-49F0-AD31-DF837F0FDED5} + {7E5E07E6-4300-438E-BD37-C9F6E5E14A41} = {F815AF74-5C82-416E-85C5-2C79C01521AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2BF04279-EB9E-4F92-99A2-FE29F4D1D9FA} diff --git a/README.md b/README.md index 03fc485..0427750 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The goal of DereTore is to improve gaming experience in [THE iDOLM@STER Cinderel **Downloads:** -- [Nightly build](https://ci.appveyor.com/api/projects/hozuki/deretore-avoh8/artifacts/deretore-toolkit-x86.zip?job=Platform%3A+x86) (Windows, x86) +- [Nightly build](https://ci.appveyor.com/api/projects/hozuki/deretore-avoh8/artifacts/deretore-toolkit-x86.zip) (Windows, x86) - [Releases](https://github.com/OpenCGSS/DereTore/releases) A newer version of Starlight Director (the beatmap editor) can be found at [hozuki/StarlightDirector](https://github.com/hozuki/StarlightDirector). However, if you @@ -33,8 +33,16 @@ Have a look at the [Wiki Page](https://github.com/OpenCGSS/DereTore/wiki). Pleas **Basic requirements:** -- Windows 7 or later -- [.NET Framework 4.5](https://www.microsoft.com/en-us/download/details.aspx?id=42642) +Windows: + + - Windows 7 or later + - [.NET Framework 4.5](https://www.microsoft.com/en-us/download/details.aspx?id=42642) + - [OpenAL](https://www.openal.org/downloads/) + +macOS/Linux: + + - [Wine](https://www.winehq.org/download) (will install wine-mono when needed) + - [OpenAL](https://www.openal.org/downloads/) **Optional requirements:** diff --git a/appveyor.yml b/appveyor.yml index 380f999..d08a497 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,23 +1,21 @@ # https://www.appveyor.com/docs/appveyor-yml/ -version: 0.7.6.{build} +version: 0.7.8.{build} branches: only: - master skip_tags: true image: Visual Studio 2017 -platform: - - x86 - - x64 -configuration: - - Release -build: - parallel: true - project: DereTore.sln +build_script: + - cmd: msbuild DereTore.sln /p:Configuration=Release /p:Platform="Any CPU" + - cmd: msbuild DereTore.sln /p:Configuration=Release /p:Platform=x86 environment: EnableNuGetPackageRestore: true before_build: - nuget restore + - nuget update -self + - git submodule update --init --recursive + - nuget restore + - nuget restore external\SharpAL\SharpAL.sln after_build: - 7z a deretore-toolkit.zip -r %APPVEYOR_BUILD_FOLDER%/Apps/AcbMaker/bin/%PLATFORM%/%CONFIGURATION%/*.exe - 7z a deretore-toolkit.zip -r %APPVEYOR_BUILD_FOLDER%/Apps/AcbMaker/bin/%PLATFORM%/%CONFIGURATION%/*.dll diff --git a/external/SharpAL b/external/SharpAL new file mode 160000 index 0000000..531c9ff --- /dev/null +++ b/external/SharpAL @@ -0,0 +1 @@ +Subproject commit 531c9ffb838972009bc92f9ddc6c86f6c265ab5d