Skip to content

Instantly share code, notes, and snippets.

@thatcosmonaut
Created May 13, 2020 09:53
Show Gist options
  • Save thatcosmonaut/064556d4fc1e9659899296f2a9d5711d to your computer and use it in GitHub Desktop.
Save thatcosmonaut/064556d4fc1e9659899296f2a9d5711d to your computer and use it in GitHub Desktop.
/* SongOgg.cs - An FAudio OGG wrapper.
Copyright (c) 2020 Evan Hemsley
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Evan Hemsley <evan@moonside.games>
*/
using System;
using System.Reflection;
using Microsoft.Xna.Framework.Audio;
public sealed class SongOgg : IDisposable
{
public FAudio.stb_vorbis_info Info { get; }
public string Filename { get; }
public uint BufferSize { get; }
private const int _bufferShrinkFactor = 8;
public SoundState SoundState { get { return _instance.State; } }
public bool Looping { get; private set; }
private IntPtr _filePointer;
private readonly float[] _buffer;
private readonly DynamicSoundEffectInstance _instance;
private static readonly MethodInfo s_applyReverbMethodInfo = typeof(DynamicSoundEffectInstance).GetMethod("INTERNAL_applyReverb", BindingFlags.Instance | BindingFlags.NonPublic);
private readonly Action<float> _applyReverb;
/// <summary>
/// Given an Ogg Vorbis file, allows audio to be streamed dynamically from disk.
/// </summary>
/// <param name="filename"></param>
public SongOgg(string filename)
{
Filename = filename;
_filePointer = GetFilePointer(filename);
Info = FAudio.stb_vorbis_get_info(GetFilePointer(filename));
BufferSize = (uint)(Info.sample_rate * Info.channels) / _bufferShrinkFactor;
FAudio.stb_vorbis_close(_filePointer);
_buffer = new float[BufferSize];
_instance = new DynamicSoundEffectInstance((int)Info.sample_rate, (AudioChannels)Info.channels);
_instance.BufferNeeded += LoadBufferEventHandler;
_applyReverb = (Action<float>)Delegate.CreateDelegate(typeof(Action<float>), _instance, s_applyReverbMethodInfo);
}
/// <summary>
/// Plays the song. If already playing, it resets to the start of the song.
/// </summary>
/// <param name="loop"></param>
public void Play(bool loop = false)
{
if (SoundState != SoundState.Stopped)
{
Stop();
}
Looping = loop;
_filePointer = GetFilePointer(Filename);
LoadBuffer(_instance);
_instance.Play();
}
/// <summary>
/// Pauses the song.
/// </summary>
public void Pause()
{
_instance.Pause();
}
/// <summary>
/// Resumes the song from pause.
/// </summary>
public void Resume()
{
_instance.Resume();
}
/// <summary>
/// Stops playback and closes the song file.
/// </summary>
public void Stop()
{
if (SoundState != SoundState.Stopped) // need to check this so we dont free memory twice
{
_instance.Stop();
FAudio.stb_vorbis_close(_filePointer);
}
}
public void SetPitch(float value)
{
_instance.Pitch = value;
}
public void SetVolume(float volume)
{
_instance.Volume = volume;
}
public void ApplyReverb(float rvGain)
{
_applyReverb(rvGain);
}
/// <summary>
/// Disposes the song. Note that this will cause the song buffer to be garbage collected.
/// </summary>
public void Dispose()
{
_instance.Dispose();
}
private void LoadBufferEventHandler(object sender, EventArgs e)
{
var samples = LoadBuffer((DynamicSoundEffectInstance)sender);
if (samples < Info.sample_rate / _bufferShrinkFactor)
{
if (Looping)
{
var result = FAudio.stb_vorbis_seek_start(_filePointer);
if (result != 1)
{
throw new Exception(); // TODO: custom exception here
}
}
else
{
Stop();
}
}
}
private int LoadBuffer(DynamicSoundEffectInstance dynamicSoundInstance)
{
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
_filePointer,
Info.channels,
_buffer,
(int)BufferSize
);
dynamicSoundInstance.SubmitFloatBufferEXT(_buffer);
return samples;
}
private static IntPtr GetFilePointer(string filename)
{
var filePointer = FAudio.stb_vorbis_open_filename(filename, out var error, IntPtr.Zero);
if (error != 0)
{
throw new Exception(); // TODO: define a custom exception here
}
return filePointer;
}
private static IntPtr GetFilePointerWithSeek(string filename, uint sampleNumber)
{
var filePointer = GetFilePointer(filename);
var seekError = FAudio.stb_vorbis_seek_frame(filePointer, sampleNumber);
if (seekError != 1)
{
throw new Exception(); // TODO: here too
}
return filePointer;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment