mirror of
https://github.com/Rosalie241/RMG.git
synced 2026-09-06 04:14:33 +02:00
279 lines
9.2 KiB
C++
279 lines
9.2 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* Mupen64plus-sdl-audio - sdl_backend.c *
|
|
* Mupen64Plus homepage: https://mupen64plus.org/ *
|
|
* Copyright (C) 2017 Bobby Smiles *
|
|
* *
|
|
* This program 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 Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program 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 this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3/SDL_audio.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "Resamplers/resamplers.hpp"
|
|
#include "main.hpp"
|
|
|
|
#include <RMG-Core/m64p/api/m64p_types.h>
|
|
|
|
#include <RMG-Core/Settings.hpp>
|
|
#include <RMG-Core/Netplay.hpp>
|
|
|
|
/* number of bytes per sample */
|
|
#define N64_SAMPLE_BYTES 4
|
|
#define SDL_SAMPLE_BYTES 4
|
|
|
|
struct sdl_backend
|
|
{
|
|
/* Audio Stream */
|
|
SDL_AudioStream* stream;
|
|
|
|
/* Primary Buffer */
|
|
void* primary_buffer;
|
|
|
|
/* Resampling buffer */
|
|
void* resample_buffer;
|
|
|
|
/* Size of buffers */
|
|
size_t buffers_size;
|
|
|
|
unsigned int frequency;
|
|
|
|
unsigned int speed_factor;
|
|
|
|
unsigned int swap_channels;
|
|
|
|
unsigned int error;
|
|
|
|
float volume;
|
|
|
|
/* Resampler */
|
|
void* resampler;
|
|
const struct resampler_interface* iresampler;
|
|
};
|
|
|
|
/* SDL_AudioFormat.format format specifier and args builder */
|
|
#define AFMT_FMTSPEC "%c%d%s"
|
|
#define AFMT_ARGS(x) \
|
|
((SDL_AUDIO_ISFLOAT(x)) ? 'F' : (SDL_AUDIO_ISSIGNED(x)) ? 'S' : 'U'), \
|
|
SDL_AUDIO_BITSIZE(x), \
|
|
SDL_AUDIO_ISBIGENDIAN(x) ? "BE" : "LE"
|
|
|
|
static void sdl_init_audio_device(struct sdl_backend* sdl_backend)
|
|
{
|
|
SDL_AudioSpec spec;
|
|
|
|
sdl_backend->error = 0;
|
|
|
|
if (SDL_WasInit(SDL_INIT_AUDIO))
|
|
{
|
|
DebugMessage(M64MSG_VERBOSE, "sdl_init_audio_device(): SDL Audio sub-system already initialized.");
|
|
SDL_DestroyAudioStream(sdl_backend->stream);
|
|
}
|
|
else
|
|
{
|
|
if (!SDL_Init(SDL_INIT_AUDIO))
|
|
{
|
|
DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem.");
|
|
sdl_backend->error = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem...");
|
|
|
|
memset(&spec, 0, sizeof(spec));
|
|
spec.freq = sdl_backend->frequency;
|
|
spec.format = SDL_AUDIO_S16LE;
|
|
spec.channels = 2;
|
|
|
|
/* Open the audio device */
|
|
sdl_backend->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
|
|
if (sdl_backend->stream == NULL)
|
|
{
|
|
DebugMessage(M64MSG_ERROR, "Couldn't open audio stream: %s", SDL_GetError());
|
|
sdl_backend->error = 1;
|
|
return;
|
|
}
|
|
|
|
DebugMessage(M64MSG_VERBOSE, "Frequency: %i", spec.freq);
|
|
DebugMessage(M64MSG_VERBOSE, "Format: " AFMT_FMTSPEC, AFMT_ARGS(spec.format));
|
|
DebugMessage(M64MSG_VERBOSE, "Channels: %i", spec.channels);
|
|
|
|
/* start audio stream */
|
|
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(sdl_backend->stream));
|
|
}
|
|
|
|
static void release_audio_device(struct sdl_backend* sdl_backend)
|
|
{
|
|
if (SDL_WasInit(SDL_INIT_AUDIO)) {
|
|
SDL_DestroyAudioStream(sdl_backend->stream);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
}
|
|
}
|
|
|
|
struct sdl_backend* init_sdl_backend(void)
|
|
{
|
|
/* allocate memory for sdl_backend */
|
|
struct sdl_backend* sdl_backend = (struct sdl_backend*)malloc(sizeof(*sdl_backend));
|
|
if (sdl_backend == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* reset sdl_backend */
|
|
memset(sdl_backend, 0, sizeof(*sdl_backend));
|
|
|
|
/* instanciate resampler */
|
|
std::string resampler_id = CoreSettingsGetStringValue(SettingsID::Audio_Resampler);
|
|
void* resampler = nullptr;
|
|
const struct resampler_interface* iresampler = get_iresampler(resampler_id.c_str(), &resampler);
|
|
if (iresampler == nullptr) {
|
|
free(sdl_backend);
|
|
return nullptr;
|
|
}
|
|
|
|
sdl_backend->frequency = CoreSettingsGetIntValue(SettingsID::Audio_DefaultFrequency);
|
|
sdl_backend->swap_channels = CoreSettingsGetBoolValue(SettingsID::Audio_SwapChannels);
|
|
sdl_backend->speed_factor = 100;
|
|
sdl_backend->resampler = resampler;
|
|
sdl_backend->iresampler = iresampler;
|
|
|
|
sdl_init_audio_device(sdl_backend);
|
|
|
|
return sdl_backend;
|
|
}
|
|
|
|
void sdl_apply_settings(struct sdl_backend* sdl_backend)
|
|
{
|
|
sdl_backend->frequency = CoreSettingsGetIntValue(SettingsID::Audio_DefaultFrequency);
|
|
sdl_backend->swap_channels = CoreSettingsGetBoolValue(SettingsID::Audio_SwapChannels);
|
|
}
|
|
|
|
void release_sdl_backend(struct sdl_backend* sdl_backend)
|
|
{
|
|
if (sdl_backend == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (sdl_backend->error == 0) {
|
|
release_audio_device(sdl_backend);
|
|
}
|
|
|
|
/* release primary buffer */
|
|
if (sdl_backend->primary_buffer != nullptr) {
|
|
free(sdl_backend->primary_buffer);
|
|
}
|
|
|
|
/* release mix buffer */
|
|
if (sdl_backend->resample_buffer != nullptr) {
|
|
free(sdl_backend->resample_buffer);
|
|
}
|
|
|
|
/* release resampler */
|
|
sdl_backend->iresampler->release(sdl_backend->resampler);
|
|
|
|
/* release sdl backend */
|
|
free(sdl_backend);
|
|
}
|
|
|
|
void sdl_set_frequency(struct sdl_backend* sdl_backend, unsigned int frequency)
|
|
{
|
|
if (sdl_backend->error != 0)
|
|
return;
|
|
|
|
sdl_backend->frequency = frequency;
|
|
sdl_init_audio_device(sdl_backend);
|
|
}
|
|
|
|
void sdl_push_samples(struct sdl_backend* sdl_backend, const void* src, size_t size)
|
|
{
|
|
if (sdl_backend->error != 0)
|
|
return;
|
|
|
|
// taken from https://github.com/gopher64/gopher64/blob/f3271cba63571d4d42c84c4c2db891af125b6f8d/src/ui/audio.rs
|
|
double audioQueued = (double)SDL_GetAudioStreamQueued(sdl_backend->stream);
|
|
double acceptableLatency = ((double)sdl_backend->frequency * 0.2) * 4.0;
|
|
if (audioQueued >= acceptableLatency)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* truncate to full samples */
|
|
if (size & 0x3) {
|
|
DebugMessage(M64MSG_VERBOSE, "sdl_push_samples: pushing non full samples: %zu bytes !", size);
|
|
}
|
|
size = (size / 4) * 4;
|
|
|
|
/* resize buffers when required */
|
|
if (size > sdl_backend->buffers_size)
|
|
{
|
|
sdl_backend->primary_buffer = realloc(sdl_backend->primary_buffer, size);
|
|
sdl_backend->resample_buffer = realloc(sdl_backend->resample_buffer, size);
|
|
|
|
sdl_backend->buffers_size = size;
|
|
}
|
|
|
|
/* Confusing logic but, for LittleEndian host using memcpy will result in swapped channels,
|
|
* whereas the other branch will result in non-swapped channels.
|
|
* For BigEndian host this logic is inverted, memcpy will result in non swapped channels
|
|
* and the other branch will result in swapped channels.
|
|
*
|
|
* This is due to the fact that the core stores 32bit words in native order in RDRAM.
|
|
* For instance N64 bytes "Lh Ll Rh Rl" will be stored as "Rl Rh Ll Lh" on LittleEndian host
|
|
* and therefore should the non-memcpy path to get non swapped channels,
|
|
* whereas on BigEndian host the bytes will be stored as "Lh Ll Rh Rl" and therefore
|
|
* memcpy path results in the non-swapped channels outcome.
|
|
*/
|
|
if (sdl_backend->swap_channels ^ (SDL_BYTEORDER == SDL_BIG_ENDIAN)) {
|
|
memcpy(sdl_backend->primary_buffer, src, size);
|
|
}
|
|
else {
|
|
size_t i;
|
|
for (i = 0 ; i < size ; i += 4 )
|
|
{
|
|
memcpy((unsigned char*)sdl_backend->primary_buffer + i + 0, (const unsigned char*)src + i + 2, 2); /* Left */
|
|
memcpy((unsigned char*)sdl_backend->primary_buffer + i + 2, (const unsigned char*)src + i + 0, 2); /* Right */
|
|
}
|
|
}
|
|
|
|
/* resample audio */
|
|
sdl_backend->iresampler->resample(sdl_backend->resampler,
|
|
sdl_backend->primary_buffer, size,
|
|
sdl_backend->frequency,
|
|
sdl_backend->resample_buffer, size,
|
|
sdl_backend->frequency);
|
|
|
|
/* push audio buffer to SDL */
|
|
SDL_PutAudioStreamData(sdl_backend->stream, sdl_backend->resample_buffer, size);
|
|
}
|
|
|
|
void sdl_set_speed_factor(struct sdl_backend* sdl_backend, unsigned int speed_factor)
|
|
{
|
|
if (speed_factor < 10 || speed_factor > 300)
|
|
return;
|
|
|
|
sdl_backend->speed_factor = speed_factor;
|
|
}
|
|
|
|
void sdl_apply_volume(struct sdl_backend* sdl_backend, float vol)
|
|
{
|
|
if (sdl_backend->stream != nullptr)
|
|
{
|
|
SDL_SetAudioStreamGain(sdl_backend->stream, vol);
|
|
}
|
|
}
|