From 6b5ad35d5b11a3a77b07ca913519514550d19102 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 18:51:43 -0700 Subject: [PATCH 01/27] Res: pokemon.lua cleanup --- res/scripts/pokemon.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/res/scripts/pokemon.lua b/res/scripts/pokemon.lua index 09cd472ef..76a1fcdbe 100644 --- a/res/scripts/pokemon.lua +++ b/res/scripts/pokemon.lua @@ -68,22 +68,16 @@ local GBAGameEn = Game:new{ local Generation1En = GBGameEn:new{ _boxMonSize=33, _partyMonSize=44, - _readBoxMon=readBoxMonGen1, - _readPartyMon=readPartyMonGen1, } local Generation2En = GBGameEn:new{ _boxMonSize=32, _partyMonSize=48, - _readBoxMon=readBoxMonGen2, - _readPartyMon=readPartyMonGen2, } local Generation3En = GBAGameEn:new{ _boxMonSize=80, _partyMonSize=100, - _readBoxMon=readBoxMonGen3, - _readPartyMon=readPartyMonGen3, } GBGameEn._charmap = { [0]= @@ -475,7 +469,6 @@ gameCrc32 = { [0x9f7fdd53] = gameRBEn, -- Red [0xd6da8a1a] = gameRBEn, -- Blue [0x7d527d62] = gameYellowEn, - [0x3358e30a] = gameCrystal, -- Crystal rev 1 [0x84ee4776] = gameFireRedEnR1, [0xdaffecec] = gameLeafGreenEnR1, } From ccbb44e95a1525cef3aecbfa0b1eac452118f55e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 21:45:48 -0700 Subject: [PATCH 02/27] Updater: Don't link against libmgba --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3afbaadc1..6181d63c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -952,8 +952,11 @@ if(BUILD_QT AND (WIN32 OR APPLE)) endif() if(BUILD_UPDATER) - add_executable(updater-stub WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c) - target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY} ${BINARY_NAME}) + add_executable(updater-stub WIN32 ${CORE_VFS_SRC} ${OS_SRC} ${UTIL_SRC} ${THIRD_PARTY_SRC} + ${CMAKE_CURRENT_SOURCE_DIR}/src/core/config.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c) + target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY}) set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES}") if(MSVC) set_target_properties(updater-stub PROPERTIES LINK_FLAGS /ENTRY:mainCRTStartup) From 61bc17953bf4de7e2ce551e48f11eb929be43d55 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 20:38:44 -0700 Subject: [PATCH 03/27] Updater: Add support for appimage --- CMakeLists.txt | 2 +- src/feature/updater-main.c | 64 ++++++++++++++++++--- src/platform/qt/ApplicationUpdatePrompt.cpp | 18 ++++-- src/platform/qt/ApplicationUpdater.cpp | 2 + 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6181d63c8..95e714e81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -947,7 +947,7 @@ if(BUILD_OPENEMU) install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${OE_LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP) endif() -if(BUILD_QT AND (WIN32 OR APPLE)) +if(BUILD_QT AND (WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux")) set(BUILD_UPDATER ON) endif() diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index 4c7277203..4b1f216ff 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -173,25 +173,73 @@ int main(int argc, char* argv[]) { prefix = false; needsUnmount = true; #endif - } else { + } else if (strcmp(extension, "appimage") != 0) { archive = VDirOpenArchive(updateArchive); } - if (!archive) { - puts("Cannot open update archive"); - } else { + if (archive) { puts("Extracting update"); if (extractArchive(archive, root, prefix)) { - puts("Complete"); - const char* command = mUpdateGetCommand(&config); - strlcpy(bin, command, sizeof(bin)); ok = 0; - mUpdateDeregister(&config); } else { puts("An error occurred"); } archive->close(archive); unlink(updateArchive); } +#ifdef __linux__ + else if (strcmp(extension, "appimage") == 0) { + const char* command = mUpdateGetCommand(&config); + strlcpy(bin, command, sizeof(bin)); + if (rename(updateArchive, bin) < 0) { + if (errno == EXDEV) { + // Cross-dev, need to copy manually + int infd = open(updateArchive, O_RDONLY); + int outfd = -1; + if (infd >= 0) { + ok = 2; + } else { + outfd = open(bin, O_CREAT | O_WRONLY | O_TRUNC, 0755); + } + if (outfd < 0) { + ok = 2; + } else { + uint8_t buffer[2048]; + ssize_t size; + while ((size = read(infd, buffer, sizeof(buffer))) > 0) { + if (write(outfd, buffer, size) < size) { + ok = 2; + break; + } + } + if (size < 0) { + ok = 2; + } + close(outfd); + close(infd); + } + if (ok == 2) { + puts("Cannot move update over old file"); + } + } else { + puts("Cannot move update over old file"); + } + } else { + ok = 0; + } + if (ok == 0) { + chmod(bin, 0755); + } + } +#endif + else { + puts("Cannot open update archive"); + } + if (ok == 0) { + puts("Complete"); + const char* command = mUpdateGetCommand(&config); + strlcpy(bin, command, sizeof(bin)); + mUpdateDeregister(&config); + } #ifdef __APPLE__ if (needsUnmount) { char* args[] = {"hdiutil", "detach", devpath, NULL}; diff --git a/src/platform/qt/ApplicationUpdatePrompt.cpp b/src/platform/qt/ApplicationUpdatePrompt.cpp index 7f7b375c7..adadbee29 100644 --- a/src/platform/qt/ApplicationUpdatePrompt.cpp +++ b/src/platform/qt/ApplicationUpdatePrompt.cpp @@ -24,13 +24,23 @@ ApplicationUpdatePrompt::ApplicationUpdatePrompt(QWidget* parent) ApplicationUpdater* updater = GBAApp::app()->updater(); ApplicationUpdater::UpdateInfo info = updater->updateInfo(); QString updateText(tr("An update to %1 is available.\n").arg(QLatin1String(projectName))); + bool available; #if defined(Q_OS_WIN) || defined(Q_OS_MAC) - updateText += tr("\nDo you want to download and install it now? You will need to restart the emulator when the download is complete."); - m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate); + available = true; +#elif defined(Q_OS_LINUX) + QString path = QCoreApplication::applicationDirPath(); + QFileInfo finfo(path + "/../../AppRun"); + available = finfo.exists() && finfo.isExecutable(); #else - updateText += tr("\nAuto-update is not available on this platform. If you wish to update you will need to do it manually."); - connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close); + available = false; #endif + if (available) { + updateText += tr("\nDo you want to download and install it now? You will need to restart the emulator when the download is complete."); + m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate); + } else { + updateText += tr("\nAuto-update is not available on this platform. If you wish to update you will need to do it manually."); + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close); + } m_ui.text->setText(updateText); m_ui.details->setText(tr("Current version: %1\nNew version: %2\nDownload size: %3") .arg(QLatin1String(projectVersion)) diff --git a/src/platform/qt/ApplicationUpdater.cpp b/src/platform/qt/ApplicationUpdater.cpp index 15334361e..c59413c26 100644 --- a/src/platform/qt/ApplicationUpdater.cpp +++ b/src/platform/qt/ApplicationUpdater.cpp @@ -151,6 +151,8 @@ const char* ApplicationUpdater::platform() { #endif #elif defined(Q_OS_MACOS) return "osx"; +#elif defined(Q_OS_LINUX) && defined(__x86_64__) + return "appimage-x64"; #else // Return one that will be up to date, but we can't download return "win64"; From 4f9cfd5a7e6dcc07c8e8c37d21aa869f9da7253a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 22:02:47 -0700 Subject: [PATCH 04/27] Qt: Attempt to fix build order --- src/platform/qt/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index bf8948573..d6061be69 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -272,6 +272,9 @@ if(BUILD_UPDATER) qt5_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) endif() set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/updater.qrc PROPERTIES GENERATED ON) + if(BUILD_UPDATER) + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/updater.qrc PROPERTIES OBJECT_DEPENDS updater-stub) + endif() list(APPEND RESOURCES ${UPDATER_RESOURCES}) endif() if(APPLE) From 14f217963ce427de163c8c188cb73ebe583aec57 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 22:43:38 -0700 Subject: [PATCH 05/27] Qt: Attempt to fix build order again --- src/platform/qt/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index d6061be69..3aac96cba 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -265,16 +265,20 @@ else() endif() if(BUILD_UPDATER) - file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in) + if(DEFINED CMAKE_CONFIGURATION_TYPES) + # Required for e.g. MSVC + file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in) + else() + # Required for qt_add_resources to manage dependencies properly + # TODO: Figure out how to do this with MSVC too + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) + endif() if(TARGET Qt6::Core) qt_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) else() qt5_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) endif() set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/updater.qrc PROPERTIES GENERATED ON) - if(BUILD_UPDATER) - set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/updater.qrc PROPERTIES OBJECT_DEPENDS updater-stub) - endif() list(APPEND RESOURCES ${UPDATER_RESOURCES}) endif() if(APPLE) From 39bb4043323c7b1412418227958469d092f37154 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 22:50:53 -0700 Subject: [PATCH 06/27] Qt: Actually fix the build order this time, except with MSVC, but I'll take it --- src/platform/qt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 3aac96cba..611f6136b 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -271,7 +271,7 @@ if(BUILD_UPDATER) else() # Required for qt_add_resources to manage dependencies properly # TODO: Figure out how to do this with MSVC too - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater-config.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) endif() if(TARGET Qt6::Core) qt_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) From 5e6d063aa17ef23f7e8a8ab36772c3088615edee Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 May 2022 22:53:21 -0700 Subject: [PATCH 07/27] Qt: I'm so tired --- src/platform/qt/updater-config.qrc.in | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/platform/qt/updater-config.qrc.in diff --git a/src/platform/qt/updater-config.qrc.in b/src/platform/qt/updater-config.qrc.in new file mode 100644 index 000000000..edf2cd38e --- /dev/null +++ b/src/platform/qt/updater-config.qrc.in @@ -0,0 +1,5 @@ + + + ${PROJECT_BINARY_DIR}/updater-stub${CMAKE_EXECUTABLE_SUFFIX} + + From 68f628a211773fe478dc38b2689f848f9572d881 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 May 2022 20:40:14 -0700 Subject: [PATCH 08/27] GB Audio: Redo channels 1 and 2 --- include/mgba/internal/gb/audio.h | 6 +- include/mgba/internal/gb/serialize.h | 19 ++-- include/mgba/internal/gba/serialize.h | 15 +-- src/gb/audio.c | 145 +++++++++----------------- src/gb/io.c | 1 + src/gb/serialize.c | 2 +- src/gba/serialize.c | 2 +- 7 files changed, 76 insertions(+), 114 deletions(-) diff --git a/include/mgba/internal/gb/audio.h b/include/mgba/internal/gb/audio.h index 4f4fdef32..3e2d4c2f8 100644 --- a/include/mgba/internal/gb/audio.h +++ b/include/mgba/internal/gb/audio.h @@ -87,7 +87,6 @@ struct GBAudioSquareControl { int frequency; int length; bool stop; - int hi; }; struct GBAudioSweep { @@ -104,6 +103,8 @@ struct GBAudioSquareChannel { struct GBAudioSweep sweep; struct GBAudioEnvelope envelope; struct GBAudioSquareControl control; + int32_t lastUpdate; + uint8_t index; int8_t sample; }; @@ -194,8 +195,6 @@ struct GBAudio { enum GBAudioStyle style; struct mTimingEvent frameEvent; - struct mTimingEvent ch1Event; - struct mTimingEvent ch2Event; struct mTimingEvent ch3Event; struct mTimingEvent ch3Fade; struct mTimingEvent ch4Event; @@ -239,6 +238,7 @@ void GBAudioWriteNR50(struct GBAudio* audio, uint8_t); void GBAudioWriteNR51(struct GBAudio* audio, uint8_t); void GBAudioWriteNR52(struct GBAudio* audio, uint8_t); +void GBAudioRun(struct GBAudio* audio, int32_t timestamp); void GBAudioUpdateFrame(struct GBAudio* audio); void GBAudioUpdateChannel4(struct GBAudio* audio); diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 6973d4e57..6043cc919 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -19,7 +19,7 @@ extern MGBA_EXPORT const uint32_t GBSavestateVersion; mLOG_DECLARE_CATEGORY(GB_STATE); /* Savestate format: - * 0x00000 - 0x00003: Version Magic (0x01000002) + * 0x00000 - 0x00003: Version Magic (0x00400003) * 0x00004 - 0x00007: ROM CRC32 * 0x00008: Game Boy model * 0x00009 - 0x0000B: Reserved (leave zero) @@ -56,20 +56,23 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * | bits 0 - 6: Remaining length * | bits 7 - 9: Next step * | bits 10 - 20: Shadow frequency register - * | bits 21 - 31: Reserved + * | bits 21 - 23: Duty index + * | bits 24 - 31: Reserved * | 0x0004C - 0x0004F: Next frame * | 0x00050 - 0x00053: Next channel 3 fade * | 0x00054 - 0x00057: Sweep state * | bits 0 - 2: Timesteps * | bits 3 - 31: Reserved - * | 0x00058 - 0x0005B: Next event + * | 0x00058 - 0x0005B: Last update * 0x0005C - 0x0006B: Audio channel 2 state * | 0x0005C - 0x0005F: Envelepe timing * | bits 0 - 2: Remaining length * | bits 3 - 5: Next step - * | bits 6 - 31: Reserved + * | bits 6 - 20: Reserved + * | bits 21 - 23: Duty index + * | bits 24 - 31: Reserved * | 0x00060 - 0x00067: Reserved - * | 0x00068 - 0x0006B: Next event + * | 0x00068 - 0x0006B: Last update * 0x0006C - 0x00093: Audio channel 3 state * | 0x0006C - 0x0008B: Wave banks * | 0x0008C - 0x0008D: Remaining length @@ -208,7 +211,7 @@ DECL_BITFIELD(GBSerializedAudioEnvelope, uint32_t); DECL_BITS(GBSerializedAudioEnvelope, Length, 0, 7); DECL_BITS(GBSerializedAudioEnvelope, NextStep, 7, 3); DECL_BITS(GBSerializedAudioEnvelope, Frequency, 10, 11); - +DECL_BITS(GBSerializedAudioEnvelope, DutyIndex, 21, 3); DECL_BITFIELD(GBSerializedAudioSweep, uint32_t); DECL_BITS(GBSerializedAudioSweep, Time, 0, 3); @@ -219,12 +222,12 @@ struct GBSerializedPSGState { int32_t nextFrame; int32_t nextCh3Fade; GBSerializedAudioSweep sweep; - uint32_t nextEvent; + uint32_t lastUpdate; } ch1; struct { GBSerializedAudioEnvelope envelope; int32_t reserved[2]; - int32_t nextEvent; + uint32_t lastUpdate; } ch2; struct { uint32_t wavebanks[8]; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 203c4fb0c..b25b3aec0 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -20,7 +20,7 @@ extern MGBA_EXPORT const uint32_t GBASavestateVersion; mLOG_DECLARE_CATEGORY(GBA_STATE); /* Savestate format: - * 0x00000 - 0x00003: Version Magic (0x01000004) + * 0x00000 - 0x00003: Version Magic (0x01000005) * 0x00004 - 0x00007: BIOS checksum (e.g. 0xBAAE187F for official BIOS) * 0x00008 - 0x0000B: ROM CRC32 * 0x0000C - 0x0000F: Master cycles @@ -39,25 +39,28 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bits 0 - 6: Remaining length * | bits 7 - 9: Next step * | bits 10 - 20: Shadow frequency register - * | bits 21 - 31: Reserved + * | bits 21 - 23: Duty index + * | bits 24 - 31: Reserved * | 0x00134 - 0x00137: Next frame * | 0x00138 - 0x0013B: Next channel 3 fade * | 0x0013C - 0x0013F: Sweep state * | bits 0 - 2: Timesteps * | bits 3 - 7: Reserved - * | 0x00140 - 0x00143: Next event + * | 0x00140 - 0x00143: Last update * 0x00144 - 0x00153: Audio channel 2 state * | 0x00144 - 0x00147: Envelepe timing * | bits 0 - 2: Remaining length * | bits 3 - 5: Next step - * | bits 6 - 31: Reserved + * | bits 6 - 20: Reserved + * | bits 21 - 23: Duty index + * | bits 24 - 31: Reserved * | 0x00148 - 0x0014F: Reserved - * | 0x00150 - 0x00153: Next event + * | 0x00150 - 0x00153: Last update * 0x00154 - 0x0017B: Audio channel 3 state * | 0x00154 - 0x00173: Wave banks * | 0x00174 - 0x00175: Remaining length * | 0x00176 - 0x00177: Reserved - * | 0x00178 - 0x0017B: Next event + * | 0x00178 - 0x0017B: Last update * 0x0017C - 0x0018B: Audio channel 4 state * | 0x0017C - 0x0017F: Linear feedback shift register state * | 0x00180 - 0x00183: Envelepe timing diff --git a/src/gb/audio.c b/src/gb/audio.c index 5dc40ac62..e65bd106a 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -35,19 +35,21 @@ static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope); static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial); static void _updateSquareSample(struct GBAudioSquareChannel* ch); -static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch); - -static int32_t _cyclesToInvert(struct GBAudioSquareChannel* ch); static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch); static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate); -static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate); -static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate); +static const int _squareChannelDuty[4][8] = { + { 0, 0, 0, 0, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 0, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 0 }, +}; + void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAudioStyle style) { audio->samples = samples; audio->left = blip_new(BLIP_BUFFER_SIZE); @@ -73,14 +75,6 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu audio->frameEvent.name = "GB Audio Frame Sequencer"; audio->frameEvent.callback = _updateFrame; audio->frameEvent.priority = 0x10; - audio->ch1Event.context = audio; - audio->ch1Event.name = "GB Audio Channel 1"; - audio->ch1Event.callback = _updateChannel1; - audio->ch1Event.priority = 0x11; - audio->ch2Event.context = audio; - audio->ch2Event.name = "GB Audio Channel 2"; - audio->ch2Event.callback = _updateChannel2; - audio->ch2Event.priority = 0x12; audio->ch3Event.context = audio; audio->ch3Event.name = "GB Audio Channel 3"; audio->ch3Event.callback = _updateChannel3; @@ -106,8 +100,6 @@ void GBAudioDeinit(struct GBAudio* audio) { void GBAudioReset(struct GBAudio* audio) { mTimingDeschedule(audio->timing, &audio->frameEvent); - mTimingDeschedule(audio->timing, &audio->ch1Event); - mTimingDeschedule(audio->timing, &audio->ch2Event); mTimingDeschedule(audio->timing, &audio->ch3Event); mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingDeschedule(audio->timing, &audio->ch4Event); @@ -168,32 +160,35 @@ void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) { } void GBAudioWriteNR10(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); if (!_writeSweep(&audio->ch1.sweep, value)) { - mTimingDeschedule(audio->timing, &audio->ch1Event); audio->playingCh1 = false; *audio->nr52 &= ~0x0001; } } void GBAudioWriteNR11(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); _writeDuty(&audio->ch1.envelope, value); audio->ch1.control.length = 64 - audio->ch1.envelope.length; } void GBAudioWriteNR12(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); if (!_writeEnvelope(&audio->ch1.envelope, value, audio->style)) { - mTimingDeschedule(audio->timing, &audio->ch1Event); audio->playingCh1 = false; *audio->nr52 &= ~0x0001; } } void GBAudioWriteNR13(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch1.control.frequency &= 0x700; audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value); } void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch1.control.frequency &= 0xFF; audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8); bool wasStop = audio->ch1.control.stop; @@ -201,12 +196,10 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { if (!wasStop && audio->ch1.control.stop && audio->ch1.control.length && !(audio->frame & 1)) { --audio->ch1.control.length; if (audio->ch1.control.length == 0) { - mTimingDeschedule(audio->timing, &audio->ch1Event); audio->playingCh1 = false; } } if (GBAudioRegisterControlIsRestart(value << 8)) { - bool wasDead = !audio->playingCh1; audio->playingCh1 = _resetEnvelope(&audio->ch1.envelope); audio->ch1.sweep.realFrequency = audio->ch1.control.frequency; _resetSweep(&audio->ch1.sweep); @@ -220,35 +213,33 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { } } _updateSquareSample(&audio->ch1); - if (wasDead && audio->playingCh1) { - mTimingSchedule(audio->timing, &audio->ch1Event, _cyclesToInvert(&audio->ch1)); - } else if (!audio->playingCh1) { - mTimingDeschedule(audio->timing, &audio->ch1Event); - } } *audio->nr52 &= ~0x0001; *audio->nr52 |= audio->playingCh1; } void GBAudioWriteNR21(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); _writeDuty(&audio->ch2.envelope, value); audio->ch2.control.length = 64 - audio->ch2.envelope.length; } void GBAudioWriteNR22(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); if (!_writeEnvelope(&audio->ch2.envelope, value, audio->style)) { - mTimingDeschedule(audio->timing, &audio->ch2Event); audio->playingCh2 = false; *audio->nr52 &= ~0x0002; } } void GBAudioWriteNR23(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch2.control.frequency &= 0x700; audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value); } void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch2.control.frequency &= 0xFF; audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8); bool wasStop = audio->ch2.control.stop; @@ -256,12 +247,10 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { if (!wasStop && audio->ch2.control.stop && audio->ch2.control.length && !(audio->frame & 1)) { --audio->ch2.control.length; if (audio->ch2.control.length == 0) { - mTimingDeschedule(audio->timing, &audio->ch2Event); audio->playingCh2 = false; } } if (GBAudioRegisterControlIsRestart(value << 8)) { - bool wasDead = !audio->playingCh2; audio->playingCh2 = _resetEnvelope(&audio->ch2.envelope); if (!audio->ch2.control.length) { @@ -271,11 +260,6 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { } } _updateSquareSample(&audio->ch2); - if (wasDead && audio->playingCh2) { - mTimingSchedule(audio->timing, &audio->ch2Event, _cyclesToInvert(&audio->ch2)); - } else if (!audio->playingCh2) { - mTimingDeschedule(audio->timing, &audio->ch2Event); - } } *audio->nr52 &= ~0x0002; *audio->nr52 |= audio->playingCh2 << 1; @@ -496,6 +480,26 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) { } } +void GBAudioRun(struct GBAudio* audio, int32_t timestamp) { + if (!audio->enable) { + return; + } + if (audio->playingCh1) { + int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor; + int32_t diff = (timestamp - audio->ch1.lastUpdate) / period; + audio->ch1.index = (audio->ch1.index + diff) & 7; + audio->ch1.lastUpdate += diff * period; + _updateSquareSample(&audio->ch1); + } + if (audio->playingCh2) { + int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor; + int32_t diff = (timestamp - audio->ch2.lastUpdate) / period; + audio->ch2.index = (audio->ch2.index + diff) & 7; + audio->ch2.lastUpdate += diff * period; + _updateSquareSample(&audio->ch2); + } +} + void GBAudioUpdateFrame(struct GBAudio* audio) { if (!audio->enable) { return; @@ -504,6 +508,8 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { audio->skipFrame = false; return; } + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + int frame = (audio->frame + 1) & 7; audio->frame = frame; @@ -516,9 +522,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { audio->playingCh1 = _updateSweep(&audio->ch1, false); *audio->nr52 &= ~0x0001; *audio->nr52 |= audio->playingCh1; - if (!audio->playingCh1) { - mTimingDeschedule(audio->timing, &audio->ch1Event); - } } } // Fall through @@ -527,7 +530,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->ch1.control.length && audio->ch1.control.stop) { --audio->ch1.control.length; if (audio->ch1.control.length == 0) { - mTimingDeschedule(audio->timing, &audio->ch1Event); audio->playingCh1 = 0; *audio->nr52 &= ~0x0001; } @@ -536,7 +538,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->ch2.control.length && audio->ch2.control.stop) { --audio->ch2.control.length; if (audio->ch2.control.length == 0) { - mTimingDeschedule(audio->timing, &audio->ch2Event); audio->playingCh2 = 0; *audio->nr52 &= ~0x0002; } @@ -565,9 +566,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { --audio->ch1.envelope.nextStep; if (audio->ch1.envelope.nextStep == 0) { _updateEnvelope(&audio->ch1.envelope); - if (audio->ch1.envelope.dead == 2) { - mTimingDeschedule(audio->timing, &audio->ch1Event); - } _updateSquareSample(&audio->ch1); } } @@ -576,9 +574,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { --audio->ch2.envelope.nextStep; if (audio->ch2.envelope.nextStep == 0) { _updateEnvelope(&audio->ch2.envelope); - if (audio->ch2.envelope.dead == 2) { - mTimingDeschedule(audio->timing, &audio->ch2Event); - } _updateSquareSample(&audio->ch2); } } @@ -626,6 +621,7 @@ void GBAudioUpdateChannel4(struct GBAudio* audio) { } void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8; int sampleLeft = dcOffset; int sampleRight = dcOffset; @@ -775,30 +771,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudi } static void _updateSquareSample(struct GBAudioSquareChannel* ch) { - ch->sample = ch->control.hi * ch->envelope.currentVolume; -} - -static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) { - ch->control.hi = !ch->control.hi; - _updateSquareSample(ch); - return _cyclesToInvert(ch); -} - -static int32_t _cyclesToInvert(struct GBAudioSquareChannel* ch) { - int period = 4 * (2048 - ch->control.frequency); - switch (ch->envelope.duty) { - case 0: - return ch->control.hi ? period : period * 7; - case 1: - return ch->control.hi ? period * 2 : period * 6; - case 2: - return period * 4; - case 3: - return ch->control.hi ? period * 6 : period * 2; - default: - // This should never be hit - return period * 4; - } + ch->sample = _squareChannelDuty[ch->envelope.duty][ch->index] * ch->envelope.currentVolume; } static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) { @@ -870,20 +843,6 @@ static bool _updateSweep(struct GBAudioSquareChannel* ch, bool initial) { return true; } -static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate) { - struct GBAudio* audio = user; - struct GBAudioSquareChannel* ch = &audio->ch1; - int cycles = _updateSquareChannel(ch); - mTimingSchedule(timing, &audio->ch1Event, audio->timingFactor * cycles - cyclesLate); -} - -static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate) { - struct GBAudio* audio = user; - struct GBAudioSquareChannel* ch = &audio->ch2; - int cycles = _updateSquareChannel(ch); - mTimingSchedule(timing, &audio->ch2Event, audio->timingFactor * cycles - cyclesLate); -} - static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct GBAudio* audio = user; struct GBAudioWaveChannel* ch = &audio->ch3; @@ -971,24 +930,24 @@ void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGStat flags = GBSerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume); flags = GBSerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead); - flags = GBSerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi); flags = GBSerializedAudioFlagsSetCh1SweepEnabled(flags, audio->ch1.sweep.enable); flags = GBSerializedAudioFlagsSetCh1SweepOccurred(flags, audio->ch1.sweep.occurred); ch1Flags = GBSerializedAudioEnvelopeSetLength(ch1Flags, audio->ch1.control.length); ch1Flags = GBSerializedAudioEnvelopeSetNextStep(ch1Flags, audio->ch1.envelope.nextStep); ch1Flags = GBSerializedAudioEnvelopeSetFrequency(ch1Flags, audio->ch1.sweep.realFrequency); + ch1Flags = GBSerializedAudioEnvelopeSetDutyIndex(ch1Flags, audio->ch1.index); sweep = GBSerializedAudioSweepSetTime(sweep, audio->ch1.sweep.time & 7); STORE_32LE(ch1Flags, 0, &state->ch1.envelope); STORE_32LE(sweep, 0, &state->ch1.sweep); - STORE_32LE(audio->ch1Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch1.nextEvent); + STORE_32LE(audio->ch1.lastUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch1.lastUpdate); flags = GBSerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume); flags = GBSerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead); - flags = GBSerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi); ch2Flags = GBSerializedAudioEnvelopeSetLength(ch2Flags, audio->ch2.control.length); ch2Flags = GBSerializedAudioEnvelopeSetNextStep(ch2Flags, audio->ch2.envelope.nextStep); + ch2Flags = GBSerializedAudioEnvelopeSetDutyIndex(ch2Flags, audio->ch2.index); STORE_32LE(ch2Flags, 0, &state->ch2.envelope); - STORE_32LE(audio->ch2Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch2.nextEvent); + STORE_32LE(audio->ch2.lastUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch2.lastUpdate); flags = GBSerializedAudioFlagsSetCh3Readable(flags, audio->ch3.readable); memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks)); @@ -1039,7 +998,6 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt LOAD_32LE(sweep, 0, &state->ch1.sweep); audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags); audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags); - audio->ch1.control.hi = GBSerializedAudioFlagsGetCh1Hi(flags); audio->ch1.sweep.enable = GBSerializedAudioFlagsGetCh1SweepEnabled(flags); audio->ch1.sweep.occurred = GBSerializedAudioFlagsGetCh1SweepOccurred(flags); audio->ch1.sweep.time = GBSerializedAudioSweepGetTime(sweep); @@ -1049,21 +1007,18 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt audio->ch1.control.length = GBSerializedAudioEnvelopeGetLength(ch1Flags); audio->ch1.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch1Flags); audio->ch1.sweep.realFrequency = GBSerializedAudioEnvelopeGetFrequency(ch1Flags); - LOAD_32LE(when, 0, &state->ch1.nextEvent); - if (audio->ch1.envelope.dead < 2 && audio->playingCh1) { - mTimingSchedule(audio->timing, &audio->ch1Event, when); - } + audio->ch1.index = GBSerializedAudioEnvelopeGetDutyIndex(ch1Flags); + LOAD_32LE(audio->ch1.lastUpdate, 0, &state->ch1.lastUpdate); + audio->ch1.lastUpdate += mTimingCurrentTime(audio->timing); LOAD_32LE(ch2Flags, 0, &state->ch2.envelope); audio->ch2.envelope.currentVolume = GBSerializedAudioFlagsGetCh2Volume(flags); audio->ch2.envelope.dead = GBSerializedAudioFlagsGetCh2Dead(flags); - audio->ch2.control.hi = GBSerializedAudioFlagsGetCh2Hi(flags); audio->ch2.control.length = GBSerializedAudioEnvelopeGetLength(ch2Flags); audio->ch2.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch2Flags); - LOAD_32LE(when, 0, &state->ch2.nextEvent); - if (audio->ch2.envelope.dead < 2 && audio->playingCh2) { - mTimingSchedule(audio->timing, &audio->ch2Event, when); - } + audio->ch2.index = GBSerializedAudioEnvelopeGetDutyIndex(ch2Flags); + LOAD_32LE(audio->ch2.lastUpdate, 0, &state->ch2.lastUpdate); + audio->ch2.lastUpdate += mTimingCurrentTime(audio->timing); audio->ch3.readable = GBSerializedAudioFlagsGetCh3Readable(flags); // TODO: Big endian? diff --git a/src/gb/io.c b/src/gb/io.c index deed1153f..a948403ec 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -620,6 +620,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { if (gb->model < GB_MODEL_CGB) { mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address); } else if (gb->audio.enable) { + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); return (gb->audio.ch1.sample) | (gb->audio.ch2.sample << 4); } break; diff --git a/src/gb/serialize.c b/src/gb/serialize.c index d3fd8f12a..ad7ebe756 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -14,7 +14,7 @@ mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate", "gb.serialize"); MGBA_EXPORT const uint32_t GBSavestateMagic = 0x00400000; -MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000002; +MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000003; void GBSerialize(struct GB* gb, struct GBSerializedState* state) { STORE_32LE(GBSavestateMagic + GBSavestateVersion, 0, &state->versionMagic); diff --git a/src/gba/serialize.c b/src/gba/serialize.c index a7294b6b5..d2bd5c5e8 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -15,7 +15,7 @@ #include MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000; -MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000004; +MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000005; mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); From 779e7bc94b5d46f5c68a9af212555e96cac14be3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 May 2022 21:24:50 -0700 Subject: [PATCH 09/27] GB Audio: Migrate channel 4 into GBRunAudio --- include/mgba/internal/gb/audio.h | 2 -- src/gb/audio.c | 61 ++++++++++++-------------------- src/gb/io.c | 2 +- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/include/mgba/internal/gb/audio.h b/include/mgba/internal/gb/audio.h index 3e2d4c2f8..ffcc71744 100644 --- a/include/mgba/internal/gb/audio.h +++ b/include/mgba/internal/gb/audio.h @@ -197,7 +197,6 @@ struct GBAudio { struct mTimingEvent frameEvent; struct mTimingEvent ch3Event; struct mTimingEvent ch3Fade; - struct mTimingEvent ch4Event; struct mTimingEvent sampleEvent; bool enable; @@ -240,7 +239,6 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t); void GBAudioRun(struct GBAudio* audio, int32_t timestamp); void GBAudioUpdateFrame(struct GBAudio* audio); -void GBAudioUpdateChannel4(struct GBAudio* audio); void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right); diff --git a/src/gb/audio.c b/src/gb/audio.c index e65bd106a..a44f6e12a 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -83,10 +83,6 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu audio->ch3Fade.name = "GB Audio Channel 3 Memory"; audio->ch3Fade.callback = _fadeChannel3; audio->ch3Fade.priority = 0x14; - audio->ch4Event.context = audio; - audio->ch4Event.name = "GB Audio Channel 4"; - audio->ch4Event.callback = NULL; // This is pending removal, so calling it will crash - audio->ch4Event.priority = 0x15; audio->sampleEvent.context = audio; audio->sampleEvent.name = "GB Audio Sample"; audio->sampleEvent.callback = _sample; @@ -102,7 +98,6 @@ void GBAudioReset(struct GBAudio* audio) { mTimingDeschedule(audio->timing, &audio->frameEvent); mTimingDeschedule(audio->timing, &audio->ch3Event); mTimingDeschedule(audio->timing, &audio->ch3Fade); - mTimingDeschedule(audio->timing, &audio->ch4Event); mTimingDeschedule(audio->timing, &audio->sampleEvent); if (audio->style != GB_AUDIO_GBA) { mTimingSchedule(audio->timing, &audio->sampleEvent, 0); @@ -335,13 +330,13 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR41(struct GBAudio* audio, uint8_t value) { - GBAudioUpdateChannel4(audio); + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); _writeDuty(&audio->ch4.envelope, value); audio->ch4.length = 64 - audio->ch4.envelope.length; } void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) { - GBAudioUpdateChannel4(audio); + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); if (!_writeEnvelope(&audio->ch4.envelope, value, audio->style)) { audio->playingCh4 = false; *audio->nr52 &= ~0x0008; @@ -349,14 +344,14 @@ void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR43(struct GBAudio* audio, uint8_t value) { - GBAudioUpdateChannel4(audio); + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch4.ratio = GBAudioRegisterNoiseFeedbackGetRatio(value); audio->ch4.frequency = GBAudioRegisterNoiseFeedbackGetFrequency(value); audio->ch4.power = GBAudioRegisterNoiseFeedbackGetPower(value); } void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) { - GBAudioUpdateChannel4(audio); + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); bool wasStop = audio->ch4.stop; audio->ch4.stop = GBAudioRegisterNoiseControlGetStop(value); if (!wasStop && audio->ch4.stop && audio->ch4.length && !(audio->frame & 1)) { @@ -379,7 +374,7 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) { --audio->ch4.length; } } - if (audio->playingCh4 && audio->ch4.envelope.dead != 2) { + if (audio->playingCh4) { audio->ch4.lastEvent = mTimingCurrentTime(audio->timing); } } @@ -498,6 +493,24 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp) { audio->ch2.lastUpdate += diff * period; _updateSquareSample(&audio->ch2); } + if (audio->playingCh4) { + int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1; + cycles <<= audio->ch4.frequency; + cycles *= 8 * audio->timingFactor; + + int32_t last = 0; + int32_t diff = timestamp - audio->ch4.lastEvent; + for (; last + cycles <= diff; last += cycles) { + int lsb = audio->ch4.lfsr & 1; + audio->ch4.sample = lsb * audio->ch4.envelope.currentVolume; + ++audio->ch4.nSamples; + audio->ch4.samples += audio->ch4.sample; + audio->ch4.lfsr >>= 1; + audio->ch4.lfsr ^= (lsb * 0x60) << (audio->ch4.power ? 0 : 8); + } + + audio->ch4.lastEvent += last; + } } void GBAudioUpdateFrame(struct GBAudio* audio) { @@ -555,7 +568,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->ch4.length && audio->ch4.stop) { --audio->ch4.length; if (audio->ch4.length == 0) { - GBAudioUpdateChannel4(audio); audio->playingCh4 = 0; *audio->nr52 &= ~0x0008; } @@ -581,7 +593,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->playingCh4 && !audio->ch4.envelope.dead) { --audio->ch4.envelope.nextStep; if (audio->ch4.envelope.nextStep == 0) { - GBAudioUpdateChannel4(audio); int8_t sample = audio->ch4.sample; _updateEnvelope(&audio->ch4.envelope); audio->ch4.sample = (sample > 0) * audio->ch4.envelope.currentVolume; @@ -595,31 +606,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { } } -void GBAudioUpdateChannel4(struct GBAudio* audio) { - struct GBAudioNoiseChannel* ch = &audio->ch4; - if (ch->envelope.dead == 2 || !audio->playingCh4) { - return; - } - - int32_t cycles = ch->ratio ? 2 * ch->ratio : 1; - cycles <<= ch->frequency; - cycles *= 8 * audio->timingFactor; - - uint32_t last = 0; - uint32_t now = mTimingCurrentTime(audio->timing) - ch->lastEvent; - - for (; last + cycles <= now; last += cycles) { - int lsb = ch->lfsr & 1; - ch->sample = lsb * ch->envelope.currentVolume; - ++ch->nSamples; - ch->samples += ch->sample; - ch->lfsr >>= 1; - ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8); - } - - ch->lastEvent += last; -} - void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { GBAudioRun(audio, mTimingCurrentTime(audio->timing)); int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8; @@ -660,7 +646,6 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { sampleRight <<= 3; if (!audio->forceDisableCh[3]) { - GBAudioUpdateChannel4(audio); int16_t sample = audio->style == GB_AUDIO_GBA ? (audio->ch4.sample << 3) : _coalesceNoiseChannel(&audio->ch4); if (audio->ch4Left) { sampleLeft += sample; diff --git a/src/gb/io.c b/src/gb/io.c index a948403ec..973ff43f7 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -628,7 +628,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { if (gb->model < GB_MODEL_CGB) { mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address); } else if (gb->audio.enable) { - GBAudioUpdateChannel4(&gb->audio); + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); return (gb->audio.ch3.sample) | (gb->audio.ch4.sample << 4); } break; From 76a8f4da2bb4fc4ef10a64eae5d3da0d7846c73f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 May 2022 22:45:00 -0700 Subject: [PATCH 10/27] GB Audio: Migrate channel 3 into GBRunAudio --- include/mgba/internal/gb/audio.h | 5 +- include/mgba/internal/gb/serialize.h | 4 +- include/mgba/internal/gba/serialize.h | 4 +- src/gb/audio.c | 182 ++++++++++++-------------- src/gb/io.c | 2 + src/gba/audio.c | 2 + 6 files changed, 91 insertions(+), 108 deletions(-) diff --git a/include/mgba/internal/gb/audio.h b/include/mgba/internal/gb/audio.h index ffcc71744..93c76d997 100644 --- a/include/mgba/internal/gb/audio.h +++ b/include/mgba/internal/gb/audio.h @@ -113,6 +113,7 @@ struct GBAudioWaveChannel { bool bank; bool enable; + int8_t sample; unsigned length; int volume; @@ -125,7 +126,7 @@ struct GBAudioWaveChannel { uint32_t wavedata32[8]; uint8_t wavedata8[16]; }; - int8_t sample; + int32_t nextUpdate; }; struct GBAudioNoiseChannel { @@ -195,8 +196,6 @@ struct GBAudio { enum GBAudioStyle style; struct mTimingEvent frameEvent; - struct mTimingEvent ch3Event; - struct mTimingEvent ch3Fade; struct mTimingEvent sampleEvent; bool enable; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 6043cc919..11bdf7dc3 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -59,7 +59,7 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * | bits 21 - 23: Duty index * | bits 24 - 31: Reserved * | 0x0004C - 0x0004F: Next frame - * | 0x00050 - 0x00053: Next channel 3 fade + * | 0x00050 - 0x00053: Reserved * | 0x00054 - 0x00057: Sweep state * | bits 0 - 2: Timesteps * | bits 3 - 31: Reserved @@ -220,7 +220,7 @@ struct GBSerializedPSGState { struct { GBSerializedAudioEnvelope envelope; int32_t nextFrame; - int32_t nextCh3Fade; + int32_t reserved; GBSerializedAudioSweep sweep; uint32_t lastUpdate; } ch1; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index b25b3aec0..364563195 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -42,7 +42,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bits 21 - 23: Duty index * | bits 24 - 31: Reserved * | 0x00134 - 0x00137: Next frame - * | 0x00138 - 0x0013B: Next channel 3 fade + * | 0x00138 - 0x0013B: Reserved * | 0x0013C - 0x0013F: Sweep state * | bits 0 - 2: Timesteps * | bits 3 - 7: Reserved @@ -60,7 +60,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | 0x00154 - 0x00173: Wave banks * | 0x00174 - 0x00175: Remaining length * | 0x00176 - 0x00177: Reserved - * | 0x00178 - 0x0017B: Last update + * | 0x00178 - 0x0017B: Next event * 0x0017C - 0x0018B: Audio channel 4 state * | 0x0017C - 0x0017F: Linear feedback shift register state * | 0x00180 - 0x00183: Envelepe timing diff --git a/src/gb/audio.c b/src/gb/audio.c index a44f6e12a..9051a3c5d 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -39,8 +39,6 @@ static void _updateSquareSample(struct GBAudioSquareChannel* ch); static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch); static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate); -static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate); -static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate); static const int _squareChannelDuty[4][8] = { @@ -75,14 +73,6 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu audio->frameEvent.name = "GB Audio Frame Sequencer"; audio->frameEvent.callback = _updateFrame; audio->frameEvent.priority = 0x10; - audio->ch3Event.context = audio; - audio->ch3Event.name = "GB Audio Channel 3"; - audio->ch3Event.callback = _updateChannel3; - audio->ch3Event.priority = 0x13; - audio->ch3Fade.context = audio; - audio->ch3Fade.name = "GB Audio Channel 3 Memory"; - audio->ch3Fade.callback = _fadeChannel3; - audio->ch3Fade.priority = 0x14; audio->sampleEvent.context = audio; audio->sampleEvent.name = "GB Audio Sample"; audio->sampleEvent.callback = _sample; @@ -96,8 +86,6 @@ void GBAudioDeinit(struct GBAudio* audio) { void GBAudioReset(struct GBAudio* audio) { mTimingDeschedule(audio->timing, &audio->frameEvent); - mTimingDeschedule(audio->timing, &audio->ch3Event); - mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingDeschedule(audio->timing, &audio->sampleEvent); if (audio->style != GB_AUDIO_GBA) { mTimingSchedule(audio->timing, &audio->sampleEvent, 0); @@ -261,28 +249,32 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR30(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch3.enable = GBAudioRegisterBankGetEnable(value); if (!audio->ch3.enable) { - mTimingDeschedule(audio->timing, &audio->ch3Event); audio->playingCh3 = false; *audio->nr52 &= ~0x0004; } } void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch3.length = 256 - value; } void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value); } void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch3.rate &= 0x700; audio->ch3.rate |= GBAudioRegisterControlGetRate(value); } void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { + GBAudioRun(audio, mTimingCurrentTime(audio->timing)); audio->ch3.rate &= 0xFF; audio->ch3.rate |= GBAudioRegisterControlGetRate(value << 8); bool wasStop = audio->ch3.stop; @@ -318,12 +310,10 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { audio->ch3.sample = 0; } } - mTimingDeschedule(audio->timing, &audio->ch3Fade); - mTimingDeschedule(audio->timing, &audio->ch3Event); if (audio->playingCh3) { audio->ch3.readable = audio->style != GB_AUDIO_DMG; // TODO: Where does this cycle delay come from? - mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * (4 + 2 * (2048 - audio->ch3.rate))); + audio->ch3.nextUpdate = mTimingCurrentTime(audio->timing) + (6 + 2 * (2048 - audio->ch3.rate)) * audio->timingFactor; } *audio->nr52 &= ~0x0004; *audio->nr52 |= audio->playingCh3 << 2; @@ -493,6 +483,78 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp) { audio->ch2.lastUpdate += diff * period; _updateSquareSample(&audio->ch2); } + if (audio->playingCh3) { + int cycles = 2 * (2048 - audio->ch3.rate) * audio->timingFactor; + int32_t diff = timestamp - audio->ch3.nextUpdate; + if (diff >= 0) { + diff = (diff / cycles) + 1; + int volume; + switch (audio->ch3.volume) { + case 0: + volume = 4; + break; + case 1: + volume = 0; + break; + case 2: + volume = 1; + break; + default: + case 3: + volume = 2; + break; + } + int start = 7; + int end = 0; + int mask = 0x1F; + int iter; + switch (audio->style) { + case GB_AUDIO_DMG: + default: + audio->ch3.window += diff; + audio->ch3.window &= 0x1F; + audio->ch3.sample = audio->ch3.wavedata8[audio->ch3.window >> 1]; + if (!(audio->ch3.window & 1)) { + audio->ch3.sample >>= 4; + } + audio->ch3.sample &= 0xF; + break; + case GB_AUDIO_GBA: + if (audio->ch3.size) { + mask = 0x3F; + } else if (audio->ch3.bank) { + end = 4; + } else { + start = 3; + } + for (iter = 0; iter < (diff & mask); ++iter) { + uint32_t bitsCarry = audio->ch3.wavedata32[end] & 0x000000F0; + uint32_t bits; + int i; + for (i = start; i >= end; --i) { + bits = audio->ch3.wavedata32[i] & 0x000000F0; + audio->ch3.wavedata32[i] = ((audio->ch3.wavedata32[i] & 0x0F0F0F0F) << 4) | ((audio->ch3.wavedata32[i] & 0xF0F0F000) >> 12); + audio->ch3.wavedata32[i] |= bitsCarry << 20; + bitsCarry = bits; + } + audio->ch3.sample = bitsCarry >> 4; + } + break; + } + if (audio->ch3.volume > 3) { + audio->ch3.sample += audio->ch3.sample << 1; + } + audio->ch3.sample >>= volume; + audio->ch3.nextUpdate += diff * cycles; + audio->ch3.readable = true; + } + if (audio->style == GB_AUDIO_DMG && audio->ch3.readable) { + diff = timestamp - audio->ch3.nextUpdate + cycles; + if (diff >= 4) { + audio->ch3.readable = false; + } + } + } if (audio->playingCh4) { int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1; cycles <<= audio->ch4.frequency; @@ -559,7 +621,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->ch3.length && audio->ch3.stop) { --audio->ch3.length; if (audio->ch3.length == 0) { - mTimingDeschedule(audio->timing, &audio->ch3Event); audio->playingCh3 = 0; *audio->nr52 &= ~0x0004; } @@ -828,80 +889,6 @@ static bool _updateSweep(struct GBAudioSquareChannel* ch, bool initial) { return true; } -static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) { - struct GBAudio* audio = user; - struct GBAudioWaveChannel* ch = &audio->ch3; - int i; - int volume; - switch (ch->volume) { - case 0: - volume = 4; - break; - case 1: - volume = 0; - break; - case 2: - volume = 1; - break; - default: - case 3: - volume = 2; - break; - } - int start; - int end; - switch (audio->style) { - case GB_AUDIO_DMG: - default: - ++ch->window; - ch->window &= 0x1F; - ch->sample = ch->wavedata8[ch->window >> 1]; - if (!(ch->window & 1)) { - ch->sample >>= 4; - } - ch->sample &= 0xF; - break; - case GB_AUDIO_GBA: - if (ch->size) { - start = 7; - end = 0; - } else if (ch->bank) { - start = 7; - end = 4; - } else { - start = 3; - end = 0; - } - uint32_t bitsCarry = ch->wavedata32[end] & 0x000000F0; - uint32_t bits; - for (i = start; i >= end; --i) { - bits = ch->wavedata32[i] & 0x000000F0; - ch->wavedata32[i] = ((ch->wavedata32[i] & 0x0F0F0F0F) << 4) | ((ch->wavedata32[i] & 0xF0F0F000) >> 12); - ch->wavedata32[i] |= bitsCarry << 20; - bitsCarry = bits; - } - ch->sample = bitsCarry >> 4; - break; - } - if (ch->volume > 3) { - ch->sample += ch->sample << 1; - } - ch->sample >>= volume; - audio->ch3.readable = true; - if (audio->style == GB_AUDIO_DMG) { - mTimingDeschedule(audio->timing, &audio->ch3Fade); - mTimingSchedule(timing, &audio->ch3Fade, 4 - cyclesLate); - } - int cycles = 2 * (2048 - ch->rate); - mTimingSchedule(timing, &audio->ch3Event, audio->timingFactor * cycles - cyclesLate); -} -static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - struct GBAudio* audio = user; - audio->ch3.readable = false; -} - void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) { uint32_t flags = 0; uint32_t sweep = 0; @@ -937,8 +924,7 @@ void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGStat flags = GBSerializedAudioFlagsSetCh3Readable(flags, audio->ch3.readable); memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks)); STORE_16LE(audio->ch3.length, 0, &state->ch3.length); - STORE_32LE(audio->ch3Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch3.nextEvent); - STORE_32LE(audio->ch3Fade.when - mTimingCurrentTime(audio->timing), 0, &state->ch1.nextCh3Fade); + STORE_32LE(audio->ch3.nextUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch3.nextEvent); flags = GBSerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume); flags = GBSerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead); @@ -1009,14 +995,8 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt // TODO: Big endian? memcpy(audio->ch3.wavedata32, state->ch3.wavebanks, sizeof(audio->ch3.wavedata32)); LOAD_16LE(audio->ch3.length, 0, &state->ch3.length); - LOAD_32LE(when, 0, &state->ch3.nextEvent); - if (audio->playingCh3) { - mTimingSchedule(audio->timing, &audio->ch3Event, when); - } - LOAD_32LE(when, 0, &state->ch1.nextCh3Fade); - if (audio->ch3.readable && audio->style == GB_AUDIO_DMG) { - mTimingSchedule(audio->timing, &audio->ch3Fade, when); - } + LOAD_32LE(audio->ch3.nextUpdate, 0, &state->ch3.nextEvent); + audio->ch3.nextUpdate += mTimingCurrentTime(audio->timing); LOAD_32LE(ch4Flags, 0, &state->ch4.envelope); audio->ch4.envelope.currentVolume = GBSerializedAudioFlagsGetCh4Volume(flags); diff --git a/src/gb/io.c b/src/gb/io.c index 973ff43f7..d03171bb4 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -403,6 +403,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case GB_REG_WAVE_D: case GB_REG_WAVE_E: case GB_REG_WAVE_F: + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); if (!gb->audio.playingCh3 || gb->audio.style != GB_AUDIO_DMG) { gb->audio.ch3.wavedata8[address - GB_REG_WAVE_0] = value; } else if(gb->audio.ch3.readable) { @@ -607,6 +608,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { case GB_REG_WAVE_E: case GB_REG_WAVE_F: if (gb->audio.playingCh3) { + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); if (gb->audio.ch3.readable || gb->audio.style != GB_AUDIO_DMG) { return gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1]; } else { diff --git a/src/gba/audio.c b/src/gba/audio.c index fc264349f..4f3cfe3de 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -229,6 +229,7 @@ void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) { bank = 1; } + GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing)); audio->psg.ch3.wavedata32[address | (bank * 4)] = value; } @@ -241,6 +242,7 @@ uint32_t GBAAudioReadWaveRAM(struct GBAAudio* audio, int address) { bank = 1; } + GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing)); return audio->psg.ch3.wavedata32[address | (bank * 4)]; } From cdabfd491b714321adbb3bfd687a2b4385249de3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 May 2022 22:59:15 -0700 Subject: [PATCH 11/27] GB Audio: Minor optimizations --- include/mgba/internal/gb/audio.h | 2 +- src/gb/audio.c | 72 +++++++++++++++++--------------- src/gb/io.c | 8 ++-- src/gba/audio.c | 4 +- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/include/mgba/internal/gb/audio.h b/include/mgba/internal/gb/audio.h index 93c76d997..3667145e1 100644 --- a/include/mgba/internal/gb/audio.h +++ b/include/mgba/internal/gb/audio.h @@ -236,7 +236,7 @@ void GBAudioWriteNR50(struct GBAudio* audio, uint8_t); void GBAudioWriteNR51(struct GBAudio* audio, uint8_t); void GBAudioWriteNR52(struct GBAudio* audio, uint8_t); -void GBAudioRun(struct GBAudio* audio, int32_t timestamp); +void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels); void GBAudioUpdateFrame(struct GBAudio* audio); void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right); diff --git a/src/gb/audio.c b/src/gb/audio.c index 9051a3c5d..1601c734b 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -143,7 +143,7 @@ void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) { } void GBAudioWriteNR10(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1); if (!_writeSweep(&audio->ch1.sweep, value)) { audio->playingCh1 = false; *audio->nr52 &= ~0x0001; @@ -151,13 +151,13 @@ void GBAudioWriteNR10(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR11(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1); _writeDuty(&audio->ch1.envelope, value); audio->ch1.control.length = 64 - audio->ch1.envelope.length; } void GBAudioWriteNR12(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1); if (!_writeEnvelope(&audio->ch1.envelope, value, audio->style)) { audio->playingCh1 = false; *audio->nr52 &= ~0x0001; @@ -165,13 +165,13 @@ void GBAudioWriteNR12(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR13(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1); audio->ch1.control.frequency &= 0x700; audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value); } void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1); audio->ch1.control.frequency &= 0xFF; audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8); bool wasStop = audio->ch1.control.stop; @@ -202,13 +202,13 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR21(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2); _writeDuty(&audio->ch2.envelope, value); audio->ch2.control.length = 64 - audio->ch2.envelope.length; } void GBAudioWriteNR22(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2); if (!_writeEnvelope(&audio->ch2.envelope, value, audio->style)) { audio->playingCh2 = false; *audio->nr52 &= ~0x0002; @@ -216,13 +216,13 @@ void GBAudioWriteNR22(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR23(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2); audio->ch2.control.frequency &= 0x700; audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value); } void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2); audio->ch2.control.frequency &= 0xFF; audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8); bool wasStop = audio->ch2.control.stop; @@ -249,7 +249,7 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR30(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4); audio->ch3.enable = GBAudioRegisterBankGetEnable(value); if (!audio->ch3.enable) { audio->playingCh3 = false; @@ -258,23 +258,23 @@ void GBAudioWriteNR30(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4); audio->ch3.length = 256 - value; } void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4); audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value); } void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4); audio->ch3.rate &= 0x700; audio->ch3.rate |= GBAudioRegisterControlGetRate(value); } void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4); audio->ch3.rate &= 0xFF; audio->ch3.rate |= GBAudioRegisterControlGetRate(value << 8); bool wasStop = audio->ch3.stop; @@ -320,13 +320,13 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR41(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8); _writeDuty(&audio->ch4.envelope, value); audio->ch4.length = 64 - audio->ch4.envelope.length; } void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8); if (!_writeEnvelope(&audio->ch4.envelope, value, audio->style)) { audio->playingCh4 = false; *audio->nr52 &= ~0x0008; @@ -334,14 +334,14 @@ void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) { } void GBAudioWriteNR43(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8); audio->ch4.ratio = GBAudioRegisterNoiseFeedbackGetRatio(value); audio->ch4.frequency = GBAudioRegisterNoiseFeedbackGetFrequency(value); audio->ch4.power = GBAudioRegisterNoiseFeedbackGetPower(value); } void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8); bool wasStop = audio->ch4.stop; audio->ch4.stop = GBAudioRegisterNoiseControlGetStop(value); if (!wasStop && audio->ch4.stop && audio->ch4.length && !(audio->frame & 1)) { @@ -465,25 +465,31 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) { } } -void GBAudioRun(struct GBAudio* audio, int32_t timestamp) { +void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { if (!audio->enable) { return; } - if (audio->playingCh1) { + if (audio->playingCh1 && (channels & 0x1)) { int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor; - int32_t diff = (timestamp - audio->ch1.lastUpdate) / period; - audio->ch1.index = (audio->ch1.index + diff) & 7; - audio->ch1.lastUpdate += diff * period; - _updateSquareSample(&audio->ch1); + int32_t diff = timestamp - audio->ch1.lastUpdate; + if (diff >= period) { + diff /= period; + audio->ch1.index = (audio->ch1.index + diff) & 7; + audio->ch1.lastUpdate += diff * period; + _updateSquareSample(&audio->ch1); + } } - if (audio->playingCh2) { + if (audio->playingCh2 && (channels & 0x2)) { int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor; - int32_t diff = (timestamp - audio->ch2.lastUpdate) / period; - audio->ch2.index = (audio->ch2.index + diff) & 7; - audio->ch2.lastUpdate += diff * period; - _updateSquareSample(&audio->ch2); + int32_t diff = timestamp - audio->ch2.lastUpdate; + if (diff >= period) { + diff /= period; + audio->ch2.index = (audio->ch2.index + diff) & 7; + audio->ch2.lastUpdate += diff * period; + _updateSquareSample(&audio->ch2); + } } - if (audio->playingCh3) { + if (audio->playingCh3 && (channels & 0x4)) { int cycles = 2 * (2048 - audio->ch3.rate) * audio->timingFactor; int32_t diff = timestamp - audio->ch3.nextUpdate; if (diff >= 0) { @@ -555,7 +561,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp) { } } } - if (audio->playingCh4) { + if (audio->playingCh4 && (channels & 0x8)) { int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1; cycles <<= audio->ch4.frequency; cycles *= 8 * audio->timingFactor; @@ -583,7 +589,7 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { audio->skipFrame = false; return; } - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x7); int frame = (audio->frame + 1) & 7; audio->frame = frame; @@ -668,7 +674,7 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { } void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing)); + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF); int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8; int sampleLeft = dcOffset; int sampleRight = dcOffset; diff --git a/src/gb/io.c b/src/gb/io.c index d03171bb4..b427f8729 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -403,7 +403,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case GB_REG_WAVE_D: case GB_REG_WAVE_E: case GB_REG_WAVE_F: - GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x4); if (!gb->audio.playingCh3 || gb->audio.style != GB_AUDIO_DMG) { gb->audio.ch3.wavedata8[address - GB_REG_WAVE_0] = value; } else if(gb->audio.ch3.readable) { @@ -608,7 +608,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { case GB_REG_WAVE_E: case GB_REG_WAVE_F: if (gb->audio.playingCh3) { - GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x4); if (gb->audio.ch3.readable || gb->audio.style != GB_AUDIO_DMG) { return gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1]; } else { @@ -622,7 +622,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { if (gb->model < GB_MODEL_CGB) { mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address); } else if (gb->audio.enable) { - GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x3); return (gb->audio.ch1.sample) | (gb->audio.ch2.sample << 4); } break; @@ -630,7 +630,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { if (gb->model < GB_MODEL_CGB) { mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address); } else if (gb->audio.enable) { - GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing)); + GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0xC); return (gb->audio.ch3.sample) | (gb->audio.ch4.sample << 4); } break; diff --git a/src/gba/audio.c b/src/gba/audio.c index 4f3cfe3de..addac27ef 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -229,7 +229,7 @@ void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) { bank = 1; } - GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing)); + GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing), 0x4); audio->psg.ch3.wavedata32[address | (bank * 4)] = value; } @@ -242,7 +242,7 @@ uint32_t GBAAudioReadWaveRAM(struct GBAAudio* audio, int address) { bank = 1; } - GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing)); + GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing), 0x4); return audio->psg.ch3.wavedata32[address | (bank * 4)]; } From cbbaa42641ca597124e9ded08cd282bd00c65ed1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Jun 2022 02:14:47 -0700 Subject: [PATCH 12/27] CHANGES: Update --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 558807a83..25bb5172e 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Emulation fixes: - GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378) - GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925) - GB Audio: Properly apply per-model audio differences + - GB Audio: Revamp channel rendering - GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032) - GB Serialize: Fix loading MBC1 states that affect bank 0 (fixes mgba.io/i/2402) - GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339) From cbbcf7478ebc9532d9e9a02e07c38bed19ef9de4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Jun 2022 02:13:16 -0700 Subject: [PATCH 13/27] GBA Audio: Adjust PSG sampling rate with SOUNDBIAS --- CHANGES | 1 + src/gb/audio.c | 2 +- src/gba/audio.c | 97 +++++++++++++++++++++++++++++-------------------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/CHANGES b/CHANGES index 25bb5172e..d781669d3 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Emulation fixes: - GBA: Improve timing when not booting from BIOS - GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450) - GBA: Fix booting multiboot ROMs with no JOY entrypoint + - GBA Audio: Adjust PSG sampling rate with SOUNDBIAS - GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059) - GBA BIOS: Initial HLE timing estimation of UnLz77 functions (fixes mgba.io/i/2141) - GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410) diff --git a/src/gb/audio.c b/src/gb/audio.c index 1601c734b..b1a3f68dd 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -674,7 +674,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { } void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) { - GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF); int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8; int sampleLeft = dcOffset; int sampleRight = dcOffset; @@ -731,6 +730,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct GBAudio* audio = user; int16_t sampleLeft = 0; int16_t sampleRight = 0; + GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF); GBAudioSamplePSG(audio, &sampleLeft, &sampleRight); sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7; sampleRight = (sampleRight * audio->masterVolume * 6) >> 7; diff --git a/src/gba/audio.c b/src/gba/audio.c index addac27ef..05f062719 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -25,6 +25,7 @@ mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio"); const unsigned GBA_AUDIO_SAMPLES = 2048; const int GBA_AUDIO_VOLUME_MAX = 0x100; +static const int SAMPLE_INTERVAL = GBA_ARM7TDMI_FREQUENCY / 0x8000; static const int CLOCKS_PER_FRAME = 0x800; static int _applyBias(struct GBAAudio* audio, int sample); @@ -218,6 +219,7 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) { void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) { audio->soundbias = value; + audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value); } void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) { @@ -318,59 +320,74 @@ static int _applyBias(struct GBAAudio* audio, int sample) { static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct GBAAudio* audio = user; - int16_t sampleLeft = 0; - int16_t sampleRight = 0; - int psgShift = 4 - audio->volume; - GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight); - sampleLeft >>= psgShift; - sampleRight >>= psgShift; + int16_t samplesLeft[8]; + int16_t samplesRight[8]; + int32_t timestamp = mTimingCurrentTime(&audio->p->timing) - cyclesLate - SAMPLE_INTERVAL; + int sample; + for (sample = 0; sample * audio->sampleInterval < (int32_t) SAMPLE_INTERVAL; ++sample) { + int16_t sampleLeft = 0; + int16_t sampleRight = 0; + int psgShift = 4 - audio->volume; + GBAudioRun(&audio->psg, timestamp + (sample + 1) * audio->sampleInterval, 0xF); + GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight); + sampleLeft >>= psgShift; + sampleRight >>= psgShift; - if (audio->mixer) { - audio->mixer->step(audio->mixer); - } - if (!audio->externalMixing) { - if (!audio->forceDisableChA) { - if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + if (audio->mixer) { + audio->mixer->step(audio->mixer); + } + if (!audio->externalMixing) { + if (!audio->forceDisableChA) { + if (audio->chALeft) { + sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + } + + if (audio->chARight) { + sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + } } - if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + if (!audio->forceDisableChB) { + if (audio->chBLeft) { + sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + } + + if (audio->chBRight) { + sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + } } } - if (!audio->forceDisableChB) { - if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; - } - - if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; - } - } + sampleLeft = _applyBias(audio, sampleLeft); + sampleRight = _applyBias(audio, sampleRight); + samplesLeft[sample] = sampleLeft; + samplesRight[sample] = sampleRight; } - sampleLeft = _applyBias(audio, sampleLeft); - sampleRight = _applyBias(audio, sampleRight); - mCoreSyncLockAudio(audio->p->sync); unsigned produced; - if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) { - blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft); - blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight); - audio->lastLeft = sampleLeft; - audio->lastRight = sampleRight; - audio->clock += audio->sampleInterval; - if (audio->clock >= CLOCKS_PER_FRAME) { - blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME); - blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME); - audio->clock -= CLOCKS_PER_FRAME; + int i; + for (i = 0; i < sample; ++i) { + int16_t sampleLeft = samplesLeft[i]; + int16_t sampleRight = samplesRight[i]; + if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) { + blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft); + blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight); + audio->lastLeft = sampleLeft; + audio->lastRight = sampleRight; + audio->clock += audio->sampleInterval; + if (audio->clock >= CLOCKS_PER_FRAME) { + blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME); + blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME); + audio->clock -= CLOCKS_PER_FRAME; + } } } - produced = blip_samples_avail(audio->psg.left); + // TODO: Post all frames if (audio->p->stream && audio->p->stream->postAudioFrame) { - audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); + audio->p->stream->postAudioFrame(audio->p->stream, samplesLeft[sample - 1], samplesRight[sample - 1]); } + produced = blip_samples_avail(audio->psg.left); bool wait = produced >= audio->samples; if (!mCoreSyncProduceAudio(audio->p->sync, audio->psg.left, audio->samples)) { // Interrupted @@ -381,7 +398,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { audio->p->stream->postAudioBuffer(audio->p->stream, audio->psg.left, audio->psg.right); } - mTimingSchedule(timing, &audio->sampleEvent, audio->sampleInterval - cyclesLate); + mTimingSchedule(timing, &audio->sampleEvent, SAMPLE_INTERVAL - cyclesLate); } void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) { From 29dbb55c8023c533bc7c97f92b67a4282c08f87f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Jun 2022 02:32:21 -0700 Subject: [PATCH 14/27] GBA I/O: Unstub SOUNDBIAS --- src/gba/io.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gba/io.c b/src/gba/io.c index 0cd3c4e25..14c05b0f5 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -416,6 +416,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { value |= gba->memory.io[REG_SOUNDCNT_X >> 1] & 0xF; break; case REG_SOUNDBIAS: + value &= 0xC3FE; GBAAudioWriteSOUNDBIAS(&gba->audio, value); break; @@ -842,7 +843,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { gba->memory.io[REG_JOYSTAT >> 1] &= ~JOYSTAT_RECV; break; - case REG_SOUNDBIAS: case REG_POSTFLG: mLOG(GBA_IO, STUB, "Stub I/O register read: %03x", address); break; @@ -878,6 +878,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { case REG_SOUND4CNT_LO: case REG_SOUND4CNT_HI: case REG_SOUNDCNT_LO: + case REG_SOUNDBIAS: if (!GBAudioEnableIsEnable(gba->memory.io[REG_SOUNDCNT_X >> 1])) { // TODO: Is writing allowed when the circuit is disabled? return 0; From 3c0d9f719718a3f547cbac5d181ec46b9365dbb9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Jun 2022 18:40:53 -0700 Subject: [PATCH 15/27] GBA Audio: Sample FIFOs at SOUNDBIAS-set frequency --- CHANGES | 1 + include/mgba/internal/gba/audio.h | 2 +- include/mgba/internal/gba/serialize.h | 13 ++++++-- src/gba/audio.c | 46 ++++++++++++++++++++------- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index d781669d3..302e1685b 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,7 @@ Emulation fixes: - GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450) - GBA: Fix booting multiboot ROMs with no JOY entrypoint - GBA Audio: Adjust PSG sampling rate with SOUNDBIAS + - GBA Audio: Sample FIFOs at SOUNDBIAS-set frequency - GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059) - GBA BIOS: Initial HLE timing estimation of UnLz77 functions (fixes mgba.io/i/2141) - GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410) diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index c9fab3ae1..2bf6c14a6 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -34,7 +34,7 @@ struct GBAAudioFIFO { uint32_t internalSample; int internalRemaining; int dmaSource; - int8_t sample; + int8_t samples[8]; }; DECL_BITFIELD(GBARegisterSOUNDCNT_HI, uint16_t); diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 364563195..20d0719ab 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -224,7 +224,10 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * 0x00320 - 0x00323: Next IRQ event * 0x00324 - 0x00327: Interruptable BIOS stall cycles * 0x00328 - 0x00367: Matrix memory mapping table - * 0x00368 - 0x003FF: Reserved (leave zero) + * 0x00368 - 0x0036F: Reserved (leave zero) + * 0x00370 - 0x00377: Audio FIFO A samples + * 0x00378 - 0x0037F: Audio FIFO B samples + * 0x00380 - 0x003FF: Reserved (leave zero) * 0x00400 - 0x007FF: I/O memory * 0x00800 - 0x00BFF: Palette * 0x00C00 - 0x00FFF: OAM @@ -381,8 +384,14 @@ struct GBASerializedState { int32_t biosStall; uint32_t matrixMappings[16]; + uint32_t reservedMatrix[2]; - uint32_t reserved[38]; + struct { + int8_t chA[8]; + int8_t chB[8]; + } samples; + + uint32_t reserved[32]; uint16_t io[SIZE_IO >> 1]; uint16_t pram[SIZE_PALETTE_RAM >> 1]; diff --git a/src/gba/audio.c b/src/gba/audio.c index 05f062719..8cd58dc78 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -67,13 +67,16 @@ void GBAAudioReset(struct GBAAudio* audio) { audio->chA.internalSample = 0; audio->chA.internalRemaining = 0; memset(audio->chA.fifo, 0, sizeof(audio->chA.fifo)); - audio->chA.sample = 0; audio->chB.fifoWrite = 0; audio->chB.fifoRead = 0; audio->chB.internalSample = 0; audio->chB.internalRemaining = 0; memset(audio->chB.fifo, 0, sizeof(audio->chB.fifo)); - audio->chB.sample = 0; + int i; + for (i = 0; i < 8; ++i) { + audio->chA.samples[i] = 0; + audio->chB.samples[i] = 0; + } audio->sampleRate = 0x8000; audio->soundbias = 0x200; audio->volume = 0; @@ -301,7 +304,17 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { channel->fifoRead = 0; } } - channel->sample = channel->internalSample; + int32_t until = mTimingUntil(&audio->p->timing, &audio->sampleEvent) - 1; + int bits = 1 << GBARegisterSOUNDBIASGetResolution(audio->soundbias); + until += 1 << (9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias)); + until >>= 9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias); + int i; + for (i = bits - until; i < bits; ++i) { + if (i < 0 || i >= bits) { + abort(); + } + channel->samples[i] = channel->internalSample; + } if (channel->internalRemaining) { channel->internalSample >>= 8; --channel->internalRemaining; @@ -339,21 +352,21 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { if (!audio->externalMixing) { if (!audio->forceDisableChA) { if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + sampleLeft += (audio->chA.samples[sample] << 2) >> !audio->volumeChA; } if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + sampleRight += (audio->chA.samples[sample] << 2) >> !audio->volumeChA; } } if (!audio->forceDisableChB) { if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + sampleLeft += (audio->chB.samples[sample] << 2) >> !audio->volumeChB; } if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + sampleRight += (audio->chB.samples[sample] << 2) >> !audio->volumeChB; } } } @@ -364,12 +377,19 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { samplesRight[sample] = sampleRight; } + memset(audio->chA.samples, audio->chA.samples[sample - 1], sizeof(audio->chA.samples)); + memset(audio->chB.samples, audio->chB.samples[sample - 1], sizeof(audio->chB.samples)); + mCoreSyncLockAudio(audio->p->sync); unsigned produced; + int32_t sampleSumLeft = 0; + int32_t sampleSumRight = 0; int i; for (i = 0; i < sample; ++i) { int16_t sampleLeft = samplesLeft[i]; int16_t sampleRight = samplesRight[i]; + sampleSumLeft += sampleLeft; + sampleSumRight += sampleRight; if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) { blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft); blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight); @@ -385,7 +405,9 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { } // TODO: Post all frames if (audio->p->stream && audio->p->stream->postAudioFrame) { - audio->p->stream->postAudioFrame(audio->p->stream, samplesLeft[sample - 1], samplesRight[sample - 1]); + sampleSumLeft /= sample; + sampleSumRight /= sample; + audio->p->stream->postAudioFrame(audio->p->stream, sampleSumLeft, sampleSumRight); } produced = blip_samples_avail(audio->psg.left); bool wait = produced >= audio->samples; @@ -406,8 +428,8 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* STORE_32(audio->chA.internalSample, 0, &state->audio.internalA); STORE_32(audio->chB.internalSample, 0, &state->audio.internalB); - state->audio.sampleA = audio->chA.sample; - state->audio.sampleB = audio->chB.sample; + memcpy(state->samples.chA, audio->chA.samples, sizeof(audio->chA.samples)); + memcpy(state->samples.chB, audio->chB.samples, sizeof(audio->chB.samples)); int readA = audio->chA.fifoRead; int readB = audio->chB.fifoRead; @@ -453,8 +475,8 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA); LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB); - audio->chA.sample = state->audio.sampleA; - audio->chB.sample = state->audio.sampleB; + memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples)); + memcpy(audio->chB.samples, state->samples.chB, sizeof(audio->chB.samples)); int readA = 0; int readB = 0; From 0a674dd377a091f5d07874c4e0cff58d73fb325c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Jun 2022 20:18:49 -0700 Subject: [PATCH 16/27] Updater: Fix stub MSVC build --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95e714e81..eafaaf82b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -957,7 +957,7 @@ if(BUILD_UPDATER) ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c) target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY}) - set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES}") + set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES};BUILD_STATIC") if(MSVC) set_target_properties(updater-stub PROPERTIES LINK_FLAGS /ENTRY:mainCRTStartup) else() From 818314a6fd27238eb446b2432069812470dc0694 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Jun 2022 22:13:00 -0700 Subject: [PATCH 17/27] GBA I/O: SOUNDBIAS is readable when sound is off (fixes #2541) --- src/gba/io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/io.c b/src/gba/io.c index 14c05b0f5..34e7471bb 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -878,7 +878,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { case REG_SOUND4CNT_LO: case REG_SOUND4CNT_HI: case REG_SOUNDCNT_LO: - case REG_SOUNDBIAS: if (!GBAudioEnableIsEnable(gba->memory.io[REG_SOUNDCNT_X >> 1])) { // TODO: Is writing allowed when the circuit is disabled? return 0; @@ -898,6 +897,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { case REG_BLDALPHA: case REG_SOUNDCNT_HI: case REG_SOUNDCNT_X: + case REG_SOUNDBIAS: case REG_DMA0CNT_HI: case REG_DMA1CNT_HI: case REG_DMA2CNT_HI: From c19457aa4b824ced62d87738a9b1b79aee69fe0a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Jun 2022 22:18:05 -0700 Subject: [PATCH 18/27] GBA Overrides: Add Game Boy Wars Advance 1+2 entry (fixes #2540) --- src/gba/overrides.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gba/overrides.c b/src/gba/overrides.c index 2e0ce10de..823957d5c 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -70,6 +70,9 @@ static const struct GBACartridgeOverride _overrides[] = { { "AI2E", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, { "AI2P", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, + // Game Boy Wars Advance 1+2 + { "BGWJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, + // Golden Sun: The Lost Age { "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A, false }, From 46b59268d384f634078352e93ad6a39b7673e6de Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Jun 2022 23:26:15 -0700 Subject: [PATCH 19/27] GB Audio: Optimize channel 4 --- src/gb/audio.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/gb/audio.c b/src/gb/audio.c index b1a3f68dd..7a1fe60bf 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -568,13 +568,24 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { int32_t last = 0; int32_t diff = timestamp - audio->ch4.lastEvent; + int samples = 0; + int positiveSamples = 0; + int lsb; + int coeff = 0x60; + if (!audio->ch4.power) { + coeff <<= 8; + } for (; last + cycles <= diff; last += cycles) { - int lsb = audio->ch4.lfsr & 1; - audio->ch4.sample = lsb * audio->ch4.envelope.currentVolume; - ++audio->ch4.nSamples; - audio->ch4.samples += audio->ch4.sample; + lsb = audio->ch4.lfsr & 1; audio->ch4.lfsr >>= 1; - audio->ch4.lfsr ^= (lsb * 0x60) << (audio->ch4.power ? 0 : 8); + audio->ch4.lfsr ^= lsb * coeff; + ++samples; + positiveSamples += lsb; + } + if (samples) { + audio->ch4.sample = lsb * audio->ch4.envelope.currentVolume; + audio->ch4.nSamples += samples; + audio->ch4.samples += positiveSamples * audio->ch4.envelope.currentVolume; } audio->ch4.lastEvent += last; From f4217a7a77d5208f410a8b0eddd9706094e1607c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Jun 2022 15:38:38 -0700 Subject: [PATCH 20/27] GBA Video: Use memset instead of manually assigning --- src/gba/renderers/video-software.c | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 35737a9e3..ef82fec93 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -141,30 +141,11 @@ static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) { for (i = 0; i < 4; ++i) { struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i]; + memset(bg, 0, sizeof(*bg)); bg->index = i; - bg->enabled = 0; - bg->priority = 0; - bg->charBase = 0; - bg->mosaic = 0; - bg->multipalette = 0; - bg->screenBase = 0; - bg->overflow = 0; - bg->size = 0; - bg->target1 = 0; - bg->target2 = 0; - bg->x = 0; - bg->y = 0; - bg->refx = 0; - bg->refy = 0; bg->dx = 256; - bg->dmx = 0; - bg->dy = 0; bg->dmy = 256; - bg->sx = 0; - bg->sy = 0; bg->yCache = -1; - bg->offsetX = 0; - bg->offsetY = 0; } } From 9f5267e24eda5e74438212ab6918261b62661054 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Jun 2022 22:36:19 -0700 Subject: [PATCH 21/27] GBA Audio: Claw back some performance --- include/mgba/internal/gba/audio.h | 3 ++- include/mgba/internal/gba/serialize.h | 10 ++++---- src/gb/audio.c | 35 +++++++++++++-------------- src/gba/audio.c | 11 +++------ 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index 2bf6c14a6..65336b299 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -16,6 +16,7 @@ CXX_GUARD_START #include #define GBA_AUDIO_FIFO_SIZE 8 +#define GBA_MAX_SAMPLES 16 #define MP2K_MAGIC 0x68736D53 #define MP2K_MAX_SOUND_CHANNELS 12 @@ -34,7 +35,7 @@ struct GBAAudioFIFO { uint32_t internalSample; int internalRemaining; int dmaSource; - int8_t samples[8]; + int8_t samples[GBA_MAX_SAMPLES]; }; DECL_BITFIELD(GBARegisterSOUNDCNT_HI, uint16_t); diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 20d0719ab..6ab814859 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -225,8 +225,8 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * 0x00324 - 0x00327: Interruptable BIOS stall cycles * 0x00328 - 0x00367: Matrix memory mapping table * 0x00368 - 0x0036F: Reserved (leave zero) - * 0x00370 - 0x00377: Audio FIFO A samples - * 0x00378 - 0x0037F: Audio FIFO B samples + * 0x00370 - 0x0037F: Audio FIFO A samples + * 0x00380 - 0x0038F: Audio FIFO B samples * 0x00380 - 0x003FF: Reserved (leave zero) * 0x00400 - 0x007FF: I/O memory * 0x00800 - 0x00BFF: Palette @@ -387,11 +387,11 @@ struct GBASerializedState { uint32_t reservedMatrix[2]; struct { - int8_t chA[8]; - int8_t chB[8]; + int8_t chA[16]; + int8_t chB[16]; } samples; - uint32_t reserved[32]; + uint32_t reserved[28]; uint16_t io[SIZE_IO >> 1]; uint16_t pram[SIZE_PALETTE_RAM >> 1]; diff --git a/src/gb/audio.c b/src/gb/audio.c index 7a1fe60bf..19f265c47 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -566,29 +566,28 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { cycles <<= audio->ch4.frequency; cycles *= 8 * audio->timingFactor; - int32_t last = 0; int32_t diff = timestamp - audio->ch4.lastEvent; - int samples = 0; - int positiveSamples = 0; - int lsb; - int coeff = 0x60; - if (!audio->ch4.power) { - coeff <<= 8; - } - for (; last + cycles <= diff; last += cycles) { - lsb = audio->ch4.lfsr & 1; - audio->ch4.lfsr >>= 1; - audio->ch4.lfsr ^= lsb * coeff; - ++samples; - positiveSamples += lsb; - } - if (samples) { + if (diff >= cycles) { + int32_t last; + int samples = 0; + int positiveSamples = 0; + int lsb; + int coeff = 0x60; + if (!audio->ch4.power) { + coeff <<= 8; + } + for (last = 0; last + cycles <= diff; last += cycles) { + lsb = audio->ch4.lfsr & 1; + audio->ch4.lfsr >>= 1; + audio->ch4.lfsr ^= lsb * coeff; + ++samples; + positiveSamples += lsb; + } audio->ch4.sample = lsb * audio->ch4.envelope.currentVolume; audio->ch4.nSamples += samples; audio->ch4.samples += positiveSamples * audio->ch4.envelope.currentVolume; + audio->ch4.lastEvent += last; } - - audio->ch4.lastEvent += last; } } diff --git a/src/gba/audio.c b/src/gba/audio.c index 8cd58dc78..00c20bd46 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -25,7 +25,7 @@ mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio"); const unsigned GBA_AUDIO_SAMPLES = 2048; const int GBA_AUDIO_VOLUME_MAX = 0x100; -static const int SAMPLE_INTERVAL = GBA_ARM7TDMI_FREQUENCY / 0x8000; +static const int SAMPLE_INTERVAL = GBA_ARM7TDMI_FREQUENCY / 0x4000; static const int CLOCKS_PER_FRAME = 0x800; static int _applyBias(struct GBAAudio* audio, int sample); @@ -305,14 +305,11 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { } } int32_t until = mTimingUntil(&audio->p->timing, &audio->sampleEvent) - 1; - int bits = 1 << GBARegisterSOUNDBIASGetResolution(audio->soundbias); + int bits = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias); until += 1 << (9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias)); until >>= 9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias); int i; for (i = bits - until; i < bits; ++i) { - if (i < 0 || i >= bits) { - abort(); - } channel->samples[i] = channel->internalSample; } if (channel->internalRemaining) { @@ -333,8 +330,8 @@ static int _applyBias(struct GBAAudio* audio, int sample) { static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct GBAAudio* audio = user; - int16_t samplesLeft[8]; - int16_t samplesRight[8]; + int16_t samplesLeft[GBA_MAX_SAMPLES]; + int16_t samplesRight[GBA_MAX_SAMPLES]; int32_t timestamp = mTimingCurrentTime(&audio->p->timing) - cyclesLate - SAMPLE_INTERVAL; int sample; for (sample = 0; sample * audio->sampleInterval < (int32_t) SAMPLE_INTERVAL; ++sample) { From 3c228dad60a214d5578b5670ff673abd7a07f1e8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Jun 2022 23:45:34 -0700 Subject: [PATCH 22/27] Qt: Fix games not displaying on macOS after first run --- src/platform/qt/DisplayGL.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 678f9d612..221acffc0 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -203,6 +203,8 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { QMetaObject::invokeMethod(m_painter.get(), "start"); if (!m_gl) { setUpdatesEnabled(false); + } else { + show(); } } @@ -265,6 +267,9 @@ void DisplayGL::stopDrawing() { m_hasStarted = false; CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection); + if (m_gl) { + hide(); + } setUpdatesEnabled(true); } m_context.reset(); @@ -274,9 +279,7 @@ void DisplayGL::pauseDrawing() { if (m_hasStarted) { m_isDrawing = false; QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection); -#ifndef Q_OS_MAC setUpdatesEnabled(true); -#endif } } @@ -284,11 +287,9 @@ void DisplayGL::unpauseDrawing() { if (m_hasStarted) { m_isDrawing = true; QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection); -#ifndef Q_OS_MAC if (!m_gl) { setUpdatesEnabled(false); } -#endif } } From e87ba99140fd5580279e45c9d7a18474b704e305 Mon Sep 17 00:00:00 2001 From: Celeste Wouters Date: Fri, 14 May 2021 22:09:43 +0200 Subject: [PATCH 23/27] GBA Memory: update the renderer on rawWrite{16,32}/GBAPatch{16,32} rawWrite{16,32}/GBAPatch{16, 32} is used by the memory editor on Qt, and changes to the VRAM area there were not reflected on the graphical output, palette or OAM writes are. This fixes this inconsistency. --- src/gba/memory.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gba/memory.c b/src/gba/memory.c index fddef9223..8bacf4a79 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1239,9 +1239,13 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o if ((address & 0x0001FFFF) < SIZE_VRAM) { LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); STORE_32(value, address & 0x0001FFFC, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFC); + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) | 2); } else { LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram); STORE_32(value, address & 0x00017FFC, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFC); + gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) | 2); } break; case REGION_OAM: @@ -1308,9 +1312,11 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o if ((address & 0x0001FFFF) < SIZE_VRAM) { LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram); STORE_16(value, address & 0x0001FFFE, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); } else { LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram); STORE_16(value, address & 0x00017FFE, gba->video.vram); + gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE); } break; case REGION_OAM: From d6accc4ef6d8cc040f022d5856005995fb4d4e22 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Jun 2022 01:28:58 -0700 Subject: [PATCH 24/27] Scripting: Add calling functions with lists --- include/mgba/script/types.h | 1 + src/script/engines/lua.c | 99 ++++++++++++++++++++++++++++++++++--- src/script/test/lua.c | 33 +++++++++++++ src/script/test/types.c | 70 +++++++++++++++++++++++++- 4 files changed, 196 insertions(+), 7 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index fd7dbe5d8..489b5eee4 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -108,6 +108,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_F64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_F64, TYPE) #define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) #define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE) +#define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 5c8906531..db1178993 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -30,7 +30,7 @@ static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*, static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*); -static struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext); +static struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool pop); static bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue*); static void _luaDeref(struct mScriptValue*); @@ -186,7 +186,7 @@ bool _luaIsScript(struct mScriptEngineContext* ctx, const char* name, struct VFi struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext* ctx, const char* name) { struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx; lua_getglobal(luaContext->lua, name); - return _luaCoerce(luaContext); + return _luaCoerce(luaContext, true); } bool _luaSetGlobal(struct mScriptEngineContext* ctx, const char* name, struct mScriptValue* value) { @@ -212,7 +212,83 @@ struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaConte return value; } -struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) { +struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) { + struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + bool isList = true; + + lua_pushnil(luaContext->lua); + while (lua_next(luaContext->lua, -2) != 0) { + struct mScriptValue* value = NULL; + int type = lua_type(luaContext->lua, -1); + switch (type) { + case LUA_TNUMBER: + case LUA_TBOOLEAN: + case LUA_TSTRING: + case LUA_TFUNCTION: + value = _luaCoerce(luaContext, true); + break; + default: + // Don't let values be something that could contain themselves + break; + } + if (!value) { + lua_pop(luaContext->lua, 3); + mScriptValueDeref(table); + return false; + } + + struct mScriptValue* key = NULL; + type = lua_type(luaContext->lua, -1); + switch (type) { + case LUA_TBOOLEAN: + case LUA_TSTRING: + isList = false; + // Fall through + case LUA_TNUMBER: + key = _luaCoerce(luaContext, false); + break; + default: + // Limit keys to hashable types + break; + } + + if (!key) { + lua_pop(luaContext->lua, 2); + mScriptValueDeref(table); + return false; + } + mScriptTableInsert(table, key, value); + mScriptValueDeref(key); + mScriptValueDeref(value); + } + lua_pop(luaContext->lua, 1); + + size_t len = mScriptTableSize(table); + if (!isList || !len) { + return table; + } + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + size_t i; + for (i = 1; i <= len; ++i) { + struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_S64(i)); + if (!value) { + mScriptValueDeref(list); + return table; + } + mScriptValueWrap(value, mScriptListAppend(list->value.list)); + } + if (i != len + 1) { + mScriptValueDeref(list); + mScriptContextFillPool(luaContext->d.context, table); + return table; + } + mScriptValueDeref(table); + mScriptContextFillPool(luaContext->d.context, list); + return list; +} + +struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool pop) { if (lua_isnone(luaContext->lua, -1)) { lua_pop(luaContext->lua, 1); return NULL; @@ -244,7 +320,16 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) { break; case LUA_TFUNCTION: // This function pops the value internally via luaL_ref + if (!pop) { + break; + } return _luaCoerceFunction(luaContext); + case LUA_TTABLE: + // This function pops the value internally via luaL_ref + if (!pop) { + break; + } + return _luaCoerceTable(luaContext); case LUA_TUSERDATA: if (!lua_getmetatable(luaContext->lua, -1)) { break; @@ -259,7 +344,9 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) { value = mScriptContextAccessWeakref(luaContext->d.context, value); break; } - lua_pop(luaContext->lua, 1); + if (pop) { + lua_pop(luaContext->lua, 1); + } return value; } @@ -467,7 +554,7 @@ bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList if (frame) { int i; for (i = 0; i < count; ++i) { - struct mScriptValue* value = _luaCoerce(luaContext); + struct mScriptValue* value = _luaCoerce(luaContext, true); if (!value) { ok = false; break; @@ -640,7 +727,7 @@ int _luaSetObject(lua_State* lua) { char key[MAX_KEY_SIZE]; const char* keyPtr = lua_tostring(lua, -2); struct mScriptValue* obj = lua_touserdata(lua, -3); - struct mScriptValue* val = _luaCoerce(luaContext); + struct mScriptValue* val = _luaCoerce(luaContext, true); if (!keyPtr) { lua_pop(lua, 2); diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 71b719966..bf3bf099a 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -61,8 +61,22 @@ static void testV1(struct Test* a, int b) { a->i += b; } +static int32_t sum(struct mScriptList* list) { + int32_t sum = 0; + size_t i; + for (i = 0; i < mScriptListSize(list); ++i) { + struct mScriptValue value; + if (!mScriptCast(mSCRIPT_TYPE_MS_S32, mScriptListGetPointer(list, i), &value)) { + continue; + } + sum += value.value.s32; + } + return sum; +} + mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a); mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b); +mSCRIPT_BIND_FUNCTION(boundSum, S32, sum, 1, LIST, list); mSCRIPT_DECLARE_STRUCT(Test); mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0); @@ -619,6 +633,24 @@ M_TEST_DEFINE(tableIterate) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(callList) { + SETUP_LUA; + + struct mScriptValue a = mSCRIPT_MAKE_S32(6); + struct mScriptValue* val; + + assert_true(lua->setGlobal(lua, "sum", &boundSum)); + TEST_PROGRAM("a = sum({1, 2, 3})"); + assert_null(lua->getError(lua)); + + val = lua->getGlobal(lua, "a"); + assert_non_null(val); + assert_true(mSCRIPT_TYPE_MS_S32->equal(&a, val)); + mScriptValueDeref(val); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(create), cmocka_unit_test(loadGood), @@ -634,4 +666,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(errorReporting), cmocka_unit_test(tableLookup), cmocka_unit_test(tableIterate), + cmocka_unit_test(callList), ) diff --git a/src/script/test/types.c b/src/script/test/types.c index 45a9a2d25..9fa83a8a5 100644 --- a/src/script/test/types.c +++ b/src/script/test/types.c @@ -52,6 +52,30 @@ static int isHello(const char* str) { return strcmp(str, "hello") == 0; } +static int isSequential(struct mScriptList* list) { + int last; + if (mScriptListSize(list) == 0) { + return true; + } + size_t i; + for (i = 0; i < mScriptListSize(list); ++i) { + struct mScriptValue* value = mScriptListGetPointer(list, i); + struct mScriptValue intValue; + if (!mScriptCast(mSCRIPT_TYPE_MS_S32, value, &intValue)) { + return false; + } + if (!i) { + last = intValue.value.s32; + } else { + if (intValue.value.s32 != last + 1) { + return false; + } + ++last; + } + } + return true; +} + mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0); mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32, ignored); mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, in); @@ -61,6 +85,7 @@ mSCRIPT_BIND_FUNCTION(boundIdentityStruct, S(Test), identityStruct, 1, S(Test), mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b); mSCRIPT_BIND_FUNCTION(boundSubInts, S32, subInts, 2, S32, a, S32, b); mSCRIPT_BIND_FUNCTION(boundIsHello, S32, isHello, 1, CHARP, str); +mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list); M_TEST_DEFINE(voidArgs) { struct mScriptFrame frame; @@ -919,6 +944,47 @@ M_TEST_DEFINE(stringIsNotHello) { mScriptFrameDeinit(&frame); } +M_TEST_DEFINE(invokeList) { + struct mScriptFrame frame; + struct mScriptList list; + int val; + + mScriptListInit(&list, 0); + + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, LIST, &list); + assert_true(mScriptInvoke(&boundIsSequential, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 1); + mScriptFrameDeinit(&frame); + + *mScriptListAppend(&list) = mSCRIPT_MAKE_S32(1); + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, LIST, &list); + assert_true(mScriptInvoke(&boundIsSequential, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 1); + mScriptFrameDeinit(&frame); + + *mScriptListAppend(&list) = mSCRIPT_MAKE_S32(2); + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, LIST, &list); + assert_true(mScriptInvoke(&boundIsSequential, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 1); + mScriptFrameDeinit(&frame); + + *mScriptListAppend(&list) = mSCRIPT_MAKE_S32(4); + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, LIST, &list); + assert_true(mScriptInvoke(&boundIsSequential, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 0); + mScriptFrameDeinit(&frame); + + mScriptListDeinit(&list); +} + M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(voidArgs), cmocka_unit_test(voidFunc), @@ -948,4 +1014,6 @@ M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(hashTableBasic), cmocka_unit_test(hashTableString), cmocka_unit_test(stringIsHello), - cmocka_unit_test(stringIsNotHello)) + cmocka_unit_test(stringIsNotHello), + cmocka_unit_test(invokeList), +) From d4c1ab172828045f13279402d2b07ba0cc5c74b4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Jun 2022 01:59:27 -0700 Subject: [PATCH 25/27] Scripting: Fix some Lua memory lifetimes --- src/script/engines/lua.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index db1178993..2770e8898 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -106,6 +106,7 @@ static const luaL_Reg _mSTTable[] = { { "__index", _luaGetTable }, { "__len", _luaLenTable }, { "__pairs", _luaPairsTable }, + { "__gc", _luaGcObject }, { NULL, NULL } }; @@ -412,7 +413,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { mScriptValueWrap(value, newValue); - mScriptValueDeref(value); } luaL_setmetatable(luaContext->lua, "mSTList"); break; @@ -422,7 +422,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { mScriptValueWrap(value, newValue); - mScriptValueDeref(value); } luaL_setmetatable(luaContext->lua, "mSTTable"); break; @@ -440,7 +439,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { mScriptValueWrap(value, newValue); - mScriptValueDeref(value); } luaL_setmetatable(luaContext->lua, "mSTStruct"); break; From 00bd093fe3812fe572ba9d143cce3e6642c59dd5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Jun 2022 02:04:09 -0700 Subject: [PATCH 26/27] Scripting: Add bitmask handling functions --- include/mgba/script/context.h | 4 +- include/mgba/script/types.h | 5 ++ src/script/CMakeLists.txt | 4 +- src/script/context.c | 12 +++++ src/script/stdlib.c | 48 ++++++++++++++++-- src/script/test/stdlib.c | 91 +++++++++++++++++++++++++++++++++++ src/script/types.c | 9 ++++ 7 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/script/test/stdlib.c diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index 8633e0ed4..780067d83 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -14,8 +14,9 @@ CXX_GUARD_START #include #include +#define mSCRIPT_KV_PAIR(KEY, VALUE) { #KEY, VALUE } #define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) } -#define mSCRIPT_CONSTANT_SENTINEL { NULL, NULL } +#define mSCRIPT_KV_SENTINEL { NULL, NULL } struct mScriptFrame; struct mScriptFunction; @@ -80,6 +81,7 @@ void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); void mScriptContextAttachStdlib(struct mScriptContext* context); void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants); +void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value); void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback); void mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value); diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 489b5eee4..471d78524 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -41,6 +41,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PS(X) void #define mSCRIPT_TYPE_C_PCS(X) void #define mSCRIPT_TYPE_C_WSTR struct mScriptValue* +#define mSCRIPT_TYPE_C_WLIST struct mScriptValue* #define mSCRIPT_TYPE_C_W(X) struct mScriptValue* #define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue* @@ -67,6 +68,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_WSTR opaque +#define mSCRIPT_TYPE_FIELD_WLIST opaque #define mSCRIPT_TYPE_FIELD_W(TYPE) opaque #define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque @@ -92,6 +94,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT) #define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructConstPtr_ ## STRUCT) #define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper) +#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper) #define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE) #define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE) @@ -116,6 +119,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) +#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) enum mScriptTypeBase { mSCRIPT_TYPE_VOID = 0, @@ -168,6 +172,7 @@ extern const struct mScriptType mSTTable; extern const struct mScriptType mSTWrapper; extern const struct mScriptType mSTWeakref; extern const struct mScriptType mSTStringWrapper; +extern const struct mScriptType mSTListWrapper; struct mScriptType; struct mScriptValue { diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index c579ccb9f..e9ecc8c60 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -10,7 +10,9 @@ set(TEST_FILES if(USE_LUA) list(APPEND SOURCE_FILES engines/lua.c) - list(APPEND TEST_FILES test/lua.c) + list(APPEND TEST_FILES + test/stdlib.c + test/lua.c) endif() source_group("Scripting" FILES ${SOURCE_FILES}) diff --git a/src/script/context.c b/src/script/context.c index f2437dc5b..43f962caf 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -237,6 +237,18 @@ void mScriptContextExportConstants(struct mScriptContext* context, const char* n mScriptValueDeref(table); } +void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* values) { + struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + size_t i; + for (i = 0; values[i].key; ++i) { + struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key); + mScriptTableInsert(table, key, values[i].value); + mScriptValueDeref(key); + mScriptValueDeref(values[i].value); + } + mScriptContextSetGlobal(context, nspace, table); +} + bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) { struct mScriptFileInfo info = { .name = name, diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 35941f6d8..32770a752 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -30,6 +30,38 @@ static void _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, struct m mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function); +static uint64_t mScriptMakeBitmask(struct mScriptList* list) { + size_t i; + uint64_t mask = 0; + for (i = 0; i < mScriptListSize(list); ++i) { + struct mScriptValue bit; + struct mScriptValue* value = mScriptListGetPointer(list, i); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + if (!mScriptCast(mSCRIPT_TYPE_MS_U64, value, &bit)) { + continue; + } + mask |= 1ULL << bit.value.u64; + } + return mask; +} + +static struct mScriptValue* mScriptExpandBitmask(uint64_t mask) { + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + size_t i; + for (i = 0; mask; ++i, mask >>= 1) { + if (!(mask & 1)) { + continue; + } + *mScriptListAppend(list->value.list) = mSCRIPT_MAKE_U32(i); + } + return list; +} + +mSCRIPT_BIND_FUNCTION(mScriptMakeBitmask_Binding, U64, mScriptMakeBitmask, 1, LIST, bits); +mSCRIPT_BIND_FUNCTION(mScriptExpandBitmask_Binding, WLIST, mScriptExpandBitmask, 1, U64, mask); + mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager) mSCRIPT_DEFINE_CLASS_DOCSTRING( "A global singleton object `callbacks` used for managing callbacks. The following callbacks are defined:\n\n" @@ -66,17 +98,17 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mSCRIPT_CONSTANT_PAIR(SAVESTATE, RTC), mSCRIPT_CONSTANT_PAIR(SAVESTATE, METADATA), mSCRIPT_CONSTANT_PAIR(SAVESTATE, ALL), - mSCRIPT_CONSTANT_SENTINEL + mSCRIPT_KV_SENTINEL }); mScriptContextExportConstants(context, "PLATFORM", (struct mScriptKVPair[]) { mSCRIPT_CONSTANT_PAIR(mPLATFORM, NONE), mSCRIPT_CONSTANT_PAIR(mPLATFORM, GBA), mSCRIPT_CONSTANT_PAIR(mPLATFORM, GB), - mSCRIPT_CONSTANT_SENTINEL + mSCRIPT_KV_SENTINEL }); mScriptContextExportConstants(context, "CHECKSUM", (struct mScriptKVPair[]) { mSCRIPT_CONSTANT_PAIR(mCHECKSUM, CRC32), - mSCRIPT_CONSTANT_SENTINEL + mSCRIPT_KV_SENTINEL }); #ifdef M_CORE_GBA mScriptContextExportConstants(context, "GBA_KEY", (struct mScriptKVPair[]) { @@ -90,7 +122,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mSCRIPT_CONSTANT_PAIR(GBA_KEY, DOWN), mSCRIPT_CONSTANT_PAIR(GBA_KEY, R), mSCRIPT_CONSTANT_PAIR(GBA_KEY, L), - mSCRIPT_CONSTANT_SENTINEL + mSCRIPT_KV_SENTINEL }); #endif #ifdef M_CORE_GB @@ -103,8 +135,14 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mSCRIPT_CONSTANT_PAIR(GB_KEY, LEFT), mSCRIPT_CONSTANT_PAIR(GB_KEY, UP), mSCRIPT_CONSTANT_PAIR(GB_KEY, DOWN), - mSCRIPT_CONSTANT_SENTINEL + mSCRIPT_KV_SENTINEL }); #endif mScriptContextSetGlobal(context, "C", context->constants); + + mScriptContextExportNamespace(context, "util", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(makeBitmask, &mScriptMakeBitmask_Binding), + mSCRIPT_KV_PAIR(expandBitmask, &mScriptExpandBitmask_Binding), + mSCRIPT_KV_SENTINEL + }); } diff --git a/src/script/test/stdlib.c b/src/script/test/stdlib.c new file mode 100644 index 000000000..c6e9fd3fe --- /dev/null +++ b/src/script/test/stdlib.c @@ -0,0 +1,91 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include +#include + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context) + +#define LOAD_PROGRAM(PROG) \ + do { \ + struct VFile* vf = VFileFromConstMemory(PROG, strlen(PROG)); \ + assert_true(lua->load(lua, NULL, vf)); \ + vf->close(vf); \ + } while(0) + +#define TEST_PROGRAM(PROG) \ + LOAD_PROGRAM(PROG); \ + assert_true(lua->run(lua)); \ + +#define TEST_VALUE(TYPE, NAME, VALUE) \ + do { \ + struct mScriptValue val = mSCRIPT_MAKE(TYPE, VALUE); \ + struct mScriptValue* global = lua->getGlobal(lua, NAME); \ + assert_non_null(global); \ + assert_true(global->type->equal(global, &val)); \ + mScriptValueDeref(global); \ + } while(0) + +M_TEST_SUITE_SETUP(mScriptStdlib) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptStdlib) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(bitMask) { + SETUP_LUA; + + TEST_PROGRAM("assert(util)"); + TEST_PROGRAM("assert(util.makeBitmask)"); + TEST_PROGRAM("assert(util.makeBitmask{0} == 1)"); + TEST_PROGRAM("assert(util.makeBitmask{1} == 2)"); + TEST_PROGRAM("assert(util.makeBitmask{0, 1} == 3)"); + TEST_PROGRAM("assert(util.makeBitmask{1, 1} == 2)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(bitUnmask) { + SETUP_LUA; + + TEST_PROGRAM("assert(util)"); + TEST_PROGRAM("assert(util.expandBitmask)"); + TEST_PROGRAM("assert(#util.expandBitmask(0) == 0)"); + TEST_PROGRAM("assert(#util.expandBitmask(1) == 1)"); + TEST_PROGRAM("assert(util.expandBitmask(1)[1] == 0)"); + TEST_PROGRAM("assert(#util.expandBitmask(2) == 1)"); + TEST_PROGRAM("assert(util.expandBitmask(2)[1] == 1)"); + TEST_PROGRAM("assert(#util.expandBitmask(3) == 2)"); + TEST_PROGRAM("assert(util.expandBitmask(3)[1] == 0 or util.expandBitmask(3)[1] == 1)"); + TEST_PROGRAM("assert(util.expandBitmask(3)[2] == 0 or util.expandBitmask(3)[2] == 1)"); + TEST_PROGRAM("assert(#util.expandBitmask(6) == 2)"); + TEST_PROGRAM("assert(util.expandBitmask(6)[1] == 1 or util.expandBitmask(6)[1] == 2)"); + TEST_PROGRAM("assert(util.expandBitmask(6)[2] == 1 or util.expandBitmask(6)[2] == 2)"); + TEST_PROGRAM("assert(#util.expandBitmask(7) == 3)"); + TEST_PROGRAM("assert(#util.expandBitmask(11) == 3)"); + TEST_PROGRAM("assert(#util.expandBitmask(15) == 4)"); + + mScriptContextDeinit(&context); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib, + cmocka_unit_test(bitMask), + cmocka_unit_test(bitUnmask), +) diff --git a/src/script/types.c b/src/script/types.c index 4ab6653e1..61fcadcd2 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -221,6 +221,15 @@ const struct mScriptType mSTStringWrapper = { .hash = NULL, }; +const struct mScriptType mSTListWrapper = { + .base = mSCRIPT_TYPE_WRAPPER, + .size = sizeof(struct mScriptValue), + .name = "wrapper list", + .alloc = NULL, + .free = NULL, + .hash = NULL, +}; + const struct mScriptType mSTWeakref = { .base = mSCRIPT_TYPE_WEAKREF, .size = sizeof(uint32_t), From 10eb2b9784266c0024ff050dc2e29203fe89390f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Jun 2022 03:28:15 -0700 Subject: [PATCH 27/27] Scripting: Add root-scope docstrings --- include/mgba/script/context.h | 4 ++++ src/core/scripting.c | 17 ++++++++--------- src/script/context.c | 10 ++++++++++ src/script/docgen.c | 32 ++++++++++++++++++++++++-------- src/script/stdlib.c | 5 +++++ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index 780067d83..80682ee29 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -30,6 +30,7 @@ struct mScriptContext { uint32_t nextWeakref; struct Table callbacks; struct mScriptValue* constants; + struct Table docstrings; }; struct mScriptEngine2 { @@ -86,6 +87,9 @@ void mScriptContextExportNamespace(struct mScriptContext* context, const char* n void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback); void mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value); +void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring); +const char* mScriptContextGetDocstring(struct mScriptContext*, const char* key); + struct VFile; bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf); bool mScriptContextLoadFile(struct mScriptContext*, const char* path); diff --git a/src/core/scripting.c b/src/core/scripting.c index d08761bbf..7c14eb08b 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -710,14 +710,20 @@ mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptConsole, createBuffer) mSCRIPT_CHARP(NULL) mSCRIPT_DEFINE_DEFAULTS_END; -void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) { +static struct mScriptConsole* _ensureConsole(struct mScriptContext* context) { struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole)); struct mScriptConsole* console = value->value.opaque; if (!console) { console = calloc(1, sizeof(*console)); value->value.opaque = console; value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + mScriptContextSetDocstring(context, "console", "Singleton instance of struct::mScriptConsole"); } + return console; +} + +void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) { + struct mScriptConsole* console = _ensureConsole(context); console->logger = logger; } @@ -771,14 +777,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptTextBuffer) mSCRIPT_DEFINE_END; void mScriptContextSetTextBufferFactory(struct mScriptContext* context, mScriptContextBufferFactory factory, void* cbContext) { - struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole)); - struct mScriptConsole* console = value->value.opaque; - if (!console) { - console = calloc(1, sizeof(*console)); - console->logger = mLogGetContext(); - value->value.opaque = console; - value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; - } + struct mScriptConsole* console = _ensureConsole(context); console->textBufferFactory = factory; console->textBufferContext = cbContext; } diff --git a/src/script/context.c b/src/script/context.c index 43f962caf..7c9b32dab 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -57,6 +57,7 @@ void mScriptContextInit(struct mScriptContext* context) { context->nextWeakref = 1; HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref); context->constants = NULL; + HashTableInit(&context->docstrings, 0, NULL); } void mScriptContextDeinit(struct mScriptContext* context) { @@ -66,6 +67,7 @@ void mScriptContextDeinit(struct mScriptContext* context) { mScriptListDeinit(&context->refPool); HashTableDeinit(&context->callbacks); HashTableDeinit(&context->engines); + HashTableDeinit(&context->docstrings); } void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* value) { @@ -249,6 +251,14 @@ void mScriptContextExportNamespace(struct mScriptContext* context, const char* n mScriptContextSetGlobal(context, nspace, table); } +void mScriptContextSetDocstring(struct mScriptContext* context, const char* key, const char* docstring) { + HashTableInsert(&context->docstrings, key, docstring); +} + +const char* mScriptContextGetDocstring(struct mScriptContext* context, const char* key) { + return HashTableLookup(&context->docstrings, key); +} + bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) { struct mScriptFileInfo info = { .name = name, diff --git a/src/script/docgen.c b/src/script/docgen.c index bc16bc9b7..519b73bb2 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -12,7 +12,7 @@ struct mScriptContext context; struct Table types; FILE* out; -void explainValue(struct mScriptValue* value, int level); +void explainValue(struct mScriptValue* value, const char* name, int level); void explainType(struct mScriptType* type, int level); void addTypesFromTuple(const struct mScriptTypeTuple*); @@ -154,7 +154,7 @@ bool printval(const struct mScriptValue* value, char* buffer, size_t bufferSize) return false; } -void explainTable(struct mScriptValue* value, int level) { +void explainTable(struct mScriptValue* value, const char* name, int level) { char indent[(level + 1) * 2 + 1]; memset(indent, ' ', sizeof(indent) - 1); indent[sizeof(indent) - 1] = '\0'; @@ -167,7 +167,14 @@ void explainTable(struct mScriptValue* value, int level) { printval(k, keyval, sizeof(keyval)); fprintf(out, "%s- key: %s\n", indent, keyval); struct mScriptValue* v = mScriptTableIteratorGetValue(value, &iter); - explainValue(v, level + 1); + + struct mScriptValue string; + if (mScriptCast(mSCRIPT_TYPE_MS_CHARP, k, &string)) { + snprintf(keyval, sizeof(keyval), "%s.%s", name, (const char*) string.value.opaque); + explainValue(v, keyval, level + 1); + } else { + explainValue(v, NULL, level + 1); + } } while (mScriptTableIteratorNext(value, &iter)); } } @@ -234,15 +241,15 @@ void explainObject(struct mScriptValue* value, int level) { struct mScriptValue* unwrappedMember; if (member.type->base == mSCRIPT_TYPE_WRAPPER) { unwrappedMember = mScriptValueUnwrap(&member); - explainValue(unwrappedMember, level + 2); + explainValue(unwrappedMember, NULL, level + 2); } else { - explainValue(&member, level + 2); + explainValue(&member, NULL, level + 2); } } } } -void explainValue(struct mScriptValue* value, int level) { +void explainValue(struct mScriptValue* value, const char* name, int level) { char valstring[1024]; char indent[(level + 1) * 2 + 1]; memset(indent, ' ', sizeof(indent) - 1); @@ -250,10 +257,19 @@ void explainValue(struct mScriptValue* value, int level) { value = mScriptContextAccessWeakref(&context, value); addType(value->type); fprintf(out, "%stype: %s\n", indent, value->type->name); + + const char* docstring = NULL; + if (name) { + docstring = mScriptContextGetDocstring(&context, name); + } + if (docstring) { + fprintf(out, "%scomment: \"%s\"\n", indent, docstring); + } + switch (value->type->base) { case mSCRIPT_TYPE_TABLE: fprintf(out, "%svalue:\n", indent); - explainTable(value, level); + explainTable(value, name, level); break; case mSCRIPT_TYPE_SINT: case mSCRIPT_TYPE_UINT: @@ -462,7 +478,7 @@ int main(int argc, char* argv[]) { const char* name = HashTableIteratorGetKey(&context.rootScope, &iter); fprintf(out, " %s:\n", name); struct mScriptValue* value = HashTableIteratorGetValue(&context.rootScope, &iter); - explainValue(value, 1); + explainValue(value, name, 1); } while (HashTableIteratorNext(&context.rootScope, &iter)); } fputs("emu:\n", out); diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 32770a752..695946d05 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -90,6 +90,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { }; lib->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; mScriptContextSetGlobal(context, "callbacks", lib); + mScriptContextSetDocstring(context, "callbacks", "Singleton instance of struct::mScriptCallbackManager"); mScriptContextExportConstants(context, "SAVESTATE", (struct mScriptKVPair[]) { mSCRIPT_CONSTANT_PAIR(SAVESTATE, SCREENSHOT), @@ -139,10 +140,14 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { }); #endif mScriptContextSetGlobal(context, "C", context->constants); + mScriptContextSetDocstring(context, "C", "A table containing the [exported constants](#constants)"); mScriptContextExportNamespace(context, "util", (struct mScriptKVPair[]) { mSCRIPT_KV_PAIR(makeBitmask, &mScriptMakeBitmask_Binding), mSCRIPT_KV_PAIR(expandBitmask, &mScriptExpandBitmask_Binding), mSCRIPT_KV_SENTINEL }); + mScriptContextSetDocstring(context, "util", "Basic utility library"); + mScriptContextSetDocstring(context, "util.makeBitmask", "Compile a list of bit indices into a bitmask"); + mScriptContextSetDocstring(context, "util.expandBitmask", "Expand a bitmask into a list of bit indices"); }