// Released under the MIT licence. // See LICENCE.txt for details. // This darned thing doesn't work - sounds just randomly refuse to play, // particularly the ones used by Organya. // If anyone could figure out what causes this, that would be great. #include "../Audio.h" #include #include #include #include #include #include #include #include #include #include #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)) struct AudioBackend_Sound { signed char *samples; size_t length; AXVoice *voice; unsigned int frequency; unsigned short volume; unsigned short pan_l; unsigned short pan_r; struct AudioBackend_Sound *next; }; static void (*organya_callback)(void); static unsigned int organya_milliseconds; static unsigned long ticks_per_second; static OSMutex sound_list_mutex; static OSMutex organya_mutex; static AudioBackend_Sound *sound_list_head; static void CullVoices(void) { // Free any voices that aren't playing anymore OSLockMutex(&sound_list_mutex); for (AudioBackend_Sound *sound = sound_list_head; sound != NULL; sound = sound->next) { if (sound->voice != NULL) { if (!AXIsVoiceRunning(sound->voice)) { AXVoiceBegin(sound->voice); AXFreeVoice(sound->voice); AXVoiceEnd(sound->voice); sound->voice = NULL; } } } OSUnlockMutex(&sound_list_mutex); } static double MillibelToScale(long volume) { // Volume is in hundredths of a decibel, from 0 to -10000 volume = CLAMP(volume, -10000, 0); return pow(10.0, volume / 2000.0); } static unsigned long GetTicksMilliseconds(void) { static uint64_t accumulator; static unsigned long last_tick; unsigned long current_tick = OSGetTick(); accumulator += current_tick - last_tick; last_tick = current_tick; return (accumulator * 1000) / ticks_per_second; } static int ThreadFunction(int argc, const char *argv[]) { for (;;) { OSTestThreadCancel(); OSLockMutex(&organya_mutex); if (organya_milliseconds == 0) { OSUnlockMutex(&organya_mutex); // Do nothing OSSleepTicks(ticks_per_second / 1000); } else { OSUnlockMutex(&organya_mutex); // Update Organya static unsigned long next_ticks; for (;;) { unsigned long ticks = GetTicksMilliseconds(); if (ticks >= next_ticks) break; OSSleepTicks(ticks_per_second / 1000); } OSLockMutex(&organya_mutex); next_ticks += organya_milliseconds; OSUnlockMutex(&organya_mutex); OSLockMutex(&sound_list_mutex); organya_callback(); OSUnlockMutex(&sound_list_mutex); } } return 0; } bool AudioBackend_Init(void) { if (!AXIsInit()) { AXInitParams initparams = { .renderer = AX_INIT_RENDERER_48KHZ, .pipeline = AX_INIT_PIPELINE_SINGLE, }; AXInitWithParams(&initparams); } ticks_per_second = OSGetSystemInfo()->busClockSpeed / 4; OSInitMutex(&sound_list_mutex); OSInitMutex(&organya_mutex); OSRunThread(OSGetDefaultThread(0), ThreadFunction, 0, NULL); return true; } void AudioBackend_Deinit(void) { OSCancelThread(OSGetDefaultThread(0)); OSJoinThread(OSGetDefaultThread(0), NULL); AXQuit(); } AudioBackend_Sound* AudioBackend_CreateSound(unsigned int frequency, const unsigned char *samples, size_t length) { AudioBackend_Sound *sound = (AudioBackend_Sound*)malloc(sizeof(AudioBackend_Sound)); if (sound != NULL) { signed char *samples_copy = (signed char*)malloc(length); if (samples_copy != NULL) { // Convert to signed for (size_t i = 0; i < length; ++i) samples_copy[i] = samples[i] - 0x80; DCStoreRange(samples_copy, length); sound->samples = samples_copy; sound->length = length; sound->voice = NULL; sound->frequency = frequency; sound->volume = 0x8000; sound->pan_l = 0x8000; sound->pan_r = 0x8000; OSLockMutex(&sound_list_mutex); sound->next = sound_list_head; sound_list_head = sound; OSUnlockMutex(&sound_list_mutex); return sound; } free(sound); } return NULL; } void AudioBackend_DestroySound(AudioBackend_Sound *sound) { if (sound == NULL) return; OSLockMutex(&sound_list_mutex); // Unhook sound from the linked-list for (AudioBackend_Sound **sound_pointer = &sound_list_head; *sound_pointer != NULL; sound_pointer = &(*sound_pointer)->next) { if (*sound_pointer == sound) { *sound_pointer = sound->next; break; } } OSUnlockMutex(&sound_list_mutex); if (sound->voice != NULL) { AXVoiceBegin(sound->voice); AXFreeVoice(sound->voice); AXVoiceEnd(sound->voice); } free(sound->samples); free(sound); } void AudioBackend_PlaySound(AudioBackend_Sound *sound, bool looping) { if (sound == NULL) return; CullVoices(); OSLockMutex(&sound_list_mutex); if (sound->voice == NULL) { AXVoice *voice = AXAcquireVoice(31, NULL, NULL); if (voice != NULL) { AXVoiceBegin(voice); AXSetVoiceType(voice, 0); AXVoiceVeData vol = {.volume = sound->volume}; AXSetVoiceVe(voice, &vol); AXVoiceDeviceMixData mix_data[6]; memset(mix_data, 0, sizeof(mix_data)); mix_data[0].bus[0].volume = sound->pan_l; mix_data[1].bus[0].volume = sound->pan_r; AXSetVoiceDeviceMix(voice, AX_DEVICE_TYPE_DRC, 0, mix_data); AXSetVoiceDeviceMix(voice, AX_DEVICE_TYPE_TV, 0, mix_data); float srcratio = (float)sound->frequency / (float)AXGetInputSamplesPerSec(); AXSetVoiceSrcRatio(voice, srcratio); AXSetVoiceSrcType(voice, AX_VOICE_SRC_TYPE_LINEAR); AXVoiceOffsets offs; offs.dataType = AX_VOICE_FORMAT_LPCM8; offs.endOffset = sound->length; offs.loopingEnabled = AX_VOICE_LOOP_DISABLED; offs.loopOffset = 0; offs.currentOffset = 0; offs.data = sound->samples; AXSetVoiceOffsets(voice, &offs); AXVoiceEnd(voice); sound->voice = voice; } } if (sound->voice != NULL) { AXVoiceBegin(sound->voice); AXSetVoiceLoop(sound->voice, looping ? AX_VOICE_LOOP_ENABLED : AX_VOICE_LOOP_DISABLED); AXSetVoiceState(sound->voice, AX_VOICE_STATE_PLAYING); AXVoiceEnd(sound->voice); } OSUnlockMutex(&sound_list_mutex); } void AudioBackend_StopSound(AudioBackend_Sound *sound) { if (sound == NULL) return; OSLockMutex(&sound_list_mutex); if (sound->voice != NULL) { AXVoiceBegin(sound->voice); AXSetVoiceState(sound->voice, AX_VOICE_STATE_STOPPED); AXVoiceEnd(sound->voice); } OSUnlockMutex(&sound_list_mutex); } void AudioBackend_RewindSound(AudioBackend_Sound *sound) { if (sound == NULL) return; OSLockMutex(&sound_list_mutex); if (sound->voice != NULL) { AXVoiceBegin(sound->voice); AXSetVoiceCurrentOffset(sound->voice, 0); AXVoiceEnd(sound->voice); } OSUnlockMutex(&sound_list_mutex); } void AudioBackend_SetSoundFrequency(AudioBackend_Sound *sound, unsigned int frequency) { if (sound == NULL) return; OSLockMutex(&sound_list_mutex); sound->frequency = frequency; if (sound->voice != NULL) { AXVoiceBegin(sound->voice); float srcratio = (float)frequency / (float)AXGetInputSamplesPerSec(); AXSetVoiceSrcRatio(sound->voice, srcratio); AXVoiceEnd(sound->voice); } OSUnlockMutex(&sound_list_mutex); } void AudioBackend_SetSoundVolume(AudioBackend_Sound *sound, long volume) { if (sound == NULL) return; OSLockMutex(&sound_list_mutex); sound->volume = (unsigned short)(0x8000 * MillibelToScale(volume)); if (sound->voice != NULL) { AXVoiceBegin(sound->voice); AXVoiceVeData vol = {.volume = sound->volume}; AXSetVoiceVe(sound->voice, &vol); AXVoiceEnd(sound->voice); } OSUnlockMutex(&sound_list_mutex); } void AudioBackend_SetSoundPan(AudioBackend_Sound *sound, long pan) { if (sound == NULL) return; OSLockMutex(&sound_list_mutex); sound->pan_l = (unsigned short)(0x8000 * MillibelToScale(-pan)); sound->pan_r = (unsigned short)(0x8000 * MillibelToScale(pan)); if (sound->voice != NULL) { AXVoiceBegin(sound->voice); AXVoiceDeviceMixData mix_data[6]; memset(mix_data, 0, sizeof(mix_data)); mix_data[0].bus[0].volume = sound->pan_l; mix_data[1].bus[0].volume = sound->pan_r; AXSetVoiceDeviceMix(sound->voice, AX_DEVICE_TYPE_DRC, 0, mix_data); AXSetVoiceDeviceMix(sound->voice, AX_DEVICE_TYPE_TV, 0, mix_data); AXVoiceEnd(sound->voice); } 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) { OSLockMutex(&organya_mutex); organya_milliseconds = milliseconds; OSUnlockMutex(&organya_mutex); }