diff --git a/RhythmBullet/RhythmBullet.csproj b/RhythmBullet/RhythmBullet.csproj index 54065a1..1c078e2 100644 --- a/RhythmBullet/RhythmBullet.csproj +++ b/RhythmBullet/RhythmBullet.csproj @@ -51,6 +51,7 @@ + @@ -151,9 +152,7 @@ - - - + diff --git a/RhythmBullet/Zer01HD/Audio/MusicController.cs b/RhythmBullet/Zer01HD/Audio/MusicController.cs index 4e3252b..982b270 100644 --- a/RhythmBullet/Zer01HD/Audio/MusicController.cs +++ b/RhythmBullet/Zer01HD/Audio/MusicController.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace RhythmBullet.Zer01HD.Audio { - internal class MusicController + internal class MusicController : IDisposable { MusicList musicList; WaveOutEvent outputDevice; @@ -105,5 +105,10 @@ namespace RhythmBullet.Zer01HD.Audio return audioInput.CurrentTime; } + public void Dispose() + { + audioInput?.Dispose(); + outputDevice?.Dispose(); + } } } diff --git a/RhythmBullet/Zer01HD/Audio/TransparentSampleProvider.cs b/RhythmBullet/Zer01HD/Audio/TransparentSampleProvider.cs index 19606b6..7e530c5 100644 --- a/RhythmBullet/Zer01HD/Audio/TransparentSampleProvider.cs +++ b/RhythmBullet/Zer01HD/Audio/TransparentSampleProvider.cs @@ -12,10 +12,11 @@ namespace RhythmBullet.Zer01HD.Audio public class TransparentSampleProvider : ISampleProvider { private const int FFT_SIZE = 2048; - private readonly int m; + private readonly int m; private ISampleProvider source; private Complex[] fftBuffer; - private float[] spectrum; + private volatile bool updatedSpectrum; + private readonly float[] currentSpectrum; private int channelCount; public bool performFFT; @@ -27,7 +28,7 @@ namespace RhythmBullet.Zer01HD.Audio source = sampleProvider; channelCount = sampleProvider.WaveFormat.Channels; fftBuffer = new Complex[FFT_SIZE]; - spectrum = new float[FFT_SIZE]; + currentSpectrum = new float[FFT_SIZE]; } public WaveFormat WaveFormat @@ -38,12 +39,20 @@ namespace RhythmBullet.Zer01HD.Audio } } - public float GetSpectrumBin(int bin) + public float[] GetCurrentSpectrum() { - lock (spectrum) + if (updatedSpectrum) { - return spectrum[bin]; + lock (fftBuffer) + { + for (int binID = 0; binID < currentSpectrum.Length; binID++) + { + currentSpectrum[binID] = fftBuffer[binID].X; + } + } } + + return currentSpectrum; } public int Read(float[] buffer, int offset, int count) @@ -57,14 +66,11 @@ namespace RhythmBullet.Zer01HD.Audio { if (s >= FFT_SIZE) { - FastFourierTransform.FFT(true, m, fftBuffer); - lock (spectrum) + lock (fftBuffer) { - for (int binID = 0; binID < spectrum.Length; binID++) - { - spectrum[binID] = fftBuffer[binID].X; - } + FastFourierTransform.FFT(true, m, fftBuffer); } + updatedSpectrum = true; fftOffset -= FFT_SIZE; } float greatestVal = 0; diff --git a/RhythmBullet/Zer01HD/Audio/Visualizer/HorizontalVisualizer.cs b/RhythmBullet/Zer01HD/Audio/Visualizer/HorizontalVisualizer.cs new file mode 100644 index 0000000..0e786b3 --- /dev/null +++ b/RhythmBullet/Zer01HD/Audio/Visualizer/HorizontalVisualizer.cs @@ -0,0 +1,109 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using RhythmBullet.Zer01HD.UI.Modular; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RhythmBullet.Zer01HD.Audio.Visualizer +{ + internal class HorizontalVisualizer : UIModule, IDisposable + { + GraphicsDevice graphicsDevice; + Texture2D barTexture; + private const int BAR_COUNT = 70; + private const int SMOOTH_RANGE = 3; + private readonly int binsPerBar; + private readonly int spaceBetweenBars; + private Rectangle bar; + private TransparentSampleProvider tsp; + private int[] barValue; + + internal HorizontalVisualizer(TransparentSampleProvider transparentSampleProvider, GraphicsDevice graphicsDevice) + { + this.graphicsDevice = graphicsDevice; + tsp = transparentSampleProvider; + bar.Width = (int)(graphicsDevice.Viewport.Width / 70f); + spaceBetweenBars = (int)(0.25f * bar.Width); + bar.Width -= spaceBetweenBars; + + barTexture = new Texture2D(graphicsDevice, 1, 1); + barTexture.SetData(new[] { Color.White }); + binsPerBar = tsp.GetCurrentSpectrum().Length / BAR_COUNT; + barValue = new int[BAR_COUNT]; + } + + public override void Update(GameTime gameTime) + { + UpdateBars((float)gameTime.ElapsedGameTime.TotalSeconds); + AverageBars(); + base.Update(gameTime); + } + + public override void Draw(SpriteBatch batch) + { + for (int i = 0; i < BAR_COUNT; i++) + { + bar.X = (i * (bar.Width + spaceBetweenBars)) + Bounds.X; + bar.Y = Bounds.Y; + + bar.Height = barValue[i]; + batch.Draw(barTexture, bar, Color); + + bar.Height = -barValue[BAR_COUNT - i - 1]; + batch.Draw(barTexture, bar, Color); + } + base.Draw(batch); + } + + private void UpdateBars(float delta) + { + const float ALPHA = 0.5f; + float[] spectrum = tsp.GetCurrentSpectrum(); + + for (int barID = 0; barID < BAR_COUNT; barID++) + { + int targetBarHeight = 0; + + for (int bin = barID * binsPerBar; bin < (barID + 1) * (binsPerBar); bin++) + { + targetBarHeight += (int)spectrum[bin]; + } + targetBarHeight /= binsPerBar; + + int distance = targetBarHeight - barValue[barID]; + distance *= (int)Math.Round((1.0f - Math.Pow(1 - ALPHA, delta / 0.02f))); + barValue[barID] += distance; + } + } + + private void AverageBars() + { + for (int barID = 0; barID < BAR_COUNT; barID++) + { + int terms = 0; + for (int pos = 0; pos < SMOOTH_RANGE; pos++) + { + if (barID + pos < BAR_COUNT) + { + barValue[barID] += barValue[barID + pos]; + terms++; + } + if (barID - pos > 0) + { + barValue[barID] += barValue[barID - pos]; + terms++; + } + } + barValue[barID] /= terms; + } + } + + public void Dispose() + { + barTexture.Dispose(); + } + } +} diff --git a/RhythmBullet/Zer01HD/Utilities/Camera/Camera2D.cs b/RhythmBullet/Zer01HD/Utilities/Camera/Camera2D.cs index 4c302d3..efe9039 100644 --- a/RhythmBullet/Zer01HD/Utilities/Camera/Camera2D.cs +++ b/RhythmBullet/Zer01HD/Utilities/Camera/Camera2D.cs @@ -32,11 +32,14 @@ namespace RhythmBullet.Zer01HD.Utilities.Camera Matrix.CreateTranslation(new Vector3(bounds.Width * 0.5f, bounds.Height * 0.5f, 0f)); } - public void LinearInterpolationToPosition(float alpha, Vector2 targetPosition) + public void LinearInterpolationToPosition(float alpha, Vector2 targetPosition, float delta) { - if (alpha < 0 && alpha > 1f) throw new ArgumentException("Alpha can't be greater than 1f, or less than 0."); - Position.X = MathHelper.Lerp(Position.X, targetPosition.X, alpha); - Position.Y = MathHelper.Lerp(Position.Y, targetPosition.Y, alpha); + if (alpha <= 0 && alpha > 1f) throw new ArgumentException("Alpha can't be greater than 1f, less than or equal to 0."); + + Vector2 distance = targetPosition - Position; + distance *= (float)(1.0f - Math.Pow(1 - alpha, delta / 0.02f)); + + Position += distance; } } } diff --git a/RhythmBullet/Zer01HD/Utilities/UI/Book/Book.cs b/RhythmBullet/Zer01HD/Utilities/UI/Book/Book.cs index 1e7730c..b6b87fa 100644 --- a/RhythmBullet/Zer01HD/Utilities/UI/Book/Book.cs +++ b/RhythmBullet/Zer01HD/Utilities/UI/Book/Book.cs @@ -45,7 +45,7 @@ namespace RhythmBullet.Zer01HD.UI.Book Rectangle targetBounds = targetPage.Bounds; position.X = targetBounds.X + (targetBounds.Width * 0.5f); position.Y = targetBounds.Y + (targetBounds.Height * 0.5f); - camera.LinearInterpolationToPosition(0.4f, position); + camera.LinearInterpolationToPosition(0.4f, position, (float)gameTime.ElapsedGameTime.TotalSeconds); if (camera.Position == position) { targetPage = null;