Refactor audio software mixer

Now the various backends have far less duplicate code, and are part
of a separate backend system specifically for the software mixer.

Now, any modifications to the MixSoundsAndUpdateOrganya function
will apply to all backends, instead of needing to manually be applied
to each one.
This commit is contained in:
Clownacy 2020-09-03 19:00:24 +01:00
parent ab09dc67eb
commit be6f46fabd
10 changed files with 765 additions and 957 deletions

View file

@ -316,15 +316,19 @@ endif()
if(BACKEND_AUDIO MATCHES "SDL2")
target_sources(CSE2 PRIVATE
"src/Backends/Audio/SDL2.cpp"
"src/Backends/Audio/SoftwareMixer.cpp"
"src/Backends/Audio/SoftwareMixer.h"
"src/Backends/Audio/SoftwareMixer/Mixer.cpp"
"src/Backends/Audio/SoftwareMixer/Mixer.h"
"src/Backends/Audio/SoftwareMixer/Backend.h"
"src/Backends/Audio/SoftwareMixer/SDL2.cpp"
)
elseif(BACKEND_AUDIO MATCHES "miniaudio")
target_sources(CSE2 PRIVATE
"src/Backends/Audio/miniaudio.cpp"
"src/Backends/Audio/SoftwareMixer.cpp"
"src/Backends/Audio/SoftwareMixer.h"
"src/Backends/Audio/SoftwareMixer/Mixer.cpp"
"src/Backends/Audio/SoftwareMixer/Mixer.h"
"src/Backends/Audio/SoftwareMixer/Backend.h"
"src/Backends/Audio/SoftwareMixer/miniaudio.cpp"
)
# Link libdl, libm, and libpthread
@ -347,9 +351,11 @@ elseif(BACKEND_AUDIO MATCHES "WiiU-Hardware")
)
elseif(BACKEND_AUDIO MATCHES "WiiU-Software")
target_sources(CSE2 PRIVATE
"src/Backends/Audio/WiiU-Software.cpp"
"src/Backends/Audio/SoftwareMixer.cpp"
"src/Backends/Audio/SoftwareMixer.h"
"src/Backends/Audio/SoftwareMixer/Mixer.cpp"
"src/Backends/Audio/SoftwareMixer/Mixer.h"
"src/Backends/Audio/SoftwareMixer/Backend.h"
"src/Backends/Audio/SoftwareMixer/WiiU-Software.cpp"
)
elseif(BACKEND_AUDIO MATCHES "Null")
target_sources(CSE2 PRIVATE

View file

@ -1,249 +0,0 @@
#include "../Audio.h"
#include <stddef.h>
#include <string.h>
#include <string>
#include "SDL.h"
#include "../Misc.h"
#include "SoftwareMixer.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static SDL_AudioDeviceID device_id;
static unsigned long output_frequency;
static void (*organya_callback)(void);
static unsigned int organya_callback_milliseconds;
static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total)
{
if (organya_callback_milliseconds == 0)
{
Mixer_MixSounds(stream, frames_total);
}
else
{
// Synchronise audio generation with Organya.
// In the original game, Organya ran asynchronously in a separate thread,
// firing off commands to DirectSound in realtime. To match that, we'd
// need a very low-latency buffer, otherwise we'd get mistimed instruments.
// Instead, we can just do this.
unsigned int frames_done = 0;
while (frames_done != frames_total)
{
static unsigned long organya_countdown;
if (organya_countdown == 0)
{
organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames
organya_callback();
}
const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done);
Mixer_MixSounds(stream + frames_done * 2, frames_to_do);
frames_done += frames_to_do;
organya_countdown -= frames_to_do;
}
}
}
static void Callback(void *user_data, Uint8 *stream_uint8, int len)
{
(void)user_data;
short *stream = (short*)stream_uint8;
const size_t frames_total = len / sizeof(short) / 2;
size_t frames_done = 0;
while (frames_done != frames_total)
{
long mix_buffer[0x800 * 2]; // 2 because stereo
size_t subframes = MIN(0x800, frames_total - frames_done);
memset(mix_buffer, 0, subframes * sizeof(long) * 2);
MixSoundsAndUpdateOrganya(mix_buffer, subframes);
for (size_t i = 0; i < subframes * 2; ++i)
{
if (mix_buffer[i] > 0x7FFF)
*stream++ = 0x7FFF;
else if (mix_buffer[i] < -0x7FFF)
*stream++ = -0x7FFF;
else
*stream++ = mix_buffer[i];
}
frames_done += subframes;
}
}
bool AudioBackend_Init(void)
{
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
std::string errorMessage = std::string("'SDL_InitSubSystem(SDL_INIT_AUDIO)' failed: ") + SDL_GetError();
Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", errorMessage.c_str());
return false;
}
Backend_PrintInfo("Available SDL audio drivers:");
for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i)
Backend_PrintInfo("%s", SDL_GetAudioDriver(i));
SDL_AudioSpec specification;
specification.freq = 48000;
specification.format = AUDIO_S16;
specification.channels = 2;
specification.samples = 0x400; // Roughly 10 milliseconds for 48000Hz
specification.callback = Callback;
specification.userdata = NULL;
SDL_AudioSpec obtained_specification;
device_id = SDL_OpenAudioDevice(NULL, 0, &specification, &obtained_specification, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (device_id == 0)
{
std::string error_message = std::string("'SDL_OpenAudioDevice' failed: ") + SDL_GetError();
Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", error_message.c_str());
return false;
}
output_frequency = obtained_specification.freq;
Mixer_Init(obtained_specification.freq);
SDL_PauseAudioDevice(device_id, 0);
Backend_PrintInfo("Selected SDL audio driver: %s", SDL_GetCurrentAudioDriver());
return true;
}
void AudioBackend_Deinit(void)
{
SDL_CloseAudioDevice(device_id);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
{
SDL_LockAudioDevice(device_id);
Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length);
SDL_UnlockAudioDevice(device_id);
return (AudioBackend_Sound*)sound;
}
void AudioBackend_DestroySound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_DestroySound((Mixer_Sound*)sound);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_PlaySound((Mixer_Sound*)sound, looping);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_StopSound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_StopSound((Mixer_Sound*)sound);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_RewindSound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_RewindSound((Mixer_Sound*)sound);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_SetSoundVolume((Mixer_Sound*)sound, volume);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan)
{
if (sound == NULL)
return;
SDL_LockAudioDevice(device_id);
Mixer_SetSoundPan((Mixer_Sound*)sound, pan);
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_SetOrganyaCallback(void (*callback)(void))
{
SDL_LockAudioDevice(device_id);
organya_callback = callback;
SDL_UnlockAudioDevice(device_id);
}
void AudioBackend_SetOrganyaTimer(unsigned int milliseconds)
{
SDL_LockAudioDevice(device_id);
organya_callback_milliseconds = milliseconds;
SDL_UnlockAudioDevice(device_id);
}

View file

@ -1,236 +1,214 @@
#include "SoftwareMixer.h"
#include "../Audio.h"
#include <math.h>
#include <stddef.h>
#include <stdlib.h>
#include "../../Attributes.h"
#include "SoftwareMixer/Backend.h"
#include "SoftwareMixer/Mixer.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(x, y, z) MIN(MAX((x), (y)), (z))
#define LANCZOS_KERNEL_RADIUS 2
struct Mixer_Sound
{
signed char *samples;
size_t frames;
size_t position;
unsigned short position_subsample;
unsigned long advance_delta; // 16.16 fixed-point
bool playing;
bool looping;
short volume; // 8.8 fixed-point
short pan_l; // 8.8 fixed-point
short pan_r; // 8.8 fixed-point
short volume_l; // 8.8 fixed-point
short volume_r; // 8.8 fixed-point
struct Mixer_Sound *next;
};
static Mixer_Sound *sound_list_head;
static unsigned long output_frequency;
static unsigned short MillibelToScale(long volume)
static void (*organya_callback)(void);
static unsigned int organya_callback_milliseconds;
static unsigned int organya_sleep_timer;
static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total)
{
// Volume is in hundredths of a decibel, from 0 to -10000
volume = CLAMP(volume, -10000, 0);
return (unsigned short)(pow(10.0, volume / 2000.0) * 256.0f);
SoftwareMixerBackend_LockOrganyaMutex();
if (organya_callback_milliseconds == 0)
{
SoftwareMixerBackend_LockMixerMutex();
Mixer_MixSounds(stream, frames_total);
SoftwareMixerBackend_UnlockMixerMutex();
}
else
{
// Synchronise audio generation with Organya.
// In the original game, Organya ran asynchronously in a separate thread,
// firing off commands to DirectSound in realtime. To match that, we'd
// need a very low-latency buffer, otherwise we'd get mistimed instruments.
// Instead, we can just do this.
unsigned int frames_done = 0;
// Don't process Organya when it's meant to be sleeping
const unsigned int frames_to_do = MIN(organya_sleep_timer, frames_total - frames_done);
if (frames_to_do != 0)
{
SoftwareMixerBackend_LockMixerMutex();
Mixer_MixSounds(stream, frames_to_do);
SoftwareMixerBackend_UnlockMixerMutex();
frames_done += frames_to_do;
organya_sleep_timer -= frames_to_do;
}
void Mixer_Init(unsigned long frequency)
while (frames_done != frames_total)
{
output_frequency = frequency;
static unsigned long organya_countdown;
if (organya_countdown == 0)
{
organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames
organya_callback();
}
Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
{
Mixer_Sound *sound = (Mixer_Sound*)malloc(sizeof(Mixer_Sound));
const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done);
SoftwareMixerBackend_LockMixerMutex();
Mixer_MixSounds(stream + frames_done * 2, frames_to_do);
SoftwareMixerBackend_UnlockMixerMutex();
frames_done += frames_to_do;
organya_countdown -= frames_to_do;
}
}
SoftwareMixerBackend_UnlockOrganyaMutex();
}
bool AudioBackend_Init(void)
{
output_frequency = SoftwareMixerBackend_Init(MixSoundsAndUpdateOrganya);
if (output_frequency != 0)
{
Mixer_Init(output_frequency);
if (SoftwareMixerBackend_Start())
return true;
SoftwareMixerBackend_Deinit();
}
return false;
}
void AudioBackend_Deinit(void)
{
return SoftwareMixerBackend_Deinit();
}
AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
{
SoftwareMixerBackend_LockMixerMutex();
Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length);
SoftwareMixerBackend_UnlockMixerMutex();
return (AudioBackend_Sound*)sound;
}
void AudioBackend_DestroySound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return NULL;
return;
// Both interpolators will read outside the array's bounds, so allocate some extra room
#ifdef LANCZOS_RESAMPLER
sound->samples = (signed char*)malloc(LANCZOS_KERNEL_RADIUS - 1 + length + LANCZOS_KERNEL_RADIUS);
#else
sound->samples = (signed char*)malloc(length + 1);
#endif
SoftwareMixerBackend_LockMixerMutex();
if (sound->samples == NULL)
Mixer_DestroySound((Mixer_Sound*)sound);
SoftwareMixerBackend_UnlockMixerMutex();
}
void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping)
{
free(sound);
return NULL;
if (sound == NULL)
return;
SoftwareMixerBackend_LockMixerMutex();
Mixer_PlaySound((Mixer_Sound*)sound, looping);
SoftwareMixerBackend_UnlockMixerMutex();
}
#ifdef LANCZOS_RESAMPLER
// Blank samples outside the array bounds (we'll deal with the other half later)
for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS - 1; ++i)
sound->samples[i] = 0;
sound->samples += LANCZOS_KERNEL_RADIUS - 1;
#endif
for (size_t i = 0; i < length; ++i)
sound->samples[i] = samples[i] - 0x80; // Convert from unsigned 8-bit PCM to signed
sound->frames = length;
sound->playing = false;
sound->position = 0;
sound->position_subsample = 0;
Mixer_SetSoundFrequency(sound, frequency);
Mixer_SetSoundVolume(sound, 0);
Mixer_SetSoundPan(sound, 0);
sound->next = sound_list_head;
sound_list_head = sound;
return sound;
}
void Mixer_DestroySound(Mixer_Sound *sound)
void AudioBackend_StopSound(AudioBackend_Sound *sound)
{
for (Mixer_Sound **sound_pointer = &sound_list_head; *sound_pointer != NULL; sound_pointer = &(*sound_pointer)->next)
if (sound == NULL)
return;
SoftwareMixerBackend_LockMixerMutex();
Mixer_StopSound((Mixer_Sound*)sound);
SoftwareMixerBackend_UnlockMixerMutex();
}
void AudioBackend_RewindSound(AudioBackend_Sound *sound)
{
if (*sound_pointer == sound)
if (sound == NULL)
return;
SoftwareMixerBackend_LockMixerMutex();
Mixer_RewindSound((Mixer_Sound*)sound);
SoftwareMixerBackend_UnlockMixerMutex();
}
void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency)
{
*sound_pointer = sound->next;
#ifdef LANCZOS_RESAMPLER
sound->samples -= LANCZOS_KERNEL_RADIUS - 1;
#endif
free(sound->samples);
free(sound);
break;
}
}
if (sound == NULL)
return;
SoftwareMixerBackend_LockMixerMutex();
Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency);
SoftwareMixerBackend_UnlockMixerMutex();
}
void Mixer_PlaySound(Mixer_Sound *sound, bool looping)
void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume)
{
sound->playing = true;
sound->looping = looping;
if (sound == NULL)
return;
// Fill the out-of-bounds part of the buffer with
// either blank samples or repeated samples
#ifdef LANCZOS_RESAMPLER
if (looping)
for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i)
sound->samples[sound->frames + i] = sound->samples[i];
else
for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i)
sound->samples[sound->frames + i] = 0;
#else
sound->samples[sound->frames] = looping ? sound->samples[0] : 0;
#endif
SoftwareMixerBackend_LockMixerMutex();
Mixer_SetSoundVolume((Mixer_Sound*)sound, volume);
SoftwareMixerBackend_UnlockMixerMutex();
}
void Mixer_StopSound(Mixer_Sound *sound)
void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan)
{
sound->playing = false;
if (sound == NULL)
return;
SoftwareMixerBackend_LockMixerMutex();
Mixer_SetSoundPan((Mixer_Sound*)sound, pan);
SoftwareMixerBackend_UnlockMixerMutex();
}
void Mixer_RewindSound(Mixer_Sound *sound)
void AudioBackend_SetOrganyaCallback(void (*callback)(void))
{
sound->position = 0;
sound->position_subsample = 0;
SoftwareMixerBackend_LockOrganyaMutex();
organya_callback = callback;
SoftwareMixerBackend_UnlockOrganyaMutex();
}
void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency)
void AudioBackend_SetOrganyaTimer(unsigned int milliseconds)
{
sound->advance_delta = (frequency << 16) / output_frequency;
SoftwareMixerBackend_LockOrganyaMutex();
organya_callback_milliseconds = milliseconds;
SoftwareMixerBackend_UnlockOrganyaMutex();
}
void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume)
void AudioBackend_SleepOrganya(unsigned int milliseconds)
{
sound->volume = MillibelToScale(volume);
SoftwareMixerBackend_LockOrganyaMutex();
sound->volume_l = (sound->pan_l * sound->volume) >> 8;
sound->volume_r = (sound->pan_r * sound->volume) >> 8;
}
void Mixer_SetSoundPan(Mixer_Sound *sound, long pan)
{
sound->pan_l = MillibelToScale(-pan);
sound->pan_r = MillibelToScale(pan);
sound->volume_l = (sound->pan_l * sound->volume) >> 8;
sound->volume_r = (sound->pan_r * sound->volume) >> 8;
}
// Most CPU-intensive function in the game (2/3rd CPU time consumption in my experience), so marked with ATTRIBUTE_HOT so the compiler considers it a hot spot (as it is) when optimizing
ATTRIBUTE_HOT void Mixer_MixSounds(long *stream, size_t frames_total)
{
for (Mixer_Sound *sound = sound_list_head; sound != NULL; sound = sound->next)
{
if (sound->playing)
{
long *stream_pointer = stream;
for (size_t frames_done = 0; frames_done < frames_total; ++frames_done)
{
#ifdef LANCZOS_RESAMPLER
// Perform Lanczos resampling
float output_sample = 0;
for (int i = -LANCZOS_KERNEL_RADIUS + 1; i <= LANCZOS_KERNEL_RADIUS; ++i)
{
const signed char input_sample = sound->samples[sound->position + i];
const float kernel_input = ((float)sound->position_subsample / 0x10000) - i;
if (kernel_input == 0.0f)
{
output_sample += input_sample;
}
else
{
const float nx = 3.14159265358979323846f * kernel_input;
const float nxa = nx / LANCZOS_KERNEL_RADIUS;
output_sample += input_sample * (sin(nx) * sin(nxa) / (nx * nxa));
}
}
// Mix, and apply volume
*stream_pointer++ += (short)(output_sample * sound->volume_l);
*stream_pointer++ += (short)(output_sample * sound->volume_r);
#else
// Perform linear interpolation
const unsigned char interpolation_scale = sound->position_subsample >> 8;
const signed char output_sample = (sound->samples[sound->position] * (0x100 - interpolation_scale)
+ sound->samples[sound->position + 1] * interpolation_scale) >> 8;
// Mix, and apply volume
*stream_pointer++ += output_sample * sound->volume_l;
*stream_pointer++ += output_sample * sound->volume_r;
#endif
// Increment sample
const unsigned long next_position_subsample = sound->position_subsample + sound->advance_delta;
sound->position += next_position_subsample >> 16;
sound->position_subsample = next_position_subsample & 0xFFFF;
// Stop or loop sample once it's reached its end
if (sound->position >= sound->frames)
{
if (sound->looping)
{
sound->position %= sound->frames;
}
else
{
sound->playing = false;
sound->position = 0;
sound->position_subsample = 0;
break;
}
}
}
}
}
organya_sleep_timer = (milliseconds * output_frequency) / 1000;
SoftwareMixerBackend_UnlockOrganyaMutex();
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total));
void SoftwareMixerBackend_Deinit(void);
bool SoftwareMixerBackend_Start(void);
void SoftwareMixerBackend_LockMixerMutex(void);
void SoftwareMixerBackend_UnlockMixerMutex(void);
void SoftwareMixerBackend_LockOrganyaMutex(void);
void SoftwareMixerBackend_UnlockOrganyaMutex(void);

View file

@ -0,0 +1,236 @@
#include "Mixer.h"
#include <math.h>
#include <stddef.h>
#include <stdlib.h>
#include "../../../Attributes.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(x, y, z) MIN(MAX((x), (y)), (z))
#define LANCZOS_KERNEL_RADIUS 2
struct Mixer_Sound
{
signed char *samples;
size_t frames;
size_t position;
unsigned short position_subsample;
unsigned long advance_delta; // 16.16 fixed-point
bool playing;
bool looping;
short volume; // 8.8 fixed-point
short pan_l; // 8.8 fixed-point
short pan_r; // 8.8 fixed-point
short volume_l; // 8.8 fixed-point
short volume_r; // 8.8 fixed-point
struct Mixer_Sound *next;
};
static Mixer_Sound *sound_list_head;
static unsigned long output_frequency;
static unsigned short MillibelToScale(long volume)
{
// Volume is in hundredths of a decibel, from 0 to -10000
volume = CLAMP(volume, -10000, 0);
return (unsigned short)(pow(10.0, volume / 2000.0) * 256.0f);
}
void Mixer_Init(unsigned long frequency)
{
output_frequency = frequency;
}
Mixer_Sound* Mixer_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
{
Mixer_Sound *sound = (Mixer_Sound*)malloc(sizeof(Mixer_Sound));
if (sound == NULL)
return NULL;
// Both interpolators will read outside the array's bounds, so allocate some extra room
#ifdef LANCZOS_RESAMPLER
sound->samples = (signed char*)malloc(LANCZOS_KERNEL_RADIUS - 1 + length + LANCZOS_KERNEL_RADIUS);
#else
sound->samples = (signed char*)malloc(length + 1);
#endif
if (sound->samples == NULL)
{
free(sound);
return NULL;
}
#ifdef LANCZOS_RESAMPLER
// Blank samples outside the array bounds (we'll deal with the other half later)
for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS - 1; ++i)
sound->samples[i] = 0;
sound->samples += LANCZOS_KERNEL_RADIUS - 1;
#endif
for (size_t i = 0; i < length; ++i)
sound->samples[i] = samples[i] - 0x80; // Convert from unsigned 8-bit PCM to signed
sound->frames = length;
sound->playing = false;
sound->position = 0;
sound->position_subsample = 0;
Mixer_SetSoundFrequency(sound, frequency);
Mixer_SetSoundVolume(sound, 0);
Mixer_SetSoundPan(sound, 0);
sound->next = sound_list_head;
sound_list_head = sound;
return sound;
}
void Mixer_DestroySound(Mixer_Sound *sound)
{
for (Mixer_Sound **sound_pointer = &sound_list_head; *sound_pointer != NULL; sound_pointer = &(*sound_pointer)->next)
{
if (*sound_pointer == sound)
{
*sound_pointer = sound->next;
#ifdef LANCZOS_RESAMPLER
sound->samples -= LANCZOS_KERNEL_RADIUS - 1;
#endif
free(sound->samples);
free(sound);
break;
}
}
}
void Mixer_PlaySound(Mixer_Sound *sound, bool looping)
{
sound->playing = true;
sound->looping = looping;
// Fill the out-of-bounds part of the buffer with
// either blank samples or repeated samples
#ifdef LANCZOS_RESAMPLER
if (looping)
for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i)
sound->samples[sound->frames + i] = sound->samples[i];
else
for (size_t i = 0; i < LANCZOS_KERNEL_RADIUS; ++i)
sound->samples[sound->frames + i] = 0;
#else
sound->samples[sound->frames] = looping ? sound->samples[0] : 0;
#endif
}
void Mixer_StopSound(Mixer_Sound *sound)
{
sound->playing = false;
}
void Mixer_RewindSound(Mixer_Sound *sound)
{
sound->position = 0;
sound->position_subsample = 0;
}
void Mixer_SetSoundFrequency(Mixer_Sound *sound, unsigned int frequency)
{
sound->advance_delta = (frequency << 16) / output_frequency;
}
void Mixer_SetSoundVolume(Mixer_Sound *sound, long volume)
{
sound->volume = MillibelToScale(volume);
sound->volume_l = (sound->pan_l * sound->volume) >> 8;
sound->volume_r = (sound->pan_r * sound->volume) >> 8;
}
void Mixer_SetSoundPan(Mixer_Sound *sound, long pan)
{
sound->pan_l = MillibelToScale(-pan);
sound->pan_r = MillibelToScale(pan);
sound->volume_l = (sound->pan_l * sound->volume) >> 8;
sound->volume_r = (sound->pan_r * sound->volume) >> 8;
}
// Most CPU-intensive function in the game (2/3rd CPU time consumption in my experience), so marked with ATTRIBUTE_HOT so the compiler considers it a hot spot (as it is) when optimizing
ATTRIBUTE_HOT void Mixer_MixSounds(long *stream, size_t frames_total)
{
for (Mixer_Sound *sound = sound_list_head; sound != NULL; sound = sound->next)
{
if (sound->playing)
{
long *stream_pointer = stream;
for (size_t frames_done = 0; frames_done < frames_total; ++frames_done)
{
#ifdef LANCZOS_RESAMPLER
// Perform Lanczos resampling
float output_sample = 0;
for (int i = -LANCZOS_KERNEL_RADIUS + 1; i <= LANCZOS_KERNEL_RADIUS; ++i)
{
const signed char input_sample = sound->samples[sound->position + i];
const float kernel_input = ((float)sound->position_subsample / 0x10000) - i;
if (kernel_input == 0.0f)
{
output_sample += input_sample;
}
else
{
const float nx = 3.14159265358979323846f * kernel_input;
const float nxa = nx / LANCZOS_KERNEL_RADIUS;
output_sample += input_sample * (sin(nx) * sin(nxa) / (nx * nxa));
}
}
// Mix, and apply volume
*stream_pointer++ += (short)(output_sample * sound->volume_l);
*stream_pointer++ += (short)(output_sample * sound->volume_r);
#else
// Perform linear interpolation
const unsigned char interpolation_scale = sound->position_subsample >> 8;
const signed char output_sample = (sound->samples[sound->position] * (0x100 - interpolation_scale)
+ sound->samples[sound->position + 1] * interpolation_scale) >> 8;
// Mix, and apply volume
*stream_pointer++ += output_sample * sound->volume_l;
*stream_pointer++ += output_sample * sound->volume_r;
#endif
// Increment sample
const unsigned long next_position_subsample = sound->position_subsample + sound->advance_delta;
sound->position += next_position_subsample >> 16;
sound->position_subsample = next_position_subsample & 0xFFFF;
// Stop or loop sample once it's reached its end
if (sound->position >= sound->frames)
{
if (sound->looping)
{
sound->position %= sound->frames;
}
else
{
sound->playing = false;
sound->position = 0;
sound->position_subsample = 0;
break;
}
}
}
}
}
}

View file

@ -0,0 +1,120 @@
#include "Backend.h"
#include <stddef.h>
#include <string.h>
#include <string>
#include "SDL.h"
#include "../../Misc.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static void (*parent_callback)(long *stream, size_t frames_total);
static SDL_AudioDeviceID device_id;
static void Callback(void *user_data, Uint8 *stream_uint8, int len)
{
(void)user_data;
short *stream = (short*)stream_uint8;
const size_t frames_total = len / sizeof(short) / 2;
size_t frames_done = 0;
while (frames_done != frames_total)
{
long mix_buffer[0x800 * 2]; // 2 because stereo
size_t subframes = MIN(0x800, frames_total - frames_done);
memset(mix_buffer, 0, subframes * sizeof(long) * 2);
parent_callback(mix_buffer, subframes);
for (size_t i = 0; i < subframes * 2; ++i)
{
if (mix_buffer[i] > 0x7FFF)
*stream++ = 0x7FFF;
else if (mix_buffer[i] < -0x7FFF)
*stream++ = -0x7FFF;
else
*stream++ = mix_buffer[i];
}
frames_done += subframes;
}
}
unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total))
{
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
std::string errorMessage = std::string("'SDL_InitSubSystem(SDL_INIT_AUDIO)' failed: ") + SDL_GetError();
Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", errorMessage.c_str());
return 0;
}
Backend_PrintInfo("Available SDL audio drivers:");
for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i)
Backend_PrintInfo("%s", SDL_GetAudioDriver(i));
SDL_AudioSpec specification;
specification.freq = 48000;
specification.format = AUDIO_S16;
specification.channels = 2;
specification.samples = 0x400; // Roughly 10 milliseconds for 48000Hz
specification.callback = Callback;
specification.userdata = NULL;
SDL_AudioSpec obtained_specification;
device_id = SDL_OpenAudioDevice(NULL, 0, &specification, &obtained_specification, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (device_id == 0)
{
std::string error_message = std::string("'SDL_OpenAudioDevice' failed: ") + SDL_GetError();
Backend_ShowMessageBox("Fatal error (SDL2 audio backend)", error_message.c_str());
return 0;
}
Backend_PrintInfo("Selected SDL audio driver: %s", SDL_GetCurrentAudioDriver());
parent_callback = callback;
return obtained_specification.freq;
}
void SoftwareMixerBackend_Deinit(void)
{
SDL_CloseAudioDevice(device_id);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
bool SoftwareMixerBackend_Start(void)
{
SDL_PauseAudioDevice(device_id, 0);
return true;
}
void SoftwareMixerBackend_LockMixerMutex(void)
{
SDL_LockAudioDevice(device_id);
}
void SoftwareMixerBackend_UnlockMixerMutex(void)
{
SDL_UnlockAudioDevice(device_id);
}
void SoftwareMixerBackend_LockOrganyaMutex(void)
{
SDL_LockAudioDevice(device_id);
}
void SoftwareMixerBackend_UnlockOrganyaMutex(void)
{
SDL_UnlockAudioDevice(device_id);
}

View file

@ -1,4 +1,4 @@
#include "../Audio.h"
#include "Backend.h"
#include <math.h>
#include <stddef.h>
@ -12,16 +12,13 @@
#include <sndcore2/voice.h>
#include <sndcore2/drcvs.h>
#include "SoftwareMixer.h"
#define AUDIO_BUFFERS 2 // Double-buffer
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(x, y, z) MIN(MAX((x), (y)), (z))
static void (*organya_callback)(void);
static unsigned int organya_callback_milliseconds;
static void (*parent_callback)(long *stream, size_t frames_total);
static OSMutex sound_list_mutex;
static OSMutex organya_mutex;
@ -32,51 +29,6 @@ static short *stream_buffers[2];
static long *stream_buffer_long;
static size_t buffer_length;
static unsigned long output_frequency;
static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total)
{
OSLockMutex(&organya_mutex);
if (organya_callback_milliseconds == 0)
{
OSLockMutex(&sound_list_mutex);
Mixer_MixSounds(stream, frames_total);
OSUnlockMutex(&sound_list_mutex);
}
else
{
// Synchronise audio generation with Organya.
// In the original game, Organya ran asynchronously in a separate thread,
// firing off commands to DirectSound in realtime. To match that, we'd
// need a very low-latency buffer, otherwise we'd get mistimed instruments.
// Instead, we can just do this.
unsigned int frames_done = 0;
while (frames_done != frames_total)
{
static unsigned long organya_countdown;
if (organya_countdown == 0)
{
organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames
organya_callback();
}
const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done);
OSLockMutex(&sound_list_mutex);
Mixer_MixSounds(stream + frames_done * 2, frames_to_do);
OSUnlockMutex(&sound_list_mutex);
frames_done += frames_to_do;
organya_countdown -= frames_to_do;
}
}
OSUnlockMutex(&organya_mutex);
}
static void FrameCallback(void)
{
// We use a double-buffer: while the Wii U is busy playing one half of the buffer, we update the other.
@ -96,7 +48,7 @@ static void FrameCallback(void)
memset(stream_buffer_long, 0, buffer_length * sizeof(long) * 2);
// Fill mixer buffer
MixSoundsAndUpdateOrganya(stream_buffer_long, buffer_length);
parent_callback(stream_buffer_long, buffer_length);
// Deinterlate samples, convert them to S16, and write them to the double-buffers
short *left_output_buffer = &stream_buffers[0][buffer_length * last_buffer];
@ -135,7 +87,7 @@ static void FrameCallback(void)
}
}
bool AudioBackend_Init(void)
unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total))
{
if (!AXIsInit())
{
@ -150,9 +102,7 @@ bool AudioBackend_Init(void)
OSInitMutex(&sound_list_mutex);
OSInitMutex(&organya_mutex);
output_frequency = AXGetInputSamplesPerSec();
Mixer_Init(output_frequency);
unsigned long output_frequency = AXGetInputSamplesPerSec();
buffer_length = output_frequency / 100; // 10ms buffer
@ -210,17 +160,17 @@ bool AudioBackend_Init(void)
};
AXSetVoiceOffsets(voices[i], &offs);
AXSetVoiceState(voices[i], AX_VOICE_STATE_PLAYING);
AXVoiceEnd(voices[i]);
}
parent_callback = callback;
// Register the frame callback.
// Apparently, this fires every 3ms - we will use
// it to update the stream buffers when needed.
AXRegisterAppFrameCallback(FrameCallback);
return true;
return output_frequency;
}
AXFreeVoice(voices[0]);
@ -237,10 +187,10 @@ bool AudioBackend_Init(void)
AXQuit();
return false;
return 0;
}
void AudioBackend_Deinit(void)
void SoftwareMixerBackend_Deinit(void)
{
AXRegisterAppFrameCallback(NULL);
@ -255,113 +205,30 @@ void AudioBackend_Deinit(void)
AXQuit();
}
AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
bool SoftwareMixerBackend_Start(void)
{
OSLockMutex(&sound_list_mutex);
AXSetVoiceState(voices[0], AX_VOICE_STATE_PLAYING);
AXSetVoiceState(voices[1], AX_VOICE_STATE_PLAYING);
Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length);
OSUnlockMutex(&sound_list_mutex);
return (AudioBackend_Sound*)sound;
return true;
}
void AudioBackend_DestroySound(AudioBackend_Sound *sound)
void SoftwareMixerBackend_LockMixerMutex(void)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
}
Mixer_DestroySound((Mixer_Sound*)sound);
void SoftwareMixerBackend_UnlockMixerMutex(void)
{
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
Mixer_PlaySound((Mixer_Sound*)sound, looping);
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_StopSound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
Mixer_StopSound((Mixer_Sound*)sound);
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_RewindSound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
Mixer_RewindSound((Mixer_Sound*)sound);
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency);
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
Mixer_SetSoundVolume((Mixer_Sound*)sound, volume);
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan)
{
if (sound == NULL)
return;
OSLockMutex(&sound_list_mutex);
Mixer_SetSoundPan((Mixer_Sound*)sound, pan);
OSUnlockMutex(&sound_list_mutex);
}
void AudioBackend_SetOrganyaCallback(void (*callback)(void))
{
// As far as thread-safety goes - this is guarded by
// `organya_milliseconds`, which is guarded by `organya_mutex`.
organya_callback = callback;
}
void AudioBackend_SetOrganyaTimer(unsigned int milliseconds)
void SoftwareMixerBackend_LockOrganyaMutex(void)
{
OSLockMutex(&organya_mutex);
}
organya_callback_milliseconds = milliseconds;
void SoftwareMixerBackend_UnlockOrganyaMutex(void)
{
OSUnlockMutex(&organya_mutex);
}

View file

@ -0,0 +1,168 @@
#include "Backend.h"
#include <stddef.h>
#include <string.h>
#define MINIAUDIO_IMPLEMENTATION
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MA_NO_WAV
#define MA_NO_FLAC
#define MA_NO_MP3
#define MA_API static
#include "../../../../external/miniaudio.h"
#include "../../Misc.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static void (*parent_callback)(long *stream, size_t frames_total);
static ma_context context;
static ma_device device;
static ma_mutex mutex;
static ma_mutex organya_mutex;
static void Callback(ma_device *device, void *output_stream, const void *input_stream, ma_uint32 frames_total)
{
(void)device;
(void)input_stream;
short *stream = (short*)output_stream;
size_t frames_done = 0;
while (frames_done != frames_total)
{
long mix_buffer[0x800 * 2]; // 2 because stereo
size_t subframes = MIN(0x800, frames_total - frames_done);
memset(mix_buffer, 0, subframes * sizeof(long) * 2);
parent_callback(mix_buffer, subframes);
for (size_t i = 0; i < subframes * 2; ++i)
{
if (mix_buffer[i] > 0x7FFF)
*stream++ = 0x7FFF;
else if (mix_buffer[i] < -0x7FFF)
*stream++ = -0x7FFF;
else
*stream++ = mix_buffer[i];
}
frames_done += subframes;
}
}
unsigned long SoftwareMixerBackend_Init(void (*callback)(long *stream, size_t frames_total))
{
ma_device_config config = ma_device_config_init(ma_device_type_playback);
config.playback.pDeviceID = NULL;
config.playback.format = ma_format_s16;
config.playback.channels = 2;
config.sampleRate = 0; // Let miniaudio decide what sample rate to use
config.dataCallback = Callback;
config.pUserData = NULL;
ma_result return_value;
return_value = ma_context_init(NULL, 0, NULL, &context);
if (return_value == MA_SUCCESS)
{
return_value = ma_device_init(&context, &config, &device);
if (return_value == MA_SUCCESS)
{
return_value = ma_mutex_init(&mutex);
if (return_value == MA_SUCCESS)
{
return_value = ma_mutex_init(&organya_mutex);
if (return_value == MA_SUCCESS)
{
parent_callback = callback;
return device.sampleRate;
}
else
{
Backend_PrintError("Failed to create organya mutex: %s", ma_result_description(return_value));
}
ma_mutex_uninit(&mutex);
}
else
{
Backend_PrintError("Failed to create mutex: %s", ma_result_description(return_value));
}
ma_device_uninit(&device);
}
else
{
Backend_PrintError("Failed to initialize playback device: %s", ma_result_description(return_value));
}
ma_context_uninit(&context);
}
else
{
Backend_PrintError("Failed to initialize context: %s", ma_result_description(return_value));
}
return 0;
}
void SoftwareMixerBackend_Deinit(void)
{
ma_result return_value = ma_device_stop(&device);
if (return_value != MA_SUCCESS)
Backend_PrintError("Failed to stop playback device: %s", ma_result_description(return_value));
ma_mutex_uninit(&organya_mutex);
ma_mutex_uninit(&mutex);
ma_device_uninit(&device);
ma_context_uninit(&context);
}
bool SoftwareMixerBackend_Start(void)
{
ma_result return_value = ma_device_start(&device);
if (return_value != MA_SUCCESS)
{
Backend_PrintError("Failed to start playback device: %s", ma_result_description(return_value));
return false;
}
return true;
}
void SoftwareMixerBackend_LockMixerMutex(void)
{
ma_mutex_lock(&mutex);
}
void SoftwareMixerBackend_UnlockMixerMutex(void)
{
ma_mutex_unlock(&mutex);
}
void SoftwareMixerBackend_LockOrganyaMutex(void)
{
ma_mutex_lock(&organya_mutex);
}
void SoftwareMixerBackend_UnlockOrganyaMutex(void)
{
ma_mutex_unlock(&organya_mutex);
}

View file

@ -1,332 +0,0 @@
#include "../Audio.h"
#include <stddef.h>
#include <string.h>
#define MINIAUDIO_IMPLEMENTATION
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MA_NO_WAV
#define MA_NO_FLAC
#define MA_NO_MP3
#define MA_API static
#include "../../../external/miniaudio.h"
#include "../Misc.h"
#include "SoftwareMixer.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static ma_context context;
static ma_device device;
static ma_mutex mutex;
static ma_mutex organya_mutex;
static unsigned long output_frequency;
static void (*organya_callback)(void);
static unsigned int organya_callback_milliseconds;
static unsigned int organya_sleep_timer;
static void MixSoundsAndUpdateOrganya(long *stream, size_t frames_total)
{
ma_mutex_lock(&organya_mutex);
if (organya_callback_milliseconds == 0)
{
ma_mutex_lock(&mutex);
Mixer_MixSounds(stream, frames_total);
ma_mutex_unlock(&mutex);
}
else
{
// Synchronise audio generation with Organya.
// In the original game, Organya ran asynchronously in a separate thread,
// firing off commands to DirectSound in realtime. To match that, we'd
// need a very low-latency buffer, otherwise we'd get mistimed instruments.
// Instead, we can just do this.
unsigned int frames_done = 0;
// Don't process Organya when it's meant to be sleeping
const unsigned int frames_to_do = MIN(organya_sleep_timer, frames_total - frames_done);
if (frames_to_do != 0)
{
ma_mutex_lock(&mutex);
Mixer_MixSounds(stream, frames_to_do);
ma_mutex_unlock(&mutex);
frames_done += frames_to_do;
organya_sleep_timer -= frames_to_do;
}
while (frames_done != frames_total)
{
static unsigned long organya_countdown;
if (organya_countdown == 0)
{
organya_countdown = (organya_callback_milliseconds * output_frequency) / 1000; // organya_timer is in milliseconds, so convert it to audio frames
organya_callback();
}
const unsigned int frames_to_do = MIN(organya_countdown, frames_total - frames_done);
ma_mutex_lock(&mutex);
Mixer_MixSounds(stream + frames_done * 2, frames_to_do);
ma_mutex_unlock(&mutex);
frames_done += frames_to_do;
organya_countdown -= frames_to_do;
}
}
ma_mutex_unlock(&organya_mutex);
}
static void Callback(ma_device *device, void *output_stream, const void *input_stream, ma_uint32 frames_total)
{
(void)device;
(void)input_stream;
short *stream = (short*)output_stream;
size_t frames_done = 0;
while (frames_done != frames_total)
{
long mix_buffer[0x800 * 2]; // 2 because stereo
size_t subframes = MIN(0x800, frames_total - frames_done);
memset(mix_buffer, 0, subframes * sizeof(long) * 2);
MixSoundsAndUpdateOrganya(mix_buffer, subframes);
for (size_t i = 0; i < subframes * 2; ++i)
{
if (mix_buffer[i] > 0x7FFF)
*stream++ = 0x7FFF;
else if (mix_buffer[i] < -0x7FFF)
*stream++ = -0x7FFF;
else
*stream++ = mix_buffer[i];
}
frames_done += subframes;
}
}
bool AudioBackend_Init(void)
{
ma_device_config config = ma_device_config_init(ma_device_type_playback);
config.playback.pDeviceID = NULL;
config.playback.format = ma_format_s16;
config.playback.channels = 2;
config.sampleRate = 0; // Let miniaudio decide what sample rate to use
config.dataCallback = Callback;
config.pUserData = NULL;
ma_result return_value;
return_value = ma_context_init(NULL, 0, NULL, &context);
if (return_value == MA_SUCCESS)
{
return_value = ma_device_init(&context, &config, &device);
if (return_value == MA_SUCCESS)
{
return_value = ma_mutex_init(&mutex);
if (return_value == MA_SUCCESS)
{
return_value = ma_mutex_init(&organya_mutex);
if (return_value == MA_SUCCESS)
{
return_value = ma_device_start(&device);
if (return_value == MA_SUCCESS)
{
output_frequency = device.sampleRate;
Mixer_Init(device.sampleRate);
return true;
}
else
{
Backend_PrintError("Failed to start playback device: %s", ma_result_description(return_value));
}
ma_mutex_uninit(&organya_mutex);
}
else
{
Backend_PrintError("Failed to create organya mutex: %s", ma_result_description(return_value));
}
ma_mutex_uninit(&mutex);
}
else
{
Backend_PrintError("Failed to create mutex: %s", ma_result_description(return_value));
}
ma_device_uninit(&device);
}
else
{
Backend_PrintError("Failed to initialize playback device: %s", ma_result_description(return_value));
}
ma_context_uninit(&context);
}
else
{
Backend_PrintError("Failed to initialize context: %s", ma_result_description(return_value));
}
return false;
}
void AudioBackend_Deinit(void)
{
ma_result return_value = ma_device_stop(&device);
if (return_value != MA_SUCCESS)
Backend_PrintError("Failed to stop playback device: %s", ma_result_description(return_value));
ma_mutex_uninit(&organya_mutex);
ma_mutex_uninit(&mutex);
ma_device_uninit(&device);
ma_context_uninit(&context);
}
AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length)
{
ma_mutex_lock(&mutex);
Mixer_Sound *sound = Mixer_CreateSound(frequency, samples, length);
ma_mutex_unlock(&mutex);
return (AudioBackend_Sound*)sound;
}
void AudioBackend_DestroySound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_DestroySound((Mixer_Sound*)sound);
ma_mutex_unlock(&mutex);
}
void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_PlaySound((Mixer_Sound*)sound, looping);
ma_mutex_unlock(&mutex);
}
void AudioBackend_StopSound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_StopSound((Mixer_Sound*)sound);
ma_mutex_unlock(&mutex);
}
void AudioBackend_RewindSound(AudioBackend_Sound *sound)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_RewindSound((Mixer_Sound*)sound);
ma_mutex_unlock(&mutex);
}
void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency);
ma_mutex_unlock(&mutex);
}
void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_SetSoundVolume((Mixer_Sound*)sound, volume);
ma_mutex_unlock(&mutex);
}
void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan)
{
if (sound == NULL)
return;
ma_mutex_lock(&mutex);
Mixer_SetSoundPan((Mixer_Sound*)sound, pan);
ma_mutex_unlock(&mutex);
}
void AudioBackend_SetOrganyaCallback(void (*callback)(void))
{
ma_mutex_lock(&organya_mutex);
organya_callback = callback;
ma_mutex_unlock(&organya_mutex);
}
void AudioBackend_SetOrganyaTimer(unsigned int milliseconds)
{
ma_mutex_lock(&organya_mutex);
organya_callback_milliseconds = milliseconds;
ma_mutex_unlock(&organya_mutex);
}
void AudioBackend_SleepOrganya(unsigned int milliseconds)
{
ma_mutex_lock(&organya_mutex);
organya_sleep_timer = (milliseconds * output_frequency) / 1000;
ma_mutex_unlock(&organya_mutex);
}