refactoring to comply with c# conventions
This commit is contained in:
127
RhythmBullet/Audio/MusicController.cs
Normal file
127
RhythmBullet/Audio/MusicController.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Microsoft.Xna.Framework.Media;
|
||||
using NAudio.Dsp;
|
||||
using NAudio.Wave;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RhythmBullet.Audio
|
||||
{
|
||||
internal class MusicController : IDisposable
|
||||
{
|
||||
public readonly MusicList musicList;
|
||||
WaveOutEvent outputDevice;
|
||||
TransparentSampleProvider transparentSampleProvider;
|
||||
AudioFileReader audioInput;
|
||||
Random random;
|
||||
float volume;
|
||||
private float Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return volume;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (outputDevice != null)
|
||||
{
|
||||
outputDevice.Volume = value;
|
||||
}
|
||||
|
||||
volume = value;
|
||||
}
|
||||
}
|
||||
private int PlayingIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return musicList.List.IndexOf(audioInput.FileName);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value < 0) value = musicList.List.Count -1;
|
||||
if (value >= musicList.List.Count) value = 0;
|
||||
|
||||
LoadMusic(musicList.List[value]);
|
||||
}
|
||||
}
|
||||
|
||||
public MusicController()
|
||||
{
|
||||
musicList = new MusicList();
|
||||
musicList.SearchCompleteEvent += MusicListRefreshListener;
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
public MusicController(MusicList musicList)
|
||||
{
|
||||
random = new Random();
|
||||
this.musicList = musicList;
|
||||
this.musicList.SearchCompleteEvent += MusicListRefreshListener;
|
||||
}
|
||||
|
||||
private void LoadMusic(string path)
|
||||
{
|
||||
outputDevice?.Dispose();
|
||||
audioInput?.Dispose();
|
||||
|
||||
audioInput = new AudioFileReader(path);
|
||||
|
||||
transparentSampleProvider = new TransparentSampleProvider(audioInput);
|
||||
|
||||
outputDevice = new WaveOutEvent();
|
||||
outputDevice.Volume = Volume;
|
||||
outputDevice.Init(transparentSampleProvider);
|
||||
}
|
||||
|
||||
public void Skip()
|
||||
{
|
||||
PlayingIndex++;
|
||||
}
|
||||
|
||||
public void Previous()
|
||||
{
|
||||
PlayingIndex--;
|
||||
}
|
||||
|
||||
public void Shuffle()
|
||||
{
|
||||
PlayingIndex = random.Next(0, musicList.List.Count);
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
outputDevice.Play();
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
outputDevice.Pause();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
outputDevice.Stop();
|
||||
}
|
||||
|
||||
public TimeSpan Position()
|
||||
{
|
||||
return audioInput.CurrentTime;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
audioInput?.Dispose();
|
||||
outputDevice?.Dispose();
|
||||
musicList.SearchCompleteEvent -= MusicListRefreshListener;
|
||||
}
|
||||
|
||||
public void MusicListRefreshListener(MusicList musicList)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
121
RhythmBullet/Audio/MusicList.cs
Normal file
121
RhythmBullet/Audio/MusicList.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Microsoft.Xna.Framework.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RhythmBullet.Audio
|
||||
{
|
||||
internal delegate void SearchListener(MusicList musicList);
|
||||
|
||||
internal class MusicList
|
||||
{
|
||||
public event SearchListener SearchCompleteEvent;
|
||||
private volatile bool work;
|
||||
private List<string> list = new List<string>();
|
||||
public List<string> List
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!thread.IsAlive)
|
||||
{
|
||||
lock (list)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public string path;
|
||||
private Thread thread;
|
||||
public bool Searched
|
||||
{
|
||||
get
|
||||
{
|
||||
return (thread != null && !thread.IsAlive);
|
||||
}
|
||||
}
|
||||
|
||||
internal MusicList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void StartSearch()
|
||||
{
|
||||
if (thread == null || !thread.IsAlive)
|
||||
{
|
||||
work = true;
|
||||
thread = new Thread(Search);
|
||||
thread.IsBackground = true;
|
||||
thread.Name = "Music Search Thread";
|
||||
Debug.WriteLine("Starting music file search with path: " + path, GetType().Name);
|
||||
thread.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopSearch();
|
||||
StartSearch();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopSearch()
|
||||
{
|
||||
work = false;
|
||||
thread.Join();
|
||||
}
|
||||
|
||||
private void Search()
|
||||
{
|
||||
lock (list)
|
||||
{
|
||||
list.Clear();
|
||||
list.AddRange(recursiveMusicSearch(path));
|
||||
}
|
||||
OnSearchComplete();
|
||||
Debug.WriteLine("Completed search. Found " + list.Count + " valid file(s).", GetType().Name);
|
||||
}
|
||||
|
||||
private List<string> recursiveMusicSearch(string path)
|
||||
{
|
||||
List<string> musicFiles = new List<string>();
|
||||
string[] folders = Directory.GetDirectories(path);
|
||||
string[] files = Directory.GetFiles(path);
|
||||
|
||||
for (int folderID = 0; folderID < folders.Length && work; folderID++)
|
||||
{
|
||||
musicFiles.AddRange(recursiveMusicSearch(folders[folderID]));
|
||||
}
|
||||
|
||||
for (int fileID = 0; fileID < files.Length && work; fileID++)
|
||||
{
|
||||
string extensionType = Path.GetExtension(files[fileID]).ToUpper().Substring(1);
|
||||
SupportedFormats format;
|
||||
bool supported = Enum.TryParse<SupportedFormats>(extensionType, out format);
|
||||
if (supported)
|
||||
{
|
||||
musicFiles.Add(files[fileID]);
|
||||
Debug.WriteLine("Music file found: " + files[fileID], GetType().Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("Unsupported file format: " + Path.GetFileName(files[fileID]), GetType().Name);
|
||||
}
|
||||
}
|
||||
return musicFiles;
|
||||
}
|
||||
|
||||
private void OnSearchComplete()
|
||||
{
|
||||
SearchCompleteEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
13
RhythmBullet/Audio/SupportedFormats.cs
Normal file
13
RhythmBullet/Audio/SupportedFormats.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RhythmBullet.Audio
|
||||
{
|
||||
public enum SupportedFormats
|
||||
{
|
||||
WAV, MP3
|
||||
}
|
||||
}
|
92
RhythmBullet/Audio/TransparentSampleProvider.cs
Normal file
92
RhythmBullet/Audio/TransparentSampleProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using NAudio.Dsp;
|
||||
using NAudio.Wave;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RhythmBullet.Audio
|
||||
{
|
||||
public class TransparentSampleProvider : ISampleProvider
|
||||
{
|
||||
private const int FFT_SIZE = 2048;
|
||||
private readonly int m;
|
||||
private ISampleProvider source;
|
||||
private Complex[] fftBuffer;
|
||||
private volatile bool updatedSpectrum;
|
||||
private readonly float[] currentSpectrum;
|
||||
private int channelCount;
|
||||
public bool performFFT;
|
||||
|
||||
|
||||
|
||||
public TransparentSampleProvider(ISampleProvider sampleProvider)
|
||||
{
|
||||
m = (int)Math.Log(FFT_SIZE);
|
||||
source = sampleProvider;
|
||||
channelCount = sampleProvider.WaveFormat.Channels;
|
||||
fftBuffer = new Complex[FFT_SIZE];
|
||||
currentSpectrum = new float[FFT_SIZE];
|
||||
}
|
||||
|
||||
public WaveFormat WaveFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
return source.WaveFormat;
|
||||
}
|
||||
}
|
||||
|
||||
public float[] GetCurrentSpectrum()
|
||||
{
|
||||
if (updatedSpectrum)
|
||||
{
|
||||
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)
|
||||
{
|
||||
int sampleCount = source.Read(buffer, offset, count);
|
||||
|
||||
if (performFFT)
|
||||
{
|
||||
int fftOffset = 0;
|
||||
for (int s = 0; s < sampleCount; s += channelCount)
|
||||
{
|
||||
if (s >= FFT_SIZE)
|
||||
{
|
||||
lock (fftBuffer)
|
||||
{
|
||||
FastFourierTransform.FFT(true, m, fftBuffer);
|
||||
}
|
||||
updatedSpectrum = true;
|
||||
fftOffset -= FFT_SIZE;
|
||||
}
|
||||
float greatestVal = 0;
|
||||
for (int c = 0; c < channelCount; c++)
|
||||
{
|
||||
greatestVal = MathHelper.Max(buffer[s + offset + c], greatestVal);
|
||||
}
|
||||
|
||||
fftBuffer[s + fftOffset].X = (float)(buffer[s + offset] * FastFourierTransform.HammingWindow(s, FFT_SIZE));
|
||||
fftBuffer[s + fftOffset].Y = 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sampleCount;
|
||||
}
|
||||
}
|
||||
}
|
109
RhythmBullet/Audio/Visualizer/HorizontalVisualizer.cs
Normal file
109
RhythmBullet/Audio/Visualizer/HorizontalVisualizer.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using RhythmBullet.UI.Modular;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RhythmBullet.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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user