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