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:
parent
ab09dc67eb
commit
be6f46fabd
10 changed files with 765 additions and 957 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
||||
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)
|
||||
if (organya_callback_milliseconds == 0)
|
||||
{
|
||||
free(sound);
|
||||
return NULL;
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
Mixer_MixSounds(stream, frames_total);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
// 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)
|
||||
{
|
||||
long *stream_pointer = stream;
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
Mixer_MixSounds(stream, frames_to_do);
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
|
||||
for (size_t frames_done = 0; frames_done < frames_total; ++frames_done)
|
||||
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)
|
||||
{
|
||||
#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_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);
|
||||
|
||||
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;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_DestroySound((Mixer_Sound*)sound);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping)
|
||||
{
|
||||
if (sound == NULL)
|
||||
return;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_PlaySound((Mixer_Sound*)sound, looping);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_StopSound(AudioBackend_Sound *sound)
|
||||
{
|
||||
if (sound == NULL)
|
||||
return;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_StopSound((Mixer_Sound*)sound);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_RewindSound(AudioBackend_Sound *sound)
|
||||
{
|
||||
if (sound == NULL)
|
||||
return;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_RewindSound((Mixer_Sound*)sound);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency)
|
||||
{
|
||||
if (sound == NULL)
|
||||
return;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_SetSoundFrequency((Mixer_Sound*)sound, frequency);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume)
|
||||
{
|
||||
if (sound == NULL)
|
||||
return;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_SetSoundVolume((Mixer_Sound*)sound, volume);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan)
|
||||
{
|
||||
if (sound == NULL)
|
||||
return;
|
||||
|
||||
SoftwareMixerBackend_LockMixerMutex();
|
||||
|
||||
Mixer_SetSoundPan((Mixer_Sound*)sound, pan);
|
||||
|
||||
SoftwareMixerBackend_UnlockMixerMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_SetOrganyaCallback(void (*callback)(void))
|
||||
{
|
||||
SoftwareMixerBackend_LockOrganyaMutex();
|
||||
|
||||
organya_callback = callback;
|
||||
|
||||
SoftwareMixerBackend_UnlockOrganyaMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_SetOrganyaTimer(unsigned int milliseconds)
|
||||
{
|
||||
SoftwareMixerBackend_LockOrganyaMutex();
|
||||
|
||||
organya_callback_milliseconds = milliseconds;
|
||||
|
||||
SoftwareMixerBackend_UnlockOrganyaMutex();
|
||||
}
|
||||
|
||||
void AudioBackend_SleepOrganya(unsigned int milliseconds)
|
||||
{
|
||||
SoftwareMixerBackend_LockOrganyaMutex();
|
||||
|
||||
organya_sleep_timer = (milliseconds * output_frequency) / 1000;
|
||||
|
||||
SoftwareMixerBackend_UnlockOrganyaMutex();
|
||||
}
|
||||
|
|
14
src/Backends/Audio/SoftwareMixer/Backend.h
Normal file
14
src/Backends/Audio/SoftwareMixer/Backend.h
Normal 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);
|
236
src/Backends/Audio/SoftwareMixer/Mixer.cpp
Normal file
236
src/Backends/Audio/SoftwareMixer/Mixer.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
src/Backends/Audio/SoftwareMixer/SDL2.cpp
Normal file
120
src/Backends/Audio/SoftwareMixer/SDL2.cpp
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
168
src/Backends/Audio/SoftwareMixer/miniaudio.cpp
Normal file
168
src/Backends/Audio/SoftwareMixer/miniaudio.cpp
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Add table
Reference in a new issue