Skip to content

Commit

Permalink
Cleanup of audio code to make it more manageable (YARC-Official#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
RileyTheFox authored Apr 20, 2023
1 parent 7a250dd commit 85610de
Show file tree
Hide file tree
Showing 20 changed files with 880 additions and 463 deletions.
4 changes: 4 additions & 0 deletions Assets/Script/Audio/AudioEnums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ public enum SfxSample {
StarPowerRelease,
Clap,
}

public enum DSPType {
Gain,
}
}
8 changes: 8 additions & 0 deletions Assets/Script/Audio/Bass.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

305 changes: 305 additions & 0 deletions Assets/Script/Audio/Bass/BassAudioManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.IO;
using ManagedBass;
using ManagedBass.DirectX8;
using ManagedBass.Fx;
using UnityEngine;
using UnityEngine.Serialization;
using Debug = UnityEngine.Debug;

namespace YARG {
public class BassAudioManager : MonoBehaviour, IAudioManager {
public bool UseStarpowerFx { get; set; }
public bool IsChipmunkSpeedup { get; set; }

public IList<string> SupportedFormats { get; private set; }

public bool IsAudioLoaded { get; private set; }
public bool IsPlaying { get; private set; }

public double CurrentPositionD => GetPosition();
public double AudioLengthD { get; private set; }

public float CurrentPositionF => (float) GetPosition();
public float AudioLengthF { get; private set; }

public double[] stemVolumes;
public double sfxVolume;

private int opusHandle;

private IStemMixer _mixer;

private ISampleChannel[] _sfxSamples;

private void Awake() {
SupportedFormats = new[] {
".ogg",
".mogg",
".wav",
".mp3",
".aiff",
".opus",
};

stemVolumes = new double[AudioHelpers.SupportedStems.Count];

_sfxSamples = new ISampleChannel[AudioHelpers.SfxPaths.Count];

opusHandle = 0;
}

public void Initialize() {
Debug.Log("Initializing BASS...");
string bassPath = GetBassDirectory();
string opusLibDirectory = Path.Combine(bassPath, "bassopus");

opusHandle = Bass.PluginLoad(opusLibDirectory);
Bass.Configure(Configuration.IncludeDefaultDevice, true);

Bass.UpdatePeriod = 5;
Bass.DeviceBufferLength = 10;
Bass.PlaybackBufferLength = 100;
Bass.DeviceNonStop = true;

Bass.Configure(Configuration.TruePlayPosition, 0);
Bass.Configure(Configuration.UpdateThreads, 2);
Bass.Configure(Configuration.FloatDSP, true);

Bass.Configure((Configuration) 68, 1);
Bass.Configure((Configuration) 70, false);

int deviceCount = Bass.DeviceCount;
Debug.Log($"Devices found: {deviceCount}");

if (!Bass.Init(-1, 44100, DeviceInitFlags.Default | DeviceInitFlags.Latency, IntPtr.Zero)) {
Debug.LogError("Failed to initialize BASS");
Debug.LogError($"Bass Error: {Bass.LastError}");
return;
}

LoadSfx();

Debug.Log($"BASS Successfully Initialized");
Debug.Log($"BASS: {Bass.Version}");
Debug.Log($"BASS.FX: {Bass.Version}");
Debug.Log($"BASS.Mix: {Bass.Version}");

Debug.Log($"Update Period: {Bass.UpdatePeriod}");
Debug.Log($"Device Buffer Length: {Bass.DeviceBufferLength}");
Debug.Log($"Playback Buffer Length: {Bass.PlaybackBufferLength}");

Debug.Log($"Current Device: {Bass.GetDeviceInfo(Bass.CurrentDevice).Name}");
}

public void Unload() {
Debug.Log("Unloading BASS plugins");

UnloadSong();

Bass.PluginFree(opusHandle);
opusHandle = 0;

// Free SFX samples
foreach (var sample in _sfxSamples) {
sample.Dispose();
}

Bass.Free();
}

public void LoadSfx() {
Debug.Log("Loading SFX");

_sfxSamples = new ISampleChannel[AudioHelpers.SfxPaths.Count];

string sfxFolder = Path.Combine(Application.streamingAssetsPath, "sfx");

foreach (string sfxFile in AudioHelpers.SfxPaths) {
string sfxPath = Path.Combine(sfxFolder, sfxFile);

foreach (string format in SupportedFormats) {
if (!File.Exists($"{sfxPath}{format}"))
continue;

// Append extension to path (e.g sfx/boop becomes sfx/boop.ogg)
sfxPath += format;
break;
}


if (!File.Exists(sfxPath)) {
Debug.LogError($"SFX path does not exist! {sfxPath}");
continue;
}
var sfxSample = AudioHelpers.GetSfxFromName(sfxFile);

var sfx = new BassSampleChannel(this, sfxPath, 8, sfxSample);
if (sfx.Load() != 0) {
Debug.LogError($"Failed to load SFX! {sfxPath}");
Debug.LogError($"Bass Error: {Bass.LastError}");
continue;
}

_sfxSamples[(int)sfxSample] = sfx;
Debug.Log($"Loaded {sfxFile}");
}

Debug.Log("Finished loading SFX");
}

public void LoadSong(IEnumerable<string> stems, bool isSpeedUp) {
Debug.Log("Loading song");
UnloadSong();

_mixer = new BassStemMixer();
if (!_mixer.Create()) {
throw new Exception("Failed to create mixer");
}

foreach (string stemPath in stems) {
// Gets the file name with no extensions (i.e guitar.ogg -> guitar)
string stemName = Path.GetFileNameWithoutExtension(stemPath);

// Gets the index (SongStem to int) from the name
var songStem = AudioHelpers.GetStemFromName(stemName);

var stemChannel = new BassStemChannel(this, stemPath, songStem);
if (stemChannel.Load(isSpeedUp, PlayMode.Play.speed) != 0) {
Debug.LogError($"Failed to load stem! {stemPath}");
Debug.LogError($"Bass Error: {Bass.LastError}");
continue;
}

if (_mixer.GetChannel(songStem) != null) {
Debug.LogError($"Stem already loaded! {stemPath}");
continue;
}

if (_mixer.AddChannel(stemChannel) != 0) {
Debug.LogError($"Failed to add stem to mixer!");
Debug.LogError($"Bass Error: {Bass.LastError}");
}
}

Debug.Log($"Loaded {_mixer.StemsLoaded} stems");

// Setup audio length
AudioLengthD = _mixer.LeadChannel.LengthD;
AudioLengthF = (float) AudioLengthD;

IsAudioLoaded = true;
}

public void UnloadSong() {
IsPlaying = false;
IsAudioLoaded = false;

// Free mixer (and all channels in it)
_mixer?.Dispose();
}

public void Play() {
// Don't try to play if there's no audio loaded or if it's already playing
if (!IsAudioLoaded || IsPlaying) {
return;
}

if (_mixer.Play() != 0) {
Debug.Log($"Play error: {Bass.LastError}");
}

IsPlaying = _mixer.IsPlaying;
}

public void Pause() {
if (!IsAudioLoaded || !IsPlaying) {
return;
}

if (_mixer.Pause() != 0) {
Debug.Log($"Pause error: {Bass.LastError}");
}

IsPlaying = _mixer.IsPlaying;
}

public void PlaySoundEffect(SfxSample sample) {
var sfx = _sfxSamples[(int) sample];

sfx?.Play();
}

public void SetStemVolume(SongStem stem, double volume) {
var channel = _mixer.GetChannel(stem);

// Multiply volume inputted by the stem's volume setting (e.g. 0.5 * 0.5 = 0.25)
channel?.SetVolume(volume * stemVolumes[(int) stem]);
}

public void UpdateVolumeSetting(SongStem stem, double volume) {
switch (stem)
{
case SongStem.Master:
Bass.GlobalStreamVolume = (int) (10_000 * volume);
break;
case SongStem.Sfx:
sfxVolume = volume;
break;
default:
stemVolumes[(int) stem] = volume;
break;
}
}

public void ApplyReverb(SongStem stem, bool reverb) {
_mixer?.GetChannel(stem)?.SetReverb(reverb);
}

public double GetPosition() {
if (_mixer is null)
return -1;

return _mixer.GetPosition();
}

public void SetPosition(double position) {
throw new NotImplementedException();
}

private void OnApplicationQuit() {
Unload();
}

private static string GetBassDirectory() {
string pluginDirectory = Path.Combine(Application.dataPath, "Plugins");

// Locate windows directory
// Checks if running on 64 bit and sets the path accordingly
#if !UNITY_EDITOR && UNITY_STANDALONE_WIN
#if UNITY_64
pluginDirectory = Path.Combine(pluginDirectory, "x86_64");
#else
pluginDirectory = Path.Combine(pluginDirectory, "x86");
#endif
#endif

// Unity Editor directory, Assets/Plugins/Bass/
#if UNITY_EDITOR
pluginDirectory = Path.Combine(pluginDirectory, "BassNative");
#endif

// Editor paths differ to standalone paths, as the project contains platform specific folders
#if UNITY_EDITOR_WIN
pluginDirectory = Path.Combine(pluginDirectory, "Windows/x86_64");
#elif UNITY_EDITOR_OSX
pluginDirectory = Path.Combine(pluginDirectory, "Mac");
#elif UNITY_EDITOR_LINUX
pluginDirectory = Path.Combine(pluginDirectory, "Linux/x86_64");
#endif

return pluginDirectory;
}
}
}
Loading

0 comments on commit 85610de

Please sign in to comment.