From be03f352ddfbd18bae63e2e24e580e2a3086b30f Mon Sep 17 00:00:00 2001 From: Rosalie Wanders Date: Sun, 26 Dec 2021 16:52:38 +0100 Subject: [PATCH] Introduce RMG-Audio --- CMakeLists.txt | 4 + Source/RMG-Audio/CMakeLists.txt | 36 ++ Source/RMG-Audio/README.md | 2 + Source/RMG-Audio/UserInterface/MainDialog.cpp | 46 +++ Source/RMG-Audio/UserInterface/MainDialog.hpp | 33 ++ Source/RMG-Audio/UserInterface/MainDialog.ui | 131 +++++++ Source/RMG-Audio/main.cpp | 351 ++++++++++++++++++ 7 files changed, 603 insertions(+) create mode 100644 Source/RMG-Audio/CMakeLists.txt create mode 100644 Source/RMG-Audio/README.md create mode 100644 Source/RMG-Audio/UserInterface/MainDialog.cpp create mode 100644 Source/RMG-Audio/UserInterface/MainDialog.hpp create mode 100644 Source/RMG-Audio/UserInterface/MainDialog.ui create mode 100644 Source/RMG-Audio/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e056348c..aa0b0a47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,9 +19,13 @@ set(INSTALL_PATH Bin/${CMAKE_BUILD_TYPE}) add_subdirectory(Source/RMG-Core) add_subdirectory(Source/RMG) +add_subdirectory(Source/RMG-Audio) install(TARGETS RMG DESTINATION ${INSTALL_PATH} ) +install(TARGETS RMG-Audio + DESTINATION ${INSTALL_PATH}/Plugin/Audio +) if (WIN32) add_subdirectory(Source/Installer) diff --git a/Source/RMG-Audio/CMakeLists.txt b/Source/RMG-Audio/CMakeLists.txt new file mode 100644 index 00000000..938bc2d1 --- /dev/null +++ b/Source/RMG-Audio/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# RMG-Audio CMakeLists.txt +# +project(RMG-Audio) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt5 COMPONENTS Gui Widgets Core REQUIRED) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(SDL2 REQUIRED sdl2) + +set(RMG_AUDIO_SOURCES + UserInterface/MainDialog.cpp + UserInterface/MainDialog.ui + main.cpp +) + +add_library(RMG-Audio SHARED ${RMG_AUDIO_SOURCES}) + +set_target_properties(RMG-Audio PROPERTIES PREFIX "") + +target_link_libraries(RMG-Audio + RMG-Core + ${SDL2_LIBRARIES} + Qt5::Gui Qt5::Widgets +) + +target_include_directories(RMG-Audio PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${SDL2_INCLUDE_DIRS} +) diff --git a/Source/RMG-Audio/README.md b/Source/RMG-Audio/README.md new file mode 100644 index 00000000..8eae3bff --- /dev/null +++ b/Source/RMG-Audio/README.md @@ -0,0 +1,2 @@ +# Rosalie's Mupen GUI - Audio Plugin +This is a simple SDL2 audio plugin, based on https://github.com/m64p/mupen64plus-audio-sdl2 diff --git a/Source/RMG-Audio/UserInterface/MainDialog.cpp b/Source/RMG-Audio/UserInterface/MainDialog.cpp new file mode 100644 index 00000000..c45b8e5e --- /dev/null +++ b/Source/RMG-Audio/UserInterface/MainDialog.cpp @@ -0,0 +1,46 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "MainDialog.hpp" +#include + +using namespace UserInterface; + +MainDialog::MainDialog(QWidget* parent) : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) +{ + this->setupUi(this); + + this->volumeSlider->setValue(CoreSettingsGetIntValue(SettingsID::Audio_Volume)); + this->mutedCheckbox->setChecked(CoreSettingsGetBoolValue(SettingsID::Audio_Muted)); +} + +MainDialog::~MainDialog() +{ +} + +void MainDialog::on_volumeSlider_valueChanged(int value) +{ + this->volumeLabel->setText(QString::number(value) + "%"); +} + +void MainDialog::on_buttonBox_clicked(QAbstractButton* button) +{ + QPushButton *pushButton = (QPushButton *)button; + QPushButton *okButton = this->buttonBox->button(QDialogButtonBox::Ok); + + int volume = this->volumeSlider->value(); + bool muted = this->mutedCheckbox->isChecked(); + + if (pushButton == okButton) + { + CoreSettingsSetValue(SettingsID::Audio_Volume, volume); + CoreSettingsSetValue(SettingsID::Audio_Muted, muted); + CoreSettingsSave(); + } +} diff --git a/Source/RMG-Audio/UserInterface/MainDialog.hpp b/Source/RMG-Audio/UserInterface/MainDialog.hpp new file mode 100644 index 00000000..e0b274ef --- /dev/null +++ b/Source/RMG-Audio/UserInterface/MainDialog.hpp @@ -0,0 +1,33 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef MAINWINDOW_HPP +#define MAINWINDOW_HPP + +#include +#include + +#include "ui_MainDialog.h" + +namespace UserInterface +{ +class MainDialog : public QDialog, private Ui::MainDialog +{ +Q_OBJECT +public: + MainDialog(QWidget *parent); + ~MainDialog(void); + +private slots: + void on_volumeSlider_valueChanged(int value); + void on_buttonBox_clicked(QAbstractButton* button); +}; +} + +#endif // MAINWINDOW_HPP \ No newline at end of file diff --git a/Source/RMG-Audio/UserInterface/MainDialog.ui b/Source/RMG-Audio/UserInterface/MainDialog.ui new file mode 100644 index 00000000..28240d4a --- /dev/null +++ b/Source/RMG-Audio/UserInterface/MainDialog.ui @@ -0,0 +1,131 @@ + + + MainDialog + + + + 0 + 0 + 330 + 476 + + + + Rosalie's Mupen GUI - Audio Plugin + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::LeftToRight + + + + + + 100 + + + 10 + + + Qt::Vertical + + + + + + + 0% + + + + + + + Muted + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MainDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MainDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Source/RMG-Audio/main.cpp b/Source/RMG-Audio/main.cpp new file mode 100644 index 00000000..d4da5d51 --- /dev/null +++ b/Source/RMG-Audio/main.cpp @@ -0,0 +1,351 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#define CORE_PLUGIN 1 +#define M64P_PLUGIN_PROTOTYPES 1 +#define AUDIO_PLUGIN_API_VERSION 0x020100 + +#include +#include + +#include + +#include + +// +// Local variables +// + +static SDL_AudioDeviceID l_SDLDevice; +static SDL_AudioSpec* l_HardwareSpec = nullptr; + +static bool l_PluginInit = false; +static int l_GameFreq = 0; +static AUDIO_INFO l_AudioInfo; +static bool l_VolIsMuted = false; +static bool l_Paused = false; +static bool l_FastForward = false; +static int l_VolSDL = SDL_MIX_MAXVOLUME; +static SDL_AudioStream* l_AudioStream = nullptr; + +static uint8_t l_PrimaryBuffer[0x40000]; +static uint8_t l_OutputBuffer[0x40000]; +static uint8_t l_MixBuffer[0x40000]; + +// +// Local Functions +// + +static void load_settings(void) +{ + l_VolIsMuted = CoreSettingsGetBoolValue(SettingsID::Audio_Muted); + l_VolSDL = SDL_MIX_MAXVOLUME * CoreSettingsGetIntValue(SettingsID::Audio_Volume) / 100; + l_FastForward = false; +} + +// +// Basic Plugin Functions +// + +EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context, void (*DebugCallback)(void *, int, const char *)) +{ + if (l_PluginInit) + { + return M64ERR_ALREADY_INIT; + } + + if (SDL_Init(SDL_INIT_AUDIO) < 0) + { + return M64ERR_SYSTEM_FAIL; + } + + if (!CoreInit(CoreLibHandle)) + { + return M64ERR_SYSTEM_FAIL; + } + + load_settings(); + + l_PluginInit = true; + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL PluginShutdown(void) +{ + if (!l_PluginInit) + { + return M64ERR_NOT_INIT; + } + + if (l_HardwareSpec != nullptr)\ + { + free(l_HardwareSpec); + l_HardwareSpec = NULL; + } + + SDL_QuitSubSystem(SDL_INIT_AUDIO); + l_PluginInit = false; + return M64ERR_SUCCESS; +} + +EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *pluginType, int *pluginVersion, + int *apiVersion, const char **pluginNamePtr, int *capabilities) +{ + if (pluginType != nullptr) + { + *pluginType = M64PLUGIN_AUDIO; + } + + if (pluginVersion != nullptr) + { + *pluginVersion = 0x010000; + } + + if (apiVersion != nullptr) + { + *apiVersion = AUDIO_PLUGIN_API_VERSION; + } + + if (pluginNamePtr != nullptr) + { + *pluginNamePtr = "Rosalie's Mupen GUI - Audio Plugin"; + } + + if (capabilities != nullptr) + { + *capabilities = 0; + } + + return M64ERR_SUCCESS; +} + +// +// Custom Plugin Functions +// + +EXPORT m64p_error CALL PluginConfig(void) +{ + if (!l_PluginInit) + { + return M64ERR_NOT_INIT; + } + + UserInterface::MainDialog dialog(nullptr); + dialog.exec(); + + // reload settings + load_settings(); + + return M64ERR_SUCCESS; +} + +// +// Audio Plugin Functions +// + +EXPORT void CALL AiDacrateChanged( int SystemType ) +{ + if (!l_PluginInit) + { + return; + } + + switch (SystemType) + { + case SYSTEM_NTSC: + l_GameFreq = 48681812 / (*l_AudioInfo.AI_DACRATE_REG + 1); + break; + case SYSTEM_PAL: + l_GameFreq = 49656530 / (*l_AudioInfo.AI_DACRATE_REG + 1); + break; + case SYSTEM_MPAL: + l_GameFreq = 48628316 / (*l_AudioInfo.AI_DACRATE_REG + 1); + break; + } + SDL_FreeAudioStream(l_AudioStream); + l_AudioStream = SDL_NewAudioStream(AUDIO_S16SYS, 2, l_GameFreq, l_HardwareSpec->format, 2, l_HardwareSpec->freq); +} + +EXPORT void CALL AiLenChanged( void ) +{ + if (!l_PluginInit) + { + return; + } + + unsigned int LenReg = *l_AudioInfo.AI_LEN_REG; + unsigned char *p = l_AudioInfo.RDRAM + (*l_AudioInfo.AI_DRAM_ADDR_REG & 0xFFFFFF); + + unsigned int i; + + for ( i = 0 ; i < LenReg ; i += 4 ) + { + // Left channel + l_PrimaryBuffer[ i ] = p[ i + 2 ]; + l_PrimaryBuffer[ i + 1 ] = p[ i + 3 ]; + + // Right channel + l_PrimaryBuffer[ i + 2 ] = p[ i ]; + l_PrimaryBuffer[ i + 3 ] = p[ i + 1 ]; + } + + if (!l_VolIsMuted && !l_FastForward) + { + unsigned int audio_queued = SDL_GetQueuedAudioSize(l_SDLDevice); + unsigned int acceptable_latency = (l_HardwareSpec->freq * 0.2) * 4; + unsigned int min_latency = (l_HardwareSpec->freq * 0.02) * 4; + + if (!l_Paused && audio_queued < min_latency) + { + SDL_PauseAudioDevice(l_SDLDevice, 1); + l_Paused = true; + } + else if (l_Paused && audio_queued >= (min_latency * 2)) + { + SDL_PauseAudioDevice(l_SDLDevice, 0); + l_Paused = true; + } + + if (audio_queued < acceptable_latency) + { + SDL_AudioStreamPut(l_AudioStream, l_PrimaryBuffer, LenReg); + } + + int output_length = SDL_AudioStreamGet(l_AudioStream, l_OutputBuffer, sizeof(l_OutputBuffer)); + if (output_length > 0) + { + SDL_memset(l_MixBuffer, 0, output_length); + SDL_MixAudioFormat(l_MixBuffer, l_OutputBuffer, l_HardwareSpec->format, output_length, l_VolSDL); + SDL_QueueAudio(l_SDLDevice, l_MixBuffer, output_length); + } + } +} + +EXPORT int CALL InitiateAudio( AUDIO_INFO Audio_Info ) +{ + if (!l_PluginInit) + { + return 0; + } + + l_GameFreq = 33600; + l_AudioInfo = Audio_Info; + + return 1; +} + +EXPORT int CALL RomOpen(void) +{ + if (!l_PluginInit) + { + return 0; + } + + SDL_AudioSpec *desired, *obtained; + if (l_HardwareSpec != nullptr) + { + free(l_HardwareSpec); + } + desired = (SDL_AudioSpec*)malloc(sizeof(SDL_AudioSpec)); + obtained = (SDL_AudioSpec*)malloc(sizeof(SDL_AudioSpec)); + desired->freq = 44100; + desired->format = AUDIO_S16SYS; + desired->channels = 2; + desired->samples = 1024; + desired->callback = nullptr; + desired->userdata = nullptr; + + const char *dev_name = SDL_GetAudioDeviceName(-1, 0); + l_SDLDevice = SDL_OpenAudioDevice(dev_name, 0, desired, obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + free(desired); + l_HardwareSpec = obtained; + SDL_PauseAudioDevice(l_SDLDevice, 0); + l_Paused = 0; + l_AudioStream = SDL_NewAudioStream(AUDIO_S16SYS, 2, l_GameFreq, l_HardwareSpec->format, 2, l_HardwareSpec->freq); + + return 1; +} + +EXPORT void CALL RomClosed( void ) +{ + if (!l_PluginInit) + { + return; + } + + SDL_ClearQueuedAudio(l_SDLDevice); + SDL_CloseAudioDevice(l_SDLDevice); + + if (l_HardwareSpec != nullptr) + { + free(l_HardwareSpec); + l_HardwareSpec = nullptr; + } + + SDL_FreeAudioStream(l_AudioStream); + l_AudioStream = nullptr; +} + +EXPORT void CALL ProcessAList(void) +{ +} + +EXPORT void CALL SetSpeedFactor(int percentage) +{ + if (percentage > 100) + { + l_FastForward = true; + } + else + { + l_FastForward = false; + } +} + +EXPORT void CALL VolumeMute(void) +{ + if (!l_PluginInit) + { + return; + } + + l_VolIsMuted = !l_VolIsMuted; +} + +EXPORT void CALL VolumeUp(void) +{ +} + +EXPORT void CALL VolumeDown(void) +{ +} + +EXPORT int CALL VolumeGetLevel(void) +{ + return l_VolIsMuted ? 0 : 100; +} + +EXPORT void CALL VolumeSetLevel(int level) +{ + if (level < 0) + { + level = 0; + } + else if (level > 100) + { + level = 100; + } + + l_VolSDL = SDL_MIX_MAXVOLUME * level / 100; +} + +EXPORT const char * CALL VolumeGetString(void) +{ + return ""; +}