From 489300076114486994ffd3deea59f9d6a647720c Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:06:39 -0400 Subject: [PATCH 01/15] Fix buffer overflow when printing `HRESULT`s --- Win/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win/Common.cpp b/Win/Common.cpp index 0537ba5..23d49a5 100644 --- a/Win/Common.cpp +++ b/Win/Common.cpp @@ -80,7 +80,7 @@ std::string common_Format(bool value) { // Format // ---------------------------------------------------------------------------- std::string common_Format(HRESULT result) { - char buffer[10] = {0}; + char buffer[11] = {0}; sprintf(buffer, "%#8x", result); return std::string(buffer); } From 97ce68cb9e340ba6c0b1126aa3a82821535b30e6 Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:00:02 -0400 Subject: [PATCH 02/15] `input_InitializeControllerDialog` is missing a return type. This is invalid C++, and newer versions of MSVC reject it. --- Win/Input.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win/Input.cpp b/Win/Input.cpp index 6ae5891..71ff259 100644 --- a/Win/Input.cpp +++ b/Win/Input.cpp @@ -213,7 +213,7 @@ BOOL CALLBACK InitJoysticksListUser(const DIDEVICEINSTANCE* // ---------------------------------------------------------------------------- // InitializeControllerDialog // ---------------------------------------------------------------------------- -static input_InitializeControllerDialog(HWND hDialog, byte controller) { +static void input_InitializeControllerDialog(HWND hDialog, byte controller) { std::string title = "Controller " + common_Format(controller); HWND hCombo; int i; From 4dd32072e0134ddb1ed92cfc2ff0f945f54610af Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:02:14 -0400 Subject: [PATCH 03/15] Fix scoping of some loop variables for newer versions of MSVC --- Core/Cartridge.cpp | 3 ++- Core/Hash.cpp | 4 ++-- Win/Configuration.cpp | 8 ++++++-- Win/Input.cpp | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Core/Cartridge.cpp b/Core/Cartridge.cpp index 0334239..18254d6 100644 --- a/Core/Cartridge.cpp +++ b/Core/Cartridge.cpp @@ -147,7 +147,8 @@ static bool cartridge_Load(const byte* data, uint size) { cartridge_Release( ); byte header[128] = {0}; - for(int index = 0; index < 128; index++) { + int index; + for(index = 0; index < 128; index++) { header[index] = data[index]; } diff --git a/Core/Hash.cpp b/Core/Hash.cpp index 135ab2e..a86a273 100644 --- a/Core/Hash.cpp +++ b/Core/Hash.cpp @@ -191,7 +191,8 @@ std::string hash_Compute(const byte* source, uint length) { length -= 64; } - for(uint index = 0; index < length; index++) { + uint index; + for(index = 0; index < length; index++) { buffer3[index] = source[index]; } @@ -202,7 +203,6 @@ std::string hash_Compute(const byte* source, uint length) { count = 63 - count; if(count < 8) { - uint index; for(index = 0; index < count; index++) { ptr[index] = 0; } diff --git a/Win/Configuration.cpp b/Win/Configuration.cpp index eed3b5a..6b0fdfe 100644 --- a/Win/Configuration.cpp +++ b/Win/Configuration.cpp @@ -190,7 +190,9 @@ static uint configuration_ReadPrivateUint(std::string section, std::string name, // ---------------------------------------------------------------------------- std::string configuration_Load(std::string filename, std::string commandLine) { configuration_filename = filename; - for(uint index = 0; index < 10; index++) { + + uint index; + for(index = 0; index < 10; index++) { console_recent[index] = configuration_ReadPrivatePath(CONFIGURATION_SECTION_RECENT, "Recent" + common_Format(index), ""); } @@ -267,7 +269,9 @@ std::string configuration_Load(std::string filename, std::string commandLine) { // ---------------------------------------------------------------------------- void configuration_Save(std::string filename) { configuration_filename = filename; - for(uint index = 0; index < 10; index++) { + + uint index; + for(index = 0; index < 10; index++) { configuration_WritePrivatePath(CONFIGURATION_SECTION_RECENT, "Recent" + common_Format(index), console_recent[index]); } diff --git a/Win/Input.cpp b/Win/Input.cpp index 71ff259..7a198ec 100644 --- a/Win/Input.cpp +++ b/Win/Input.cpp @@ -914,7 +914,8 @@ bool input_GetKeyboardState(byte* input) { } // Check for User Keys - for(int index = 0; index < 2; index++) { + int index; + for(index = 0; index < 2; index++) { if ( !user_devices[index] ) { if ( (keyboard[user_keys[index]]) ) { if ( !user_modifiers[index] ) { From ab5bbea3d6c68b770612cb6203984d124a7d6afa Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:07:54 -0400 Subject: [PATCH 04/15] Set `OFN_NOCHANGEDIR` when opening file pickers. The database is opened via a relative path, so opening a file picker before opening the database can cause the database not to be found. --- Win/Console.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Win/Console.cpp b/Win/Console.cpp index 502a7f6..7674c99 100644 --- a/Win/Console.cpp +++ b/Win/Console.cpp @@ -196,7 +196,7 @@ static void console_Open( ) { openDialog.lpstrFile = path; openDialog.nMaxFile = _MAX_PATH; openDialog.nMaxFileTitle = _MAX_PATH; - openDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + openDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; if(!cartridge_IsLoaded( )) { openDialog.lpstrInitialDir = console_recent[0].c_str( ); } @@ -225,7 +225,7 @@ static void console_Load( ) { loadDialog.lpstrFile = path; loadDialog.nMaxFile = _MAX_PATH; loadDialog.nMaxFileTitle = _MAX_PATH; - loadDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + loadDialog.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; loadDialog.lpstrInitialDir = console_savePath.c_str( ); if(GetOpenFileName(&loadDialog)) { @@ -355,7 +355,7 @@ static void console_OpenPalette( ) { openDialog.lpstrFile = path; openDialog.nMaxFile = _MAX_PATH; openDialog.nMaxFileTitle = _MAX_PATH; - openDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP; + openDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP | OFN_NOCHANGEDIR; openDialog.lpstrTitle = "Palette"; openDialog.lCustData = false; openDialog.lpfnHook = console_OpenPaletteHook; @@ -481,7 +481,7 @@ static void console_OpenBios( ) { biosDialog.lpstrFile = path; biosDialog.nMaxFile = _MAX_PATH; biosDialog.nMaxFileTitle = _MAX_PATH; - biosDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP; + biosDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP | OFN_NOCHANGEDIR; biosDialog.lpstrTitle = "Bios"; biosDialog.lCustData = false; biosDialog.lpfnHook = console_OpenHook; @@ -562,7 +562,7 @@ static void console_OpenDatabase( ) { databaseDialog.lpstrFile = path; databaseDialog.nMaxFile = _MAX_PATH; databaseDialog.nMaxFileTitle = _MAX_PATH; - databaseDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP; + databaseDialog.Flags = OFN_EXPLORER | OFN_ENABLEHOOK | OFN_SHOWHELP | OFN_NOCHANGEDIR; databaseDialog.lpstrTitle = "Database"; databaseDialog.lCustData = false; databaseDialog.lpfnHook = console_OpenHook; From 2079f75136eab8a068fbb32ef7ee6132ab3520ac Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:18:21 -0400 Subject: [PATCH 05/15] `string::empty( )` implies `string::length == 0`, so remove the latter test --- Win/Display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Win/Display.cpp b/Win/Display.cpp index a9f5e29..73be1ae 100644 --- a/Win/Display.cpp +++ b/Win/Display.cpp @@ -645,7 +645,7 @@ bool display_ResetPalette( ) { // TakeScreenshot // ---------------------------------------------------------------------------- bool display_TakeScreenshot(std::string filename) { - if(filename.empty( ) || filename.length == 0) { + if(filename.empty( )) { logger_LogError(IDS_DISPLAY30,""); return false; } From 3f6f7d66f35157d1c357c0488e7a7f3a0be7a9ef Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:30:06 -0400 Subject: [PATCH 06/15] Define `DIRECTINPUT_VERSION` for compatibility with newer Windows SDKs --- Win/Input.cpp | 1 + Win/Input.h | 1 + 2 files changed, 2 insertions(+) diff --git a/Win/Input.cpp b/Win/Input.cpp index 7a198ec..2df9d0d 100644 --- a/Win/Input.cpp +++ b/Win/Input.cpp @@ -22,6 +22,7 @@ // ---------------------------------------------------------------------------- // Input.cpp // ---------------------------------------------------------------------------- +#define DIRECTINPUT_VERSION 0x0700 #include #include "Input.h" #include "Console.h" diff --git a/Win/Input.h b/Win/Input.h index 93b7554..1c70a53 100644 --- a/Win/Input.h +++ b/Win/Input.h @@ -52,6 +52,7 @@ typedef enum { JOY_BUTTON_12 } e_joy_value; +#define DIRECTINPUT_VERSION 0x0700 #include #include #include "Resource.h" From f6faa5989d769f51e084d00927499dca06e1a09b Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:03:30 -0400 Subject: [PATCH 07/15] Abstract MARIA's memory reads into a single function. This is needed because SOUPER cartridges can trap MARIA's reads and handle them specially. --- Core/Maria.cpp | 79 +++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/Core/Maria.cpp b/Core/Maria.cpp index a8b6b53..7feee3f 100644 --- a/Core/Maria.cpp +++ b/Core/Maria.cpp @@ -42,6 +42,13 @@ static byte maria_h08; static byte maria_h16; static byte maria_wmode; +// ---------------------------------------------------------------------------- +// ReadByte +// ---------------------------------------------------------------------------- +static byte maria_ReadByte(word address) { + return memory_ram[address]; +} + // ---------------------------------------------------------------------------- // StoreCell // ---------------------------------------------------------------------------- @@ -51,7 +58,7 @@ static void maria_StoreCell(byte data) { maria_lineRAM[maria_horizontal] = maria_palette | data; } else { - byte kmode = memory_ram[CTRL] & 4; + byte kmode = maria_ReadByte(CTRL) & 4; if(kmode) { maria_lineRAM[maria_horizontal] = 0; } @@ -69,7 +76,7 @@ static void maria_StoreCell(byte high, byte low) { maria_lineRAM[maria_horizontal] = maria_palette & 16 | high | low; } else { - byte kmode = memory_ram[CTRL] & 4; + byte kmode = maria_ReadByte(CTRL) & 4; if(kmode) { maria_lineRAM[maria_horizontal] = 0; } @@ -98,10 +105,10 @@ static bool maria_IsHolyDMA( ) { // ---------------------------------------------------------------------------- static byte maria_GetColor(byte data) { if(data & 3) { - return memory_ram[BACKGRND + data]; + return maria_ReadByte(BACKGRND + data); } else { - return memory_ram[BACKGRND]; + return maria_ReadByte(BACKGRND); } } @@ -109,7 +116,7 @@ static byte maria_GetColor(byte data) { // StoreGraphic // ---------------------------------------------------------------------------- static void maria_StoreGraphic( ) { - byte data = memory_ram[maria_pp.w]; + byte data = maria_ReadByte(maria_pp.w); if(maria_wmode) { if(maria_IsHolyDMA( )) { maria_StoreCell(0, 0); @@ -141,7 +148,7 @@ static void maria_StoreGraphic( ) { // WriteLineRAM // ---------------------------------------------------------------------------- static void maria_WriteLineRAM(byte* buffer) { - byte rmode = memory_ram[CTRL] & 3; + byte rmode = maria_ReadByte(CTRL) & 3; if(rmode == 0) { int pixel = 0; for(int index = 0; index < MARIA_LINERAM_SIZE; index += 4) { @@ -196,29 +203,29 @@ static void maria_StoreLineRAM( ) { maria_lineRAM[index] = 0; } - byte mode = memory_ram[maria_dp.w + 1]; + byte mode = maria_ReadByte(maria_dp.w + 1); while(mode & 0x5f) { byte width; byte indirect = 0; - maria_pp.b.l = memory_ram[maria_dp.w]; - maria_pp.b.h = memory_ram[maria_dp.w + 2]; + maria_pp.b.l = maria_ReadByte(maria_dp.w); + maria_pp.b.h = maria_ReadByte(maria_dp.w + 2); if(mode & 31) { maria_cycles += 8; - maria_palette = (memory_ram[maria_dp.w + 1] & 224) >> 3; - maria_horizontal = memory_ram[maria_dp.w + 3]; - width = memory_ram[maria_dp.w + 1] & 31; + maria_palette = (maria_ReadByte(maria_dp.w + 1) & 224) >> 3; + maria_horizontal = maria_ReadByte(maria_dp.w + 3); + width = maria_ReadByte(maria_dp.w + 1) & 31; width = ((~width) & 31) + 1; maria_dp.w += 4; } else { maria_cycles += 10; - maria_palette = (memory_ram[maria_dp.w + 3] & 224) >> 3; - maria_horizontal = memory_ram[maria_dp.w + 4]; - indirect = memory_ram[maria_dp.w + 1] & 32; - maria_wmode = memory_ram[maria_dp.w + 1] & 128; - width = memory_ram[maria_dp.w + 3] & 31; + maria_palette = (maria_ReadByte(maria_dp.w + 3) & 224) >> 3; + maria_horizontal = maria_ReadByte(maria_dp.w + 4); + indirect = maria_ReadByte(maria_dp.w + 1) & 32; + maria_wmode = maria_ReadByte(maria_dp.w + 1) & 128; + width = maria_ReadByte(maria_dp.w + 3) & 31; width = (width == 0)? 32: ((~width) & 31) + 1; maria_dp.w += 5; } @@ -231,12 +238,12 @@ static void maria_StoreLineRAM( ) { } } else { - byte cwidth = memory_ram[CTRL] & 16; + byte cwidth = maria_ReadByte(CTRL) & 16; pair basePP = maria_pp; for(int index = 0; index < width; index++) { maria_cycles += 3; - maria_pp.b.l = memory_ram[basePP.w++]; - maria_pp.b.h = memory_ram[CHARBASE] + maria_offset; + maria_pp.b.l = maria_ReadByte(basePP.w++); + maria_pp.b.h = maria_ReadByte(CHARBASE) + maria_offset; maria_cycles += 6; maria_StoreGraphic( ); @@ -246,7 +253,7 @@ static void maria_StoreLineRAM( ) { } } } - mode = memory_ram[maria_dp.w + 1]; + mode = maria_ReadByte(maria_dp.w + 1); } } @@ -265,18 +272,18 @@ void maria_Reset( ) { // ---------------------------------------------------------------------------- uint maria_RenderScanline( ) { maria_cycles = 0; - if((memory_ram[CTRL] & 96) == 64 && maria_scanline >= maria_displayArea.top && maria_scanline <= maria_displayArea.bottom) { + if((maria_ReadByte(CTRL) & 96) == 64 && maria_scanline >= maria_displayArea.top && maria_scanline <= maria_displayArea.bottom) { maria_cycles += 31; if(maria_scanline == maria_displayArea.top) { maria_cycles += 7; - maria_dpp.b.l = memory_ram[DPPL]; - maria_dpp.b.h = memory_ram[DPPH]; - maria_h08 = memory_ram[maria_dpp.w] & 32; - maria_h16 = memory_ram[maria_dpp.w] & 64; - maria_offset = memory_ram[maria_dpp.w] & 15; - maria_dp.b.l = memory_ram[maria_dpp.w + 2]; - maria_dp.b.h = memory_ram[maria_dpp.w + 1]; - if(memory_ram[maria_dpp.w] & 128) { + maria_dpp.b.l = maria_ReadByte(DPPL); + maria_dpp.b.h = maria_ReadByte(DPPH); + maria_h08 = maria_ReadByte(maria_dpp.w) & 32; + maria_h16 = maria_ReadByte(maria_dpp.w) & 64; + maria_offset = maria_ReadByte(maria_dpp.w) & 15; + maria_dp.b.l = maria_ReadByte(maria_dpp.w + 2); + maria_dp.b.h = maria_ReadByte(maria_dpp.w + 1); + if(maria_ReadByte(maria_dpp.w) & 128) { sally_ExecuteNMI( ); } } @@ -284,16 +291,16 @@ uint maria_RenderScanline( ) { maria_WriteLineRAM(maria_surface + ((maria_scanline - maria_displayArea.top) * maria_displayArea.GetLength( ))); } if(maria_scanline != maria_displayArea.bottom) { - maria_dp.b.l = memory_ram[maria_dpp.w + 2]; - maria_dp.b.h = memory_ram[maria_dpp.w + 1]; + maria_dp.b.l = maria_ReadByte(maria_dpp.w + 2); + maria_dp.b.h = maria_ReadByte(maria_dpp.w + 1); maria_StoreLineRAM( ); maria_offset--; if(maria_offset < 0) { maria_dpp.w += 3; - maria_h08 = memory_ram[maria_dpp.w] & 32; - maria_h16 = memory_ram[maria_dpp.w] & 64; - maria_offset = memory_ram[maria_dpp.w] & 15; - if(memory_ram[maria_dpp.w] & 128) { + maria_h08 = maria_ReadByte(maria_dpp.w) & 32; + maria_h16 = maria_ReadByte(maria_dpp.w) & 64; + maria_offset = maria_ReadByte(maria_dpp.w) & 15; + if(maria_ReadByte(maria_dpp.w) & 128) { sally_ExecuteNMI( ); } } From 30d56027273ca3dfc1a88eeeca02c9545ed7cfb0 Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 03:58:25 -0400 Subject: [PATCH 08/15] Add support for the SOUPER cartridge type, used by "Rikki & Vikki" --- Core/Cartridge.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++-- Core/Cartridge.h | 15 ++++++++ Core/Maria.cpp | 19 ++++++++++- Core/Memory.cpp | 25 ++++++++++++++ Core/Memory.h | 3 ++ Core/ProSystem.cpp | 29 +++++++++++++--- 6 files changed, 168 insertions(+), 8 deletions(-) mode change 100644 => 100755 Core/Cartridge.cpp diff --git a/Core/Cartridge.cpp b/Core/Cartridge.cpp old mode 100644 new mode 100755 index 18254d6..1855d27 --- a/Core/Cartridge.cpp +++ b/Core/Cartridge.cpp @@ -23,6 +23,7 @@ // Cartridge.cpp // ---------------------------------------------------------------------------- #include "Cartridge.h" +#include std::string cartridge_title; std::string cartridge_description; @@ -37,7 +38,12 @@ byte cartridge_controller[2]; byte cartridge_bank; uint cartridge_flags; -static byte* cartridge_buffer = NULL; +// SOUPER-specific stuff, used for "Rikki & Vikki" +byte cartridge_souper_chr_bank[2]; +byte cartridge_souper_mode; +byte cartridge_souper_ram_page_bank[2]; + +byte* cartridge_buffer = NULL; static uint cartridge_size = 0; // ---------------------------------------------------------------------------- @@ -83,6 +89,31 @@ static void cartridge_WriteBank(word address, byte bank) { cartridge_bank = bank; } } +// ---------------------------------------------------------------------------- +// SOUPER StoreChrBank +// ---------------------------------------------------------------------------- +static void cartridge_souper_StoreChrBank(byte page, byte bank) { + if(page < 2) { + cartridge_souper_chr_bank[page] = bank; + } +} + +// ---------------------------------------------------------------------------- +// SOUPER SetMode +// ---------------------------------------------------------------------------- +static void cartridge_souper_SetMode(byte data) { + cartridge_souper_mode = data; +} + +// ---------------------------------------------------------------------------- +// SOUPER SetVideoPageBank +// ---------------------------------------------------------------------------- +static void cartridge_souper_SetRamPageBank(byte which, byte data) { + if(which < 2) { + cartridge_souper_ram_page_bank[which] = data & 7; + } +} + // ---------------------------------------------------------------------------- // ReadHeader @@ -123,11 +154,14 @@ static void cartridge_ReadHeader(const byte* header) { else if(header[53] == 2) { cartridge_type = CARTRIDGE_TYPE_ACTIVISION; } + else if(header[53] == 16) { + cartridge_type = CARTRIDGE_TYPE_SOUPER; + } else { cartridge_type = CARTRIDGE_TYPE_NORMAL; } } - + cartridge_pokey = (header[54] & 1)? true: false; cartridge_controller[0] = header[55]; cartridge_controller[1] = header[56]; @@ -177,6 +211,16 @@ if (cartridge_CC2(header)) { return true; } +// ---------------------------------------------------------------------------- +// LoadROM +// ---------------------------------------------------------------------------- +byte cartridge_LoadROM(uint address) { + if(address >= cartridge_size) { + return 0; + } + return cartridge_buffer[address]; +} + // ---------------------------------------------------------------------------- // Load // ---------------------------------------------------------------------------- @@ -283,6 +327,11 @@ void cartridge_Store( ) { memory_WriteROM(57344, 8192, cartridge_buffer + 114688); } break; + case CARTRIDGE_TYPE_SOUPER: + memory_WriteROM(0xc000, 0x4000, cartridge_buffer + cartridge_GetBankOffset(31)); + memory_WriteROM(0x8000, 0x4000, cartridge_buffer + cartridge_GetBankOffset(0)); + memory_ClearROM(0x4000, 0x4000); + break; } } @@ -313,6 +362,35 @@ void cartridge_Write(word address, byte data) { cartridge_StoreBank(address & 7); } break; + case CARTRIDGE_TYPE_SOUPER: + if(address >= 0x4000 && address < 0x8000) { + memory_souper_ram[memory_souper_GetRamAddress(address)] = data; + break; + } + + switch(address) { + case CARTRIDGE_SOUPER_BANK_SEL: + cartridge_StoreBank(data & 31); + break; + case CARTRIDGE_SOUPER_CHR_A_SEL: + cartridge_souper_StoreChrBank(0, data); + break; + case CARTRIDGE_SOUPER_CHR_B_SEL: + cartridge_souper_StoreChrBank(1, data); + break; + case CARTRIDGE_SOUPER_MODE_SEL: + cartridge_souper_SetMode(data); + break; + case CARTRIDGE_SOUPER_EXRAM_V_SEL: + cartridge_souper_SetRamPageBank(0, data); + break; + case CARTRIDGE_SOUPER_EXRAM_D_SEL: + cartridge_souper_SetRamPageBank(1, data); + break; + case CARTRIDGE_SOUPER_AUDIO_CMD: + break; + } + break; } if(cartridge_pokey && address >= 0x4000 && address < 0x4009) { @@ -371,6 +449,9 @@ void cartridge_StoreBank(byte bank) { case CARTRIDGE_TYPE_ACTIVISION: cartridge_WriteBank(40960, bank); break; + case CARTRIDGE_TYPE_SOUPER: + cartridge_WriteBank(32768, bank); + break; } } diff --git a/Core/Cartridge.h b/Core/Cartridge.h index f0e65f6..f82f426 100644 --- a/Core/Cartridge.h +++ b/Core/Cartridge.h @@ -31,11 +31,22 @@ #define CARTRIDGE_TYPE_SUPERCART_ROM 4 #define CARTRIDGE_TYPE_ABSOLUTE 5 #define CARTRIDGE_TYPE_ACTIVISION 6 +#define CARTRIDGE_TYPE_SOUPER 7 // Used by "Rikki & Vikki" #define CARTRIDGE_CONTROLLER_NONE 0 #define CARTRIDGE_CONTROLLER_JOYSTICK 1 #define CARTRIDGE_CONTROLLER_LIGHTGUN 2 #define CARTRIDGE_WSYNC_MASK 2 #define CARTRIDGE_CYCLE_STEALING_MASK 1 +#define CARTRIDGE_SOUPER_BANK_SEL 0x8000 +#define CARTRIDGE_SOUPER_CHR_A_SEL 0x8001 +#define CARTRIDGE_SOUPER_CHR_B_SEL 0x8002 +#define CARTRIDGE_SOUPER_MODE_SEL 0x8003 +#define CARTRIDGE_SOUPER_EXRAM_V_SEL 0x8004 +#define CARTRIDGE_SOUPER_EXRAM_D_SEL 0x8005 +#define CARTRIDGE_SOUPER_AUDIO_CMD 0x8007 +#define CARTRIDGE_SOUPER_MODE_MFT 0x1 +#define CARTRIDGE_SOUPER_MODE_CHR 0x2 +#define CARTRIDGE_SOUPER_MODE_EXS 0x4 #define NULL 0 #include @@ -51,6 +62,7 @@ typedef unsigned char byte; typedef unsigned short word; typedef unsigned int uint; +extern byte cartridge_LoadROM(uint address); extern bool cartridge_Load(std::string filename); extern void cartridge_Store( ); extern void cartridge_StoreBank(byte bank); @@ -69,5 +81,8 @@ extern bool cartridge_pokey; extern byte cartridge_controller[2]; extern byte cartridge_bank; extern uint cartridge_flags; +extern byte cartridge_souper_chr_bank[2]; +extern byte cartridge_souper_mode; +extern byte cartridge_souper_ram_page_bank[2]; #endif \ No newline at end of file diff --git a/Core/Maria.cpp b/Core/Maria.cpp index 7feee3f..de09bf7 100644 --- a/Core/Maria.cpp +++ b/Core/Maria.cpp @@ -46,7 +46,24 @@ static byte maria_wmode; // ReadByte // ---------------------------------------------------------------------------- static byte maria_ReadByte(word address) { - return memory_ram[address]; + if(cartridge_type != CARTRIDGE_TYPE_SOUPER) { + return memory_ram[address]; + } + if((cartridge_souper_mode & CARTRIDGE_SOUPER_MODE_MFT) == 0 || address < 0x8000 || + ((cartridge_souper_mode & CARTRIDGE_SOUPER_MODE_CHR) == 0 && address < 0xc000)) { + return memory_Read(address); + } + if(address >= 0xc000) { + // EXRAM + return memory_Read(address - 0x8000); + } + if(address < 0xa000) { + // Fixed ROM + return memory_Read(address + 0x4000); + } + uint page = word(cartridge_souper_chr_bank[(address & 0x80) != 0? 1: 0]); + uint chrOffset = (((page & 0xfe) << 4) | (page & 1)) << 7; + return cartridge_LoadROM((address & 0x0f7f) | chrOffset); } // ---------------------------------------------------------------------------- diff --git a/Core/Memory.cpp b/Core/Memory.cpp index f5c9526..21317a7 100644 --- a/Core/Memory.cpp +++ b/Core/Memory.cpp @@ -26,6 +26,7 @@ byte memory_ram[MEMORY_SIZE] = {0}; byte memory_rom[MEMORY_SIZE] = {0}; +byte memory_souper_ram[MEMORY_SOUPER_EXRAM_SIZE] = {0}; // ---------------------------------------------------------------------------- // Reset @@ -40,6 +41,22 @@ void memory_Reset( ) { memory_rom[index] = 0; } } + +// ---------------------------------------------------------------------------- +// SOUPER GetRamAddress +// ---------------------------------------------------------------------------- +word memory_souper_GetRamAddress(word address) { + byte page = (address - 0x4000) >> 12; + if((cartridge_souper_mode & CARTRIDGE_SOUPER_MODE_EXS) != 0) { + if(address >= 0x6000 && address < 0x7000) { + page = cartridge_souper_ram_page_bank[0]; + } else if(address >= 0x7000 && address < 0x8000) { + page = cartridge_souper_ram_page_bank[1]; + } + } + return (address & 0x0fff) | (word(page) << 12); +} + // ---------------------------------------------------------------------------- // Read // ---------------------------------------------------------------------------- @@ -59,6 +76,10 @@ byte memory_Read(word address) { return tmp_byte; break; default: + if(cartridge_type == CARTRIDGE_TYPE_SOUPER && address >= 0x4000 && address < 0x8000) { + return memory_souper_ram[memory_souper_GetRamAddress(address)]; + break; + } return memory_ram[address]; break; } @@ -134,6 +155,10 @@ void memory_Write(word address, byte data) { riot_SetTimer(T1024T, data); break; default: + if(cartridge_type == CARTRIDGE_TYPE_SOUPER && address >= 0x4000 && address < 0x8000) { + memory_souper_ram[memory_souper_GetRamAddress(address)] = data; + break; + } memory_ram[address] = data; if(address >= 8256 && address <= 8447) { memory_ram[address - 8192] = data; diff --git a/Core/Memory.h b/Core/Memory.h index 19c7171..0a2be6e 100644 --- a/Core/Memory.h +++ b/Core/Memory.h @@ -25,6 +25,7 @@ #ifndef MEMORY_H #define MEMORY_H #define MEMORY_SIZE 65536 +#define MEMORY_SOUPER_EXRAM_SIZE 32768 #define NULL 0 #include "Equates.h" @@ -42,7 +43,9 @@ extern byte memory_Read(word address); extern void memory_Write(word address, byte data); extern void memory_WriteROM(word address, word size, const byte* data); extern void memory_ClearROM(word address, word size); +extern word memory_souper_GetRamAddress(word address); extern byte memory_ram[MEMORY_SIZE]; extern byte memory_rom[MEMORY_SIZE]; +extern byte memory_souper_ram[MEMORY_SOUPER_EXRAM_SIZE]; #endif diff --git a/Core/ProSystem.cpp b/Core/ProSystem.cpp index 6cfbceb..b47758b 100644 --- a/Core/ProSystem.cpp +++ b/Core/ProSystem.cpp @@ -127,7 +127,7 @@ bool prosystem_Save(std::string filename, bool compress) { logger_LogInfo(IDS_PROSYSTEM2,filename); - byte buffer[32829] = {0}; + byte buffer[49218] = {0}; uint size = 0; uint index; @@ -166,6 +166,16 @@ bool prosystem_Save(std::string filename, bool compress) { buffer[size + index] = memory_ram[16384 + index]; } size += 16384; + } else if(cartridge_type == CARTRIDGE_TYPE_SOUPER) { + buffer[size++] = cartridge_souper_chr_bank[0]; + buffer[size++] = cartridge_souper_chr_bank[1]; + buffer[size++] = cartridge_souper_mode; + buffer[size++] = cartridge_souper_ram_page_bank[0]; + buffer[size++] = cartridge_souper_ram_page_bank[1]; + for(index = 0; index < sizeof(memory_souper_ram); index++) { + buffer[size + index] = memory_souper_ram[index]; + } + size += sizeof(memory_souper_ram); } if(!compress) { @@ -204,7 +214,7 @@ bool prosystem_Load(const std::string filename) { logger_LogInfo(IDS_PROSYSTEM6,filename); - byte buffer[32829] = {0}; + byte buffer[49218] = {0}; uint size = archive_GetUncompressedFileSize(filename); if(size == 0) { FILE* file = fopen(filename.c_str( ), "rb"); @@ -226,7 +236,7 @@ bool prosystem_Load(const std::string filename) { return false; } - if(size != 16445 && size != 32829) { + if(size != 16445 && size != 32829 && size != 49218) { fclose(file); logger_LogError(IDS_PROSYSTEM10,""); return false; @@ -239,7 +249,7 @@ bool prosystem_Load(const std::string filename) { } fclose(file); } - else if(size == 16445 || size == 32829) { + else if(size == 16445 || size == 32829 || size == 49218) { archive_Uncompress(filename, buffer, size); } else { @@ -299,7 +309,16 @@ bool prosystem_Load(const std::string filename) { memory_ram[16384 + index] = buffer[offset + index]; } offset += 16384; - } + } else if(cartridge_type == CARTRIDGE_TYPE_SOUPER) { + cartridge_souper_chr_bank[0] = buffer[offset++]; + cartridge_souper_chr_bank[1] = buffer[offset++]; + cartridge_souper_mode = buffer[offset++]; + cartridge_souper_ram_page_bank[0] = buffer[offset++]; + cartridge_souper_ram_page_bank[1] = buffer[offset++]; + for(index = 0; index < sizeof(memory_souper_ram); index++) { + memory_souper_ram[index] = buffer[offset++]; + } + } return true; } From 1b818d875b29903ccc7e010d57fe563b753b26e5 Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:10:20 -0400 Subject: [PATCH 09/15] Add `stdint.h`. BupBoop uses this header, and older versions of MSVC don't supply it, so we supply it ourselves. --- Lib/Stdint.h | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100755 Lib/Stdint.h diff --git a/Lib/Stdint.h b/Lib/Stdint.h new file mode 100755 index 0000000..b784504 --- /dev/null +++ b/Lib/Stdint.h @@ -0,0 +1,247 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2008 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C++" { +#endif +# include +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#define INTMAX_C INT64_C +#define UINTMAX_C UINT64_C + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] From b3c840c7cc7c62a846dff67ee8c0c96e4df9b4f1 Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:12:24 -0400 Subject: [PATCH 10/15] Import the BupBoop library, needed for "Rikki & Vikki". This is located at: http://tailchao.com/Audio/index.php#BupBoop It uses the zlib license, which is GPL compatible. This library is needed to emulate the sound hardware that "Rikki & Vikki" uses. It's an entirely separate chip included in the cartridge that produces 16-bit stereo audio. Note that BupBoop supplies a library called WinTone that provides DirectSound bindings, but we don't use it both because we already use DirectSound and because using WinTone would complicate `libretro` portability. --- Contrib/BupBoop/BupBoop.dsp | 134 ++++++ Contrib/BupBoop/CoreTone/channel.c | 626 +++++++++++++++++++++++++++ Contrib/BupBoop/CoreTone/channel.h | 134 ++++++ Contrib/BupBoop/CoreTone/coretone.c | 631 ++++++++++++++++++++++++++++ Contrib/BupBoop/CoreTone/coretone.h | 132 ++++++ Contrib/BupBoop/CoreTone/music.c | 555 ++++++++++++++++++++++++ Contrib/BupBoop/CoreTone/music.h | 148 +++++++ Contrib/BupBoop/CoreTone/sample.c | 196 +++++++++ Contrib/BupBoop/CoreTone/sample.h | 61 +++ Contrib/BupBoop/License.txt | 18 + Contrib/BupBoop/types.h | 70 +++ ProSystem.dsp | 12 + ProSystem.dsw | 15 + ProSystem.opt | Bin 50688 -> 58880 bytes 14 files changed, 2732 insertions(+) create mode 100755 Contrib/BupBoop/BupBoop.dsp create mode 100644 Contrib/BupBoop/CoreTone/channel.c create mode 100644 Contrib/BupBoop/CoreTone/channel.h create mode 100644 Contrib/BupBoop/CoreTone/coretone.c create mode 100644 Contrib/BupBoop/CoreTone/coretone.h create mode 100644 Contrib/BupBoop/CoreTone/music.c create mode 100644 Contrib/BupBoop/CoreTone/music.h create mode 100644 Contrib/BupBoop/CoreTone/sample.c create mode 100644 Contrib/BupBoop/CoreTone/sample.h create mode 100644 Contrib/BupBoop/License.txt create mode 100644 Contrib/BupBoop/types.h mode change 100644 => 100755 ProSystem.dsp mode change 100644 => 100755 ProSystem.dsw mode change 100644 => 100755 ProSystem.opt diff --git a/Contrib/BupBoop/BupBoop.dsp b/Contrib/BupBoop/BupBoop.dsp new file mode 100755 index 0000000..c956dc1 --- /dev/null +++ b/Contrib/BupBoop/BupBoop.dsp @@ -0,0 +1,134 @@ +# Microsoft Developer Studio Project File - Name="BupBoop" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=BupBoop - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "BupBoop.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "BupBoop.mak" CFG="BupBoop - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "BupBoop - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "BupBoop - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "BupBoop - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\..\Lib" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 + +!ELSEIF "$(CFG)" == "BupBoop - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\..\Lib" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "BUPBOOP_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "BupBoop - Win32 Release" +# Name "BupBoop - Win32 Debug" +# Begin Group "CoreTone" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\CoreTone\channel.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\channel.h +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\coretone.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\coretone.h +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\music.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\music.h +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\sample.c +# End Source File +# Begin Source File + +SOURCE=.\CoreTone\sample.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Lib\stdint.h +# End Source File +# Begin Source File + +SOURCE=.\types.h +# End Source File +# End Target +# End Project diff --git a/Contrib/BupBoop/CoreTone/channel.c b/Contrib/BupBoop/CoreTone/channel.c new file mode 100644 index 0000000..3efb4f9 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/channel.c @@ -0,0 +1,626 @@ +/****************************************************************************** + * channel.c + * Waveform rendering and patch script decoding. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" +#include "channel.h" +#include "music.h" + +/****************************************************************************** + * !!!!---- EXTERNS AND GLOBALS ----!!!! + ******************************************************************************/ +extern CoreChannel_t aCoreChannels[]; +extern CorePatch_t aCorePatches[]; +extern CoreTrack_t aCoreTracks[]; + +const char szCoreSfx_Magic[] = CORETONE_SFXPAK_HEAD_MAGICWORD; + + +/****************************************************************************** + * !!!!---- CHANNEL WAVEFORM RENDERING ----!!!! + ******************************************************************************/ +/* void ct_channel_render(CoreChannel_t *pChannel, int16_t *pBuffer, const int32_t iStamp) + * Render the channel's output into the supplied stereo buffer, writing the + * amplitudes directly (0 != iStamp) or summing them with the previous buffer + * contents (0 == iStamp). Render length is always CORETONE_BUFFER_LEN samples. + *----------------------------------------------------------------------------*/ +void ct_channel_render(CoreChannel_t *pChannel, int16_t *pBuffer, const int32_t iStamp) +{ + int16_t *pCursor,*pEnd; + + int16_t sSample; + int8p8_t sample_L,sample_R; + int8p8_t scale_L,scale_R; + + uint32_t uiLoop; + + pCursor = pBuffer; + pEnd = pBuffer + CORETONE_BUFFER_LEN; + + scale_L.sWhole = pChannel->cVolMain * pChannel->cVolLeft; + scale_L.sWhole = scale_L.cPair.cHi; + scale_R.sWhole = pChannel->cVolMain * pChannel->cVolRight; + scale_R.sWhole = scale_R.cPair.cHi; + + switch(pChannel->eMode) + { + case eCHANNEL_MODE_SINGLESHOT: + /** + * ---- SINGLE SHOT MODE ---- + * Just walk through the waveform from our current position + * until we reach its end, then shut off the channel. + * + * No need to have separate cases for forward and backward + * traversal here, since wandering outside the sample bounds + * in either direction is considered stopping criteria. + */ + if(iStamp) + { + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) = sample_L.sWhole + CORETONE_BUFFER_CENTER; + *(pCursor++) = sample_R.sWhole + CORETONE_BUFFER_CENTER; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + if(pChannel->phaseAcc.usPair.usHi >= pChannel->usSampleLen) + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pChannel->phaseAdj.iWhole = 0; + + while(pCursor != pEnd) + { + *(pCursor++) = CORETONE_BUFFER_CENTER; + *(pCursor++) = CORETONE_BUFFER_CENTER; + } + break; + } + } + } + else + { + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) += sample_L.sWhole; + *(pCursor++) += sample_R.sWhole; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + if(pChannel->phaseAcc.usPair.usHi >= pChannel->usSampleLen) + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pChannel->phaseAdj.iWhole = 0; + break; + } + } + } + break; + + case eCHANNEL_MODE_LOOP: + /** + * ---- LOOP MODE ---- + * Advance our waveform phase until it has passed by our loop + * endpoint, upon which we'll back it up into the loop region. + */ + uiLoop = pChannel->usLoopEnd - pChannel->usLoopStart; + if(iStamp) + { + /* ==== STAMP THE BUFFER ==== + * We're first in, so we get to clear the buffer contents + * with our render results. + */ + if(pChannel->phaseAdj.sPair.sHi < 0) + { + /*-- <<<< BACKWARD SAMPLE TRAVERSAL <<<< --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) = sample_L.sWhole + CORETONE_BUFFER_CENTER; + *(pCursor++) = sample_R.sWhole + CORETONE_BUFFER_CENTER; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi < pChannel->usLoopStart) + { + pChannel->phaseAcc.usPair.usHi += uiLoop; + } + } + } + else + { + /*-- >>>> FORWARD SAMPLE TRAVERSAL >>>> --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) = sample_L.sWhole + CORETONE_BUFFER_CENTER; + *(pCursor++) = sample_R.sWhole + CORETONE_BUFFER_CENTER; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi >= pChannel->usLoopEnd) + { + pChannel->phaseAcc.usPair.usHi -= uiLoop; + } + } + } + } + else + { + /* ++++ ACCUMULATE WITH BUFFER CONTENTS +++++ + * Someone's already marked the buffer, so we need to mix with + * their work rather than stomping over it. + */ + if(pChannel->phaseAdj.sPair.sHi < 0) + { + /*-- <<<< BACKWARD SAMPLE TRAVERSAL <<<< --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) += sample_L.sWhole; + *(pCursor++) += sample_R.sWhole; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi < pChannel->usLoopStart) + { + pChannel->phaseAcc.usPair.usHi += uiLoop; + } + } + } + else + { + /*-- >>>> FORWARD SAMPLE TRAVERSAL >>>> --*/ + while(pCursor != pEnd) + { + sSample = pChannel->pSample[pChannel->phaseAcc.usPair.usHi]; + sample_L.sWhole = sSample * scale_L.sWhole; + sample_R.sWhole = sSample * scale_R.sWhole; + + *(pCursor++) += sample_L.sWhole; + *(pCursor++) += sample_R.sWhole; + + pChannel->phaseAcc.iWhole += pChannel->phaseAdj.iWhole; + while(pChannel->phaseAcc.usPair.usHi >= pChannel->usLoopEnd) + { + pChannel->phaseAcc.usPair.usHi -= uiLoop; + } + } + } + } + break; + } +} + + + + +/****************************************************************************** + * !!!!---- PATCH STATE RECALCULATION ----!!!! + ******************************************************************************/ +/* void ct_patch_recalc(CorePatch_t *pPatch) + * Calculate the next state of a channel's frequency and volume based + * upon its currently configured patch. Should be called once per tick. + *----------------------------------------------------------------------------*/ +void ct_patch_recalc(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + + /** + * Final Phase Adjustment = Base + Pitch + Offset + * Pitch += Pitch Adjustment + * Offset += Offset Adjustment + */ + pPatch->freqPitch.iWhole += pPatch->pitchAdj.iWhole; + pPatch->freqOffset.iWhole += pPatch->offsetAdj.iWhole; + + pChannel->phaseAdj.iWhole = (pPatch->iInstrument) + ? (pPatch->freqBase.iWhole + pPatch->freqPitch.iWhole + pPatch->freqOffset.iWhole) + : pPatch->freqOffset.iWhole; + + /** + * Volume is just a straight 8.8 accumulation: + * Current += Adjustment + */ + pPatch->volCur.sWhole += pPatch->volAdj.sWhole; + + pChannel->cVolMain = pPatch->volCur.cPair.cHi; +} + + + + +/****************************************************************************** + * !!!!---- PATCH SCRIPT COMMANDS ----!!!! + ******************************************************************************/ +/* CORETONE_PATCH_END() + *----------------------------------------------------------------------------*/ +void ct_patchCom_end(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + pChannel->eMode = eCHANNEL_MODE_OFF; + pPatch->iInstrument = 0; + pPatch->iPriority = 0; +} + +/* CORETONE_PATCH_MODE_SINGLESHOT() + *----------------------------------------------------------------------------*/ +void ct_patchCom_modeSingle(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + pChannel->eMode = eCHANNEL_MODE_SINGLESHOT; +} + +/* CORETONE_PATCH_MODE_LOOP(usLoopStart, usLoopEnd) + *----------------------------------------------------------------------------*/ +void ct_patchCom_modeLoop(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + int8p8_t loopStart,loopEnd; + + loopStart.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + loopStart.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + loopEnd.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + loopEnd.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + + pChannel->eMode = eCHANNEL_MODE_LOOP; + pChannel->usLoopStart = loopStart.usWhole; + pChannel->usLoopEnd = loopEnd.usWhole; +} + +/* CORETONE_PATCH_VOLUME(cVol, cAdj_Lo, cAdj_Hi) + *----------------------------------------------------------------------------*/ +void ct_patchCom_vol(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + pPatch->volCur.ucPair.ucLo = 0; + pPatch->volCur.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->volAdj.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + pPatch->volAdj.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; +} + +/* CORETONE_PATCH_FREQUENCY(sOffset_Lo, sOffset_Hi, sAdj_Lo, sAdj_Hi) + *----------------------------------------------------------------------------*/ +void ct_patchCom_freq(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + int8p8_t fetchFreq; + + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->freqOffset.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->freqOffset.usPair.usHi = fetchFreq.usWhole; + + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->offsetAdj.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pPatch->pScript[pPatch->uiOffset++]; + fetchFreq.ucPair.ucHi = pPatch->pScript[pPatch->uiOffset++]; + pPatch->offsetAdj.usPair.usHi = fetchFreq.usWhole; +} + +/* CORETONE_PATCH_LOOP_START(cCount) + *----------------------------------------------------------------------------*/ +void ct_patchCom_loopStart(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + int8_t cCount; + + if(pPatch->uiStackPos < CORETONE_PATCH_STACKDEPTH) + { + cCount = pPatch->pScript[pPatch->uiOffset++]; + pPatch->aiLoopStack[pPatch->uiStackPos] = cCount; + pPatch->auiAddrStack[pPatch->uiStackPos] = pPatch->uiOffset; + + pPatch->uiStackPos++; + } +} + +/* CORETONE_PATCH_LOOP_END() + *----------------------------------------------------------------------------*/ +void ct_patchCom_loopEnd(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + uint32_t uiX; + + if(pPatch->uiStackPos > 0) + { + uiX = pPatch->uiStackPos - 1; + + if((pPatch->aiLoopStack[uiX] >= 0) && (pPatch->aiLoopStack[uiX] < 2)) + { + /** + * Loop counts of zero or one will allow the decoder to + * proceed past the loop end marker. + */ + pPatch->uiStackPos = uiX; + } + else if(pPatch->aiLoopStack[uiX] < 0) + { + /** + * Loop counts less than zero are considered infinite + * and will always cause the decoder to wrap back. + */ + pPatch->uiOffset = pPatch->auiAddrStack[uiX]; + } + else + { + /** + * Loop counts of two or greater will cause the decoder + * to wrap back and decrement their count until they + * eventually reach the first case of this triad. + */ + pPatch->uiOffset = pPatch->auiAddrStack[uiX]; + pPatch->aiLoopStack[uiX]--; + } + } +} + +/* CORETONE_PATCH_NOP() + *----------------------------------------------------------------------------*/ +void ct_patchCom_nop(CorePatch_t *pPatch, CoreChannel_t *pChannel) +{ + +} + + + + +/****************************************************************************** + * !!!!---- PATCH COMMAND TABLE ----!!!! + *----------------------------------------------------------------------------*/ +typedef void (*ct_patchCom_t)(CorePatch_t *pPatch, CoreChannel_t *pChannel); +ct_patchCom_t aPatchComs[] = +{ + ct_patchCom_end, + ct_patchCom_modeSingle, ct_patchCom_modeLoop, + ct_patchCom_vol, ct_patchCom_freq, + + ct_patchCom_loopStart, ct_patchCom_loopEnd, + ct_patchCom_nop +}; + +/****************************************************************************** + * !!!!---- PATCH SCRIPT DECODING ----!!!! + ******************************************************************************/ +/* void ct_patch_keyOn(CorePatch_t *pPatch) + * Reset waveform and patch decoding parameters for the given channel, should + * only be called once the given channel and patch have had their waveform, + * script, and priority configured. + *----------------------------------------------------------------------------*/ +void ct_patch_keyOn(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + + pChannel->eMode = eCHANNEL_MODE_OFF; + pChannel->phaseAcc.iWhole = 0; + + pPatch->freqOffset.iWhole = 0; + pPatch->offsetAdj.iWhole = 0; + pPatch->volCur.sWhole = 0; + pPatch->volAdj.sWhole = 0; + + pPatch->uiOffset = 0; + pPatch->uiStackPos = 0; + pPatch->uiDel = 0; +} + +/* void ct_patch_keyOff(CorePatch_t *pPatch) + * If the current patch is an instrument, the script decoder will be sent to + * the note off portion of the patch. If the current patch is a sound effect + * it will be terminated outright. + *----------------------------------------------------------------------------*/ +void ct_patch_keyOff(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + + if(pPatch->iInstrument) + { + pPatch->uiOffset = pPatch->uiNoteOff; + pPatch->uiStackPos = 0; + pPatch->uiDel = 0; + } + else + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pPatch->iPriority = 0; + } +} + +/* void ct_patch_decode(CorePatch_t *pPatch) + * Walk through the patch script if the given channel descriptors are both + * active (have a nonzero priority) and have no pending delays. + *----------------------------------------------------------------------------*/ +void ct_patch_decode(CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel = pPatch->pChannel; + uint8_t *pScript; + uint8_t ucByte; + + uint32_t uiX,uiY; + + /** + * We'll only decode the next command in a patch script if the channel is + * actually enabled (nonzero priority) and has no currently active delays. + */ + pScript = pPatch->pScript; + while((0 != pPatch->iPriority) && (0 == pPatch->uiDel)) + { + ucByte = pScript[pPatch->uiOffset]; + + if(ucByte & CORETONE_PATCH_WAIT) + { + /** + * Delay commands are a special case and operate similarly to MIDI's + * varlength delays. Any command byte with its MSB set is interpreted + * as the start of a delay string which continues until a command + * with its MSB clear is encountered. + * + * The seven remaining bits of each byte in the delay string are used + * as the delay count itself, and are shifted into bits 7-0, 14-8, + * 21-15, or 28-22 of the accumulated delay count. + */ + for((uiX = 0, uiY = 0); + ((ucByte & CORETONE_PATCH_WAIT) && (uiY < sizeof(uint32_t))); + (uiX += 7, uiY++)) + { + pPatch->uiDel |= ((ucByte & CORETONE_PATCH_WAIT_MASK) << uiX); + + pPatch->uiOffset++; + ucByte = pScript[pPatch->uiOffset]; + } + } + else + { + /** + * Normal commands are just called directly after + * incrementing the decoder offset. + */ + pPatch->uiOffset++; + if(ucByte < CORETONE_MUSIC_FOOTER) + { + (*(aPatchComs[ucByte]))(pPatch, pChannel); + } + else + { + pChannel->eMode = eCHANNEL_MODE_OFF; + pPatch->iPriority = 0; + } + } + } + + if(0 != pPatch->iPriority) + { + pPatch->uiDel--; + } +} + + + + +/****************************************************************************** + * !!!!---- SOUND EFFECT MANAGEMENT ----!!!! + ******************************************************************************/ +/* void ct_sfx_dispatch(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right) + * Dispatch the sound effect pSFX with the supplied priority and panning on + * any channels which are currently idle or occupied by a less important patch. + *----------------------------------------------------------------------------*/ +void ct_sfx_dispatch(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right) +{ + CoreChannel_t *pChannel; + CorePatch_t *pPatch; + + uint32_t *pDir; + uint8_t *pScript; + + uint32_t uiChannels; + uint32_t uiSample,uiLen; + uint32_t uiX,uiY; + + /** + * Ensure the sound effect actually has a valid header and channel count + * larger than zero before proceeding... + */ + for(uiX = 0; uiX < CORETONE_SFXPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreSfx_Magic[uiX] != pSFX[uiX]) return; + } + + uiChannels = *((uint32_t*)(pSFX + CORETONE_SFXPAK_HEAD_COUNT)); + if(0 == uiChannels) return; + + /** + * As instruments are usually played upward from channel zero, we'll be + * dispatching sound effects downward from the last channel. The available + * channel scan is broken into three passes : + * + * 1) Channels which are idle and have no associated music tracks. + * 2) Channels with sound effects of lesser importance. + * 3) Channels with any patch of lesser importance. + * + * Essentially, we're trying our best to not interfere with any music + * unless it is absolutely necessary. + */ + pDir = (uint32_t*)(pSFX + CORETONE_SFXPAK_DIR_BASE); + uiX = 0; + while(uiX < uiChannels) + { + for(uiY = (CORETONE_CHANNELS - 1); uiY != 0xFFFFFFFF; uiY--) + { + if((0 == aCorePatches[uiY].iPriority) + && (0 == aCoreTracks[uiY].iPriority)) goto dispatch; + } + for(uiY = (CORETONE_CHANNELS - 1); uiY != 0xFFFFFFFF; uiY--) + { + if((cPriority > aCorePatches[uiY].iPriority) + && (0 == aCoreTracks[uiY].iPriority)) goto dispatch; + } + for(uiY = (CORETONE_CHANNELS - 1); uiY != 0xFFFFFFFF; uiY--) + { + if(cPriority > aCorePatches[uiY].iPriority) goto dispatch; + } + + /** + * If all three of the searches above have failed, the channels are + * completely saturated and there's no point in trying to enqueue + * any additional patches this sound effect may have. + */ + break; + + /** + * However, dispatching means there might be one channel left, so + * we'll let the search continue after taking over the slot. + */ + dispatch: + pPatch = aCorePatches + uiY; + pChannel = aCoreChannels + uiY; + uiSample = pDir[CORETONE_SFXPAK_ENTRY_SAMPLE]; + pScript = pSFX + pDir[CORETONE_SFXPAK_ENTRY_SCRIPT]; + + pPatch->iPriority = cPriority; + pPatch->iInstrument = 0; + pPatch->pScript = pScript; + pPatch->uiNoteOff = 0; + + ct_sample_get(uiSample, &(pChannel->pSample), &uiLen); + pChannel->usSampleLen = uiLen; + ct_patch_keyOn(pPatch); + pChannel->cVolLeft = cVol_Left; + pChannel->cVolRight = cVol_Right; + + pDir += CORETONE_SFXPAK_ENTRY_LEN; + uiX++; + } +} + +/* void ct_sfx_stop(int8_t cPriority) + * Cease playback of any currently decoding sound effects with the priority + * cPriority, instruments with this priority will be left alone. + *----------------------------------------------------------------------------*/ +void ct_sfx_stop(int8_t cPriority) +{ + uint32_t uiX; + + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + if(!(aCorePatches[uiX].iInstrument) + && (cPriority == aCorePatches[uiX].iPriority)) + { + ct_patch_keyOff(aCorePatches + uiX); + } + } +} diff --git a/Contrib/BupBoop/CoreTone/channel.h b/Contrib/BupBoop/CoreTone/channel.h new file mode 100644 index 0000000..a04d61d --- /dev/null +++ b/Contrib/BupBoop/CoreTone/channel.h @@ -0,0 +1,134 @@ +/****************************************************************************** + * channel.h + * Waveform rendering and patch script decoding. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE_CHANNEL +#define CORETONE_CHANNEL +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +/* This indicates the stack depth used to track patch script loops, + * the default of four should be adequate for most users. + */ +#ifndef CORETONE_PATCH_STACKDEPTH + #define CORETONE_PATCH_STACKDEPTH 4 +#endif + +/****************************************************************************** + * Channel Descriptors + ******************************************************************************/ +typedef enum CoreChannel_Mode_e +{ + eCHANNEL_MODE_OFF = 0, + eCHANNEL_MODE_SINGLESHOT, + eCHANNEL_MODE_LOOP, + + eCHANNEL_MODE_FOOTER +} CoreChannel_Mode_t; + +typedef struct CoreChannel_s +{ + CoreChannel_Mode_t eMode; + + int8_t *pSample; + uint16_t usSampleLen; + + int8_t cVolMain; + int8_t cVolLeft,cVolRight; + + int16p16_t phaseAcc,phaseAdj; + int16_t usLoopStart,usLoopEnd; +} CoreChannel_t; + +/****************************************************************************** + * Sound Effect Format + ******************************************************************************/ +/* Sound Effect Binaries are composed of three major regions: + * + * The HEADER, which contains an identifier string and the number of patch + * channels present in the sound effect data. + * + * The DIRECTORY, which contains the SAMPLE ID and SCRIPT OFFSET of each + * channel, in order. Both of these values are 32-Bits, yielding + * 8-Bytes per directory entry. + * + * The DATA AREA, which contains the patch scripts for all channels. + * + * Sound effects should be located at a 32-Bit aligned address and not fiddled + * with while they're in use. Playback priorities and panning are assigned + * during dispatch requests using ct_playSFX(). + */ +#define CORETONE_SFXPAK_HEAD_MAGICWORD "CSFX" +#define CORETONE_SFXPAK_HEAD_MAGICLEN 4 +#define CORETONE_SFXPAK_HEAD_COUNT 4 +#define CORETONE_SFXPAK_HEAD_SIZE 8 + +#define CORETONE_SFXPAK_DIR_BASE 8 + +/* Note that the entry offsets below are in uint32_t counts intead of bytes */ +#define CORETONE_SFXPAK_ENTRY_SAMPLE 0 +#define CORETONE_SFXPAK_ENTRY_SCRIPT 1 +#define CORETONE_SFXPAK_ENTRY_LEN 2 + +/****************************************************************************** + * Patch Script Commands and Decode Descriptors + ******************************************************************************/ +#define CORETONE_PATCH_END 0 +#define CORETONE_PATCH_MODE_SINGLESHOT 1 +#define CORETONE_PATCH_MODE_LOOP 2 +#define CORETONE_PATCH_VOLUME 3 +#define CORETONE_PATCH_FREQUENCY 4 +#define CORETONE_PATCH_LOOP_START 5 +#define CORETONE_PATCH_LOOP_END 6 +#define CORETONE_PATCH_NOP 7 + +#define CORETONE_PATCH_FOOTER 8 + +/* Wait commands are a special case which is somewhat borrowed from MIDI, + * the delay (in ticks) is variable length up to four bytes long. The MSB + * of each byte indicates whether or not to extend the delay count and the + * seven remaining bits are placed into bits 7-0, 14-8, 21-15, or 28-22 + * of said accumulated count. + * All other patch commands are below 0x7F and have their MSB clear, so + * there shouldn't be any concern of crossover. + */ +#define CORETONE_PATCH_WAIT 0x80 +#define CORETONE_PATCH_WAIT_MASK 0x7F + +typedef struct CorePatch_s +{ + CoreChannel_t *pChannel; + + int32_t iInstrument,iPriority; + + uint8_t *pScript; + uint32_t uiOffset,uiNoteOff; + uint32_t uiDel; + + int16p16_t freqBase; + int16p16_t freqPitch,pitchAdj; + int16p16_t freqOffset,offsetAdj; + + int8p8_t volCur,volAdj; + + uint32_t uiStackPos; + int32_t aiLoopStack[CORETONE_PATCH_STACKDEPTH]; + uint32_t auiAddrStack[CORETONE_PATCH_STACKDEPTH]; +} CorePatch_t; + +/****************************************************************************** + * Function Defines + ******************************************************************************/ +void ct_channel_render(CoreChannel_t *pChannel, int16_t *pBuffer, const int32_t iStamp); + +void ct_patch_recalc(CorePatch_t *pPatch); +void ct_patch_keyOn(CorePatch_t *pPatch); +void ct_patch_keyOff(CorePatch_t *pPatch); +void ct_patch_decode(CorePatch_t *pPatch); + +void ct_sfx_dispatch(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right); +void ct_sfx_stop(int8_t cPriority); +#endif diff --git a/Contrib/BupBoop/CoreTone/coretone.c b/Contrib/BupBoop/CoreTone/coretone.c new file mode 100644 index 0000000..69d3a92 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/coretone.c @@ -0,0 +1,631 @@ +/****************************************************************************** + * cortone.c + * User-facing portions of CoreTone. These include playback requests, + * parameter adjustments, and the grand rendering update routine. + *----------------------------------------------------------------------------- + * Version 1.2.2cz, November 26th, 2016 + * Copyright (C) 2015 - 2016 Osman Celimli + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + ******************************************************************************/ +#include +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" +#include "channel.h" +#include "music.h" + +/****************************************************************************** + * !!!!---- CHANNEL AND SYSTEM STATE ----!!!! + ******************************************************************************/ +int32_t iCoreReady; + +volatile int32_t iAmPaused; +volatile int32_t iAllStopReq; + + +struct { + uint8_t *pSFX; + int8_t cPriority; + + int8_t cVol_Left,cVol_Right; +} aDispatchQueue[CORETONE_DISPATCH_DEPTH], + aBatchQueue[CORETONE_DISPATCH_DEPTH]; +volatile uint32_t uiDispatchIn,uiDispatchOut; +volatile uint32_t uiBatchIn,uiBatchOut; + + +typedef enum CoreReq_e +{ + eREQUEST_STOP_SFX = 0, + + eREQUEST_FOOTER +} CoreReq_t; + +struct { + int8_t cTarget; + + CoreReq_t eAction; + uint32_t uiArg_A,uiArgB,uiArg_C; +} aReqQueue[CORETONE_REQUEST_DEPTH]; +volatile uint32_t uiReqIn,uiReqOut; + + +volatile uint8_t *pMusTrack; +volatile int8_t cMusVol; + +volatile int32_t iMusPlaying,iMusMood; +volatile int32_t iMusPlayReq,iMusStopReq,iMusAttenReq; + +volatile ct_renderCall_t pRenderCall; + +CoreChannel_t aCoreChannels[CORETONE_CHANNELS]; +CorePatch_t aCorePatches[CORETONE_CHANNELS]; +CoreTrack_t aCoreTracks[CORETONE_CHANNELS]; + + +/****************************************************************************** + * !!!!---- UPDATE TICK AND WAVEFORM RENDERING ----!!!! + ******************************************************************************/ +/* void ct_update(int16_t *pBuffer) + * Update patch scripts of any currently decoding music and sound effects, + * then render the summed output of all channels to the supplied buffer . + * Render length is fixed at CORETONE_BUFFER_LEN stereo samples, and this + * function should be called at a rate of CORETONE_DECODE_RATE Hz. + *----------------------------------------------------------------------------*/ +void ct_update(int16_t *pBuffer) +{ + uint32_t uiX,uiZ; + int32_t iY; + + /** + * ---- STOP REQUESTS ---- + * For music, sound effects, or both. This is performed first in the + * update procedure in order to ensure we'll free up any channels + * which will no longer be needed before advancing to the more + * expensive decode, dispatch, and render steps. + */ + if(0 != (iMusStopReq || iAllStopReq)) + { + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + aCoreTracks[uiX].iPriority = 0; + aCoreTracks[uiX].ucNote = CORETONE_MUSIC_NOTE_INVALID; + + if(aCorePatches[uiX].iInstrument || iAllStopReq) + { + aCorePatches[uiX].iInstrument = 0; + aCorePatches[uiX].iPriority = 0; + + aCoreChannels[uiX].eMode = eCHANNEL_MODE_OFF; + } + } + + iMusPlaying = 0; + iMusMood = 0; + } + iAllStopReq = 0; + iMusStopReq = 0; + + + /** + * ---- MUSIC DISPATCH AND DECODE ---- + * If the playback of a new music track has been requested, kick + * it off. If one is already playing, continue its decoding unless + * we're in a paused state.. + */ + if(iMusPlayReq) + { + iMusPlaying = !ct_music_setup((uint8_t*)pMusTrack); + iMusMood = 0; + } + iMusPlayReq = 0; + + if(iMusAttenReq) + { + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + aCoreTracks[uiX].iRecalcVol = -1; + aCoreTracks[uiX].cVolMain = cMusVol; + } + + iMusAttenReq = 0; + } + + if(iMusPlaying && !iAmPaused) + { + uiZ = 0; + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + ct_music_decode(&(aCoreTracks[uiX])); + + if(0 != aCoreTracks[uiX].iPriority) + { + uiZ++; + } + } + + if(0 == uiZ) + { + iMusPlaying = 0; + iMusMood = 0; + } + } + + /** + * ---- SOUND EFFECT DISPATCH ---- + * Check the sound effect dispatch queue for any new noises to play, + * assigning each of their patches to channels which are either free + * or playing a macro of lower priority. + * + * Sound effects begin dispatch from the last channel while music + * traditionally plays relative to the first in order to reduce + * contention between the two. + */ + if(uiDispatchIn != uiDispatchOut) + { + uiZ = uiDispatchOut; + while(uiZ != uiDispatchIn) + { + ct_sfx_dispatch(aDispatchQueue[uiZ].pSFX, + aDispatchQueue[uiZ].cPriority, + aDispatchQueue[uiZ].cVol_Left, + aDispatchQueue[uiZ].cVol_Right); + + uiZ = (uiZ + 1) % CORETONE_DISPATCH_DEPTH; + } + + uiDispatchOut = uiZ; + } + + /** + * ---- ACTION REQUESTS --- + * General effects such as stopping the playback of a sound effect. + */ + if(uiReqIn != uiReqOut) + { + uiZ = uiReqOut; + while(uiZ != uiReqIn) + { + switch(aReqQueue[uiZ].eAction) + { + case eREQUEST_STOP_SFX: + ct_sfx_stop(aReqQueue[uiZ].cTarget); + break; + + default: + break; + } + + uiZ = (uiZ + 1) % CORETONE_REQUEST_DEPTH; + } + + uiReqOut = uiZ; + } + + /** + * ---- PATCH DECODE AND WAVEFORM RENDERING ---- + * Walk through each of the active patches (nonzero priority) and their + * respective channels, in order. The first active channel gets to write + * directly to the new outgoing buffer, "stamping" it. Others will just + * accumulate their rendered output with the previous buffer value. + * + * If we're paused, this entire step will be skipped and we'll fall into + * the buffer clear routine below. + */ + iY = -1; + if(!iAmPaused) + { + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + if(aCorePatches[uiX].iPriority) + { + ct_patch_decode(&(aCorePatches[uiX])); + ct_patch_recalc(&(aCorePatches[uiX])); + } + + if(eCHANNEL_MODE_OFF != aCoreChannels[uiX].eMode) + { + ct_channel_render(&(aCoreChannels[uiX]), pBuffer, iY); + iY = 0; + } + } + } + + /** + * If none of the channels were enabled (complete silence) just + * zero out the buffer before we leave. + */ + if(0 != iY) + { + for(uiX = 0; uiX < CORETONE_BUFFER_LEN; uiX++) + { + pBuffer[uiX] = CORETONE_BUFFER_CENTER; + } + } + + /** + * Supply our finished buffer to any post-render callbacks if + * they're currently active. The callback will be automatically + * disabled if it returns zero. + */ + if(NULL != pRenderCall) + { + if(0 == pRenderCall(pBuffer, + CORETONE_RENDER_RATE, CORETONE_BUFFER_SAMPLES, + iAmPaused)) + { + pRenderCall = NULL; + } + } +} + + + + +/****************************************************************************** + * !!!!---- GLOBAL PLAYBACK CONTROL ----!!!! + ******************************************************************************/ +/* void ct_pause(void) + * Pause (and silence) the decoding of all active music and sound effects, + * post-render callbacks will still be allowed to run and will be notified + * regarding the current pause status. + *----------------------------------------------------------------------------*/ +void ct_pause(void) +{ + iAmPaused = -1; +} + +/* void ct_resume(void) + * Resume audio playback from a paused state. + *----------------------------------------------------------------------------*/ +void ct_resume(void) +{ + iAmPaused = 0; +} + +/* int32_t ct_isPaused(void) + * Indicates if CoreTone is currently paused (nonzero) or unpaused (zero). + *----------------------------------------------------------------------------*/ +int32_t ct_isPaused(void) +{ + return iAmPaused; +} + +/* void ct_stopAll(void) + * Halt the decoding and playback off all sound effects and music. + *----------------------------------------------------------------------------*/ +void ct_stopAll(void) +{ + if(iCoreReady) + { + iAllStopReq = -1; + } +} + +/* void ct_setRenderCall(ct_renderCall_t pCall) + * Set the current render callback to pCall. + *----------------------------------------------------------------------------*/ +void ct_setRenderCall(ct_renderCall_t pCall) +{ + if(iCoreReady) + { + pRenderCall = pCall; + } +} + + + + +/****************************************************************************** + * !!!!---- MUSIC PLAYBACK CONTROL ----!!!! + ******************************************************************************/ +/* void ct_playMusic(uint8_t *pMusic) + * Reqest the playback of the music track whose base address is at pMusic + * on the next update tick. Will "prestop" the current playing track (if any). + *----------------------------------------------------------------------------*/ +void ct_playMusic(uint8_t *pMusic) +{ + if(iCoreReady && (NULL != pMusic)) + { + ct_stopMusic(); + + pMusTrack = pMusic; + iMusPlayReq = -1; + } +} + +/* void ct_stopMusic(void) + * Request any currently playing music to cease on the next update tick. + *----------------------------------------------------------------------------*/ +void ct_stopMusic(void) +{ + if(iCoreReady) + { + iMusStopReq = -1; + } +} + +/* void ct_attenMusic(int8_t cVol) + * Request a change in volume for the currently playing music (if any) on + * the next update tick, zero is silent and 127 is loudest. + *----------------------------------------------------------------------------*/ +void ct_attenMusic(int8_t cVol) +{ + if(iCoreReady) + { + cMusVol = cVol; + iMusAttenReq = -1; + } +} + +/* int32_t ct_checkMusic(void) + * Check to see whether the music is playing (nonzero return) or not (zero), + * useful to determine if a single-shot track has finished or if a stop + * request has completed. + *----------------------------------------------------------------------------*/ +int32_t ct_checkMusic(void) +{ + if(iCoreReady) + { + return iMusPlaying; + } + + return 0; +} + +/* int32_t ct_getMood(void) + * Get the mood flag of the currently playing music track. If no music is + * playing, the mood flag will be zero (neutral). + *----------------------------------------------------------------------------*/ +int32_t ct_getMood(void) +{ + return iMusMood; +} + + + + +/****************************************************************************** + * !!!!---- SFX PLAYBACK CONTROL ----!!!! + ******************************************************************************/ +/* void ct_playSFX(uint8_t *pSFX, int8_t cPriority, + * int8_t cVol_Left, int8_t cVol_Right) + * Request the playback of the sound effect pSFX on the next update tick with + * priority ucPriority and panning cVol_Left and cVol_Right. + *----------------------------------------------------------------------------*/ +void ct_playSFX(uint8_t *pSFX, int8_t cPriority, + int8_t cVol_Left, int8_t cVol_Right) +{ + uint32_t uiX,uiY; + + if(iCoreReady && (0 != cPriority) && (NULL != pSFX)) + { + uiX = uiDispatchIn; + uiY = (uiX + 1) % CORETONE_DISPATCH_DEPTH; + if(uiY != uiDispatchOut) + { + aDispatchQueue[uiX].pSFX = pSFX; + + aDispatchQueue[uiX].cPriority = cPriority; + aDispatchQueue[uiX].cVol_Left = cVol_Left; + aDispatchQueue[uiX].cVol_Right = cVol_Right; + uiDispatchIn = uiY; + } + } +} + +/* void ct_stopSFX(uint8_t ucPriority) + * Request all currently decoding sound effects with the priority ucPriority + * to cease playback on the next update tick. + *----------------------------------------------------------------------------*/ +void ct_stopSFX(int8_t cPriority) +{ + uint32_t uiX,uiY; + + if(iCoreReady && (0 != cPriority)) + { + uiX = uiReqIn; + uiY = (uiX + 1) % CORETONE_REQUEST_DEPTH; + if(uiY != uiReqOut) + { + aReqQueue[uiX].cTarget = cPriority; + + aReqQueue[uiX].eAction = eREQUEST_STOP_SFX; + uiReqIn = uiY; + } + } +} + +/* void ct_addSFX(uint8_t *pSFX, int8_t cPriority, + * int8_t cVol_Left, int8_t cVol_Right) + * Request the playback of the sound effect pSFX on the next batch dump with + * priority ucPriority and panning cVol_Left and cVol_Right. This can be used + * to synchronize the start of multiple sound effects. + *----------------------------------------------------------------------------*/ +void ct_addSFX(uint8_t *pSFX, int8_t cPriority, + int8_t cVol_Left, int8_t cVol_Right) +{ + uint32_t uiX,uiY; + + if(iCoreReady && (0 != cPriority) && (NULL != pSFX)) + { + uiX = uiBatchIn; + uiY = (uiX + 1) % CORETONE_DISPATCH_DEPTH; + if(uiY != uiBatchOut) + { + aBatchQueue[uiX].pSFX = pSFX; + + aBatchQueue[uiX].cPriority = cPriority; + aBatchQueue[uiX].cVol_Left = cVol_Left; + aBatchQueue[uiX].cVol_Right = cVol_Right; + uiBatchIn = uiY; + } + } +} + +/* void ct_dumpSFX(void) + * Play all sound effects in the current batch set using ct_playSFX(), this + * can be used to synchronize the start of multiple sound effects. + *----------------------------------------------------------------------------*/ +void ct_dumpSFX(void) +{ + uint32_t uiX,uiY; + + if(iCoreReady) + { + while(uiBatchIn != uiBatchOut) + { + uiX = uiBatchOut; + uiY = (uiX + 1) % CORETONE_DISPATCH_DEPTH; + + ct_playSFX(aBatchQueue[uiX].pSFX, + aBatchQueue[uiX].cPriority, + aBatchQueue[uiX].cVol_Left, aBatchQueue[uiX].cVol_Right); + uiBatchOut = uiY; + } + } +} + + + + +/****************************************************************************** + * !!!!---- MUTEX ACCESS ----!!!! + ******************************************************************************/ +/* int32_t ct_getMutex(void) + * Aquire the CoreTone access mutex, upon which the caller will be guaranteed + * that CoreTone will not perform an update until the mutex is released. This + * is expected to be implemented on the platform-specific layer on top of + * CoreTone, and therefore this function will always return nonzero (failure). + *----------------------------------------------------------------------------*/ +int32_t ct_getMutex(void) +{ + return -1; +} + +/* int32_t ct_giveMutex(void) + * Release the CoreTone access mutex. This is expected to be implemented on + * the platform-specific layer on top of CoreTone, and therefore this function + * will always return nonzero (failure). + *----------------------------------------------------------------------------*/ +int32_t ct_giveMutex(void) +{ + return -1; +} + + + + +/****************************************************************************** + * !!!!---- INITIALIZATION & DIAGNOSTICS ----!!!! + ******************************************************************************/ +/* int32_t ct_init(uint8_t *pSamplePak, uint8_t *pInstrPak) + * Initialize the CoreTone library with the supplied sample and instrument + * packages, should be run once at startup. Will return a nonzero value if + * any errors occured during the setup procedure. + *----------------------------------------------------------------------------*/ +int32_t ct_init(uint8_t *pSamplePak, uint8_t *pInstrPak) +{ + int32_t iFailed = 0; + uint32_t uiX; + + iAllStopReq = 0; + + uiDispatchIn = 0; + uiDispatchOut = 0; + uiBatchIn = 0; + uiBatchOut = 0; + uiReqIn = 0; + uiReqOut = 0; + + pMusTrack = NULL; + cMusVol = CORETONE_DEFAULT_VOLUME; + iMusAttenReq = -1; + + iMusPlaying = 0; + iMusPlayReq = 0; + iMusStopReq = 0; + + pRenderCall = NULL; + + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + memset(&(aCoreChannels[uiX]), 0, sizeof(CoreChannel_t)); + aCoreChannels[uiX].eMode = eCHANNEL_MODE_OFF; + + memset(&(aCorePatches[uiX]), 0, sizeof(CorePatch_t)); + aCorePatches[uiX].iPriority = 0; + aCorePatches[uiX].pChannel = &(aCoreChannels[uiX]); + + memset(&(aCoreTracks[uiX]), 0, sizeof(CoreTrack_t)); + aCoreTracks[uiX].iPriority = 0; + aCoreTracks[uiX].ucNote = CORETONE_MUSIC_NOTE_INVALID; + aCoreTracks[uiX].pChannel = &(aCoreChannels[uiX]); + aCoreTracks[uiX].pPatch = &(aCorePatches[uiX]); + } + + iFailed |= ct_sample_setup(pSamplePak); + iFailed |= ct_instr_setup(pInstrPak); + + iCoreReady = !iFailed; + return iFailed; +} + +/* void ct_getState(void **ppaChannels, void **ppaPatches, void **ppaTracks) + * Store the base addresses of CoreTone's channel, patch, and track states + * into *ppaChannels, *ppaPatches, and *ppaTracks respectively which can be + * used to observe (and only observe) internal activity. + *----------------------------------------------------------------------------*/ +void ct_getState(void **ppaChannels, void **ppaPatches, void **ppaTracks) +{ + if(NULL != ppaChannels) + *ppaChannels = (void*)&aCoreChannels; + if(NULL != ppaPatches) + *ppaPatches = (void*)&aCorePatches; + if(NULL != ppaTracks) + *ppaTracks = (void*)&aCoreTracks; +} + +/* void ct_getInfo(uint32_t *puiChannels, uint32_t *puiRenderFreq, + * uint32_t *puiDecodeRate, + * uint32_t *puiSamples, uint32_t *puiSampleLen) + * Get information about the CoreTone build's capabilities and requirements + * including channel count, render frequency, decode rate, maximum sample + * package size, and sample length. + *----------------------------------------------------------------------------*/ +void ct_getInfo(uint32_t *puiChannels, uint32_t *puiRenderFreq, + uint32_t *puiDecodeRate, + uint32_t *puiSamples, uint32_t *puiSampleLen) +{ + if(NULL != puiChannels) + *puiChannels = CORETONE_CHANNELS; + if(NULL != puiRenderFreq) + *puiRenderFreq = CORETONE_RENDER_RATE; + + if(NULL != puiDecodeRate) + *puiDecodeRate = CORETONE_DECODE_RATE; + + if(NULL != puiSamples) + *puiSamples = CORETONE_SAMPLES_MAXENTRIES; + if(NULL != puiSampleLen) + *puiSampleLen = CORETONE_SAMPLES_MAXLENGTH; +} + diff --git a/Contrib/BupBoop/CoreTone/coretone.h b/Contrib/BupBoop/CoreTone/coretone.h new file mode 100644 index 0000000..8133da1 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/coretone.h @@ -0,0 +1,132 @@ +/****************************************************************************** + * coretone.h + * Software wavetable synthesizer. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE +#define CORETONE +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +/* CORETONE_CHANNELS is the total number of channels available for rendering + * music and sound effects into. Increasing this number will only drastically + * increase render time when more channels are active, but memory usage will + * always go up. + * + * CORETONE_DEFAULT_VOLUME is how loud music will be played when CoreTone is + * first initialized. + * + * CORETONE_DISPATCH_DEPTH and CORETONE_REQUEST_DEPTH specify the depth of the + * dispatch and request queues for sound effects. Increasing these allows more + * sound effects to be queued up for playback or manipulation by the user in + * between driver ticks. + */ +#ifndef CORETONE_CHANNELS + #define CORETONE_CHANNELS 16 +#endif + +#ifndef CORETONE_DEFAULT_VOLUME + #define CORETONE_DEFAULT_VOLUME 127 +#endif + +#ifndef CORETONE_DISPATCH_DEPTH + #define CORETONE_DISPATCH_DEPTH 32 +#endif + +#ifndef CORETONE_REQUEST_DEPTH + #define CORETONE_REQUEST_DEPTH 32 +#endif + +#ifdef _WIN32 + #ifdef CORETONE_EXPORTS + #define CORETONE_API __declspec(dllexport) + #else + #define CORETONE_API __declspec(dllimport) + #endif +#else + #define CORETONE_API +#endif + + +/* CORETONE_RENDER_RATE defines the samplerate of the output audio while + * CORETONE_DECODE_RATE indicates the rate at which the buffer is rendered and + * all decoding operations (music, instruments, sfx) are performed. In short + * this is the base driver tick. + * + * For proper operation ensure CORETONE_RENDER_RATE is evenly divisible + * by CORETONE_DECODE_RATE. + */ +#ifndef CORETONE_RENDER_RATE + #define CORETONE_RENDER_RATE 48000 +#endif + +#ifndef CORETONE_DECODE_RATE + #define CORETONE_DECODE_RATE 240 +#endif + + +/* CORETONE_BUFFER_LEN is the buffer length in MONO SAMPLES, which are + * interleaved as LEFT, RIGHT, LEFT, RIGHT, etc in order to create the stereo + * output stream while CORETONE_BUFFER_SAMPLES is the stereo sample count. + * + * This should be an even number for what I hope are obvious reasons. + * + * CORETONE_BUFFER_CENTER is the center (silent) value to stamp the buffer + * with, this will usually be zero for signed output but can be adjusted up + * or down for unsigned platforms. + */ +#define CORETONE_BUFFER_SAMPLES (CORETONE_RENDER_RATE / CORETONE_DECODE_RATE) +#define CORETONE_BUFFER_LEN (CORETONE_BUFFER_SAMPLES * 2) +#define CORETONE_BUFFER_CENTER 0 + + +/* Functions of type ct_renderCall_t may be configured as a post-render + * callback through ct_setRenderCall(). These are called each time CoreTone + * completes a rendering update and are supplied with the raw render buffer + * for any additional mixing or post processing. + * + * A post-render callback will remain active until it returns zero, upon + * which it will be disabled. If the post-render callback returns zero + * on its first update, it is effetively one-shot. + * + * Note that uiLen is the count of STEREO samples in the buffer. + */ +typedef int32_t (*ct_renderCall_t)(void *pBuffer, + uint32_t uiFreq, uint32_t uiLen, + int32_t iAmPaused); + + +/****************************************************************************** + * Function Defines (User-Facing) + ******************************************************************************/ +CORETONE_API void ct_update(int16_t *pBuffer); + +CORETONE_API void ct_pause(void); +CORETONE_API void ct_resume(void); +CORETONE_API int32_t ct_isPaused(void); +CORETONE_API void ct_stopAll(void); +CORETONE_API void ct_setRenderCall(ct_renderCall_t pCall); + +CORETONE_API void ct_playMusic(uint8_t *pMusic); +CORETONE_API void ct_stopMusic(void); +CORETONE_API void ct_attenMusic(int8_t cVol); +CORETONE_API int32_t ct_checkMusic(void); +CORETONE_API int32_t ct_getMood(void); + +CORETONE_API void ct_playSFX(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right); +CORETONE_API void ct_stopSFX(int8_t cPriority); +CORETONE_API void ct_addSFX(uint8_t *pSFX, int8_t cPriority, int8_t cVol_Left, int8_t cVol_Right); +CORETONE_API void ct_dumpSFX(void); + +CORETONE_API int32_t ct_getMutex(void); +CORETONE_API int32_t ct_giveMutex(void); + +CORETONE_API int32_t ct_init(uint8_t *pSamplePak, uint8_t *pInstrPak); + +CORETONE_API void ct_getState(void **ppaChannels, void **ppaPatches, void **ppaTracks); +CORETONE_API void ct_getInfo(uint32_t *puiChannels, uint32_t *puiRenderFreq, + uint32_t *puiDecodeRate, + uint32_t *puiSamples, uint32_t *puiSampleLen); +#endif diff --git a/Contrib/BupBoop/CoreTone/music.c b/Contrib/BupBoop/CoreTone/music.c new file mode 100644 index 0000000..bf57859 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/music.c @@ -0,0 +1,555 @@ +/****************************************************************************** + * music.c + * Music script decoding and playback control. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#include +#include +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" +#include "channel.h" +#include "music.h" + +/****************************************************************************** + * !!!!---- ACTIVE INSTRUMENT PACK and the NOTE FREQUENCY TABLE ----!!!! + ******************************************************************************/ +extern CoreTrack_t aCoreTracks[]; +extern int32_t iMusMood; + +const char szCoreInstr_Magic[] = CORETONE_INSPAK_HEAD_MAGICWORD; +const char szCoreMusic_Magic[] = CORETONE_MUSPAK_HEAD_MAGICWORD; + +uint8_t *pCoreInstr_PackBase = NULL; +uint32_t *pCoreInstr_DirBase = NULL; +uint32_t uiCoreInstr_Count = 0; + +uint8_t *pCoreMusic_PackBase = NULL; +uint8_t *pCoreMusic_DirBase = NULL; + +/* The NOTE FREQUENCY TABLE is generated during the setup of an instrument + * package and contains the 16.16 frequencies (in Hz) assigned to each note byte. + * + * It should be noted (horrible pun not intended) that the values contained + * in this table are equivalent to the usual 128 frequencies used for General + * MIDI notes with A440 tuning. + */ +int16p16_t ausNoteFreqs[128]; + + +/****************************************************************************** + * !!!!---- INSTRUMENT MANAGEMENT ----!!!! + ******************************************************************************/ +/* int32_t ct_instr_setup(uint8_t *pInstrPak) + * Configure CoreTone's music decoder to use the supplied instrument package + * and populate the NOTE FREQUENCY TABLE. Returns a nonzero value if any + * errors were detected in the package integrity. + *----------------------------------------------------------------------------*/ +int32_t ct_instr_setup(uint8_t *pInstrPak) +{ + double dFr,dEx; + double dFr_int,dFr_frac; + + int16p16_t iFr; + uint32_t uiX,uiY; + + /** + * In order for an instrument pack to be considered valid it must reside + * at a 32-Bit aligned address and have a valid leader / magic word. + * + * Nothing elaborate, assuming good intentions with the data we're given. + */ + uiY = (uint32_t)pInstrPak; + if(0 != (uiY % sizeof(uint32_t))) + { + return -1; + } + + for(uiX = 0; uiX < CORETONE_INSPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreInstr_Magic[uiX] != pInstrPak[uiX]) + { + return -1; + } + } + + memcpy(&uiCoreInstr_Count, (pInstrPak + CORETONE_INSPAK_HEAD_COUNT), sizeof(uint32_t)); + pCoreInstr_PackBase = pInstrPak; + pCoreInstr_DirBase = (uint32_t*)(pInstrPak + CORETONE_INSPAK_DIR_BASE); + + /** + * Generate the NOTE FREQUENCY TABLE before we leave, using the + * same A440-tuned and 128 entry setup as General MIDI. + * + * F(n) = (2^((n - 69) / 12)) * 440Hz + */ + for(uiX = 0; uiX < 128; uiX++) + { + dEx = uiX; + dFr = pow(2.0, ((dEx - 69.0) / 12.0)) * 440.0; + + dFr_int = floor(dFr); + dFr_frac = dFr - dFr_int; + dFr_frac *= 65536.0; + iFr.sPair.sHi = (int16_t)dFr_int; + iFr.usPair.usLo = (uint16_t)dFr_frac; + + ausNoteFreqs[uiX].iWhole = iFr.iWhole; + } + return 0; +} + + + + +/****************************************************************************** + * !!!!---- MUSIC SCRIPT COMMANDS ----!!!! + ******************************************************************************/ +/* CORETONE_MUSIC_SET_PRIORITY(cPriority) + *----------------------------------------------------------------------------*/ +void ct_musicCom_setPriority(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel; + + pTrack->iPriority = pTrack->pScript[pTrack->uiOffset++]; + if(0 == pTrack->iPriority) + { + pTrack->ucNote = CORETONE_MUSIC_NOTE_INVALID; + + if(pPatch->iInstrument) + { + pChannel = pTrack->pChannel; + pChannel->eMode = eCHANNEL_MODE_OFF; + + pPatch->iPriority = 0; + pPatch->iInstrument = 0; + } + } +} + +/* CORETONE_MUSIC_SET_PANNING(cPanLeft, cPanRight) + *----------------------------------------------------------------------------*/ +void ct_musicCom_setPanning(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + pTrack->cPanLeft = pTrack->pScript[pTrack->uiOffset++]; + pTrack->cPanRight = pTrack->pScript[pTrack->uiOffset++]; + ct_music_recalcVol(pTrack); +} + +/* CORETONE_MUSIC_SET_INSTRUMENT(ucInstrument) + *----------------------------------------------------------------------------*/ +void ct_musicCom_setInstrument(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + pTrack->uiInstSel = pTrack->pScript[pTrack->uiOffset++]; +} + +/* CORETONE_MUSIC_NOTE_ON(ucNote) + *----------------------------------------------------------------------------*/ +void ct_musicCom_noteOn(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + CoreChannel_t *pChannel; + + uint8_t ucNote; + uint32_t uiOffset,uiSample,uiLen; + + /** + * We'll only dispatch an instrument on a given channel if it is + * either already occupied by an instrument (which indicates we're + * in control of it) or is occupied by a sound effect of a LOWER + * PRIORITY than the current music track. + */ + if((pPatch->iPriority < pTrack->iPriority) || pPatch->iInstrument) + { + ucNote = pTrack->pScript[pTrack->uiOffset++]; + pTrack->ucNote = ucNote; + + pChannel = pTrack->pChannel; + pPatch->iPriority = pTrack->iPriority; + pPatch->iInstrument = -1; + + /** + * Slightly unintuitive, but the value we fetch from the NOTE + * FREQUENCY TABLE cannot just be jammed into a patch's freqBase. + * + * All the patch and channel internals operate on phase adjustment + * rather than frequencies in Hz, so we need to use the ct_sample + * library to convert this frequency to an appropriate phase + * adjustment value. + */ + uiOffset = pTrack->uiInstSel * CORETONE_INSPAK_ENTRY_LEN; + + uiSample = pCoreInstr_DirBase[uiOffset + CORETONE_INSPAK_ENTRY_SAMPLE]; + pPatch->pScript = pCoreInstr_PackBase + + pCoreInstr_DirBase[uiOffset + CORETONE_INSPAK_ENTRY_SCRIPT]; + pPatch->uiNoteOff = + pCoreInstr_DirBase[uiOffset + CORETONE_INSPAK_ENTRY_NOTE_OFF]; + + ct_sample_get(uiSample, &(pChannel->pSample), &uiLen); + pPatch->freqBase = ct_sample_calcPhase(uiSample, ausNoteFreqs[ucNote]); + pChannel->usSampleLen = uiLen; + ct_patch_keyOn(pPatch); + + pChannel->cVolLeft = pTrack->cVolLeft; + pChannel->cVolRight = pTrack->cVolRight; + } +} + +/* CORETONE_MUSIC_NOTE_OFF() + *----------------------------------------------------------------------------*/ +void ct_musicCom_noteOff(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + if(pPatch->iInstrument) + { + pTrack->ucNote = CORETONE_MUSIC_NOTE_INVALID; + + ct_patch_keyOff(pPatch); + } +} + +/* CORETONE_MUSIC_PITCH(sPitch_Lo, sPitch_Hi, sAdj_Lo, sAdj_Hi) + *----------------------------------------------------------------------------*/ +void ct_musicCom_pitch(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int8p8_t fetchFreq; + + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->freqPitch.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->freqPitch.usPair.usHi = fetchFreq.usWhole; + + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->pitchAdj.usPair.usLo = fetchFreq.usWhole; + fetchFreq.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchFreq.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + pPatch->pitchAdj.usPair.usHi = fetchFreq.usWhole; +} + +/* CORETONE_MUSIC_LOOP_START(cCount) + *----------------------------------------------------------------------------*/ +void ct_musicCom_loopStart(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int8_t cCount; + + if(pTrack->uiStackPos < CORETONE_MUSIC_STACKDEPTH) + { + cCount = pTrack->pScript[pTrack->uiOffset++]; + pTrack->aiLoopStack[pTrack->uiStackPos] = cCount; + pTrack->auiAddrStack[pTrack->uiStackPos] = pTrack->uiOffset; + + pTrack->uiStackPos++; + } +} + +/* CORETONE_MUSIC_LOOP_END() + *----------------------------------------------------------------------------*/ +void ct_musicCom_loopEnd(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + uint32_t uiX; + + /** + * Behaves identically to the loops available to patches : counts of + * zero or one will never loop, counts of two or more will loop until + * they have been decremented down to zero or one, negative counts + * will loop infinitely. + */ + if(pTrack->uiStackPos > 0) + { + uiX = pTrack->uiStackPos - 1; + + if((pTrack->aiLoopStack[uiX] >= 0) && (pTrack->aiLoopStack[uiX] < 2)) + { + pTrack->uiStackPos = uiX; + } + else if(pTrack->aiLoopStack[uiX] < 0) + { + pTrack->uiOffset = pTrack->auiAddrStack[uiX]; + } + else + { + pTrack->uiOffset = pTrack->auiAddrStack[uiX]; + pTrack->aiLoopStack[uiX]--; + } + } +} + +/* CORETONE_MUSIC_CALL(iOffset) + *----------------------------------------------------------------------------*/ +void ct_musicCom_call(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int16p16_t fullOff; + int8p8_t fetchOff; + + if(pTrack->uiStackPos < CORETONE_MUSIC_STACKDEPTH) + { + pTrack->aiLoopStack[pTrack->uiStackPos] = + CORETONE_MUSIC_CALL_TAG; + pTrack->auiAddrStack[pTrack->uiStackPos] = + pTrack->uiOffset + sizeof(uint32_t); + + /** + * While it is expensive storage-wise, all CALLs use 32-Bit + * signed offsets for calculating their destination. These + * are relative to the byte immediately after the CALL + * command itself. + */ + fetchOff.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchOff.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullOff.usPair.usLo = fetchOff.usWhole; + fetchOff.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchOff.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullOff.usPair.usHi = fetchOff.usWhole; + + pTrack->uiOffset += fullOff.iWhole; + pTrack->uiStackPos++; + } +} + +/* CORETONE_MUSIC_RETURN() + *----------------------------------------------------------------------------*/ +void ct_musicCom_return(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + uint32_t uiX; + + if(pTrack->uiStackPos > 0) + { + uiX = pTrack->uiStackPos - 1; + + if(CORETONE_MUSIC_CALL_TAG == pTrack->aiLoopStack[uiX]) + { + pTrack->uiOffset = pTrack->auiAddrStack[uiX]; + pTrack->uiStackPos = uiX; + } + } +} + +/* CORETONE_MUSIC_BREAK() + *----------------------------------------------------------------------------*/ +void ct_musicCom_break(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + uint32_t uiX,uiY; + + for(uiX = 0; uiX < CORETONE_CHANNELS; uiX++) + { + for(uiY = 0; uiY < aCoreTracks[uiX].uiStackPos; uiY++) + { + if(CORETONE_MUSIC_CALL_TAG == aCoreTracks[uiX].aiLoopStack[uiY]) + { + aCoreTracks[uiX].uiOffset = aCoreTracks[uiX].auiAddrStack[uiY]; + aCoreTracks[uiX].uiStackPos = uiY; + + aCoreTracks[uiX].uiDel = 0; + } + } + } +} + +/* CORETONE_MUSIC_NOP() + *----------------------------------------------------------------------------*/ +void ct_musicCom_nop(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + +} + +/* CORETONE_MUSIC_SET_MOOD() + *----------------------------------------------------------------------------*/ +void ct_musicCom_setMood(CoreTrack_t *pTrack, CorePatch_t *pPatch) +{ + int16p16_t fullMood; + int8p8_t fetchMood; + + fetchMood.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchMood.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullMood.usPair.usLo = fetchMood.usWhole; + fetchMood.ucPair.ucLo = pTrack->pScript[pTrack->uiOffset++]; + fetchMood.ucPair.ucHi = pTrack->pScript[pTrack->uiOffset++]; + fullMood.usPair.usHi = fetchMood.usWhole; + + iMusMood = fullMood.iWhole; +} + + + + +/****************************************************************************** + * !!!!---- MUSIC COMMAND TABLE ----!!!! + *----------------------------------------------------------------------------*/ +typedef void (*ct_musicCom_t)(CoreTrack_t *pTrack, CorePatch_t *pPatch); +ct_musicCom_t aMusicComs[] = +{ + ct_musicCom_setPriority, ct_musicCom_setPanning, + ct_musicCom_setInstrument, + ct_musicCom_noteOn, ct_musicCom_noteOff, + ct_musicCom_pitch, + + ct_musicCom_loopStart, ct_musicCom_loopEnd, + ct_musicCom_call, ct_musicCom_return, ct_musicCom_break, + ct_musicCom_nop, ct_musicCom_setMood +}; + +/****************************************************************************** + * !!!!---- MUSIC SCRIPT DECODING ----!!!! + ******************************************************************************/ +/* void ct_music_recalcVol(CoreTrack_t *pTrack) + * Should be called any time a change is made to a track's global volume + * (cVolMain) or either of the panning values (cPanLeft/Right). + * Recalculates the final volume scalars for the track and propagates them + * downward to any active instrument under the particular track's control. + *----------------------------------------------------------------------------*/ +void ct_music_recalcVol(CoreTrack_t *pTrack) +{ + CorePatch_t *pPatch = pTrack->pPatch; + CoreChannel_t *pChannel = pTrack->pChannel; + int8p8_t scale_L,scale_R; + + scale_L.sWhole = pTrack->cVolMain * pTrack->cPanLeft; + scale_L.sWhole = scale_L.sWhole << 1; + pTrack->cVolLeft = scale_L.cPair.cHi; + scale_R.sWhole = pTrack->cVolMain * pTrack->cPanRight; + scale_R.sWhole = scale_R.sWhole << 1; + pTrack->cVolRight = scale_R.cPair.cHi; + + pTrack->iRecalcVol = 0; + if(pPatch->iInstrument) + { + pChannel->cVolLeft = pTrack->cVolLeft; + pChannel->cVolRight = pTrack->cVolRight; + } +} + +/* void ct_music_decode(CoreTrack_t *pTrack) + * Advance through the music track script if the particular track is active + * (nonzero priority) and has no pending delays. + * Mostly identical to the patch script decoder aside from the additional + * requirement of checking for volume recalculation requests in case the user + * has decided to attenuate or amplify the currently playing music. + *----------------------------------------------------------------------------*/ +void ct_music_decode(CoreTrack_t *pTrack) +{ + CorePatch_t *pPatch = pTrack->pPatch; + CoreChannel_t *pChannel = pTrack->pChannel; + uint8_t *pScript; + uint8_t ucByte; + + uint32_t uiX,uiY; + + if(pTrack->iRecalcVol) + { + ct_music_recalcVol(pTrack); + } + + pScript = pTrack->pScript; + while((0 != pTrack->iPriority) && (0 == pTrack->uiDel)) + { + ucByte = pScript[pTrack->uiOffset]; + + if(ucByte & CORETONE_MUSIC_WAIT) + { + for((uiX = 0, uiY = 0); + ((ucByte & CORETONE_MUSIC_WAIT) && (uiY < sizeof(uint32_t))); + (uiX += 7, uiY++)) + { + pTrack->uiDel |= ((ucByte & CORETONE_MUSIC_WAIT_MASK) << uiX); + + pTrack->uiOffset++; + ucByte = pScript[pTrack->uiOffset]; + } + } + else + { + pTrack->uiOffset++; + if(ucByte < CORETONE_MUSIC_FOOTER) + { + (*(aMusicComs[ucByte]))(pTrack, pPatch); + } + else + { + pTrack->iPriority = 0; + if(pPatch->iInstrument) + { + pChannel->eMode = eCHANNEL_MODE_OFF; + + pPatch->iPriority = 0; + pPatch->iInstrument = 0; + } + } + } + } + + if(0 != pTrack->iPriority) + { + pTrack->uiDel--; + } +} + +/* int32_t ct_music_setup(uint8_t *pMusic) + * Begin playback of the supplied music track package, expects and previously + * decoding track to have been halted. Will return a nonzero value and cancel + * the playback dispatch if any problems are encountered during initialization. + *----------------------------------------------------------------------------*/ +int32_t ct_music_setup(uint8_t *pMusic) +{ + CorePatch_t *pPatch; + uint8_t *pCurEntry; + int8_t cPri; + + uint32_t uiX,uiY,uiZ; + + /** + * We'll assume the rest of a music package is fine as long as it + * begins with our magic word, and attempt dispatch if everything + * looks good. + * + * One point of notice is that we'll dispatch however many tracks + * are contained in the piece of music itself. If this value is greater + * than the number of audio channels assigned to CoreTone, we'll just + * drop the extra tracks. + */ + for(uiX = 0; uiX < CORETONE_MUSPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreMusic_Magic[uiX] != pMusic[uiX]) + { + return -1; + } + } + + pCoreMusic_PackBase = pMusic; + pCoreMusic_DirBase = pMusic + CORETONE_MUSPAK_DIR_BASE; + memcpy(&uiZ, (pMusic + CORETONE_MUSPAK_HEAD_TRACKS), sizeof(uint32_t)); + + pCurEntry = pCoreMusic_DirBase; + for(uiX = 0; ((uiX < uiZ) && (uiX < CORETONE_CHANNELS)); uiX++) + { + memcpy(&cPri, (pCurEntry + CORETONE_MUSPAK_ENTRY_PRIORITY), sizeof(int8_t)); + memcpy(&uiY, (pCurEntry + CORETONE_MUSPAK_ENTRY_OFFSET), sizeof(uint32_t)); + + aCoreTracks[uiX].iPriority = cPri; + aCoreTracks[uiX].iRecalcVol = -1; + + aCoreTracks[uiX].uiInstSel = 0; + aCoreTracks[uiX].ucNote = CORETONE_MUSIC_NOTE_INVALID; + + aCoreTracks[uiX].pScript = pCoreMusic_PackBase + uiY; + aCoreTracks[uiX].uiOffset = 0; + aCoreTracks[uiX].uiDel = 0; + + aCoreTracks[uiX].cPanLeft = CORETONE_DEFAULT_VOLUME; + aCoreTracks[uiX].cPanRight = CORETONE_DEFAULT_VOLUME; + + aCoreTracks[uiX].uiStackPos = 0; + + pPatch = aCoreTracks[uiX].pPatch; + pPatch->freqPitch.iWhole = 0; + pPatch->pitchAdj.iWhole = 0; + pCurEntry += CORETONE_MUSPAK_ENTRY_LEN; + } + + return 0; +} diff --git a/Contrib/BupBoop/CoreTone/music.h b/Contrib/BupBoop/CoreTone/music.h new file mode 100644 index 0000000..e073a6d --- /dev/null +++ b/Contrib/BupBoop/CoreTone/music.h @@ -0,0 +1,148 @@ +/****************************************************************************** + * music.h + * Music script decoding and playback control. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE_MUSIC +#define CORETONE_MUSIC +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +/* This indicates the stack depth used to track music script loops, + * the default of four should be adequate for most users. + */ +#ifndef CORETONE_MUSIC_STACKDEPTH + #define CORETONE_MUSIC_STACKDEPTH 4 +#endif + +/****************************************************************************** + * Instrument Package Format + ******************************************************************************/ +/* Instrument Packages are composed of three major regions: + * + * The HEADER, which contains an identifier string and the number of + * instruments included within the package. + * + * The DIRECTORY, which contains the SAMPLE ID, SCRIPT OFFSET, and NOTE OFF + * OFFSET (relative to the script start) of each instrument in the package. + * All of these values are 32-Bits long, yielding 12-Bytes per entry. + * + * The DATA AREA, which contains the patch scripts for all instruments + * in the package. + * + * It is expected that the instrument package starts at a 32-Bit aligned address + * and will remain at said address for the duration of its use by the SoftSynth. + */ +#define CORETONE_INSPAK_HEAD_MAGICWORD "CINS" +#define CORETONE_INSPAK_HEAD_MAGICLEN 4 +#define CORETONE_INSPAK_HEAD_COUNT 4 +#define CORETONE_INSPAK_HEAD_SIZE 8 + +#define CORETONE_INSPAK_DIR_BASE 8 + +/* Note that the entry offsets below are in uint32_t counts intead of bytes */ +#define CORETONE_INSPAK_ENTRY_SAMPLE 0 +#define CORETONE_INSPAK_ENTRY_SCRIPT 1 +#define CORETONE_INSPAK_ENTRY_NOTE_OFF 2 +#define CORETONE_INSPAK_ENTRY_LEN 3 + +/****************************************************************************** + * Music Binary Format + ******************************************************************************/ +/* Music Binaries also use the traditional three-region structure: + * + * The HEADER, which contains an identifier string and the number of + * tracks in the piece of music. + * + * The DIRECTORY, which contains the INITIAL PRIORITY (8-Bit) and + * TRACK SCRIPT OFFSET (32-Bit), yielding 5-Bytes per entry. + * + * The DATA AREA, which contains the track scripts. + * + * Unlike Sample and Instrument packages, no alignment restrictions are + * imposed on Music Binaries. + */ +#define CORETONE_MUSPAK_HEAD_MAGICWORD "CMUS" +#define CORETONE_MUSPAK_HEAD_MAGICLEN 4 +#define CORETONE_MUSPAK_HEAD_TRACKS 4 +#define CORETONE_MUSPAK_HEAD_SIZE 8 + +#define CORETONE_MUSPAK_DIR_BASE 8 + +#define CORETONE_MUSPAK_ENTRY_PRIORITY 0 +#define CORETONE_MUSPAK_ENTRY_OFFSET 1 +#define CORETONE_MUSPAK_ENTRY_LEN 5 + +/****************************************************************************** + * Track Script Commands and Decode Descriptors + ******************************************************************************/ +#define CORETONE_MUSIC_SET_PRIORITY 0 +#define CORETONE_MUSIC_SET_PANNING 1 +#define CORETONE_MUSIC_SET_INSTRUMENT 2 +#define CORETONE_MUSIC_NOTE_ON 3 +#define CORETONE_MUSIC_NOTE_OFF 4 +#define CORETONE_MUSIC_PITCH 5 +#define CORETONE_MUSIC_LOOP_START 6 +#define CORETONE_MUSIC_LOOP_END 7 +#define CORETONE_MUSIC_CALL 8 +#define CORETONE_MUSIC_RETURN 9 +#define CORETONE_MUSIC_BREAK 10 +#define CORETONE_MUSIC_NOP 11 +#define CORETONE_MUSIC_SET_MOOD 12 + +#define CORETONE_MUSIC_FOOTER 13 + +/* Wait commands work identically for music and patch scripts, varlength delay + * in driver ticks where the MSB of each byte is used to indicate the value is + * either the start or continuation of a delay. + * + * See "channel.h" for a full description. + */ +#define CORETONE_MUSIC_WAIT 0x80 +#define CORETONE_MUSIC_WAIT_MASK 0x7F + +/* The loop stack isn't just used for LOOPs, CALLs and RETURNs will also push + * and pop their decode addresses to it. However, they use a special count + * value in order to allow backwards traversal of the stack for BREAKs. + */ +#define CORETONE_MUSIC_CALL_TAG -128 + +/* The last dispatched note will be cached in each track's ucNote, but when + * nothing is playing this will be tagged with the value below. + */ +#define CORETONE_MUSIC_NOTE_INVALID 0x80 + +typedef struct CoreTrack_s +{ + CoreChannel_t *pChannel; + CorePatch_t *pPatch; + + int32_t iPriority,iRecalcVol; + + uint32_t uiInstSel; + uint8_t ucNote; + + uint8_t *pScript; + uint32_t uiOffset; + uint32_t uiDel; + + int8_t cVolMain; + int8_t cPanLeft,cPanRight; + int8_t cVolLeft,cVolRight; + + uint32_t uiStackPos; + int32_t aiLoopStack[CORETONE_PATCH_STACKDEPTH]; + uint32_t auiAddrStack[CORETONE_PATCH_STACKDEPTH]; +} CoreTrack_t; + +/****************************************************************************** + * Function Defines + ******************************************************************************/ +int32_t ct_instr_setup(uint8_t *pInstrPak); + +void ct_music_recalcVol(CoreTrack_t *pTrack); +void ct_music_decode(CoreTrack_t *pTrack); +int32_t ct_music_setup(uint8_t *pMusic); +#endif diff --git a/Contrib/BupBoop/CoreTone/sample.c b/Contrib/BupBoop/CoreTone/sample.c new file mode 100644 index 0000000..6859466 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/sample.c @@ -0,0 +1,196 @@ +/****************************************************************************** + * sample.c + * Audio sample management. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#include +#include +#include + +#include "../types.h" +#include "coretone.h" + +#include "sample.h" + +/****************************************************************************** + * !!!!---- ACTIVE SAMPLE PACKAGE and the FREQUENCY RATIO TABLE ----!!!! + ******************************************************************************/ +/* Once a sample package has been verified and setup by this library, its + * base address, directory start address, and sample count will be cached + * for later use. However, we'll also be generating a 32.32 table to speed + * up calculations later on. This is the FREQUENCY RATIO TABLE and to fully + * understand its purpose we must first go over PHASE INCREMENT CALCULATIONs. + * + * Calculating playback phase increment (Pi) of a particular sample based + * upon a desired frequency requires the knowledge of four components: + * Rendering Frequency (Rf), Playback Frequency (Pf), Sample Frequency (Sf), + * and the Sampled Frequency (Bf). + * + * The naive formula is: Pi = (Sf / Rf) * (Pf / Bf), but the two divides + * aren't desirable. Noting that Sf, Rf, and Bf are fixed once the sample + * package is loaded, we can reformat the equation : + * + * Pi = Pf * Fr, where Fr = (Sf / (Rf * Bf)) which will be precalculated and + * stored in 32.32 precision which should give tolerable accuracy when + * multiplied by a 16.16 frequency to yield the final 16.16 phase increment. + */ +const char szCoreSample_Magic[] = CORETONE_SMPPAK_HEAD_MAGICWORD; +const int8_t acCoreSample_Dummy[] = {0,0,0,0}; + +uint8_t *pCoreSample_PackBase = NULL; +uint32_t *pCoreSample_DirBase = NULL; +uint32_t uiCoreSample_Count = 0; + +int32p32_t aCoreSample_Fr[CORETONE_SAMPLES_MAXENTRIES]; + + +/* void ct_sample_get(uint32_t uiSample, int8_t **ppData, uint32_t *puiLen) + * Fetch the base address and length of sample number uiSample, if the sample + * is outside the range of the current pack a dummy sample will be provided. + *----------------------------------------------------------------------------*/ +void ct_sample_get(uint32_t uiSample, int8_t **ppData, uint32_t *puiLen) +{ + uint32_t *pEntry; + + if(uiSample < uiCoreSample_Count) + { + pEntry = pCoreSample_DirBase + (CORETONE_SMPPAK_ENTRY_SIZE * uiSample); + *ppData = (int8_t*)(pCoreSample_PackBase + pEntry[CORETONE_SMPPAK_ENTRY_OFF]); + *puiLen = pEntry[CORETONE_SMPPAK_ENTRY_SLEN]; + } + else + { + *ppData = (int8_t*)acCoreSample_Dummy; + *puiLen = 0; + } +} + +/* int16p16_t ct_sample_calcPhase(uint32_t uiSample, int16p16_t freq) + * Calculate the phase adjustment value for the given sample number based + * upon a desired playback frequency in Hz. + *----------------------------------------------------------------------------*/ +int16p16_t ct_sample_calcPhase(uint32_t uiSample, int16p16_t freq) +{ + int32p32_t phaseAdj,freqBase,freqRatio; + int16p16_t phaseTrim; + + /** + * Mixed precision multiply to yield a 16.16 phase increment from a + * 32.32 Frequency Ratio (R) * 16.16 Frequency (F) in the form of a + * 64 x 32 = 64-Bit multiply as : + * + * RRRR RRRR.RRRR RRRR The top 32-Bits of our 64-Bit result will + * x FFFF.FFFF be the 16.16 phase increment (P), and the + * ------------------- lower 32-Bits can be discarded. + * PPPP.PPPP DDDD DDDD + * + * The frequency ratio has been predoubled to remove the requirement + * to shift the result leftward. + */ + if(uiSample < uiCoreSample_Count) + { + freqBase.lWhole = freq.iWhole; + freqRatio.lWhole = aCoreSample_Fr[uiSample].lWhole; + phaseAdj.ulWhole = freqBase.ulWhole * freqRatio.ulWhole; + + phaseTrim.uiWhole = phaseAdj.uiPair.uiHi; + } + else + { + phaseTrim.uiWhole = 0; + } + + return phaseTrim; +} + + + + +/* int32_t ct_sample_setup(uint8_t *pSamplePak) + * Verify the integrity of the supplied sample package, setting it up as our + * active sample pack if it checks out. The frequency ratio table is also + * calculated during this time. + * Will return a nonzero value if any issues are encountered during the + * verification or precalculation phase. + *----------------------------------------------------------------------------*/ +int32_t ct_sample_setup(uint8_t *pSamplePak) +{ + double dFr,dSf,dRf,dBf; + double dFr_int,dFr_frac; + int16p16_t iSf,iBf; + int32p32_t lFr; + + uint32_t *pEntry; + uint32_t uiX,uiY; + + /** + * We'll consider a sample package valid for use as long as its magic + * word matches what we expect, the sample count is below our limit, + * and it's living at a 32-Bit aligned address. + */ + uiY = (uint32_t)pSamplePak; + if(0 != (uiY % sizeof(uint32_t))) + { + return -1; + } + + for(uiX = 0; uiX < CORETONE_SMPPAK_HEAD_MAGICLEN; uiX++) + { + if(szCoreSample_Magic[uiX] != pSamplePak[uiX]) + { + return -1; + } + } + + memcpy(&uiY, (pSamplePak + CORETONE_SMPPAK_HEAD_COUNT), sizeof(uint32_t)); + if(uiY > CORETONE_SAMPLES_MAXENTRIES) + { + return -1; + } + + pCoreSample_PackBase = pSamplePak; + pCoreSample_DirBase = (uint32_t*)(pSamplePak + CORETONE_SMPPAK_DIR_BASE); + uiCoreSample_Count = uiY; + + /** + * We can now precalculate the frequency ratio table for each sample + * within the package using Fr = (Sf / (Rf * Bf)). Each of these results + * is then doubled prior to insertion in the table in order to remove + * a post-multiply shift requirement during phase increment calculations. + * + * I'm using floats for the basic math here since this isn't time + * critical and (as of writing anyway) most microcontrollers in the + * $5 and below bin actually include a somewhat functional FPU. + * + * For the phase increment calculations later on, we'll have to keep + * things purely in the integer realm. + */ + pEntry = pCoreSample_DirBase; + dRf = CORETONE_RENDER_RATE; + for(uiX = 0; uiX < uiY; uiX++) + { + iSf.uiWhole = pEntry[CORETONE_SMPPAK_ENTRY_SFREQ]; + iBf.uiWhole = pEntry[CORETONE_SMPPAK_ENTRY_BFREQ]; + + dFr = iSf.usPair.usLo; + dSf = iSf.usPair.usHi; + dSf = (dFr / 65536.0) + dSf; + dFr = iBf.usPair.usLo; + dBf = iBf.usPair.usHi; + dBf = (dFr / 65536.0) + dBf; + + dFr = (dSf / (dRf * dBf)) * 2.0; + + dFr_int = floor(dFr); + dFr_frac = dFr - dFr_int; + dFr_frac *= 4294967296.0; + lFr.iPair.iHi = (int32_t)dFr_int; + lFr.uiPair.uiLo = (uint32_t)dFr_frac; + + aCoreSample_Fr[uiX].lWhole = lFr.lWhole; + pEntry += CORETONE_SMPPAK_ENTRY_SIZE; + } + return 0; +} diff --git a/Contrib/BupBoop/CoreTone/sample.h b/Contrib/BupBoop/CoreTone/sample.h new file mode 100644 index 0000000..3dcbec7 --- /dev/null +++ b/Contrib/BupBoop/CoreTone/sample.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * sample.h + * Audio sample management. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in coretone.c + ******************************************************************************/ +#ifndef CORETONE_SAMPLE +#define CORETONE_SAMPLE + +/****************************************************************************** + * Operating Parameters + ******************************************************************************/ +#ifndef CORETONE_SAMPLES_MAXENTRIES + #define CORETONE_SAMPLES_MAXENTRIES 256 +#endif + +#ifndef CORETONE_SAMPLES_MAXLENGTH + #define CORETONE_SAMPLES_MAXLENGTH 32768 +#endif + +/****************************************************************************** + * Sample Package Format + ******************************************************************************/ +/* Sample Packages are composed of three major regions: + * + * The HEADER, which contains an identifier string and the number of samples + * included within the package. + * + * The DIRECTORY, which contains the DATA OFFSET, LENGTH, SAMPLE FREQUENCY (Sf), + * and CONTENT FREQUENCY (Bf) of each sample in the package. Each of these + * values is 32-Bits yielding 16-Bytes per entry. + * + * The DATA AREA, which contains the 8-Bit signed PCM data for all of the + * samples in the package. + * + * It is expected that the sample package starts at a 32-Bit aligned address + * and will remain at said address for the duration of its use by the SoftSynth. + */ +#define CORETONE_SMPPAK_HEAD_MAGICWORD "CSMP" +#define CORETONE_SMPPAK_HEAD_MAGICLEN 4 +#define CORETONE_SMPPAK_HEAD_COUNT 4 +#define CORETONE_SMPPAK_HEAD_SIZE 8 + +#define CORETONE_SMPPAK_DIR_BASE 8 + +/* Note that the entry offsets below are in uint32_t counts intead of bytes */ +#define CORETONE_SMPPAK_ENTRY_OFF 0 +#define CORETONE_SMPPAK_ENTRY_SLEN 1 +#define CORETONE_SMPPAK_ENTRY_SFREQ 2 +#define CORETONE_SMPPAK_ENTRY_BFREQ 3 +#define CORETONE_SMPPAK_ENTRY_SIZE 4 + +/****************************************************************************** + * Function Defines + ******************************************************************************/ +void ct_sample_get(uint32_t uiSample, int8_t **ppData, uint32_t *puiLen); +int16p16_t ct_sample_calcPhase(uint32_t uiSample, int16p16_t freq); + +int32_t ct_sample_setup(uint8_t *pSamplePak); +#endif diff --git a/Contrib/BupBoop/License.txt b/Contrib/BupBoop/License.txt new file mode 100644 index 0000000..eb5033e --- /dev/null +++ b/Contrib/BupBoop/License.txt @@ -0,0 +1,18 @@ +Copyright (C) 2015 - 2016 Osman Celimli + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/Contrib/BupBoop/types.h b/Contrib/BupBoop/types.h new file mode 100644 index 0000000..25806f7 --- /dev/null +++ b/Contrib/BupBoop/types.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * types.h + * The usual gang of pairings for the 8.8, 16.16, and 32.32 fixed precision + * math we love oh-so-very-much on platforms that aren't bouyant. + *----------------------------------------------------------------------------- + * Copyright (C) 2015 - 2016 Osman Celimli + * For conditions of distribution and use, see copyright notice in bupboop.h + ******************************************************************************/ +#ifndef CORE_TYPES +#define CORE_TYPES + +typedef struct int8p_s +{ + int8_t cLo; + int8_t cHi; +} int8p_t; +typedef struct uint8p_s +{ + uint8_t ucLo; + uint8_t ucHi; +} uint8p_t; + +typedef struct int16p_s +{ + int16_t sLo; + int16_t sHi; +} int16p_t; +typedef struct uint16p_s +{ + uint16_t usLo; + uint16_t usHi; +} uint16p_t; + +typedef struct int32p_s +{ + int32_t iLo; + int32_t iHi; +} int32p_t; +typedef struct uint32p_s +{ + uint32_t uiLo; + uint32_t uiHi; +} uint32p_t; + + +typedef union int8p8_u +{ + int16_t sWhole; + uint16_t usWhole; + int8p_t cPair; + uint8p_t ucPair; +} int8p8_t; + +typedef union int16p16_u +{ + int32_t iWhole; + uint32_t uiWhole; + int16p_t sPair; + uint16p_t usPair; +} int16p16_t; + +typedef union int32p32_u +{ + int64_t lWhole; + uint64_t ulWhole; + int32p_t iPair; + uint32p_t uiPair; +} int32p32_t; + +#endif diff --git a/ProSystem.dsp b/ProSystem.dsp old mode 100644 new mode 100755 index 8521191..e6c8c4d --- a/ProSystem.dsp +++ b/ProSystem.dsp @@ -109,6 +109,14 @@ SOURCE=.\Core\Bios.h # End Source File # Begin Source File +SOURCE=.\Core\BupChip.cpp +# End Source File +# Begin Source File + +SOURCE=.\Core\BupChip.h +# End Source File +# Begin Source File + SOURCE=.\Core\Cartridge.cpp # End Source File # Begin Source File @@ -341,6 +349,10 @@ SOURCE=.\Lib\Ioapi.h # End Source File # Begin Source File +SOURCE=.\Lib\stdint.h +# End Source File +# Begin Source File + SOURCE=.\Lib\Unzip.h # End Source File # Begin Source File diff --git a/ProSystem.dsw b/ProSystem.dsw old mode 100644 new mode 100755 index b34b337..6f24c21 --- a/ProSystem.dsw +++ b/ProSystem.dsw @@ -3,6 +3,18 @@ Microsoft Developer Studio Workspace File, Format Version 6.00 ############################################################################### +Project: "BupBoop"=.\Contrib\BupBoop\BupBoop.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + Project: "ProSystem"=.\ProSystem.dsp - Package Owner=<4> Package=<5> @@ -11,6 +23,9 @@ Package=<5> Package=<4> {{{ + Begin Project Dependency + Project_Dep_Name BupBoop + End Project Dependency }}} ############################################################################### diff --git a/ProSystem.opt b/ProSystem.opt old mode 100644 new mode 100755 index e33ae344473d218cf0740468677711e79cd229ea..900dd83548cd91283b71e0d94a620c26ca82e599 GIT binary patch literal 58880 zcmeHQeQX@Zb)VxM$rB|}v?Q966DJ$m)<P8mQIG@>8n-s&vO39W;-qQABt~Gnagrh~ z>LyOq-`m}}+dJ*?L!m6Ym^r+gw>uwi=FP{>zI|^dfAOZ7C;tAHAFwjFhlSa1t}J0S zzWX8I1deN92)}ZLaPAY1Tf)7IIPhzfRYJRk0TDnofZDtk5Ctp&)B%MPd?*;S$4gmTA1Av2oLBJ4z;vIs27%&1D1>6U?AMjy70`LIf z_W(x#6kmp)1So(MKm{BFi~+_08sH;<;{Y9y24nyRAPe|?0OfP7ZV)*c%w`g5o`G+& zN#rz%Cy$#cl1D#;_9K2Q$(n{WY1wMJ;r?BT0n-?AE?QGoHiY2XWG9a*|OAhQlC7GSS~lhtkyh( zD}vu3&GxI(Eg3P)<5pHy)Vnc7SEDah;~CvxQSFHvoQKZ>=R5>j=r7*@{v>jxKFnL^ zi@2uv;i+h!n#oB?*}MngVPqUaLS|2wIm(zOc)#P#Op`zIb26uJYI=%`%)z8gN;Xq% zzRW{`nbQ-4JIa_cJ%SFk13qOAn4<*U1fh7*du^R#QFk! zDmKrVazV+QTwce8o6FMf95tq5s%nN>lky@Kgl&38(zEUZ0DAUNUIgUAQ_CIdQlFO5 zI%SjBk4h2Bjeb z=$ag5&HL~#fKym_swQUWhG{kVvZ-~lIgC3ekG55j&9k`ohX4x0v@@(u*wV0J z=uMzQq^LmA+^3M5Qf@#_&%-C2fLwSg(v{L|&hjcGxDv>}VuI?pn#_%Dr^JA@NWo4} zUwatvD&Wh2QW7)x zm?@|Ii9>TFt{$f8+Ml9ojucT&j@OFH7@i|hC_A>=yom}8`rV{Au-v+QYtkTl-YjS z8V^X)YmAMlWOt+> zlgS1ayHD1FSVgO?0J4~-GO2+3!&=(y2@ll8OmBE7U(v zbX6NjtFmF+miCR(E}n-J+q(JgExV-kihX1Zqq8q(s}t)Hu4ORdEn50#Y$=7+95{1a z#)y;XN}a%DVoO+g=hAAbwJzZGCXn{l?G<$oN_v6X-cb7XUXX-Mxs*Ji>vYJvmH<~WJ?xSS{SiHl^H|;#s!|ZQaC=_M(3m4ev6M44dFZ1lp&*vdS`#Y>2 zqHHwW#x@*%(TO|r!#w-vZ{;2Q@t5=LB@dr?YMQ<5;WNOa-*)3r#MM4r^5J*+@Etzf z$=vsM6C{sRK5|FBcrzFCO!uXk@Zlf#;irB0=Y9B-KKyAPe%^=sir}EtkX}+bUiwL% zoqIjcQmq%+$G)Fu=K;>MLdlYt4)2QV9lkm7)_)?p%F9Ul&R)G;arr`WR(kIz7l~kv z<~h1|Ctm&XudhF+eEFr&*==m#%$pY-IV_LE{V-eZl!so`T`8#>QVnS>wOhkiF0qEU z@{XL9ry-wcliSE%bJ9!SyTsn@aPAYASrDB5#Y$$Wtt4K(ZtiT=su1y8Vi(!Dz86_L zA0OY%W8GiH);w~V<$;siy)SZ*T8rMXvlwrW-oXyOb&4G%xgI&EkNo(OgBN=RU6x)s z5%5RxIqvtVjqmW^08Z~Ao+Cua|G)%#10x67fsp?R`Ja&gVP+8WKOz4U@;~>vKo15X z|05~C#}9npE0q`u`JYdmNFPXr{I6VAD&&7x%TR^-kM0TiU$MhP@n+xGdv7o92>BnFK(ap}{}cLujxx}c(Ek(q ze?tEc@-CtOSN32K@;@Q}6Z(Heok{3U3jM#UI)u>w<90+s|8FtW4u$+r$p3`?A4xBS z{7>lr&843x5%@v=*KR|z;{c#Vh z=zCxd-^p^Y0Ph4%!?A0pDD!b^K%Xp=NO(yFm`|EJDN-5z0mlw}2-feGV|h+?_VaP? z1%0>X?*Dw!Y@OwUZ!BT_je+70usVs^I`?4YIfh1}Nfk~T|O(;)i* z7@5E-*Rfc4ELl7P^Sw8p{QW#DD;^W}-=0aKu>Zz%CG5Y2{WmwbE)^^a`)|^_7xv%9 zXFWaLh5a{nMiTbluuV@0`)|K$t+4+lyKrIuJtpkGh5fg?c?VYy753l4{<~;zF6_UB z{Wq5f3;oaH+r`n>T-j-3=b?el{{8{l0E0G+kesa|mh#Ee*#;83OF^_o!nEMnOKufmcPWbv%O4CX=C8CuHP>7cq zcBXSTmI+}41gCQQOe2@|LaU>zcJ+3340SCpsMEP8ycDH? zmD8*(q$%G}m!x}aivCr%vG;aL*FSm6*Q$ z6#bG~asErGUYR)m1v3plMn;_fBF=w-mZ3QRg&tva+7zCWQx59mXWLC9Yyl6K}Ba#%p$R~+gC6U?ef;LJ`dGK0Q%7rKh`5$y4U4a*G zI3fS@>uZYhpD{>C21pJTmD~cKA4W{a!iw{s#re;!LY_GP+0dOWUolw3`Oow$_vz+| z^Pl~vLyPmD)pUTUOUVBMm?GqVLjEV@e-+N!7Uw_r$2xk49@>-`R@2$U;Lt#aIR9Ck z|LnZ5iSwTq;rwZC?_2H*`(L+@(SMBTOvavv7CVexA64x5D6s|Z|L92mNcu;vm~&<9 zUJCN|Ka7KB5_><6;iqB`#)Z0--u{cUx8;244-=6!E~Pm3n^ZtQ4eA-}{Ajti=l#DL z@=mAX-rsx@9;HaP&nM>m;w+TDKi3dmcS)WKV_mYVj_Fpno*L4PB$p~u9s8`5; z)nsmrI3x5wxt^&{NmS^6Vpma3Pf2}RM(dP?{kO3H#wly`WehWQj_>=(eUi}^<@93z zN3s7SV61cGV#&HTmPxA_o6-?fGj_hALs3-Ak`8Nzj_uzK z7*V@{F!w&cyeA_K$YZM1WtxV`LvBULsvcce$7EgV$ym0TQ~W6(MR;R}Y08sQr;*E~ zEU9ZkQPq^1;yL^YV%*%V8?r6M)r?`Lkx_qEHRVhp%AX?2YA=d*LQAPqa#FfqH4UD} zpA{qYWYh^+u_Y+r<@7@BP9s8-n?q0L1S+3$Y50r!=ZI79#_2zyn#XkGB+vg-2)fA) z!gF6aF2%-Gn(DBU>5!8HV)jA!?8afOJo*b6>;EgJHma3jZktl?uY5`%F1It|?X{g1F&!O|Bgi z2St1k{=En}44g~PDC^%qbB!b>zW`2Q;i;OKp&O>vVgHTM;W}ae-Q4Pz zDzz2uoQ3_ju>a2BY;#xkN^2fno(xd*+^_1{fZIa7sk5*_v&qqG!IBX9RP(T2@PT;_S@jx#RltYGk)L z>3+U1a&R)##zw#CPak2Af2(-C8*!+HB%#c{aDhE~2Rr!IDR!{d%aH1M`b!s`t5Qys zUU|_M#FsF3#iIJsn@e*?Q+W3C+1VHHM?KDF7?{Tjyv@_U)9Ez+X zD0(iXCWeLwdk*Yd_t7m|cWmFjb4R>o>&~uDa4Xi`(%HRrJ8`R{y?twJ)8?+tb8zcs z6oudgk6X*%A>3*}nTT8DbN8WZ<5myY)z#6rDD0w+NALrWUCZ7f>{^O45xdCe?i$(E zzliMWL|F*_$79#hcQCtlUSsd-A6k^2H3~KnEXT@-rmebn2&-u7E}ypUxi(heyR9mx zleuGy!mtbqMsSgXFVxlF_q^e6!^UwS+&(3<^NFIMV&(3-H#8cDkSr4B9{)&fFqBndv z`+l)6E%V`Pe7M`sivP7(u+jIo;`f1K7-95~4RRmD#xC?7XtWyN7XPaFeTd@jzs3EY zcy!t=&-k(ng)E-G0Q_Grur~n_+-+fd*=~G`IdkeVo4E2Eo4J?|{MJ#(pah|B4OhEL zb)n}zpMQupxu9>IZb<1rS@i_D*=X^PX0y?v?`Y)wx&Jn_co}q9H{Q2W*-^hEq+ zU5j0gXk6^S{Y5tVQ+Hg9WBh$K^a#d3tQWdZMcF}jy!+E(Uw9|o`1dcdbH+Cd{glU{ zzDsU7zNXT6%&KVIM18=)pN|Byxkh`MUTqc z5WU3CKhaa`Z?`l5J->E4`%KF=-fkNzw%c;2e}q8I@2Z*VwA;_oR!QCMyDM(|C&M}XqutzG5#m; zyyN%Z4So<0>E7GH$67Y%@bT%P%ghf01D6>={+H7%I|$vRx2D;S|16IE1iQ}}Z!@>{ z)Q(2l*oN2g4$Rf=T;Gc3?1i{S$iHd6(hI{pG_f+z_G6557iR06@^gJ2?l*eP`z4kcu+P@S!J(R1$HK4z6H=p)8{T+7>FRe-O G>i+^YvVISJLQ~IFTWzoUqkuZkCvfZoP6D_Dv{S%o;0$mUI0q=b0URF#9tWNP zo&=r(o(8@EJOe1b0*-^gv%qt}^S}k*1>i;CCE!bd(tQQTAs__|18HCc7zM_FaX{%^ z!tqt$GH?Z$0A2&G0w$4MXqWPu!z2d01kKtl1D7V#}5@Hp&*b&2^6_R2}NQI$TAwSMe(=YBk+4*|gY_s&nc=`$(UKN9>QpR#hk)`3L4=KsLK{jl$LAZj9$p&Y0`OMOdL6+QU zsI_%m8iRwZx9J9`a)s1ogRNRN9LY_T7Ex4MD9(>%(kZ{@4zhL2ufku)=Z^37-tJxF z(&6^lXv((AtnP6cWh37bA_-24fNEsE0IJu@9J)>=2f{Jw-3T(n zj<7{Vuwk!W4zHpK)HcPs2f7_Y@)`)x?Fk1eu34Vg@eDzT6I0WTDo*Z1VeIlHHZ1D? z&J}R(Q2~Cqx?i0boVHNEchmVVO-i(Om#0Meoh_1#CA@^$Czp5*mSb)jxQ?SarFt=XuAxw6k<_@lz`eTdMBQ=5!u{7gS3i+b<(ksZX>(W7G ze8RZwN{)efxGcDD4{UNy5SAU>NzlZ^1fEXYKhyh}N7$63S0b+m{ zAO;SPfzH+w9TnB1_#YT(20ig#Y>NLOQfX5BFA64?DE`-qtd}VMmk<}E_}}4un~`N= zfEXYKhyh}N7$63S0b+m{AO?tmofzoG%-@;mH`LTtivMk$^hohPn*X}NF_Yu!Ek+yd(yj5F3VTO`$FD&0(x>K#=SbZ2QPHY)C(=yOpxp*vR=~zu1+IY5*%dO-JlhboI zRZLme)l4!Pw$VL>rQ9v=o?RAl&2x2wvlp6+28aP-fEXYKhyh}N7$63S0b=0)h=I313@3g*-u%zt z)}0zpG$rBIkKI}pB`W&0bDf4D*{!n<9#*hROLSGigW)CawyUUnwFbB<#@SX zNoqswf%%WFVC&kyckS5XvF@|QVrBtTT_4r5i@L0EzkmhbJG;A^8%h>67zF(~uu*P6 z%?&B)ZY1gIKAU@ES;Yf)6Jy)L-Neg$SFy>lBj;Hyo8vBbOzs#B?6c=%GdnIfd$G4- zkJ@9;_smqa16%yIwWtE4n;Hu{7XD_m|0C`H*fuYe_J2%>IyMJ0wGA!p|ENY!O2hy$ zKnxHA!~iis3=jjv05L!e5CfkX19ANCk+xZYh|6_E5iPtJDhOTS7{sX5XBCXX3!$bh zgFAR3gTLXc&+TDCJt&E%W2$i7PNBTrBX5cYHKnvh@xR@|gL{YdqA29viWL9zc!}Qs zvh7Vd^!}G^l+gNbH9XV%Uji2B7coE#5Cg;jF+dCu1H=F^KnxHA4?P3%`=9UM59Y*W zZEwmpi?%4Y76pfWp!gr(5{6W~(EL9&y^-es(fgm<-nd?(`F}M3@1g(ZBL|5AVt^PR z28aP-fEXYKhyh}N82EfKki(vXWv#AN@WZhO<3ZEukFo#Z8_LLIp`{#lnKWR(2I~%X ze)JCLz*+1+*^N3~Ks@1auO@COg~V|5d>@TM7k-Z|H{4u!UvaZuag+P#6eXC^&b~c{8-p?&^=B(--Q@-Q{xC7VB~QKY{2} zC@T88vBt7hZmh-9pF(;vlrCXe_okWykoC+8Hp6Q|HsTu3qx7*^;TSlBmQ&ZuHfRMe zZo5AhpmZkoHfMRR@T_+E&mccvaC~0jlI0S4`8ibjW0kF)`h}`LFHsR^kDEzn?<1)6 zg+1O{`(g(!iQM!wn-xBrQl~cVTn3WIm89UnUvT|ya4oSZ9fq1(-_mOL`e%e$N7uJC z@ofmz=AQ;P>oP%(f#hCaOwCPPDK?Lwm?c{_b4xqReKe@kET3T~&i23Kvp;#OO~(pNk~cWi78aMDFr8lHOH{i@V!{MfSY8`Tj!(g-r6ab_&ejBDCI zF&sSCp-xEn3LJX>cS4+(-v6!c14_gIF+dCu1H=F^KnxHA!~iis3=jhk7X#-yzjH+E z*N$v-bZYk+|5E?Ar$h`81D_KH)V`@b+ONC5+u5!Ca#NqP!w7O}*bWraIh|FjQkOxz zH6h{!xhHI%AW@G7)Q+M_B1E2Tz0s1W=MS#{$D`v@ZQdMDm9Py(X@)!XRQ1Apqm%j3 zS0~4ELnE(FT^t%2$)$%Tr$#bE>GWhednG+}C6~YS&9F7qh#J(|pOL7%vNQo)JPH{U z9y#RRQuG{Q?_7n8~JQ~MC7VVj+|Hlrw|I6^jiaH!0qmSA^ z*#FI!;e#R~HqsLj(RIi~EWn|+0ChXW$JocyDcp;amnNlAweIrNgW}*#81R90aPSS> z3`Y-{1Jz%3Bd8P~!%v#lA6^Xc+u|P3a1ghyZU@DHdMJ85wA8k5tI-?2nvQLLDc$12 z-1KyQVcUD@F1$B3e)-a+E0@QI#;@cv7^t#SLz$`ZOKPB+OsB`QBcu7zeGXKo@vL~G Date: Thu, 25 Aug 2022 20:05:30 -0400 Subject: [PATCH 11/15] Bump patch and music stack depths from 4 to 16 in BupBoop. According to the author of BupBoop (and of "Rikki & Vikki"), this is necessary in order to play the music of that game properly. --- Contrib/BupBoop/CoreTone/channel.h | 4 ++-- Contrib/BupBoop/CoreTone/music.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Contrib/BupBoop/CoreTone/channel.h b/Contrib/BupBoop/CoreTone/channel.h index a04d61d..6f53bd6 100644 --- a/Contrib/BupBoop/CoreTone/channel.h +++ b/Contrib/BupBoop/CoreTone/channel.h @@ -11,10 +11,10 @@ * Operating Parameters ******************************************************************************/ /* This indicates the stack depth used to track patch script loops, - * the default of four should be adequate for most users. + * the default of 16 should be adequate for most users. */ #ifndef CORETONE_PATCH_STACKDEPTH - #define CORETONE_PATCH_STACKDEPTH 4 + #define CORETONE_PATCH_STACKDEPTH 16 #endif /****************************************************************************** diff --git a/Contrib/BupBoop/CoreTone/music.h b/Contrib/BupBoop/CoreTone/music.h index e073a6d..c1972ed 100644 --- a/Contrib/BupBoop/CoreTone/music.h +++ b/Contrib/BupBoop/CoreTone/music.h @@ -11,10 +11,10 @@ * Operating Parameters ******************************************************************************/ /* This indicates the stack depth used to track music script loops, - * the default of four should be adequate for most users. + * the default of 16 should be adequate for most users. */ #ifndef CORETONE_MUSIC_STACKDEPTH - #define CORETONE_MUSIC_STACKDEPTH 4 + #define CORETONE_MUSIC_STACKDEPTH 16 #endif /****************************************************************************** From abdbce2bbdf2ed15b097b9878fa812c2f27cddad Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:17:24 -0400 Subject: [PATCH 12/15] Change output audio from 8-bit mono to 16-bit stereo. This doesn't affect the sound of any other cartridge, but it's needed to play BupBoop audio (from "Rikki & Vikki") at full fidelity. --- Win/Sound.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Win/Sound.cpp b/Win/Sound.cpp index 0f08502..03adf05 100644 --- a/Win/Sound.cpp +++ b/Win/Sound.cpp @@ -27,7 +27,7 @@ byte sound_latency = SOUND_LATENCY_VERY_LOW; -static const WAVEFORMATEX SOUND_DEFAULT_FORMAT = {WAVE_FORMAT_PCM, 1, 44100, 44100, 1, 8, 0}; +static const WAVEFORMATEX SOUND_DEFAULT_FORMAT = {WAVE_FORMAT_PCM, 2, 44100, 44100 * 4, 4, 16, 0}; static LPDIRECTSOUND sound_dsound = NULL; static LPDIRECTSOUNDBUFFER sound_primaryBuffer = NULL; static LPDIRECTSOUNDBUFFER sound_buffer = NULL; @@ -50,14 +50,15 @@ static uint sound_GetSampleLength(uint length, uint unit, uint unitMax) { // ---------------------------------------------------------------------------- // Resample // ---------------------------------------------------------------------------- -static void sound_Resample(const byte* source, byte* target, int length) { +static void sound_Resample(const byte* source, short* target, int length) { int measurement = sound_format.nSamplesPerSec; int sourceIndex = 0; int targetIndex = 0; while(targetIndex < length) { if(measurement >= 31440) { - target[targetIndex++] = source[sourceIndex]; + target[targetIndex * 2 + 0] = target[targetIndex * 2 + 1] = short(int(source[sourceIndex]) << 7); + targetIndex++; measurement -= 31440; } else { @@ -185,7 +186,7 @@ bool sound_SetFormat(WAVEFORMATEX format) { secondaryDesc.dwReserved = 0; secondaryDesc.dwSize = sizeof(DSBUFFERDESC); secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS; - secondaryDesc.dwBufferBytes = format.nSamplesPerSec; + secondaryDesc.dwBufferBytes = format.nSamplesPerSec * 4; secondaryDesc.lpwfxFormat = &format; hr = sound_dsound->CreateSoundBuffer(&secondaryDesc, &sound_buffer, NULL); @@ -216,25 +217,25 @@ bool sound_Store( ) { return false; } - byte sample[1920]; + short sample[3840]; uint length = sound_GetSampleLength(sound_format.nSamplesPerSec, prosystem_frame, prosystem_frequency); sound_Resample(tia_buffer, sample, length); if(cartridge_pokey) { - byte pokeySample[1920]; + short pokeySample[3840]; sound_Resample(pokey_buffer, pokeySample, length); - for(int index = 0; index < length; index++) { + for(int index = 0; index < length * 2; index++) { sample[index] += pokeySample[index]; sample[index] = sample[index] / 2; } } DWORD lockCount = 0; - byte* lockStream = NULL; + short* lockStream = NULL; DWORD wrapCount = 0; - byte* wrapStream = NULL; + short* wrapStream = NULL; - HRESULT hr = sound_buffer->Lock(sound_counter, length, (void**)&lockStream, &lockCount, (void**)&wrapStream, &wrapCount, 0); + HRESULT hr = sound_buffer->Lock(sound_counter * 4, length * 4, (void**)&lockStream, &lockCount, (void**)&wrapStream, &wrapCount, 0); if(FAILED(hr) || lockStream == NULL) { logger_LogError(IDS_SOUND12,""); logger_LogError("",common_Format(hr)); @@ -244,11 +245,11 @@ bool sound_Store( ) { } uint bufferCounter = 0; - for(uint lockIndex = 0; lockIndex < lockCount; lockIndex++) { + for(uint lockIndex = 0; lockIndex < lockCount / 2; lockIndex++) { lockStream[lockIndex] = sample[bufferCounter++]; } - for(uint wrapIndex = 0; wrapIndex < wrapCount; wrapIndex++) { + for(uint wrapIndex = 0; wrapIndex < wrapCount / 2; wrapIndex++) { wrapStream[wrapIndex] = sample[bufferCounter++]; } @@ -286,7 +287,7 @@ bool sound_Clear( ) { return false; } - byte* lockStream = NULL; + short* lockStream = NULL; DWORD lockCount = 0; HRESULT hr = sound_buffer->Lock(0, sound_format.nSamplesPerSec, (void**)&lockStream, &lockCount, NULL, NULL, DSBLOCK_ENTIREBUFFER); if(FAILED(hr) || lockStream == NULL) { @@ -385,7 +386,7 @@ bool sound_Stop( ) { // ---------------------------------------------------------------------------- bool sound_SetSampleRate(uint rate) { sound_format.nSamplesPerSec = rate; - sound_format.nAvgBytesPerSec = rate; + sound_format.nAvgBytesPerSec = rate * 4; return sound_SetFormat(sound_format); } From 11cb0a82175f44acf7bbb0ca2efb035e960964b6 Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:25:08 -0400 Subject: [PATCH 13/15] Add support for the BupChip, used by "Rikki & Vikki". Note that this commit itself doesn't actually allow the BupChip to be used for anything, because the only way to supply music for the BupChip to play is through the CDF format. --- Core/BupChip.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++ Core/BupChip.h | 46 ++++++++++ Core/Cartridge.cpp | 3 + Core/Cartridge.h | 1 + Core/ProSystem.cpp | 31 ++++++- Win/Sound.cpp | 39 ++++++++- 6 files changed, 320 insertions(+), 5 deletions(-) create mode 100755 Core/BupChip.cpp create mode 100755 Core/BupChip.h diff --git a/Core/BupChip.cpp b/Core/BupChip.cpp new file mode 100755 index 0000000..e34cd07 --- /dev/null +++ b/Core/BupChip.cpp @@ -0,0 +1,205 @@ +// ---------------------------------------------------------------------------- +// ___ ___ ___ ___ ___ ____ ___ _ _ +// /__/ /__/ / / /__ /__/ /__ / /_ / |/ / +// / / \ /__/ ___/ ___/ ___/ / /__ / / emulator +// +// ---------------------------------------------------------------------------- +// Copyright 2005 Greg Stanton +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// ---------------------------------------------------------------------------- +// BupChip.cpp +// ---------------------------------------------------------------------------- +#include "BupChip.h" +#include "Cartridge.h" +#include +#include +#include + +#define BUPCHIP_FLAGS_PLAYING 1 +#define BUPCHIP_FLAGS_PAUSED 2 + +struct BupchipFileContents { + uint8_t *data; + size_t size; +}; + +byte *bupchip_sample_data; +byte *bupchip_instrument_data; +BupchipFileContents bupchip_songs[32]; +byte bupchip_song_count; + +byte bupchip_flags; +byte bupchip_volume; +byte bupchip_current_song; + +short bupchip_buffer[CORETONE_BUFFER_LEN * 4]; + +// ---------------------------------------------------------------------------- +// Init +// ---------------------------------------------------------------------------- +bool bupchip_Init() { + size_t songIndex = 0; + std::vector fileData; + + // TODO: Load song list from CDF. + + if(fileData.size( ) < 2) { + goto err; + } + bupchip_sample_data = fileData[0].data; + bupchip_instrument_data = fileData[1].data; + if(ct_init(bupchip_sample_data, bupchip_instrument_data) != 0) { + goto err; + } + for(songIndex = 0; songIndex < fileData.size( ) - 2; songIndex++) { + bupchip_songs[songIndex] = fileData[songIndex + 2]; + } + bupchip_song_count = byte(fileData.size( ) - 2); + return true; + +err: + for(size_t fileIndex = 0; fileIndex < fileData.size( ); fileIndex++) { + delete [ ] fileData[fileIndex].data; + fileData[fileIndex].data = NULL; + } + bupchip_song_count = 0; + bupchip_instrument_data = NULL; + bupchip_sample_data = NULL; + return false; +} + +// ---------------------------------------------------------------------------- +// Stop +// ---------------------------------------------------------------------------- +void bupchip_Stop( ) { + bupchip_flags &= ~BUPCHIP_FLAGS_PLAYING; + ct_stopMusic( ); +} + +// ---------------------------------------------------------------------------- +// Play +// ---------------------------------------------------------------------------- +void bupchip_Play(unsigned char song) { + if(song >= bupchip_song_count) { + bupchip_Stop( ); + return; + } + bupchip_flags |= BUPCHIP_FLAGS_PLAYING; + bupchip_current_song = song; + ct_playMusic(bupchip_songs[bupchip_current_song].data); +} + +// ---------------------------------------------------------------------------- +// Pause +// ---------------------------------------------------------------------------- +void bupchip_Pause( ) { + bupchip_flags |= BUPCHIP_FLAGS_PAUSED; + ct_pause( ); +} + +// ---------------------------------------------------------------------------- +// Resume +// ---------------------------------------------------------------------------- +void bupchip_Resume( ) { + bupchip_flags &= ~BUPCHIP_FLAGS_PAUSED; + ct_resume( ); +} + +// ---------------------------------------------------------------------------- +// SetVolume +// ---------------------------------------------------------------------------- +void bupchip_SetVolume(byte volume) { + bupchip_volume = volume & 0x1f; + // This matches BupSystem. + int attenuation = volume << 2; + if((volume & 1) != 0) { + attenuation += 0x3; + } + ct_attenMusic(attenuation); +} + +// ---------------------------------------------------------------------------- +// ProcessAudioCommand +// ---------------------------------------------------------------------------- +void bupchip_ProcessAudioCommand(unsigned char data) { + switch(data & 0xc0) { + case 0: + switch(data) { + case 0: + bupchip_flags = 0; + bupchip_volume = 0x1f; + ct_stopAll( ); + ct_resume( ); + ct_attenMusic(127); + return; + case 2: + bupchip_Resume( ); + return; + case 3: + bupchip_Pause( ); + return; + } + return; + case 0x40: + bupchip_Stop( ); + return; + case 0x80: + bupchip_Play(data & 0x1f); + return; + case 0xc0: + bupchip_SetVolume(data); + return; + } +} + +// ---------------------------------------------------------------------------- +// Process +// ---------------------------------------------------------------------------- +void bupchip_Process(unsigned tick) { + ct_update(&bupchip_buffer[tick * CORETONE_BUFFER_LEN]); +} + +// ---------------------------------------------------------------------------- +// Release +// ---------------------------------------------------------------------------- +void bupchip_Release( ) { + for(int i = 0; i < bupchip_song_count; i++) { + delete [ ] bupchip_songs[i].data; + bupchip_songs[i].data = NULL; + } + delete [ ] bupchip_instrument_data; + bupchip_instrument_data = NULL; + delete [ ] bupchip_sample_data; + bupchip_sample_data = NULL; +} + +// ---------------------------------------------------------------------------- +// StateLoaded +// ---------------------------------------------------------------------------- +void bupchip_StateLoaded( ) { + ct_stopAll( ); + if((bupchip_flags & BUPCHIP_FLAGS_PLAYING) == 0) { + return; + } + ct_playMusic(bupchip_songs[bupchip_current_song].data); + if((bupchip_flags & BUPCHIP_FLAGS_PAUSED) != 0) { + ct_pause( ); + } + else { + ct_resume( ); + } + bupchip_SetVolume(bupchip_volume); +} diff --git a/Core/BupChip.h b/Core/BupChip.h new file mode 100755 index 0000000..48ec23c --- /dev/null +++ b/Core/BupChip.h @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------- +// ___ ___ ___ ___ ___ ____ ___ _ _ +// /__/ /__/ / / /__ /__/ /__ / /_ / |/ / +// / / \ /__/ ___/ ___/ ___/ / /__ / / emulator +// +// ---------------------------------------------------------------------------- +// Copyright 2005 Greg Stanton +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// ---------------------------------------------------------------------------- +// BupChip.h +// ---------------------------------------------------------------------------- +#ifndef BUPCHIP_H +#define BUPCHIP_H + +#include +extern "C" { +#include "../Contrib/BupBoop/types.h" +#include "../Contrib/BupBoop/CoreTone/coretone.h" +} +#include + +extern unsigned char bupchip_flags; +extern unsigned char bupchip_volume; +extern unsigned char bupchip_current_song; +extern short bupchip_buffer[CORETONE_BUFFER_LEN * 4]; + +bool bupchip_Init(); +void bupchip_ProcessAudioCommand(unsigned char data); +void bupchip_Process(unsigned tick); +void bupchip_Release( ); +void bupchip_StateLoaded( ); + +#endif \ No newline at end of file diff --git a/Core/Cartridge.cpp b/Core/Cartridge.cpp index 1855d27..3f6e3eb 100755 --- a/Core/Cartridge.cpp +++ b/Core/Cartridge.cpp @@ -23,6 +23,7 @@ // Cartridge.cpp // ---------------------------------------------------------------------------- #include "Cartridge.h" +#include "BupChip.h" #include std::string cartridge_title; @@ -37,6 +38,7 @@ bool cartridge_pokey; byte cartridge_controller[2]; byte cartridge_bank; uint cartridge_flags; +bool cartridge_bupchip; // SOUPER-specific stuff, used for "Rikki & Vikki" byte cartridge_souper_chr_bank[2]; @@ -388,6 +390,7 @@ void cartridge_Write(word address, byte data) { cartridge_souper_SetRamPageBank(1, data); break; case CARTRIDGE_SOUPER_AUDIO_CMD: + bupchip_ProcessAudioCommand(data); break; } break; diff --git a/Core/Cartridge.h b/Core/Cartridge.h index f82f426..2ca8ac9 100644 --- a/Core/Cartridge.h +++ b/Core/Cartridge.h @@ -81,6 +81,7 @@ extern bool cartridge_pokey; extern byte cartridge_controller[2]; extern byte cartridge_bank; extern uint cartridge_flags; +extern bool cartridge_bupchip; extern byte cartridge_souper_chr_bank[2]; extern byte cartridge_souper_mode; extern byte cartridge_souper_ram_page_bank[2]; diff --git a/Core/ProSystem.cpp b/Core/ProSystem.cpp index b47758b..9135a1a 100644 --- a/Core/ProSystem.cpp +++ b/Core/ProSystem.cpp @@ -23,6 +23,7 @@ // ProSystem.cpp // ---------------------------------------------------------------------------- #include "ProSystem.h" +#include "BupChip.h" #define PRO_SYSTEM_STATE_HEADER "PRO-SYSTEM STATE" bool prosystem_active = false; @@ -65,6 +66,10 @@ void prosystem_Reset( ) { void prosystem_ExecuteFrame(const byte* input) { riot_SetInput(input); + uint scanlinesPerBupchipTick = (prosystem_scanlines - 1) / 4; + uint bupchipTickScanlines = 0; + uint currentBupchipTick = 0; + for(maria_scanline = 1; maria_scanline <= prosystem_scanlines; maria_scanline++) { if(maria_scanline == maria_displayArea.top) { memory_ram[MSTAT] = 0; @@ -109,6 +114,15 @@ void prosystem_ExecuteFrame(const byte* input) { if(cartridge_pokey) { pokey_Process(2); } + + if(cartridge_bupchip) { + bupchipTickScanlines++; + if(bupchipTickScanlines == scanlinesPerBupchipTick) { + bupchipTickScanlines = 0; + bupchip_Process(currentBupchipTick); + currentBupchipTick++; + } + } } prosystem_frame++; if(prosystem_frame >= prosystem_frequency) { @@ -127,7 +141,7 @@ bool prosystem_Save(std::string filename, bool compress) { logger_LogInfo(IDS_PROSYSTEM2,filename); - byte buffer[49218] = {0}; + byte buffer[49221] = {0}; uint size = 0; uint index; @@ -176,6 +190,9 @@ bool prosystem_Save(std::string filename, bool compress) { buffer[size + index] = memory_souper_ram[index]; } size += sizeof(memory_souper_ram); + buffer[size++] = bupchip_flags; + buffer[size++] = bupchip_volume; + buffer[size++] = bupchip_current_song; } if(!compress) { @@ -214,7 +231,7 @@ bool prosystem_Load(const std::string filename) { logger_LogInfo(IDS_PROSYSTEM6,filename); - byte buffer[49218] = {0}; + byte buffer[49221] = {0}; uint size = archive_GetUncompressedFileSize(filename); if(size == 0) { FILE* file = fopen(filename.c_str( ), "rb"); @@ -236,7 +253,7 @@ bool prosystem_Load(const std::string filename) { return false; } - if(size != 16445 && size != 32829 && size != 49218) { + if(size != 16445 && size != 32829 && size != 49221) { fclose(file); logger_LogError(IDS_PROSYSTEM10,""); return false; @@ -249,7 +266,7 @@ bool prosystem_Load(const std::string filename) { } fclose(file); } - else if(size == 16445 || size == 32829 || size == 49218) { + else if(size == 16445 || size == 32829 || size == 49221) { archive_Uncompress(filename, buffer, size); } else { @@ -318,6 +335,11 @@ bool prosystem_Load(const std::string filename) { for(index = 0; index < sizeof(memory_souper_ram); index++) { memory_souper_ram[index] = buffer[offset++]; } + + bupchip_flags = buffer[offset++]; + bupchip_volume = buffer[offset++]; + bupchip_current_song = buffer[offset++]; + bupchip_StateLoaded( ); } return true; @@ -338,6 +360,7 @@ void prosystem_Pause(bool pause) { void prosystem_Close( ) { prosystem_active = false; prosystem_paused = false; + bupchip_Release( ); cartridge_Release( ); maria_Reset( ); maria_Clear( ); diff --git a/Win/Sound.cpp b/Win/Sound.cpp index 03adf05..807630c 100644 --- a/Win/Sound.cpp +++ b/Win/Sound.cpp @@ -23,6 +23,8 @@ // Sound.cpp // ---------------------------------------------------------------------------- #include "Sound.h" +#include "BupChip.h" +#include #define SOUND_LATENCY_SCALE 4 byte sound_latency = SOUND_LATENCY_VERY_LOW; @@ -68,6 +70,34 @@ static void sound_Resample(const byte* source, short* target, int length) { } } +// ---------------------------------------------------------------------------- +// Lerp +// ---------------------------------------------------------------------------- +static short sound_Lerp(short a, short b, float t) { + return short(floorf(float(a) + float(b - a) * t + 0.5f)); +} + +// ---------------------------------------------------------------------------- +// ResampleBupChip +// ---------------------------------------------------------------------------- +static void sound_ResampleBupChip(const short* source, short* target, int length) { + uint bupchipBufferSize = CORETONE_BUFFER_SAMPLES * 4; + + for(int targetIndex = 0; targetIndex < length; targetIndex++) { + float sourceIndex = float(targetIndex) / float(length) * float(bupchipBufferSize); + uint sourceLo = uint(floorf(sourceIndex)), sourceHi = uint(ceilf(sourceIndex)); + if(sourceHi >= bupchipBufferSize) { + sourceHi = bupchipBufferSize; + } + float t = sourceIndex - float(sourceLo); + + for(int channel = 0; channel < 2; channel++) { + target[targetIndex * 2 + channel] = + sound_Lerp(source[sourceLo * 2 + channel], source[sourceHi * 2 + channel], t); + } + } +} + // ---------------------------------------------------------------------------- // RestoreBuffer // ---------------------------------------------------------------------------- @@ -226,7 +256,14 @@ bool sound_Store( ) { sound_Resample(pokey_buffer, pokeySample, length); for(int index = 0; index < length * 2; index++) { sample[index] += pokeySample[index]; - sample[index] = sample[index] / 2; + } + } + + if(cartridge_bupchip) { + short bupchipSample[3840]; + sound_ResampleBupChip(bupchip_buffer, bupchipSample, length); + for(int index = 0; index < length * 2; index++) { + sample[index] += bupchipSample[index]; } } From 336f5de30949835d450a3a38b782791174856c86 Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:28:11 -0400 Subject: [PATCH 14/15] Add support for the BupSystem CDF file format. This format allows song files to be supplied to the emulated BupChip for playback. It matches the format used by BupSystem: http://tailchao.com/BupSystem/index.php "Rikki & Vikki" is supplied as a CDF file, along with an unheadered `.bin` image and several music files. --- Core/BupChip.cpp | 13 +++-- Core/BupChip.h | 2 +- Core/Cartridge.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++-- Core/Cartridge.h | 3 ++ Win/Console.cpp | 2 +- 5 files changed, 141 insertions(+), 10 deletions(-) diff --git a/Core/BupChip.cpp b/Core/BupChip.cpp index e34cd07..f761814 100755 --- a/Core/BupChip.cpp +++ b/Core/BupChip.cpp @@ -48,13 +48,20 @@ byte bupchip_current_song; short bupchip_buffer[CORETONE_BUFFER_LEN * 4]; // ---------------------------------------------------------------------------- -// Init +// InitFromCDF // ---------------------------------------------------------------------------- -bool bupchip_Init() { +bool bupchip_InitFromCDF(std::istringstream &cdf, const char *workingDir) { size_t songIndex = 0; + std::string line; std::vector fileData; - // TODO: Load song list from CDF. + while(cartridge_GetNextNonemptyLine(cdf, line)) { + BupchipFileContents contents; + if(!cartridge_ReadFile(&contents.data, &contents.size, line.c_str( ), workingDir)) { + goto err; + } + fileData.push_back(contents); + } if(fileData.size( ) < 2) { goto err; diff --git a/Core/BupChip.h b/Core/BupChip.h index 48ec23c..a188dbb 100755 --- a/Core/BupChip.h +++ b/Core/BupChip.h @@ -37,7 +37,7 @@ extern unsigned char bupchip_volume; extern unsigned char bupchip_current_song; extern short bupchip_buffer[CORETONE_BUFFER_LEN * 4]; -bool bupchip_Init(); +bool bupchip_InitFromCDF(std::istringstream &cdf, const char *workingDir); void bupchip_ProcessAudioCommand(unsigned char data); void bupchip_Process(unsigned tick); void bupchip_Release( ); diff --git a/Core/Cartridge.cpp b/Core/Cartridge.cpp index 3f6e3eb..ffca056 100755 --- a/Core/Cartridge.cpp +++ b/Core/Cartridge.cpp @@ -213,6 +213,112 @@ if (cartridge_CC2(header)) { return true; } +// ---------------------------------------------------------------------------- +// GetNextNonemptyLine +// ---------------------------------------------------------------------------- +bool cartridge_GetNextNonemptyLine(std::istringstream& stream, std::string& line) { + do { + if(!std::getline(stream, line)) { + return false; + } + if(!line.empty( ) && line[line.size( ) - 1] == '\r') { + line.resize(line.size( ) - 1); + } + } while(line.empty( )); + return true; +} + +// ---------------------------------------------------------------------------- +// ReadFile +// ---------------------------------------------------------------------------- +bool cartridge_ReadFile(byte **outData, size_t *outSize, const char *subpath, const char *relativeTo) { + std::string path(relativeTo); +#ifdef _WIN32 + path += "\\"; +#else + path += "/"; +#endif + + path.append(subpath); + + std::ifstream file(path.c_str( ), std::ios::binary); + if(!file) { + return false; + } + std::streampos beginPos = file.tellg( ); + file.seekg(0, std::ios::end); + std::streampos end_pos = file.tellg( ); + file.seekg(0, std::ios::beg); + size_t fileSize = size_t(end_pos - beginPos); + byte *data = new byte[fileSize]; + if(!file.read((char *)data, fileSize)) { + delete [ ] data; + return false; + } + + *outData = data; + *outSize = fileSize; + return true; +} + +// ---------------------------------------------------------------------------- +// LoadFromCDF +// ---------------------------------------------------------------------------- +static bool cartridge_LoadFromCDF(const byte* data, uint size, const char *workingDir) { + static const char *cartridgeTypes[ ] = { + "EMPTY", + "SUPER", + NULL, + NULL, + NULL, + "ABSOLUTE", + "ACTIVISION", + "SOUPER", + }; + + std::string string((const char *)data, size); + std::istringstream data_stream(string); + std::string line; + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + if(line != "ProSystem") { + return false; + } + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + for(int i = 0; i < sizeof(cartridgeTypes) / sizeof(cartridgeTypes[0]); i++) { + if(cartridgeTypes[i] != NULL && std::equal(line.begin( ), line.end( ), cartridgeTypes[i])) { + cartridge_type = i; + break; + } + } + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + cartridge_title = line; + + if(!cartridge_GetNextNonemptyLine(data_stream, line)) { + return false; + } + size_t cartSize; + if(!cartridge_ReadFile(&cartridge_buffer, &cartSize, line.c_str( ), workingDir)) { + return false; + } + cartridge_size = uint(cartSize); + cartridge_digest = hash_Compute(cartridge_buffer, cartridge_size); + + cartridge_bupchip = cartridge_GetNextNonemptyLine(data_stream, line) && line == "CORETONE"; + if(cartridge_bupchip) { + if(!bupchip_InitFromCDF(data_stream, workingDir)) { + delete [ ] cartridge_buffer; + return false; + } + } + return true; +} + // ---------------------------------------------------------------------------- // LoadROM // ---------------------------------------------------------------------------- @@ -271,12 +377,27 @@ bool cartridge_Load(std::string filename) { data = new byte[size]; archive_Uncompress(filename, data, size); } - - if(!cartridge_Load(data, size)) { - logger_LogError(IDS_CARTRIDGE7,""); - delete [ ] data; - return false; + + if(size >= 10 && std::equal(&data[0], &data[9], "ProSystem")) { + size_t slashPos = filename.find_last_of("\\/"); + std::string workingDir; + if(slashPos != std::string::npos) { + workingDir = filename.substr(0, slashPos); + } + + if(!cartridge_LoadFromCDF(data, size, workingDir.c_str( ))) { + delete [ ] data; + return false; + } } + else { + if(!cartridge_Load(data, size)) { + logger_LogError(IDS_CARTRIDGE7,""); + delete [ ] data; + return false; + } + } + if(data != NULL) { delete [ ] data; } diff --git a/Core/Cartridge.h b/Core/Cartridge.h index 2ca8ac9..709bd44 100644 --- a/Core/Cartridge.h +++ b/Core/Cartridge.h @@ -51,6 +51,7 @@ #include #include +#include #include "Equates.h" #include "Memory.h" #include "Hash.h" @@ -62,6 +63,8 @@ typedef unsigned char byte; typedef unsigned short word; typedef unsigned int uint; +extern bool cartridge_GetNextNonemptyLine(std::istringstream& stream, std::string& line); +extern bool cartridge_ReadFile(byte **outData, size_t *outSize, const char *subpath, const char *relativeTo); extern byte cartridge_LoadROM(uint address); extern bool cartridge_Load(std::string filename); extern void cartridge_Store( ); diff --git a/Win/Console.cpp b/Win/Console.cpp index 7674c99..f50dbd4 100644 --- a/Win/Console.cpp +++ b/Win/Console.cpp @@ -191,7 +191,7 @@ static void console_Open( ) { OPENFILENAME openDialog = {0}; openDialog.lStructSize = sizeof(OPENFILENAME); openDialog.hwndOwner = console_hWnd; - openDialog.lpstrFilter = "All Files (*.*)\0*.*\0Atari Files (*.a78)\0*.a78\0Zip Files (*.zip)\0*.zip\0"; + openDialog.lpstrFilter = "All Files (*.*)\0*.*\0Atari Files (*.a78, *.cdf)\0*.a78;*.cdf\0Zip Files (*.zip)\0*.zip\0"; openDialog.nFilterIndex = filterIndex; openDialog.lpstrFile = path; openDialog.nMaxFile = _MAX_PATH; From d941ebad76dd89513ecb62c0416014845407c53b Mon Sep 17 00:00:00 2001 From: Tachi Date: Wed, 24 Aug 2022 04:30:31 -0400 Subject: [PATCH 15/15] Add the Steam version of "Rikki & Vikki" to the cartridge database --- ProSystem.dat | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ProSystem.dat b/ProSystem.dat index dd8fdec..40c1993 100644 --- a/ProSystem.dat +++ b/ProSystem.dat @@ -710,6 +710,14 @@ controller1=1 controller2=1 region=0 flags=0 +[b3bc889e9cc498636990c5a4d980e85c] +title=Rikki & Vikki +type=7 +pokey=false +controller1=1 +controller2=1 +region=0 +flags=0 [66ecaafe1b82ae68ffc96267aaf7a4d7] title=Robotron type=0