/* RetroArch - A frontend for libretro. * Copyright (C) 2017 - Andre Leiradella * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see <http://www.gnu.org/licenses/>. */ #include "audio_mixer.h" #include "audio_driver.h" #include <audio/audio_resampler.h> #include <streams/file_stream.h> #include <formats/rwav.h> #include <memalign.h> #include <stdlib.h> #include <string.h> #include <math.h> #ifdef HAVE_CONFIG_H #include "../config.h" #endif #ifdef HAVE_STB_VORBIS #define STB_VORBIS_NO_PUSHDATA_API #define STB_VORBIS_NO_STDIO #define STB_VORBIS_NO_CRT #include "../deps/stb/stb_vorbis.h" #endif #define AUDIO_MIXER_MAX_VOICES 8 #define AUDIO_MIXER_TEMP_OGG_BUFFER 8192 #define AUDIO_MIXER_TYPE_NONE 0 #define AUDIO_MIXER_TYPE_WAV 1 #define AUDIO_MIXER_TYPE_OGG 2 struct audio_mixer_sound_t { unsigned type; union { struct { /* wav */ unsigned frames; const float* pcm; } wav; #ifdef HAVE_STB_VORBIS struct { /* ogg */ unsigned size; const void* data; } ogg; #endif } types; }; struct audio_mixer_voice_t { unsigned type; bool repeat; float volume; audio_mixer_sound_t* sound; audio_mixer_stop_cb_t stop_cb; union { struct { /* wav */ unsigned position; } wav; #ifdef HAVE_STB_VORBIS struct { /* ogg */ unsigned position; unsigned samples; stb_vorbis* stream; float* buffer; unsigned buf_samples; void* resampler_data; float ratio; const retro_resampler_t* resampler; } ogg; #endif } types; }; static audio_mixer_voice_t s_voices[AUDIO_MIXER_MAX_VOICES]; static unsigned s_rate = 0; static bool wav2float(const rwav_t* wav, float** pcm, size_t* samples_out) { size_t i; float sample = 0.0f; const uint8_t* u8 = NULL; const int16_t* s16 = NULL; float* f = NULL; /* Allocate on a 16-byte boundary, and pad to a multiple of 16 bytes */ *samples_out = wav->numsamples * 2; f = (float*)memalign_alloc(16, ((*samples_out + 15) & ~15) * sizeof(float)); if (!f) return false; *pcm = f; if (wav->numchannels == 1) { if (wav->bitspersample == 8) { u8 = (const uint8_t*)wav->samples; for (i = wav->numsamples; i != 0; i--) { sample = (float)*u8++ / 255.0f; sample = sample * 2.0f - 1.0f; *f++ = sample; *f++ = sample; } } else { s16 = (const int16_t*)wav->samples; for (i = wav->numsamples; i != 0; i--) { sample = (float)((int)*s16++ + 32768) / 65535.0f; sample = sample * 2.0f - 1.0f; *f++ = sample; *f++ = sample; } } } else if (wav->numchannels == 2) { if (wav->bitspersample == 8) { u8 = (const uint8_t*)wav->samples; for (i = wav->numsamples; i != 0; i--) { sample = (float)*u8++ / 255.0f; sample = sample * 2.0f - 1.0f; *f++ = sample; sample = (float)*u8++ / 255.0f; sample = sample * 2.0f - 1.0f; *f++ = sample; } } else { s16 = (const int16_t*)wav->samples; for (i = wav->numsamples; i != 0; i--) { sample = (float)((int)*s16++ + 32768) / 65535.0f; sample = sample * 2.0f - 1.0f; *f++ = sample; sample = (float)((int)*s16++ + 32768) / 65535.0f; sample = sample * 2.0f - 1.0f; *f++ = sample; } } } return true; } static bool one_shot_resample(const float* in, size_t samples_in, unsigned rate, float** out, size_t* samples_out) { struct resampler_data info; void* data = NULL; const retro_resampler_t* resampler = NULL; float ratio = (double)s_rate / (double)rate; if (!retro_resampler_realloc(&data, &resampler, NULL, ratio)) return false; /* * Allocate on a 16-byte boundary, and pad to a multiple of 16 bytes. We * add four more samples in the formula below just as safeguard, because * resampler->process sometimes reports more output samples than the * formula below calculates. Ideally, audio resamplers should have a * function to return the number of samples they will output given a * count of input samples. */ *samples_out = samples_in * ratio + 4; *out = (float*)memalign_alloc(16, ((*samples_out + 15) & ~15) * sizeof(float)); if (*out == NULL) return false; info.data_in = in; info.data_out = *out; info.input_frames = samples_in / 2; info.output_frames = 0; info.ratio = ratio; resampler->process(data, &info); resampler->free(data); return true; } void audio_mixer_init(unsigned rate) { unsigned i; s_rate = rate; for (i = 0; i < AUDIO_MIXER_MAX_VOICES; i++) s_voices[i].type = AUDIO_MIXER_TYPE_NONE; } void audio_mixer_done(void) { unsigned i; for (i = 0; i < AUDIO_MIXER_MAX_VOICES; i++) s_voices[i].type = AUDIO_MIXER_TYPE_NONE; } audio_mixer_sound_t* audio_mixer_load_wav(const char* path) { /* WAV data */ rwav_t wav; /* Raw WAV bytes */ void* buffer = NULL; ssize_t size = 0; /* WAV samples converted to float */ float* pcm = NULL; float* resampled = NULL; size_t samples = 0; /* Result */ audio_mixer_sound_t* sound = NULL; if (filestream_read_file(path, &buffer, &size) == 0) return NULL; if (rwav_load(&wav, buffer, size) != RWAV_ITERATE_DONE) { free(buffer); return NULL; } free(buffer); if (!wav2float(&wav, &pcm, &samples)) return NULL; if (wav.samplerate != s_rate) { if (!one_shot_resample(pcm, samples, wav.samplerate, &resampled, &samples)) return NULL; memalign_free((void*)pcm); pcm = resampled; } sound = (audio_mixer_sound_t*)malloc(sizeof(audio_mixer_sound_t)); if (!sound) { memalign_free((void*)pcm); return NULL; } sound->type = AUDIO_MIXER_TYPE_WAV; sound->types.wav.frames = (unsigned)(samples / 2); sound->types.wav.pcm = pcm; rwav_free(&wav); return sound; } audio_mixer_sound_t* audio_mixer_load_ogg(const char* path) { #ifdef HAVE_STB_VORBIS ssize_t size; void* buffer = NULL; audio_mixer_sound_t* sound = NULL; if (filestream_read_file(path, &buffer, &size) == 0) return NULL; sound = (audio_mixer_sound_t*)malloc(sizeof(audio_mixer_sound_t)); if (!sound) { free(buffer); return NULL; } sound->type = AUDIO_MIXER_TYPE_OGG; sound->types.ogg.size = size; sound->types.ogg.data = buffer; return sound; #else return NULL; #endif } void audio_mixer_destroy(audio_mixer_sound_t* sound) { switch (sound->type) { case AUDIO_MIXER_TYPE_WAV: memalign_free((void*)sound->types.wav.pcm); break; case AUDIO_MIXER_TYPE_OGG: #ifdef HAVE_STB_VORBIS memalign_free((void*)sound->types.ogg.data); #endif break; } free(sound); } static bool audio_mixer_play_wav(audio_mixer_sound_t* sound, audio_mixer_voice_t* voice, bool repeat, float volume, audio_mixer_stop_cb_t stop_cb) { voice->type = AUDIO_MIXER_TYPE_WAV; voice->repeat = repeat; voice->volume = volume; voice->sound = sound; voice->stop_cb = stop_cb; voice->types.wav.position = 0; return true; } #ifdef HAVE_STB_VORBIS static bool audio_mixer_play_ogg( audio_mixer_sound_t* sound, audio_mixer_voice_t* voice, bool repeat, float volume, audio_mixer_stop_cb_t stop_cb) { stb_vorbis_info info; int res = 0; float ratio = 0.0f; unsigned samples = 0; voice->repeat = repeat; voice->volume = volume; voice->sound = sound; voice->stop_cb = stop_cb; voice->types.ogg.stream = stb_vorbis_open_memory( (const unsigned char*)sound->types.ogg.data, sound->types.ogg.size, &res, NULL); if (!voice->types.ogg.stream) return false; info = stb_vorbis_get_info(voice->types.ogg.stream); /* Only stereo supported for now */ if (info.channels != 2) { stb_vorbis_close(voice->types.ogg.stream); return false; } if (info.sample_rate != s_rate) { voice->types.ogg.ratio = ratio = (double)s_rate / (double)info.sample_rate; if (!retro_resampler_realloc(&voice->types.ogg.resampler_data, &voice->types.ogg.resampler, NULL, ratio)) { stb_vorbis_close(voice->types.ogg.stream); return false; } } samples = voice->types.ogg.buf_samples = (unsigned)(AUDIO_MIXER_TEMP_OGG_BUFFER * ratio); voice->types.ogg.buffer = (float*)memalign_alloc(16, ((samples + 15) & ~15) * sizeof(float)); if (!voice->types.ogg.buffer) { voice->types.ogg.resampler->free(voice->types.ogg.resampler_data); stb_vorbis_close(voice->types.ogg.stream); return false; } voice->type = AUDIO_MIXER_TYPE_OGG; voice->types.ogg.position = voice->types.ogg.samples = 0; return true; } #endif audio_mixer_voice_t* audio_mixer_play(audio_mixer_sound_t* sound, bool repeat, float volume, audio_mixer_stop_cb_t stop_cb) { unsigned i; audio_mixer_voice_t* voice = NULL; bool res = false; for (i = 0, voice = s_voices; i < AUDIO_MIXER_MAX_VOICES; i++, voice++) { if (voice->type == AUDIO_MIXER_TYPE_NONE) { switch (sound->type) { case AUDIO_MIXER_TYPE_WAV: res = audio_mixer_play_wav(sound, voice, repeat, volume, stop_cb); break; case AUDIO_MIXER_TYPE_OGG: #ifdef HAVE_STB_VORBIS res = audio_mixer_play_ogg(sound, voice, repeat, volume, stop_cb); #endif break; } break; } } if (res) return voice; return NULL; } void audio_mixer_stop(audio_mixer_voice_t* voice) { voice->stop_cb(voice, AUDIO_MIXER_SOUND_STOPPED); } static void mix_wav(float* buffer, size_t num_frames, audio_mixer_voice_t* voice) { int i; unsigned buf_free = (unsigned)(num_frames * 2); const audio_mixer_sound_t* sound = voice->sound; unsigned pcm_available = sound->types.wav.frames * 2 - voice->types.wav.position; const float* pcm = sound->types.wav.pcm + voice->types.wav.position; float volume = voice->volume; again: if (pcm_available < buf_free) { for (i = pcm_available; i != 0; i--) *buffer++ += *pcm++ * volume; if (voice->repeat) { if (voice->stop_cb) voice->stop_cb(voice, AUDIO_MIXER_SOUND_REPEATED); buf_free -= pcm_available; pcm_available = sound->types.wav.frames * 2; pcm = sound->types.wav.pcm; voice->types.wav.position = 0; goto again; } if (voice->stop_cb) voice->stop_cb(voice, AUDIO_MIXER_SOUND_FINISHED); voice->type = AUDIO_MIXER_TYPE_NONE; } else { for (i = buf_free; i != 0; i--) *buffer++ += *pcm++ * volume; voice->types.wav.position += buf_free; } } #ifdef HAVE_STB_VORBIS static void mix_ogg(float* buffer, size_t num_frames, audio_mixer_voice_t* voice) { int i; float temp_buffer[AUDIO_MIXER_TEMP_OGG_BUFFER]; struct resampler_data info; unsigned buf_free = num_frames * 2; unsigned temp_samples = 0; float volume = voice->volume; float* pcm = NULL; #if 0 const audio_mixer_sound_t* sound = voice->sound; #endif if (voice->types.ogg.position == voice->types.ogg.samples) { again: temp_samples = stb_vorbis_get_samples_float_interleaved( voice->types.ogg.stream, 2, temp_buffer, AUDIO_MIXER_TEMP_OGG_BUFFER) * 2; if (temp_samples == 0) { if (voice->repeat) { if (voice->stop_cb) voice->stop_cb(voice, AUDIO_MIXER_SOUND_REPEATED); stb_vorbis_seek_start(voice->types.ogg.stream); goto again; } else { if (voice->stop_cb) voice->stop_cb(voice, AUDIO_MIXER_SOUND_FINISHED); voice->type = AUDIO_MIXER_TYPE_NONE; return; } } info.data_in = temp_buffer; info.data_out = voice->types.ogg.buffer; info.input_frames = temp_samples / 2; info.output_frames = 0; info.ratio = voice->types.ogg.ratio; voice->types.ogg.resampler->process(voice->types.ogg.resampler_data, &info); voice->types.ogg.position = 0; voice->types.ogg.samples = voice->types.ogg.buf_samples; } pcm = voice->types.ogg.buffer + voice->types.ogg.position; if (voice->types.ogg.samples < buf_free) { for (i = voice->types.ogg.samples; i != 0; i--) *buffer++ += *pcm++ * volume; buf_free -= voice->types.ogg.samples; goto again; } else { int i; for (i = buf_free; i != 0; --i ) *buffer++ += *pcm++ * volume; voice->types.ogg.position += buf_free; voice->types.ogg.samples -= buf_free; } } #endif void audio_mixer_mix(float* buffer, size_t num_frames) { unsigned i; size_t j = 0; float* sample = NULL; audio_mixer_voice_t* voice = NULL; for (i = 0, voice = s_voices; i < AUDIO_MIXER_MAX_VOICES; i++, voice++) { if (voice->type == AUDIO_MIXER_TYPE_WAV) mix_wav(buffer, num_frames, voice); #ifdef HAVE_STB_VORBIS else if (voice->type == AUDIO_MIXER_TYPE_OGG) mix_ogg(buffer, num_frames, voice); #endif } for (j = 0, sample = buffer; j < num_frames; j++, sample++) { if (*sample < -1.0f) *sample = -1.0f; else if (*sample > 1.0f) *sample = 1.0f; } }