From 83e7b5f45de84736e30a3c3b5ff0b3a2772d50ec Mon Sep 17 00:00:00 2001 From: Ian Curtis Date: Thu, 9 Jun 2022 21:10:39 +0000 Subject: [PATCH] njz3: Proper quadrophonic audio support. Need 4+ speakers to take advantage otherwise down mixed to stereo. --- Config/Games.xml | 15 + Src/Game.h | 12 + Src/GameLoader.cpp | 182 ++++--- Src/Graphics/Legacy3D/Models.cpp | 2 +- Src/Model3/IEmulator.h | 2 +- Src/Model3/Model3.cpp | 18 +- Src/Model3/Model3.h | 9 +- Src/Model3/SoundBoard.cpp | 51 +- Src/Model3/SoundBoard.h | 3 +- Src/OSD/Audio.h | 21 +- Src/OSD/Outputs.cpp | 9 + Src/OSD/Outputs.h | 8 + Src/OSD/SDL/Audio.cpp | 771 ++++++++++++++++++--------- Src/OSD/SDL/Main.cpp | 29 +- Src/OSD/Windows/WinOutputs.cpp | 13 + Src/OSD/Windows/WinOutputs.h | 201 +++---- Src/Sound/SCSP.cpp | 158 ++++-- Src/Sound/SCSP.h | 2 +- VS2008/Musashi68K/Musashi68K.vcxproj | 9 +- VS2008/SDL/SDL.vcxproj | 10 +- VS2008/SDLmain/SDLmain.vcxproj | 10 +- VS2008/SDLnet/SDL_net.vcxproj | 10 +- VS2008/Supermodel.vcxproj | 10 +- VS2008/ZLib/ZLib.vcxproj | 8 +- 24 files changed, 982 insertions(+), 581 deletions(-) diff --git a/Config/Games.xml b/Config/Games.xml index 621b210..180ae98 100644 --- a/Config/Games.xml +++ b/Config/Games.xml @@ -174,6 +174,7 @@ Wheel 24 true + @@ -266,6 +267,7 @@ DSB2 Wheel true + @@ -551,6 +553,7 @@ Sega Model 3 2.1 Wheel + @@ -632,6 +635,7 @@ Sega Model 3 2.1 Wheel + @@ -678,6 +682,7 @@ Sega Model 3 2.1 Wheel + @@ -711,6 +716,7 @@ Sega Model 3 2.1 Wheel + @@ -1030,6 +1036,7 @@ 1.5 Wheel true + @@ -1360,6 +1367,7 @@ DSB1 Wheel true + @@ -1444,6 +1452,7 @@ DSB1 Wheel true + @@ -1478,6 +1487,7 @@ DSB1 Wheel true + @@ -1550,6 +1560,7 @@ DSB1 Wheel true + @@ -1597,6 +1608,7 @@ Wheel MPC106 true + @@ -1891,6 +1903,7 @@ DSB2 Wheel true + @@ -1989,6 +2002,7 @@ 2.0 DSB2 Wheel + @@ -2098,6 +2112,7 @@ DSB2 Wheel 48 + diff --git a/Src/Game.h b/Src/Game.h index 4ffbf33..eea1c45 100644 --- a/Src/Game.h +++ b/Src/Game.h @@ -14,6 +14,18 @@ struct Game unsigned year = 0; std::string stepping; std::string mpeg_board; + enum AudioTypes + { + MONO = 0, // Merge DSB+SCSP1+SCSP2 to 1 channel mono + STEREO_LR, // Merge DSP+SCSP1+SCSP2 to 2 channels stereo Left/Right (most common) + STEREO_RL, // Merge DSP+SCSP1+SCSP2 to 2 channels stereo reversed Right/Left + QUAD_1_FLR_2_RLR, // Split DSB+SCSP1 to FrontLeft/FrontRight and SCSP2 to RearLeft/RearRight (Daytona2) + QUAD_1_FRL_2_RRL, // Split DSB+SCSP1 to FrontRight/FrontLeft and SCSP2 to RearRight/RearLeft + QUAD_1_RLR_2_FLR, // Split DSB+SCSP1 to RearLeft/RearRight and SCSP2 to FrontLeft/FrontRight + QUAD_1_RRL_2_FRL, // Split DSB+SCSP1 to RearRight/RearLeft and SCSP2 to FrontRight/FrontLeft + QUAD_1_LR_2_FR_MIX, // Specific srally2: Split SCSP2 and mix first channel to DSB+SCP11 Front Left/Right and second to Read Left/Right + }; + AudioTypes audio = STEREO_LR; std::string pci_bridge; // overrides default PCI bridge type for stepping (empty string for default) uint32_t real3d_pci_id = 0; // overrides default Real3D PCI ID for stepping (0 for default) float real3d_status_bit_set_percent_of_frame = 0; // overrides default status bit timing (0 for default) diff --git a/Src/GameLoader.cpp b/Src/GameLoader.cpp index 152fed5..5db434e 100644 --- a/Src/GameLoader.cpp +++ b/Src/GameLoader.cpp @@ -198,6 +198,20 @@ static void PopulateGameInfo(Game *game, const Util::Config::Node &game_node) game->year = game_node["identity/year"].ValueAsDefault(0); game->stepping = game_node["hardware/stepping"].ValueAsDefault(""); game->mpeg_board = game_node["hardware/mpeg_board"].ValueAsDefault(""); + std::map audio_types + { + { "", Game::STEREO_LR }, // default to stereo + { "Mono", Game::MONO }, + { "Stereo", Game::STEREO_LR }, + { "StereoReversed", Game::STEREO_RL }, + { "QuadFrontRear", Game::QUAD_1_FLR_2_RLR }, + { "QuadFrontRearReversed", Game::QUAD_1_FRL_2_RRL }, + { "QuadRearFront", Game::QUAD_1_RLR_2_FLR }, + { "QuadRearFrontReversed", Game::QUAD_1_RRL_2_FRL }, + { "QuadMix", Game::QUAD_1_LR_2_FR_MIX} + }; + std::string audio_type = game_node["hardware/audio"].ValueAsDefault(std::string()); + game->audio = audio_types[audio_type]; game->pci_bridge = game_node["hardware/pci_bridge"].ValueAsDefault(""); game->real3d_pci_id = game_node["hardware/real3d_pci_id"].ValueAsDefault(0); game->real3d_status_bit_set_percent_of_frame = game_node["hardware/real3d_status_bit_set_percent_of_frame"].ValueAsDefault(0); @@ -250,80 +264,80 @@ static void PopulateGameInfo(Game *game, const Util::Config::Node &game_node) } bool GameLoader::LoadGamesFromXML(const Util::Config::Node &xml) -{ - for (auto it = xml.begin(); it != xml.end(); ++it) - { - // Root games node - auto &root_node = *it; - if (root_node.Key() != "games") - continue; - for (auto &game_node: root_node) - { - // Game node - if (game_node.Key() != "game") - continue; - if (game_node["name"].Empty()) - { - //TODO: associate line numbers in config - //ErrorLog("%s: Ignoring tag with missing 'name' attribute.", m_xml_filename.c_str()); - continue; - } - std::string game_name = game_node["name"].ValueAs(); - if (m_regions_by_game.find(game_name) != m_regions_by_game.end()) - { - ErrorLog("%s: Ignoring redefinition of game '%s'.", m_xml_filename.c_str(), game_name.c_str()); - continue; - } +{ + for (auto it = xml.begin(); it != xml.end(); ++it) + { + // Root games node + auto &root_node = *it; + if (root_node.Key() != "games") + continue; + for (auto &game_node: root_node) + { + // Game node + if (game_node.Key() != "game") + continue; + if (game_node["name"].Empty()) + { + //TODO: associate line numbers in config + //ErrorLog("%s: Ignoring tag with missing 'name' attribute.", m_xml_filename.c_str()); + continue; + } + std::string game_name = game_node["name"].ValueAs(); + if (m_regions_by_game.find(game_name) != m_regions_by_game.end()) + { + ErrorLog("%s: Ignoring redefinition of game '%s'.", m_xml_filename.c_str(), game_name.c_str()); + continue; + } RegionsByName_t ®ions_by_name = m_regions_by_game[game_name]; - PatchesByRegion_t &patches_by_region = m_patches_by_game[game_name]; - PopulateGameInfo(&m_game_info_by_game[game_name], game_node); - - for (auto &roms_node: game_node) - { - if (roms_node.Key() != "roms") - continue; - - /* - * Regions define contiguous memory areas that individual ROM files are - * loaded into. It is possible to have multiple region tags identifying - * the same region. They will be aggregated. This is useful for parent - * and child ROM sets, which each may need to define the same region, - * with the child set loading in different files to overwrite the parent - * set. - */ - for (auto ®ion_node: roms_node) - { - if (region_node.Key() != "region") - continue; - - // Look up region structure or create new one if needed - std::string region_name = region_node["name"].Value(); - auto it = regions_by_name.find(region_name); - Region::ptr_t region = (it != regions_by_name.end()) ? it->second : Region::Create(*this, region_node); - if (!region) - continue; - - /* - * Files are defined by the offset they are loaded at. Normally, there - * should be one file per offset but parent/child ROM sets will violate - * this, and so it is allowed. - */ - std::vector &files = region->files; - for (auto &file_node: region_node) - { - if (file_node.Key() != "file") - continue; - File::ptr_t file = File::Create(*this, file_node); - if (!file) - continue; - files.push_back(file); - } - - // Check to ensure that some files were defined in the region - if (files.empty()) - ErrorLog("%s: No files defined in region '%s' of '%s'.", m_xml_filename.c_str(), region->region_name.c_str(), game_name.c_str()); - else - regions_by_name[region->region_name] = region; + PatchesByRegion_t &patches_by_region = m_patches_by_game[game_name]; + PopulateGameInfo(&m_game_info_by_game[game_name], game_node); + + for (auto &roms_node: game_node) + { + if (roms_node.Key() != "roms") + continue; + + /* + * Regions define contiguous memory areas that individual ROM files are + * loaded into. It is possible to have multiple region tags identifying + * the same region. They will be aggregated. This is useful for parent + * and child ROM sets, which each may need to define the same region, + * with the child set loading in different files to overwrite the parent + * set. + */ + for (auto ®ion_node: roms_node) + { + if (region_node.Key() != "region") + continue; + + // Look up region structure or create new one if needed + std::string region_name = region_node["name"].Value(); + auto it = regions_by_name.find(region_name); + Region::ptr_t region = (it != regions_by_name.end()) ? it->second : Region::Create(*this, region_node); + if (!region) + continue; + + /* + * Files are defined by the offset they are loaded at. Normally, there + * should be one file per offset but parent/child ROM sets will violate + * this, and so it is allowed. + */ + std::vector &files = region->files; + for (auto &file_node: region_node) + { + if (file_node.Key() != "file") + continue; + File::ptr_t file = File::Create(*this, file_node); + if (!file) + continue; + files.push_back(file); + } + + // Check to ensure that some files were defined in the region + if (files.empty()) + ErrorLog("%s: No files defined in region '%s' of '%s'.", m_xml_filename.c_str(), region->region_name.c_str(), game_name.c_str()); + else + regions_by_name[region->region_name] = region; } // ROM patches, if any @@ -348,17 +362,17 @@ bool GameLoader::LoadGamesFromXML(const Util::Config::Node &xml) else patches_by_region[region].push_back(ROM::BigEndianPatch(offset, value, bits)); } - } - } - - // Check to ensure that some ROM regions were defined for the game - if (regions_by_name.empty()) - ErrorLog("%s: No ROM regions defined for '%s'.", m_xml_filename.c_str(), game_name.c_str()); - } - } - // Check to ensure some games were defined - if (m_regions_by_game.empty()) - { + } + } + + // Check to ensure that some ROM regions were defined for the game + if (regions_by_name.empty()) + ErrorLog("%s: No ROM regions defined for '%s'.", m_xml_filename.c_str(), game_name.c_str()); + } + } + // Check to ensure some games were defined + if (m_regions_by_game.empty()) + { ErrorLog("%s: No games defined.", m_xml_filename.c_str()); return true; } diff --git a/Src/Graphics/Legacy3D/Models.cpp b/Src/Graphics/Legacy3D/Models.cpp index f86db71..ceb3683 100644 --- a/Src/Graphics/Legacy3D/Models.cpp +++ b/Src/Graphics/Legacy3D/Models.cpp @@ -725,7 +725,7 @@ void CLegacy3D::InsertVertex(ModelCache *Cache, const Vertex *V, const Poly *P, // Specular shininess GLfloat specularCoefficient = (GLfloat) ((P->header[0]>>26) & 0x3F) * (1.0f/63.0f); int shinyBits = (P->header[6] >> 5) & 3; - float shininess = pow(2.0f, 1+shinyBits); + float shininess = std::pow(2.0f, 1.f + shinyBits); if (!(P->header[0]&0x80)) //|| (shininess == 0)) // bit 0x80 seems to enable specular lighting { specularCoefficient = 0.; // disable diff --git a/Src/Model3/IEmulator.h b/Src/Model3/IEmulator.h index e1f2942..46ab331 100644 --- a/Src/Model3/IEmulator.h +++ b/Src/Model3/IEmulator.h @@ -163,7 +163,7 @@ public: virtual void AttachInputs(CInputs *InputsPtr) = 0; /* - * AttachInputs(InputsPtr): + * AttachOutputs(InputsPtr): * * Attaches OSD-managed outputs (cabinet lamps, etc.) * diff --git a/Src/Model3/Model3.cpp b/Src/Model3/Model3.cpp index 6d6811c..5250c32 100644 --- a/Src/Model3/Model3.cpp +++ b/Src/Model3/Model3.cpp @@ -472,6 +472,11 @@ UINT8 CModel3::ReadInputs(unsigned reg) } return data; + case 0x10: // Drive board + return OutputRegister[0]; + case 0x14: // Lamps + return OutputRegister[1]; + case 0x0C: // game-specific inputs data = 0xFF; @@ -651,9 +656,11 @@ void CModel3::WriteInputs(unsigned reg, UINT8 data) break; case 0x10: // Drive board - DriveBoard->Write(data); + if (DriveBoard->IsAttached()) + DriveBoard->Write(data); if (NULL != Outputs) // TODO - check gameInputs Outputs->SetValue(OutputRawDrive, data); + OutputRegister[0] = data; break; case 0x14: // Lamp outputs (Daytona/Scud Race/Sega Rally/Le Mans 24) @@ -667,6 +674,7 @@ void CModel3::WriteInputs(unsigned reg, UINT8 data) Outputs->SetValue(OutputLampLeader, !!(data&0x80)); Outputs->SetValue(OutputRawLamps, data); } + OutputRegister[1] = data; break; case 0x24: // Serial FIFO 1 @@ -2017,7 +2025,8 @@ void CModel3::RunFrame(void) } timings.frameTicks = CThread::GetTicks() - start; - + // Frame counter + timings.frameId++; return; ThreadError: @@ -2810,7 +2819,8 @@ void CModel3::Reset(void) NetBoard->Reset(); #endif timings.frameTicks = 0; - + timings.frameId = 0; + DebugLog("Model 3 reset\n"); } @@ -3176,7 +3186,7 @@ INetBoard *CModel3::GetNetBoard(void) } #endif -CModel3::CModel3(const Util::Config::Node &config) +CModel3::CModel3(Util::Config::Node &config) : m_config(config), m_multiThreaded(config["MultiThreaded"].ValueAs()), m_gpuMultiThreaded(config["GPUMultiThreaded"].ValueAs()), diff --git a/Src/Model3/Model3.h b/Src/Model3/Model3.h index fe6471a..07a515c 100644 --- a/Src/Model3/Model3.h +++ b/Src/Model3/Model3.h @@ -63,6 +63,7 @@ struct FrameTimings UINT32 netTicks; #endif UINT32 frameTicks; + UINT64 frameId; }; /* @@ -95,6 +96,8 @@ public: void AttachInputs(CInputs *InputsPtr); void AttachOutputs(COutputs *OutputsPtr); bool Init(void); + // For Scripting tweaks + Util::Config::Node& GetConfig() { return this->m_config; } // IPCIDevice interface UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width); @@ -184,7 +187,7 @@ public: * config Run-time configuration. The reference should be held because * this changes at run-time. */ - CModel3(const Util::Config::Node &config); + CModel3(Util::Config::Node &config); ~CModel3(void); /* @@ -228,7 +231,7 @@ private: int RunDriveBoardThread(void); // Runs drive board thread (sync'd in step with render thread) // Runtime configuration - const Util::Config::Node &m_config; + Util::Config::Node &m_config; bool m_multiThreaded; bool m_gpuMultiThreaded; @@ -262,6 +265,7 @@ private: UINT8 *driveROM; // 32 KB drive board ROM (Z80 program) (optional) UINT8 *netRAM; // 64 KB RAM UINT8 *netBuffer; // 128 KB buffer + UINT8 OutputRegister[2]; // Input/output register for driveboard and lamps // Banked CROM UINT8 *cromBank; // currently mapped in CROM bank @@ -321,7 +325,6 @@ private: INetBoard *NetBoard; // Net board bool m_runNetBoard; #endif - }; diff --git a/Src/Model3/SoundBoard.cpp b/Src/Model3/SoundBoard.cpp index 52f0c30..2ae6cff 100644 --- a/Src/Model3/SoundBoard.cpp +++ b/Src/Model3/SoundBoard.cpp @@ -350,16 +350,25 @@ bool CSoundBoard::RunFrame(void) } else { - memset(audioL, 0, 44100/60*sizeof(INT16)); - memset(audioR, 0, 44100/60*sizeof(INT16)); + memset(audioFL, 0, 44100/60*sizeof(INT16)); + memset(audioFR, 0, 44100/60*sizeof(INT16)); + memset(audioRL, 0, 44100/60*sizeof(INT16)); + memset(audioRR, 0, 44100/60*sizeof(INT16)); } // Run DSB and mix with existing audio - if (NULL != DSB) - DSB->RunFrame(audioL, audioR); + if (NULL != DSB) { + // Will need to mix with proper front, rear channels or both (game specific) + bool mixDSBWithFront = true; // Everything to front channels for now + // Case "both" not handled for now + if (mixDSBWithFront) + DSB->RunFrame(audioFL, audioFR); + else + DSB->RunFrame(audioRL, audioRR); + } // Output the audio buffers - bool bufferFull = OutputAudio(44100/60, audioL, audioR, m_config["FlipStereo"].ValueAs()); + bool bufferFull = OutputAudio(44100/60, audioFL, audioFR, audioRL, audioRR, m_config["FlipStereo"].ValueAs()); #ifdef SUPERMODEL_LOG_AUDIO // Output to binary file @@ -446,9 +455,13 @@ void CSoundBoard::AttachDSB(CDSB *DSBPtr) // Offsets of memory regions within sound board's pool #define OFFSET_RAM1 0 // 1 MB SCSP1 RAM #define OFFSET_RAM2 0x100000 // 1 MB SCSP2 RAM -#define OFFSET_AUDIO_LEFT 0x200000 // 1470 bytes (16 bits, 44.1 KHz, 1/60th second) left audio channel -#define OFFSET_AUDIO_RIGHT 0x2005BE // 1470 bytes right audio channel -#define MEMORY_POOL_SIZE (0x100000 + 0x100000 + 0x5BE + 0x5BE) +#define LENGTH_CHANNEL_BUFFER 0x5BE +#define OFFSET_AUDIO_FRONTLEFT 0x200000 // 1470 bytes (16 bits, 44.1 KHz, 1/60th second) left audio channel +#define OFFSET_AUDIO_FRONTRIGHT (OFFSET_AUDIO_FRONTLEFT + LENGTH_CHANNEL_BUFFER) // 1470 bytes right audio channel +#define OFFSET_AUDIO_REARLEFT (OFFSET_AUDIO_FRONTRIGHT + LENGTH_CHANNEL_BUFFER) // 1470 bytes (16 bits, 44.1 KHz, 1/60th second) left audio channel +#define OFFSET_AUDIO_REARRIGHT (OFFSET_AUDIO_REARLEFT + LENGTH_CHANNEL_BUFFER) // 1470 bytes right audio channel + +#define MEMORY_POOL_SIZE (0x100000 + 0x100000 + 4*LENGTH_CHANNEL_BUFFER) bool CSoundBoard::Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr) { @@ -469,9 +482,11 @@ bool CSoundBoard::Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr) // Set up memory pointers ram1 = &memoryPool[OFFSET_RAM1]; ram2 = &memoryPool[OFFSET_RAM2]; - audioL = (INT16 *) &memoryPool[OFFSET_AUDIO_LEFT]; - audioR = (INT16 *) &memoryPool[OFFSET_AUDIO_RIGHT]; - + audioFL = (INT16*)&memoryPool[OFFSET_AUDIO_FRONTLEFT]; + audioFR = (INT16*)&memoryPool[OFFSET_AUDIO_FRONTRIGHT]; + audioRL = (INT16*)&memoryPool[OFFSET_AUDIO_REARLEFT]; + audioRR = (INT16*)&memoryPool[OFFSET_AUDIO_REARRIGHT]; + // Initialize 68K core M68KSetContext(&M68K); M68KInit(); @@ -480,7 +495,7 @@ bool CSoundBoard::Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr) M68KGetContext(&M68K); // Initialize SCSPs - SCSP_SetBuffers(audioL, audioR, 44100/60); + SCSP_SetBuffers(audioFL, audioFR, audioRL, audioRR, 44100/60); SCSP_SetCB(SCSP68KRunCallback, SCSP68KIRQCallback); if (OKAY != SCSP_Init(m_config, 2)) return FAIL; @@ -514,8 +529,10 @@ CSoundBoard::CSoundBoard(const Util::Config::Node &config) memoryPool = NULL; ram1 = NULL; ram2 = NULL; - audioL = NULL; - audioR = NULL; + audioFL = NULL; + audioFR = NULL; + audioRL = NULL; + audioRR = NULL; soundROM = NULL; sampleROM = NULL; @@ -553,8 +570,10 @@ CSoundBoard::~CSoundBoard(void) } ram1 = NULL; ram2 = NULL; - audioL = NULL; - audioR = NULL; + audioFL = NULL; + audioFR = NULL; + audioRL = NULL; + audioRR = NULL; soundROM = NULL; sampleROM = NULL; diff --git a/Src/Model3/SoundBoard.h b/Src/Model3/SoundBoard.h index f840d12..a8730ec 100644 --- a/Src/Model3/SoundBoard.h +++ b/Src/Model3/SoundBoard.h @@ -203,7 +203,8 @@ private: UINT8 ctrlReg; // control register: ROM banking // Audio - INT16 *audioL, *audioR; // left and right audio channels (1/60th second, 44.1 KHz) + INT16* audioFL, * audioFR; // left and right front audio channels (1/60th second, 44.1 KHz) + INT16* audioRL, * audioRR; // left and right rear audio channels (1/60th second, 44.1 KHz) }; diff --git a/Src/OSD/Audio.h b/Src/OSD/Audio.h index 7f732b4..c49bdd2 100755 --- a/Src/OSD/Audio.h +++ b/Src/OSD/Audio.h @@ -25,36 +25,39 @@ * Header file for OS-dependent audio playback interface. */ -#ifndef INCLUDED_AUDIO_H -#define INCLUDED_AUDIO_H - -#include "Types.h" +#ifndef INCLUDED_AUDIO_H +#define INCLUDED_AUDIO_H + +#include "Types.h" +#include "Util/NewConfig.h" +#include "Game.h" typedef void (*AudioCallbackFPtr)(void *data); extern void SetAudioCallback(AudioCallbackFPtr callback, void *data); extern void SetAudioEnabled(bool enabled); +extern void SetAudioType(Game::AudioTypes type); /* * OpenAudio() * * Initializes the audio system. */ -extern bool OpenAudio(); +extern bool OpenAudio(const Util::Config::Node& config); /* * OutputAudio(unsigned numSamples, *INT16 leftBuffer, *INT16 rightBuffer) * * Sends a chunk of two-channel audio with the given number of samples to the audio system. */ -extern bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, bool flipStereo); +extern bool OutputAudio(unsigned numSamples, INT16* leftFrontBuffer, INT16* rightFrontBuffer, INT16* leftRearBuffer, INT16* rightRearBuffer, bool flipStereo); /* * CloseAudio() * * Shuts down the audio system. */ -extern void CloseAudio(); - -#endif // INCLUDED_AUDIO_H +extern void CloseAudio(); + +#endif // INCLUDED_AUDIO_H diff --git a/Src/OSD/Outputs.cpp b/Src/OSD/Outputs.cpp index a944e90..2fd16e5 100755 --- a/Src/OSD/Outputs.cpp +++ b/Src/OSD/Outputs.cpp @@ -104,3 +104,12 @@ void COutputs::SetValue(EOutputs output, UINT8 value) if (firstSet || value != prevValue) SendOutput(output, prevValue, value); } + +bool COutputs::HasValue(EOutputs output) +{ + int idx = (unsigned)output; + if (idx < 0 || idx >= NUM_OUTPUTS) + return false; + return !m_first[output]; +} + diff --git a/Src/OSD/Outputs.h b/Src/OSD/Outputs.h index e2ed37a..23e3066 100755 --- a/Src/OSD/Outputs.h +++ b/Src/OSD/Outputs.h @@ -124,6 +124,14 @@ public: */ void SetValue(EOutputs output, UINT8 value); + /* + * HasValue(EOutputs output) + * + * Returns if the value has been set at least once, + * meaning it is used by the game. + */ + bool HasValue(EOutputs output); + protected: /* * COutputs(): diff --git a/Src/OSD/SDL/Audio.cpp b/Src/OSD/SDL/Audio.cpp index 725dbd5..b8b71cc 100755 --- a/Src/OSD/SDL/Audio.cpp +++ b/Src/OSD/SDL/Audio.cpp @@ -19,16 +19,39 @@ ** with Supermodel. If not, see . **/ -/* - * Audio.cpp - * - * SDL audio playback. Implements the OSD audio interface. - * - * Buffer sizes and read/write positions must be sample-aligned. A sample is - * defined to encompass both channels so for, e.g., 16-bit audio as used here, - * a sample is 4 bytes. Static assertions are employed to ensure that the - * initial set up of the buffer is correct. - */ + /* + * Audio.cpp + * + * SDL audio playback. Implements the OSD audio interface. + * + * Buffer sizes and read/write positions must be sample-aligned. A sample is + * defined to encompass both channels so for, e.g., 16-bit audio as used here, + * a sample is 4 bytes. Static assertions are employed to ensure that the + * initial set up of the buffer is correct. + * + * Model 3 Audio is always 4 channels. SCSP1 is usually for each front + * channels (on CN8 connector) and SCSP2 for rear channels (on CN7). + * The downmix to 2 channels will be performed here in case supermodel audio + * subsystem does not allow such playback. + * The default DSB board is supposed to be plug and mixed with the front output + * channel. The rear channel is usually plugged to the gullbow speakers that + * are present in most model 3 racing cabinets, while front speakers are only + * present in Daytona2, Scud Race, Sega Rally 2. + * As each cabinet as its own wiring, with different mixing, the games.xml + * database will provide which can of mixing should be applied for each game. + * + * From twin uk cabinet diagrams, at least: + * - lemans24: only stereo on rear speakers (gullbox) on front channels SCSP1/CN8. + * - scud race: DSB mixed with SCSP2/CN7 for rear (gullbox) speakers, front + * connected to SCSP1/CN8. + * - daytona2: front on SCSP1/CN8, rear on SCSP2/CN7 + * - srally2: SCSP2/CN7 gives left/right, and SCSP1/CN8 is split in 2 channels: + * mono front, mono rear. These two channels are them mixed with L/R to give + * the quad output. + * - oceanhun: SCSP1/CN8 and SCSP2/CN7 are mixed together for stereo output. + * Others are unknown so far, but it is expected that most Step 2.x games should + * have quad output. + */ #include "Audio.h" @@ -38,16 +61,38 @@ #include #include -// Model3 audio output is 44.1KHz 2-channel sound and frame rate is 60fps -#define SAMPLE_RATE 44100 -#define NUM_CHANNELS 2 -#define SUPERMODEL_FPS 60 + // Model3 audio output is 44.1KHz 4-channel sound and frame rate is 60fps +#define SAMPLE_RATE_M3 (44100) +#define SUPERMODEL_FPS (60.0f) +#define MODEL3_FPS (57.53f) -#define BYTES_PER_SAMPLE (NUM_CHANNELS * sizeof(INT16)) -#define SAMPLES_PER_FRAME (SAMPLE_RATE / SUPERMODEL_FPS) -#define BYTES_PER_FRAME (SAMPLES_PER_FRAME * BYTES_PER_SAMPLE) +#define MAX_SND_FREQ (75) +#define MIN_SND_FREQ (45) +#define MAX_LATENCY (100) -#define MAX_LATENCY 100 +#define NUM_CHANNELS_M3 (4) + +Game::AudioTypes AudioType; +int nbHostAudioChannels = NUM_CHANNELS_M3; // Number of channels on host + +#define SAMPLES_PER_FRAME_M3 (INT32)(SAMPLE_RATE_M3 / MODEL3_FPS) + +#define BYTES_PER_SAMPLE_M3 (NUM_CHANNELS_M3 * sizeof(INT16)) +#define BYTES_PER_FRAME_M3 (SAMPLES_PER_FRAME_M3 * BYTES_PER_SAMPLE_M3) + + +static int samples_per_frame_host = SAMPLES_PER_FRAME_M3; +static int bytes_per_sample_host = BYTES_PER_SAMPLE_M3; +static int bytes_per_frame_host = BYTES_PER_FRAME_M3; + +// Balance percents for mixer +float BalanceLeftRight = 0; // 0 mid balance, 100: left only, -100:right only +float BalanceFrontRear = 0; // 0 mid balance, 100: front only, -100:right only +// Mixer factor (depends on values above) +float balanceFactorFrontLeft = 1.0f; +float balanceFactorFrontRight = 1.0f; +float balanceFactorRearLeft = 1.0f; +float balanceFactorRearRight = 1.0f; static bool enabled = true; // True if sound output is enabled static constexpr unsigned latency = 20; // Audio latency to use (ie size of audio buffer) as percentage of max buffer size @@ -56,7 +101,7 @@ static constexpr bool underRunLoop = true; // True if should loop back to beg static constexpr unsigned playSamples = 512; // Size (in samples) of callback play buffer static UINT32 audioBufferSize = 0; // Size (in bytes) of audio buffer -static INT8 *audioBuffer = NULL; // Audio buffer +static INT8* audioBuffer = NULL; // Audio buffer static UINT32 writePos = 0; // Current position at which writing into buffer static UINT32 playPos = 0; // Current position at which playing data in buffer via callback @@ -67,378 +112,572 @@ static unsigned underRuns = 0; // Number of buffer under-runs that have occ static unsigned overRuns = 0; // Number of buffer over-runs that have occured static AudioCallbackFPtr callback = NULL; // Pointer to audio callback that is called when audio buffer is less than half empty -static void *callbackData = NULL; // Pointer to data to be passed to audio callback when it is called +static void* callbackData = NULL; // Pointer to data to be passed to audio callback when it is called -void SetAudioCallback(AudioCallbackFPtr newCallback, void *newData) +static const Util::Config::Node* s_config = 0; + + +void SetAudioCallback(AudioCallbackFPtr newCallback, void* newData) { - // Lock audio whilst changing callback pointers - SDL_LockAudio(); + // Lock audio whilst changing callback pointers + SDL_LockAudio(); - callback = newCallback; - callbackData = newData; + callback = newCallback; + callbackData = newData; - SDL_UnlockAudio(); + SDL_UnlockAudio(); } void SetAudioEnabled(bool newEnabled) { - enabled = newEnabled; + enabled = newEnabled; } -static void PlayCallback(void *data, Uint8 *stream, int len) +/// +/// Set game audio mixing type +/// +/// +void SetAudioType(Game::AudioTypes type) { - //printf("PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", - // len, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); + AudioType = type; +} - // Get current write position and adjust it if write has wrapped but play position has not - UINT32 adjWritePos = writePos; - if (writeWrapped) - adjWritePos += audioBufferSize; +static INT16 AddAndClampINT16(INT32 x, INT32 y) +{ + INT32 sum = x + y; + if (sum > INT16_MAX) { + sum = INT16_MAX; + } + if (sum < INT16_MIN) { + sum = INT16_MIN; + } + return (INT16)sum; +} - // Check if play position overlaps write position (ie buffer under-run) +static void PlayCallback(void* data, Uint8* stream, int len) +{ + //printf("PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", + // len, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); + + // Get current write position and adjust it if write has wrapped but play position has not + UINT32 adjWritePos = writePos; + if (writeWrapped) + adjWritePos += audioBufferSize; + + // Check if play position overlaps write position (ie buffer under-run) if (playPos + len > adjWritePos) { - underRuns++; + underRuns++; - //printf("Audio buffer under-run #%u in PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", - // underRuns, len, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); + //printf("Audio buffer under-run #%u in PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", + // underRuns, len, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); - // See what action to take on under-run + // See what action to take on under-run if (underRunLoop) { - // If loop, then move play position back to beginning of data in buffer - playPos = adjWritePos + BYTES_PER_FRAME; + // If loop, then move play position back to beginning of data in buffer + playPos = adjWritePos + bytes_per_frame_host; - // Check if play position has moved past end of buffer - if (playPos >= audioBufferSize) - // If so, wrap it around to beginning again (but keep write wrapped flag as before) - playPos -= audioBufferSize; - else - // Otherwise, set write wrapped flag as will now appear as if write has wrapped but play position has not - writeWrapped = true; + // Check if play position has moved past end of buffer + if (playPos >= audioBufferSize) + // If so, wrap it around to beginning again (but keep write wrapped flag as before) + playPos -= audioBufferSize; + else + // Otherwise, set write wrapped flag as will now appear as if write has wrapped but play position has not + writeWrapped = true; } else { - // Otherwise, just copy silence to audio output stream and exit - memset(stream, 0, len); - return; - } - } + // Otherwise, just copy silence to audio output stream and exit + memset(stream, 0, len); + return; + } + } - INT8* src1; - INT8* src2; - UINT32 len1; - UINT32 len2; + INT8* src1; + INT8* src2; + UINT32 len1; + UINT32 len2; - // Check if play region extends past end of buffer + // Check if play region extends past end of buffer if (playPos + len > audioBufferSize) { - // If so, split play region into two - src1 = audioBuffer + playPos; - src2 = audioBuffer; - len1 = audioBufferSize - playPos; - len2 = len - len1; + // If so, split play region into two + src1 = audioBuffer + playPos; + src2 = audioBuffer; + len1 = audioBufferSize - playPos; + len2 = len - len1; } else { - // Otherwise, just copy whole region - src1 = audioBuffer + playPos; - src2 = 0; - len1 = len; - len2 = 0; - } + // Otherwise, just copy whole region + src1 = audioBuffer + playPos; + src2 = 0; + len1 = len; + len2 = 0; + } - // Check if audio is enabled + // Check if audio is enabled if (enabled) { - // If so, copy play region into audio output stream - memcpy(stream, src1, len1); + // If so, copy play region into audio output stream + memcpy(stream, src1, len1); - // Also, if not looping on under-runs then blank region out - if (!underRunLoop) - memset(src1, 0, len1); + // Also, if not looping on under-runs then blank region out + if (!underRunLoop) + memset(src1, 0, len1); if (len2) { - // If region was split into two, copy second half into audio output stream as well - memcpy(stream + len1, src2, len2); + // If region was split into two, copy second half into audio output stream as well + memcpy(stream + len1, src2, len2); - // Also, if not looping on under-runs then blank region out - if (!underRunLoop) - memset(src2, 0, len2); - } + // Also, if not looping on under-runs then blank region out + if (!underRunLoop) + memset(src2, 0, len2); + } } else - // Otherwise, just copy silence to audio output stream - memset(stream, 0, len); + // Otherwise, just copy silence to audio output stream + memset(stream, 0, len); - // Move play position forward for next time - playPos += len; + // Move play position forward for next time + playPos += len; - bool bufferFull = adjWritePos + 2 * BYTES_PER_FRAME > playPos + audioBufferSize; + bool bufferFull = adjWritePos + 2 * bytes_per_frame_host > playPos + audioBufferSize; - // Check if play position has moved past end of buffer + // Check if play position has moved past end of buffer if (playPos >= audioBufferSize) { - // If so, wrap it around to beginning again and reset write wrapped flag - playPos -= audioBufferSize; - writeWrapped = false; - } + // If so, wrap it around to beginning again and reset write wrapped flag + playPos -= audioBufferSize; + writeWrapped = false; + } - // If buffer is not full then call audio callback - if (callback && !bufferFull) - callback(callbackData); + // If buffer is not full then call audio callback + if (callback && !bufferFull) + callback(callbackData); } -static void MixChannels(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, void *dest, bool flipStereo) +static void MixChannels(unsigned numSamples, INT16* leftFrontBuffer, INT16* rightFrontBuffer, INT16* leftRearBuffer, INT16* rightRearBuffer, void* dest, bool flipStereo) { - INT16 *p = (INT16*)dest; + INT16* p = (INT16*)dest; -#if (NUM_CHANNELS == 1) - for (unsigned i = 0; i < numSamples; i++) - *p++ = leftBuffer[i] + rightBuffer[i]; // TODO: these should probably be clipped! -#else - if (flipStereo) // swap left and right channels - { - for (unsigned i = 0; i < numSamples; i++) - { - *p++ = rightBuffer[i]; - *p++ = leftBuffer[i]; - } - } - else // correct stereo - { - for (unsigned i = 0; i < numSamples; i++) - { - *p++ = leftBuffer[i]; - *p++ = rightBuffer[i]; - } - } -#endif // NUM_CHANNELS + if (nbHostAudioChannels == 1) { + for (unsigned i = 0; i < numSamples; i++) { + INT16 monovalue = AddAndClampINT16( + (INT32)(leftFrontBuffer[i] * balanceFactorFrontLeft) + (INT32)(rightFrontBuffer[i] * balanceFactorFrontRight), + (INT32)(leftRearBuffer[i] * balanceFactorRearLeft) + (INT32)(rightRearBuffer[i] * balanceFactorRearRight)); + *p++ = monovalue; + } + } else { + // Flip again left/right if configured in audio + switch (AudioType) { + case Game::STEREO_RL: + case Game::QUAD_1_FRL_2_RRL: + case Game::QUAD_1_RRL_2_FRL: + flipStereo = !flipStereo; + break; + } + + // Now order channels according to audio type + if (nbHostAudioChannels == 2) { + for (unsigned i = 0; i < numSamples; i++) { + INT16 leftvalue = AddAndClampINT16((INT32)(leftFrontBuffer[i] * balanceFactorFrontLeft), (INT32)(leftRearBuffer[i] * balanceFactorRearLeft)); + INT16 rightvalue = AddAndClampINT16((INT32)(rightFrontBuffer[i]*balanceFactorFrontRight), (INT32)(rightRearBuffer[i]*balanceFactorRearRight)); + if (flipStereo) // swap left and right channels + { + *p++ = rightvalue; + *p++ = leftvalue; + } else { + *p++ = leftvalue; + *p++ = rightvalue; + } + } + } else if (nbHostAudioChannels == 4) { + for (unsigned i = 0; i < numSamples; i++) { + INT16 frontLeftValue = (INT16)(leftFrontBuffer[i]*balanceFactorFrontLeft); + INT16 frontRightValue = (INT16)(rightFrontBuffer[i]*balanceFactorFrontRight); + INT16 rearLeftValue = (INT16)(leftRearBuffer[i]*balanceFactorRearLeft); + INT16 rearRightValue = (INT16)(rightRearBuffer[i]*balanceFactorRearRight); + + // Check game audio type + switch (AudioType) { + case Game::MONO: { + INT16 monovalue = AddAndClampINT16(AddAndClampINT16(frontLeftValue, frontRightValue), AddAndClampINT16(rearLeftValue, rearRightValue)); + *p++ = monovalue; + *p++ = monovalue; + *p++ = monovalue; + *p++ = monovalue; + } break; + + case Game::STEREO_LR: + case Game::STEREO_RL: { + INT16 leftvalue = AddAndClampINT16(frontLeftValue, frontRightValue); + INT16 rightvalue = AddAndClampINT16(rearLeftValue, rearRightValue); + if (flipStereo) // swap left and right channels + { + *p++ = rightvalue; + *p++ = leftvalue; + *p++ = rightvalue; + *p++ = leftvalue; + } else { + *p++ = leftvalue; + *p++ = rightvalue; + *p++ = leftvalue; + *p++ = rightvalue; + } + } break; + + case Game::QUAD_1_FLR_2_RLR: + case Game::QUAD_1_FRL_2_RRL: { + // Normal channels Front Left/Right then Rear Left/Right + if (flipStereo) // swap left and right channels + { + *p++ = frontRightValue; + *p++ = frontLeftValue; + *p++ = rearRightValue; + *p++ = rearLeftValue; + } else { + *p++ = frontLeftValue; + *p++ = frontRightValue; + *p++ = rearLeftValue; + *p++ = rearRightValue; + } + } break; + + case Game::QUAD_1_RLR_2_FLR: + case Game::QUAD_1_RRL_2_FRL: + // Reversed channels Front/Rear Left then Front/Rear Right + if (flipStereo) // swap left and right channels + { + *p++ = rearRightValue; + *p++ = rearLeftValue; + *p++ = frontRightValue; + *p++ = frontLeftValue; + } else { + *p++ = rearLeftValue; + *p++ = rearRightValue; + *p++ = frontLeftValue; + *p++ = frontRightValue; + } + break; + + case Game::QUAD_1_LR_2_FR_MIX: + // Split mix: one goes to left/right, other front/rear (mono) + // =>Remix all! + INT16 newfrontLeftValue = AddAndClampINT16(frontLeftValue, rearLeftValue); + INT16 newfrontRightValue = AddAndClampINT16(frontLeftValue, rearRightValue); + INT16 newrearLeftValue = AddAndClampINT16(frontRightValue, rearLeftValue); + INT16 newrearRightValue = AddAndClampINT16(frontRightValue, rearRightValue); + + if (flipStereo) // swap left and right channels + { + *p++ = newfrontRightValue; + *p++ = newfrontLeftValue; + *p++ = newrearRightValue; + *p++ = newrearLeftValue; + } else { + *p++ = newfrontLeftValue; + *p++ = newfrontRightValue; + *p++ = newrearLeftValue; + *p++ = newrearRightValue; + } + break; + } + } + } + } } /* static void LogAudioInfo(SDL_AudioSpec *fmt) { - InfoLog("Audio device information:"); - InfoLog(" Frequency: %d", fmt->freq); - InfoLog(" Channels: %d", fmt->channels); - InfoLog("Sample Format: %d", fmt->format); - InfoLog(""); + InfoLog("Audio device information:"); + InfoLog(" Frequency: %d", fmt->freq); + InfoLog(" Channels: %d", fmt->channels); + InfoLog("Sample Format: %d", fmt->format); + InfoLog(""); } */ -bool OpenAudio() +/// +/// Prepare audio subsystem on host. +/// The requested channels is deduced, and SDL will make sure it is compatible with this. +/// +/// +/// +bool OpenAudio(const Util::Config::Node& config) { - // Initialize SDL audio sub-system - if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) - return ErrorLog("Unable to initialize SDL audio sub-system: %s\n", SDL_GetError()); + s_config = &config; + // Initialize SDL audio sub-system + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) + return ErrorLog("Unable to initialize SDL audio sub-system: %s\n", SDL_GetError()); - // Set up audio specification - SDL_AudioSpec fmt; - memset(&fmt, 0, sizeof(SDL_AudioSpec)); - fmt.freq = SAMPLE_RATE; - fmt.channels = NUM_CHANNELS; - fmt.format = AUDIO_S16SYS; - fmt.samples = playSamples; - fmt.callback = PlayCallback; + // Number of channels requested in config (default is 4) + nbHostAudioChannels = (int)s_config->Get("NbSoundChannels").ValueAs(); - // Force SDL to use the format we requested; it will convert if necessary - if (SDL_OpenAudio(&fmt, nullptr) < 0) - return ErrorLog("Unable to open 44.1KHz 2-channel audio with SDL: %s\n", SDL_GetError()); + // If game is only stereo or mono, enforce host to reduce number of channels + switch (AudioType) { + case Game::MONO: + nbHostAudioChannels = std::min(nbHostAudioChannels, 1); + break; + case Game::STEREO_LR: + case Game::STEREO_RL: + nbHostAudioChannels = std::min(nbHostAudioChannels, 2); + break; + } + // Mixer Balance + float balancelr = (float)s_config->Get("BalanceLeftRight").ValueAs(); + if (balancelr < -100.0f) + balancelr = -100.0f; + else if (balancelr > 100.0f) + balancelr = 100.0f; + balancelr *= 0.01f; + BalanceLeftRight = balancelr; - // Create audio buffer - constexpr uint32_t bufferSize = SAMPLE_RATE * BYTES_PER_SAMPLE * latency / MAX_LATENCY; - static_assert(bufferSize % BYTES_PER_SAMPLE == 0, "must be an integer multiple of the sample size"); - audioBufferSize = bufferSize; - - int minBufferSize = 3 * BYTES_PER_FRAME; - audioBufferSize = std::max(minBufferSize, audioBufferSize); - audioBuffer = new(std::nothrow) INT8[audioBufferSize]; - if (audioBuffer == NULL) - { - float audioBufMB = (float)audioBufferSize / (float)0x100000; - return ErrorLog("Insufficient memory for audio latency buffer (need %1.1f MB).", audioBufMB); - } - memset(audioBuffer, 0, sizeof(INT8) * audioBufferSize); + float balancefr = (float)s_config->Get("BalanceFrontRear").ValueAs(); + if (balancefr < -100.0f) + balancefr = -100.0f; + else if (balancefr > 100.0f) + balancefr = 100.0f; + balancefr *= 0.01f; + BalanceFrontRear = balancefr; - // Set initial play position to be beginning of buffer and initial write position to be half-way into buffer - playPos = 0; - constexpr uint32_t endOfBuffer = bufferSize - BYTES_PER_FRAME; - constexpr uint32_t midpointAfterFirstFrameUnaligned = BYTES_PER_FRAME + (bufferSize - BYTES_PER_FRAME) / 2; - constexpr uint32_t extraPaddingNeeded = (BYTES_PER_SAMPLE - midpointAfterFirstFrameUnaligned % BYTES_PER_SAMPLE) % BYTES_PER_SAMPLE; - constexpr uint32_t midpointAfterFirstFrame = midpointAfterFirstFrameUnaligned + extraPaddingNeeded; - static_assert(endOfBuffer % BYTES_PER_SAMPLE == 0, "make sure we are aligned to a sample boundary otherwise underrun/overrun adjustment will end up shifting playback by one channel causing stereo to flip"); - static_assert(midpointAfterFirstFrame % BYTES_PER_SAMPLE == 0,"error"); - writePos = std::min(endOfBuffer, midpointAfterFirstFrame); - writeWrapped = false; + balanceFactorFrontLeft = (BalanceLeftRight < 0.f ? 1.f + BalanceLeftRight : 1.f) * (BalanceFrontRear < 0 ? 1.f + BalanceFrontRear : 1.f); + balanceFactorFrontRight = (BalanceLeftRight > 0.f ? 1.f - BalanceLeftRight : 1.f) * (BalanceFrontRear < 0 ? 1.f + BalanceFrontRear : 1.f); + balanceFactorRearLeft = (BalanceLeftRight < 0.f ? 1.f + BalanceLeftRight : 1.f) * (BalanceFrontRear > 0 ? 1.f - BalanceFrontRear : 1.f); + balanceFactorRearRight = (BalanceLeftRight > 0.f ? 1.f - BalanceLeftRight : 1.f) * (BalanceFrontRear > 0 ? 1.f - BalanceFrontRear : 1.f); - // Reset counters - underRuns = 0; - overRuns = 0; + // Set up audio specification + SDL_AudioSpec desired; + memset(&desired, 0, sizeof(SDL_AudioSpec)); + desired.freq = SAMPLE_RATE_M3; + // Number of host channels to use (choice limited to 1,2,4) + desired.channels = nbHostAudioChannels; + desired.format = AUDIO_S16SYS; + desired.samples = playSamples; + desired.callback = PlayCallback; - // Start audio playing - SDL_PauseAudio(0); - return OKAY; + // Now force SDL to use the format we requested (nullptr); it will convert if necessary + if (SDL_OpenAudio(&desired, nullptr) < 0) { + if (desired.channels==2) { + return ErrorLog("Unable to open 44.1KHz 2-channel audio with SDL: %s\n", SDL_GetError()); + } else if (desired.channels==4) { + return ErrorLog("Unable to open 44.1KHz 4-channel audio with SDL: %s\n", SDL_GetError()); + } else { + return ErrorLog("Unable to open 44.1KHz channel audio with SDL: %s\n", SDL_GetError()); + } + } + + float soundFreq_Hz = (float)s_config->Get("SoundFreq").ValueAs(); + if (soundFreq_Hz>MAX_SND_FREQ) + soundFreq_Hz = MAX_SND_FREQ; + if (soundFreq_Hz(minBufferSize, audioBufferSize); + audioBuffer = new(std::nothrow) INT8[audioBufferSize]; + if (audioBuffer == NULL) { + float audioBufMB = (float)audioBufferSize / (float)0x100000; + return ErrorLog("Insufficient memory for audio latency buffer (need %1.1f MB).", audioBufMB); + } + memset(audioBuffer, 0, sizeof(INT8) * audioBufferSize); + + // Set initial play position to be beginning of buffer and initial write position to be half-way into buffer + playPos = 0; + uint32_t endOfBuffer = bufferSize - bytes_per_frame_host; + uint32_t midpointAfterFirstFrameUnaligned = bytes_per_frame_host + (bufferSize - bytes_per_frame_host) / 2; + uint32_t extraPaddingNeeded = (bytes_per_frame_host - midpointAfterFirstFrameUnaligned % bytes_per_frame_host) % bytes_per_frame_host; + uint32_t midpointAfterFirstFrame = midpointAfterFirstFrameUnaligned + extraPaddingNeeded; + if (!((endOfBuffer % (nbHostAudioChannels*sizeof(INT16))) == 0)) { + return ErrorLog("must be an integer multiple of the sample size\n"); + } + if (!((midpointAfterFirstFrame % nbHostAudioChannels*sizeof(INT16)) == 0)) { + return ErrorLog("must be an integer multiple of the sample size\n"); + } + + writePos = std::min(endOfBuffer, midpointAfterFirstFrame); + writeWrapped = false; + + // Reset counters + underRuns = 0; + overRuns = 0; + + // Start audio playing + SDL_PauseAudio(0); + return OKAY; } -bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, bool flipStereo) +bool OutputAudio(unsigned numSamples, INT16* leftFrontBuffer, INT16* rightFrontBuffer, INT16* leftRearBuffer, INT16* rightRearBuffer, bool flipStereo) { - //printf("OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", - // numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); + //printf("OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", + // numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); - UINT32 bytesRemaining; - UINT32 bytesToCopy; - INT16 *src; + UINT32 bytesRemaining; + UINT32 bytesToCopy; + INT16* src; - // Number of samples should never be more than max number of samples per frame - if (numSamples > SAMPLES_PER_FRAME) - numSamples = SAMPLES_PER_FRAME; + // Number of samples should never be more than max number of samples per frame + if (numSamples > (unsigned)samples_per_frame_host) + numSamples = samples_per_frame_host; - // Mix together left and right channels into single chunk of data - INT16 mixBuffer[NUM_CHANNELS * SAMPLES_PER_FRAME]; - MixChannels(numSamples, leftBuffer, rightBuffer, mixBuffer, flipStereo); + // Mix together left and right channels into single chunk of data + INT16 mixBuffer[NUM_CHANNELS_M3 * (SAMPLE_RATE_M3 / MIN_SND_FREQ)]; + MixChannels(numSamples, leftFrontBuffer, rightFrontBuffer, leftRearBuffer, rightRearBuffer, mixBuffer, flipStereo); - // Lock SDL audio callback so that it doesn't interfere with following code - SDL_LockAudio(); + // Lock SDL audio callback so that it doesn't interfere with following code + SDL_LockAudio(); - // Calculate number of bytes for current sound chunk - UINT32 numBytes = numSamples * BYTES_PER_SAMPLE; + // Calculate number of bytes for current sound chunk + UINT32 numBytes = numSamples * bytes_per_sample_host; - // Get end of current play region (writing must occur past this point) - UINT32 playEndPos = playPos + BYTES_PER_FRAME; + // Get end of current play region (writing must occur past this point) + UINT32 playEndPos = playPos + bytes_per_frame_host; - // Undo any wrap-around of the write position that may have occured to create following ordering: playPos < playEndPos < writePos - if (playEndPos > writePos && writeWrapped) - writePos += audioBufferSize; + // Undo any wrap-around of the write position that may have occured to create following ordering: playPos < playEndPos < writePos + if (playEndPos > writePos && writeWrapped) + writePos += audioBufferSize; - // Check if play region has caught up with write position and now overlaps it (ie buffer under-run) + // Check if play region has caught up with write position and now overlaps it (ie buffer under-run) if (playEndPos > writePos) { - underRuns++; + underRuns++; - //printf("Audio buffer under-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n", - // underRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes); + //printf("Audio buffer under-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n", + // underRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes); - // See what action to take on under-run + // See what action to take on under-run if (underRunLoop) { - // If loop, then move play position back to beginning of data in buffer - playPos = writePos + numBytes + BYTES_PER_FRAME; + // If loop, then move play position back to beginning of data in buffer + playPos = writePos + numBytes + bytes_per_frame_host; - // Check if play position has moved past end of buffer - if (playPos >= audioBufferSize) - // If so, wrap it around to beginning again (but keep write wrapped flag as before) - playPos -= audioBufferSize; + // Check if play position has moved past end of buffer + if (playPos >= audioBufferSize) + // If so, wrap it around to beginning again (but keep write wrapped flag as before) + playPos -= audioBufferSize; else { - // Otherwise, set write wrapped flag as will now appear as if write has wrapped but play position has not - writeWrapped = true; - writePos += audioBufferSize; - } + // Otherwise, set write wrapped flag as will now appear as if write has wrapped but play position has not + writeWrapped = true; + writePos += audioBufferSize; + } } else { - // Otherwise, bump write position forward in chunks until it is past end of play region + // Otherwise, bump write position forward in chunks until it is past end of play region do { - writePos += numBytes; + writePos += numBytes; } while (playEndPos > writePos); - } - } + } + } - // Check if write position has caught up with play region and now overlaps it (ie buffer over-run) - bool overRun = writePos + numBytes > playPos + audioBufferSize; + // Check if write position has caught up with play region and now overlaps it (ie buffer over-run) + bool overRun = writePos + numBytes > playPos + audioBufferSize; - bool bufferFull = writePos + 2 * BYTES_PER_FRAME > playPos + audioBufferSize; + bool bufferFull = writePos + 2 * bytes_per_frame_host > playPos + audioBufferSize; - // Move write position back to within buffer - if (writePos >= audioBufferSize) - writePos -= audioBufferSize; + // Move write position back to within buffer + if (writePos >= audioBufferSize) + writePos -= audioBufferSize; - // Handle buffer over-run + // Handle buffer over-run if (overRun) { - overRuns++; + overRuns++; - //printf("Audio buffer over-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n", - // overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes); + //printf("Audio buffer over-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n", + // overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes); - bufferFull = true; + bufferFull = true; - // Discard current chunk of data - goto Finish; - } + // Discard current chunk of data + goto Finish; + } - src = mixBuffer; - INT8 *dst1; - INT8 *dst2; - UINT32 len1; - UINT32 len2; + src = mixBuffer; + INT8* dst1; + INT8* dst2; + UINT32 len1; + UINT32 len2; - // Check if write region extends past end of buffer + // Check if write region extends past end of buffer if (writePos + numBytes > audioBufferSize) { - // If so, split write region into two - dst1 = audioBuffer + writePos; - dst2 = audioBuffer; - len1 = audioBufferSize - writePos; - len2 = numBytes - len1; + // If so, split write region into two + dst1 = audioBuffer + writePos; + dst2 = audioBuffer; + len1 = audioBufferSize - writePos; + len2 = numBytes - len1; } else { - // Otherwise, just copy whole region - dst1 = audioBuffer + writePos; - dst2 = 0; - len1 = numBytes; - len2 = 0; - } + // Otherwise, just copy whole region + dst1 = audioBuffer + writePos; + dst2 = 0; + len1 = numBytes; + len2 = 0; + } - // Copy chunk to write position in buffer - bytesRemaining = numBytes; - bytesToCopy = (bytesRemaining > len1 ? len1 : bytesRemaining); - memcpy(dst1, src, bytesToCopy); + // Copy chunk to write position in buffer + bytesRemaining = numBytes; + bytesToCopy = (bytesRemaining > len1 ? len1 : bytesRemaining); + memcpy(dst1, src, bytesToCopy); - // Adjust for number of bytes copied - bytesRemaining -= bytesToCopy; - src = (INT16*)((UINT8*)src + bytesToCopy); + // Adjust for number of bytes copied + bytesRemaining -= bytesToCopy; + src = (INT16*)((UINT8*)src + bytesToCopy); if (bytesRemaining) { - // If write region was split into two, copy second half of chunk into buffer as well - bytesToCopy = (bytesRemaining > len2 ? len2 : bytesRemaining); - memcpy(dst2, src, bytesToCopy); - } + // If write region was split into two, copy second half of chunk into buffer as well + bytesToCopy = (bytesRemaining > len2 ? len2 : bytesRemaining); + memcpy(dst2, src, bytesToCopy); + } - // Move write position forward for next time - writePos += numBytes; + // Move write position forward for next time + writePos += numBytes; - // Check if write position has moved past end of buffer + // Check if write position has moved past end of buffer if (writePos >= audioBufferSize) { - // If so, wrap it around to beginning again and set write wrapped flag - writePos -= audioBufferSize; - writeWrapped = true; - } + // If so, wrap it around to beginning again and set write wrapped flag + writePos -= audioBufferSize; + writeWrapped = true; + } Finish: - // Unlock SDL audio callback - SDL_UnlockAudio(); + // Unlock SDL audio callback + SDL_UnlockAudio(); - // Return whether buffer is half full - return bufferFull; + // Return whether buffer is half full + return bufferFull; } void CloseAudio() { - // Close SDL audio output - SDL_CloseAudio(); + // Close SDL audio output + SDL_CloseAudio(); - // Delete audio buffer + // Delete audio buffer if (audioBuffer != NULL) { - delete[] audioBuffer; - audioBuffer = NULL; - } + delete[] audioBuffer; + audioBuffer = NULL; + } } \ No newline at end of file diff --git a/Src/OSD/SDL/Main.cpp b/Src/OSD/SDL/Main.cpp index 53b2bc4..fac66f2 100644 --- a/Src/OSD/SDL/Main.cpp +++ b/Src/OSD/SDL/Main.cpp @@ -58,16 +58,17 @@ #include #include +#ifdef SUPERMODEL_WIN32 +#include "DirectInputSystem.h" +#include "WinOutputs.h" +#endif + #include "Supermodel.h" #include "Util/Format.h" #include "Util/NewConfig.h" #include "Util/ConfigBuilders.h" #include "GameLoader.h" #include "SDLInputSystem.h" -#ifdef SUPERMODEL_WIN32 -#include "DirectInputSystem.h" -#include "WinOutputs.h" -#endif #include "SDLIncludes.h" #include "Debugger/SupermodelDebugger.h" #include "Graphics/Legacy3D/Legacy3D.h" @@ -91,7 +92,7 @@ static Util::Config::Node s_runtime_config("Global"); Display Management ******************************************************************************/ -static SDL_Window *s_window = nullptr; +SDL_Window *s_window = nullptr; /* * Position and size of rectangular region within OpenGL display to render to. @@ -837,7 +838,7 @@ static void SuperSleep(UINT32 time) ******************************************************************************/ #ifdef SUPERMODEL_DEBUGGER -int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *Inputs, COutputs *Outputs, std::shared_ptr Debugger) +int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *Inputs, COutputs *Outputs, IScripting* scripting, std::shared_ptr Debugger) { std::shared_ptr oldLogger; #else @@ -880,7 +881,8 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In PrintGLInfo(false, true, false); // Initialize audio system - if (OKAY != OpenAudio()) + SetAudioType(game.audio); + if (OKAY != OpenAudio(s_runtime_config)) return 1; // Hide mouse if fullscreen, enable crosshairs for gun games @@ -908,7 +910,7 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In if (OKAY != Render3D->Init(xOffset, yOffset, xRes, yRes, totalXRes, totalYRes)) goto QuitError; Model3->AttachRenderers(Render2D,Render3D); - + // Reset emulator Model3->Reset(); @@ -1388,6 +1390,10 @@ static Util::Config::Node DefaultConfig() // CSoundBoard config.Set("EmulateSound", true); config.Set("Balance", "0"); + config.Set("BalanceLeftRight", "0"); + config.Set("BalanceFrontRear", "0"); + config.Set("NbSoundChannels", "4"); + config.Set("SoundFreq", "57.6"); // 60.0f? 57.524160f? // CDSB config.Set("EmulateDSB", true); config.Set("SoundVolume", "100"); @@ -1454,8 +1460,8 @@ static Util::Config::Node DefaultConfig() static void Title(void) { puts("Supermodel: A Sega Model 3 Arcade Emulator (Version " SUPERMODEL_VERSION ")"); - puts("Copyright 2011-2021 by Bart Trzynadlowski, Nik Henson, Ian Curtis,"); - puts(" Harry Tuttle, and Spindizzi\n"); + puts("Copyright 2011-2022 by Bart Trzynadlowski, Nik Henson, Ian Curtis, Harry Tuttle,"); + puts(" Spindizzi, gm_mathew and njz3\n"); } static void Help(void) @@ -1510,6 +1516,7 @@ static void Help(void) puts(" when Digital Sound Board is present [Default: 100]"); puts(" -music-volume= Digital Sound Board volume in % [Default: 100]"); puts(" -balance= Relative front/rear balance in % [Default: 0]"); + puts(" -channels= Number of sound channels to use on host [Default: 4]"); puts(" -flip-stereo Swap left and right audio channels"); puts(" -no-sound Disable sound board emulation (sound effects)"); puts(" -no-dsb Disable Digital Sound Board (MPEG music)"); @@ -1585,6 +1592,8 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv) { "-sound-volume", "SoundVolume" }, { "-music-volume", "MusicVolume" }, { "-balance", "Balance" }, + { "-channels", "NbSoundChannels" }, + { "-soundfreq", "SoundFreq" }, { "-input-system", "InputSystem" }, { "-outputs", "Outputs" }, { "-log-output", "LogOutput" }, diff --git a/Src/OSD/Windows/WinOutputs.cpp b/Src/OSD/Windows/WinOutputs.cpp index 7f48f47..63a619a 100755 --- a/Src/OSD/Windows/WinOutputs.cpp +++ b/Src/OSD/Windows/WinOutputs.cpp @@ -57,6 +57,7 @@ CWinOutputs::~CWinOutputs() // Broadcast a shutdown message if (m_hwnd) PostMessage(HWND_BROADCAST, m_onStop, (WPARAM)m_hwnd, 0); + DeleteWindowClass(); } bool CWinOutputs::Initialize() @@ -143,7 +144,19 @@ bool CWinOutputs::CreateWindowClass() return false; } +bool CWinOutputs::DeleteWindowClass() +{ + if (!s_createdClass) + return true; + // Register class + if (UnregisterClass(OUTPUT_WINDOW_CLASS, GetModuleHandle(NULL))) { + s_createdClass = false; + return true; + } + + return false; +} bool CWinOutputs::AllocateMessageId(UINT ®Id, LPCSTR str) { regId = RegisterWindowMessage(str); diff --git a/Src/OSD/Windows/WinOutputs.h b/Src/OSD/Windows/WinOutputs.h index 28aabde..6fe61bb 100755 --- a/Src/OSD/Windows/WinOutputs.h +++ b/Src/OSD/Windows/WinOutputs.h @@ -25,17 +25,17 @@ * Implementation of COutputs that sends MAMEHooker compatible messages via Windows messages. */ -#ifndef INCLUDED_WINOUTPUTS_H +#ifndef INCLUDED_WINOUTPUTS_H #define INCLUDED_WINOUTPUTS_H #define WIN32_LEAN_AND_MEAN -#include - -#include "OSD/Outputs.h" - -#include - -using namespace std; +#include + +#include "OSD/Outputs.h" + +#include + +using namespace std; // Struct that represents a client (eg MAMEHooker) currently registered with the emulator struct RegisteredClient @@ -50,57 +50,58 @@ struct CopyDataIdString char string[1]; // String containing data }; -class CWinOutputs : public COutputs -{ -public: - /* - * CWinOutputs(): - * ~CWinOutputs(): - * - * Constructor and destructor. - */ - CWinOutputs(); - - virtual ~CWinOutputs(); - - /* - * Initialize(): - * - * Initializes this class. - */ - bool Initialize(); - - /* - * Attached(): - * - * Lets the class know that it has been attached to the emulator. - */ - void Attached(); - -protected: - /* - * SendOutput(): - * - * Sends the appropriate output message to all registered clients. - */ - void SendOutput(EOutputs output, UINT8 prevValue, UINT8 value); - +class CWinOutputs : public COutputs +{ +public: + /* + * CWinOutputs(): + * ~CWinOutputs(): + * + * Constructor and destructor. + */ + CWinOutputs(); + + virtual ~CWinOutputs(); + + /* + * Initialize(): + * + * Initializes this class. + */ + bool Initialize(); + + /* + * Attached(): + * + * Lets the class know that it has been attached to the emulator. + */ + void Attached(); + +protected: + /* + * SendOutput(): + * + * Sends the appropriate output message to all registered clients. + */ + void SendOutput(EOutputs output, UINT8 prevValue, UINT8 value); + private: static bool s_createdClass; - /* - * CreateWindowClass(): - * - * Registers the window class and sets up OutputWindowProcCallback to process all messages sent to the emulator window. - */ + /* + * CreateWindowClass(): + * + * Registers the window class and sets up OutputWindowProcCallback to process all messages sent to the emulator window. + */ static bool CreateWindowClass(); + static bool DeleteWindowClass(); - /* - * OutputWindowProcCallback(hwnd, msg, wParam, lParam): - * - * Receives all messages sent to the emulator window and passes them on to the CWinOutputs object (whose pointer is passed - * via GWLP_USERDATA). - */ + /* + * OutputWindowProcCallback(hwnd, msg, wParam, lParam): + * + * Receives all messages sent to the emulator window and passes them on to the CWinOutputs object (whose pointer is passed + * via GWLP_USERDATA). + */ static LRESULT CALLBACK OutputWindowProcCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); HWND m_hwnd; @@ -127,49 +128,49 @@ private: * Processes the messages sent to the emulator window. */ LRESULT OutputWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - - /* - * RegisterClient(hwnd, id): - * - * Registers a client (eg MAMEHooker) with the emulator. - */ - LRESULT RegisterClient(HWND hwnd, LPARAM id); - - /* - * SendAllToClient(client): - * - * Sends the current state of all the outputs to the given registered client. - * Called whenever a client is registered with the emulator. - */ - void SendAllToClient(RegisteredClient &client); - - /* - * UnregisterClient(hwnd, id): - * - * Unregisters a client from the emulator. - */ - LRESULT UnregisterClient(HWND hwnd, LPARAM id); - - /* - * SendIdString(hwnd, id): - * - * Sends the name of the requested output back to a client, or the name of the current running game if an id of zero is requested. - */ - LRESULT SendIdString(HWND hwnd, LPARAM id); - - /* - * MapIdToName(id): - * - * Maps the given id to an output's name. - */ - const char *MapIdToName(LPARAM id); - - /* - * MapNameToId(name): - * - * Maps the given name to an output's id. - */ - LPARAM MapNameToId(const char *name); -}; - -#endif // INCLUDED_WINOUTPUTS_H + + /* + * RegisterClient(hwnd, id): + * + * Registers a client (eg MAMEHooker) with the emulator. + */ + LRESULT RegisterClient(HWND hwnd, LPARAM id); + + /* + * SendAllToClient(client): + * + * Sends the current state of all the outputs to the given registered client. + * Called whenever a client is registered with the emulator. + */ + void SendAllToClient(RegisteredClient &client); + + /* + * UnregisterClient(hwnd, id): + * + * Unregisters a client from the emulator. + */ + LRESULT UnregisterClient(HWND hwnd, LPARAM id); + + /* + * SendIdString(hwnd, id): + * + * Sends the name of the requested output back to a client, or the name of the current running game if an id of zero is requested. + */ + LRESULT SendIdString(HWND hwnd, LPARAM id); + + /* + * MapIdToName(id): + * + * Maps the given id to an output's name. + */ + const char *MapIdToName(LPARAM id); + + /* + * MapNameToId(name): + * + * Maps the given name to an output's id. + */ + LPARAM MapNameToId(const char *name); +}; + +#endif // INCLUDED_WINOUTPUTS_H diff --git a/Src/Sound/SCSP.cpp b/Src/Sound/SCSP.cpp index 8b3e68a..a463861 100644 --- a/Src/Sound/SCSP.cpp +++ b/Src/Sound/SCSP.cpp @@ -89,12 +89,15 @@ bool legacySound; // For LegacySound (SCSP DSP) config option. // These globals control the operation of the SCSP, they are no longer extern and are set through SCSP_SetBuffers(). --Bart float SoundClock; // Originally titled SysFPS; seems to be for the sound CPU. const float Freq = 76; -signed short *bufferl; -signed short *bufferr; +signed short* bufferfl; +signed short* bufferfr; +signed short* bufferrl; +signed short* bufferrr; int length; int cnts; -signed int *buffertmpl,*buffertmpr; // these are allocated inside this file +signed int* buffertmpfl, * buffertmpfr; // these are allocated inside this file +signed int* buffertmprl, * buffertmprr; // these are allocated inside this file unsigned int srate=44100; @@ -312,11 +315,11 @@ struct _SCSP _SLOT Slots[32]; signed short RINGBUF[64]; unsigned char BUFPTR; -#if FM_DELAY - signed short DELAYBUF[FM_DELAY]; - BYTE DELAYPTR; +#if FM_DELAY + signed short DELAYBUF[FM_DELAY]; + BYTE DELAYPTR; #endif - unsigned char *SCSPRAM; + unsigned char *SCSPRAM; UINT32 SCSPRAM_LENGTH; char Master; #ifdef USEDSP @@ -625,8 +628,8 @@ bool SCSP_Init(const Util::Config::Node &config, int n) SCSPDSP_Init(&SCSP->DSP); #endif SCSP->Master=1; - SCSP->SCSPRAM_LENGTH = 512 * 1024; - SCSP->DSP.SCSPRAM = (UINT16 *)SCSP->SCSPRAM; + SCSP->SCSPRAM_LENGTH = 512 * 1024; + SCSP->DSP.SCSPRAM = (UINT16 *)SCSP->SCSPRAM; SCSP->DSP.SCSPRAM_LENGTH = (512 * 1024) / 2; MidiR=MidiW=0; MidiOutR=MidiOutW=0; @@ -759,19 +762,32 @@ bool SCSP_Init(const Util::Config::Node &config, int n) #endif LFO_Init(); - buffertmpl = NULL; - buffertmpr = NULL; - buffertmpl=(signed int*) malloc(44100*sizeof(signed int)); - if (NULL == buffertmpl) + buffertmpfl = NULL; + buffertmpfr = NULL; + buffertmprl = NULL; + buffertmprr = NULL; + buffertmpfl=(signed int*) malloc(44100*sizeof(signed int)); + if (NULL == buffertmpfl) return ErrorLog("Insufficient memory for internal SCSP buffers."); - buffertmpr=(signed int*) malloc(44100*sizeof(signed int)); - if (NULL == buffertmpl) + buffertmpfr=(signed int*) malloc(44100*sizeof(signed int)); + if (NULL == buffertmpfr) { - free(buffertmpl); + free(buffertmpfr); return ErrorLog("Insufficient memory for internal SCSP buffers."); } - memset(buffertmpl,0,44100*sizeof(signed int)); - memset(buffertmpr,0,44100*sizeof(signed int)); + + buffertmprl=(signed int*)malloc(44100*sizeof(signed int)); + if (NULL == buffertmprl) + return ErrorLog("Insufficient memory for internal SCSP buffers."); + buffertmprr=(signed int*)malloc(44100*sizeof(signed int)); + if (NULL == buffertmprr) + return ErrorLog("Insufficient memory for internal SCSP buffers."); + + + memset(buffertmpfl, 0, 44100*sizeof(signed int)); + memset(buffertmpfr, 0, 44100*sizeof(signed int)); + memset(buffertmprl, 0, 44100*sizeof(signed int)); + memset(buffertmprr, 0, 44100*sizeof(signed int)); SCSPs->data[0x20 / 2] = 0; TimCnt[0] = 0xffff; TimCnt[1] = 0xffff; @@ -781,8 +797,10 @@ bool SCSP_Init(const Util::Config::Node &config, int n) MIDILock = CThread::CreateMutex(); if (NULL == MIDILock) { - free(buffertmpl); - free(buffertmpr); + free(buffertmpfl); + free(buffertmpfr); + free(buffertmprl); + free(buffertmprr); return ErrorLog("Unable to create MIDI mutex!"); } @@ -1111,7 +1129,7 @@ void SCSP_w16(unsigned int addr,unsigned short val) SCSP_UpdateReg(addr & 0x3f); } } - else if (addr < 0x700) + else if (addr < 0x700) SCSP->RINGBUF[(addr - 0x600) / 2] = val; else { @@ -1544,21 +1562,25 @@ void SCSP_DoMasterSamples(int nsamples) balance /= 100.0f; float masterBalance = 1.0f + balance; float slaveBalance = 1.0f - balance; - signed short *bufl, *bufr; + signed short* buffl, * buffr; + signed short* bufrl, * bufrr; INT32 sl, s, i; - bufl = bufferl; - bufr = bufferr; + buffl = bufferfl; + buffr = bufferfr; + bufrl = bufferrl; + bufrr = bufferrr; /* * Generate samples */ for (s = 0; s < nsamples; ++s) { - signed int smpl = 0, smpr = 0; + signed int smpfl = 0, smpfr = 0; + signed int smprl = 0, smprr = 0; - for (sl = 0; sl < 32; ++sl) + for (sl = 0; sl < 32; ++sl) { #if FM_DELAY RBUFDST = SCSPs[0].DELAYBUF + SCSPs[0].DELAYPTR; @@ -1578,12 +1600,12 @@ void SCSP_DoMasterSamples(int nsamples) SCSPDSP_SetSample(&SCSPs[0].DSP, (sample*LPANTABLE[Enc]) >> (SHIFT - 2), ISEL(slot), IMXL(slot)); Enc = ((TL(slot)) << 0x0) | ((DIPAN(slot)) << 0x8) | ((DISDL(slot)) << 0xd); #ifdef RB_VOLUME - smpl += (sample * volume[TL(slot) + pan_left[DIPAN(slot)]]) >> 17; - smpr += (sample * volume[TL(slot) + pan_right[DIPAN(slot)]]) >> 17; + smpfl += (sample * volume[TL(slot) + pan_left[DIPAN(slot)]]) >> 17; + smpfr += (sample * volume[TL(slot) + pan_right[DIPAN(slot)]]) >> 17; #else { - smpl += (sample*LPANTABLE[Enc]) >> SHIFT; - smpr += (sample*RPANTABLE[Enc]) >> SHIFT; + smpfl += (sample*LPANTABLE[Enc]) >> SHIFT; + smpfr += (sample*RPANTABLE[Enc]) >> SHIFT; } #endif } @@ -1615,11 +1637,11 @@ void SCSP_DoMasterSamples(int nsamples) Enc = ((TL(slot)) << 0x0) | ((DIPAN(slot)) << 0x8) | ((DISDL(slot)) << 0xd); { #ifdef RB_VOLUME - smpl += (sample * volume[TL(slot) + pan_left[DIPAN(slot)]]) >> 17; - smpr += (sample * volume[TL(slot) + pan_right[DIPAN(slot)]]) >> 17; + smprl += (sample * volume[TL(slot) + pan_left[DIPAN(slot)]]) >> 17; + smprr += (sample * volume[TL(slot) + pan_right[DIPAN(slot)]]) >> 17; #else - smpl += (sample*LPANTABLE[Enc]) >> SHIFT; - smpr += (sample*RPANTABLE[Enc]) >> SHIFT; + smprl += (sample*LPANTABLE[Enc]) >> SHIFT; + smprr += (sample*RPANTABLE[Enc]) >> SHIFT; } #endif } @@ -1650,8 +1672,8 @@ void SCSP_DoMasterSamples(int nsamples) { // For legacy option, 14 is the most reasonable value I can set at the moment for the EFSDL slot. - Paul UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xe); - smpl += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); - smpr += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); + smpfl += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); + smpfr += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); } if (HasSlaveSCSP) { @@ -1659,8 +1681,8 @@ void SCSP_DoMasterSamples(int nsamples) if (EFSDL(slot)) { UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xe); - smpl += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); - smpr += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); + smprl += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); + smprr += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); } } } @@ -1668,8 +1690,8 @@ void SCSP_DoMasterSamples(int nsamples) if (EFSDL(slot)) { UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xd); - smpl += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); - smpr += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); + smpfl += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); + smpfr += (int)(masterBalance*(float)(((SCSPs[0].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); } if (HasSlaveSCSP) { @@ -1677,32 +1699,47 @@ void SCSP_DoMasterSamples(int nsamples) if (EFSDL(slot)) { UINT16 Enc = ((EFPAN(slot)) << 0x8) | ((EFSDL(slot)) << 0xd); - smpl += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); - smpr += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); + smprl += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * LPANTABLE[Enc]) >> SHIFT))); + smprr += (int)(slaveBalance*(float)(((SCSPs[1].DSP.EFREG[i] * RPANTABLE[Enc]) >> SHIFT))); } } } } - if (DAC18B(SCSP)) + if (DAC18B((&SCSP[0]))) { - smpl = ICLIP18(smpl); - smpr = ICLIP18(smpr); + smpfl = ICLIP18(smpfl); + smpfr = ICLIP18(smpfr); } else { - smpl = ICLIP16(smpl >> 2); - smpr = ICLIP16(smpr >> 2); + smpfl = ICLIP16(smpfl >> 2); + smpfr = ICLIP16(smpfr >> 2); } - *bufl++ = ICLIP16(smpl); - *bufr++ = ICLIP16(smpr); + *buffl++ = ICLIP16(smpfl); + *buffr++ = ICLIP16(smpfr); + if (HasSlaveSCSP) + { + if (DAC18B((&SCSPs[1]))) + { + smprl = ICLIP18(smprl); + smprr = ICLIP18(smprr); + } + else + { + smprl = ICLIP16(smprl >> 2); + smprr = ICLIP16(smprr >> 2); + } + } + *bufrl++ = ICLIP16(smprl); + *bufrr++ = ICLIP16(smprr); SCSP_TimersAddTicks(1); CheckPendingIRQ(); lastdiff = Run68kCB(slice - lastdiff); - } } +} void SCSP_Update() { @@ -2107,11 +2144,14 @@ void SCSP_LoadState(CBlockFile *StateFile) } } -void SCSP_SetBuffers(INT16 *leftBufferPtr, INT16 *rightBufferPtr, int bufferLength) +void SCSP_SetBuffers(INT16 *leftBufferPtr, INT16 *rightBufferPtr, INT16* leftRearBufferPtr, INT16* rightRearBufferPtr, int bufferLength) { SoundClock = 76; - bufferl = leftBufferPtr; - bufferr = rightBufferPtr; + bufferfl = leftBufferPtr; + bufferfr = rightBufferPtr; + bufferrl = leftRearBufferPtr; + bufferrr = rightRearBufferPtr; + length = bufferLength; cnts = 0; // what is this for? seems unimportant but need to find out } @@ -2121,10 +2161,14 @@ void SCSP_Deinit(void) #ifdef USEDSP free(SCSP->MIXBuf); #endif - free(buffertmpl); - free(buffertmpr); + free(buffertmpfl); + free(buffertmpfr); + free(buffertmprl); + free(buffertmprr); delete MIDILock; - buffertmpl = NULL; - buffertmpr = NULL; + buffertmpfl = NULL; + buffertmpfr = NULL; + buffertmprl = NULL; + buffertmprr = NULL; MIDILock = NULL; } diff --git a/Src/Sound/SCSP.h b/Src/Sound/SCSP.h index 3b46b64..137b35b 100644 --- a/Src/Sound/SCSP.h +++ b/Src/Sound/SCSP.h @@ -83,7 +83,7 @@ UINT32 SCSP_Slave_r32(UINT32 addr); // Supermodel interface functions void SCSP_SaveState(CBlockFile *StateFile); void SCSP_LoadState(CBlockFile *StateFile); -void SCSP_SetBuffers(INT16 *leftBufferPtr, INT16 *rightBufferPtr, int bufferLength); +void SCSP_SetBuffers(INT16 *leftBufferPtr, INT16 *rightBufferPtr, INT16* leftRearBufferPtr, INT16* rightRearBufferPtr, int bufferLength); void SCSP_Deinit(void); diff --git a/VS2008/Musashi68K/Musashi68K.vcxproj b/VS2008/Musashi68K/Musashi68K.vcxproj index 873642d..5ffa5a1 100644 --- a/VS2008/Musashi68K/Musashi68K.vcxproj +++ b/VS2008/Musashi68K/Musashi68K.vcxproj @@ -21,28 +21,29 @@ {1248CF7C-B122-461C-9624-196AEFAE5046} Musashi68K + 10.0 Application - v141 + v142 MultiByte true Application - v141 + v142 MultiByte Application - v141 + v142 MultiByte true Application - v141 + v142 MultiByte diff --git a/VS2008/SDL/SDL.vcxproj b/VS2008/SDL/SDL.vcxproj index 1c63541..595bb0d 100644 --- a/VS2008/SDL/SDL.vcxproj +++ b/VS2008/SDL/SDL.vcxproj @@ -22,24 +22,24 @@ SDL2 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68} SDL - 8.1 + 10.0 DynamicLibrary - v141 + v142 DynamicLibrary - v141 + v142 DynamicLibrary - v141 + v142 DynamicLibrary - v141 + v142 diff --git a/VS2008/SDLmain/SDLmain.vcxproj b/VS2008/SDLmain/SDLmain.vcxproj index 09d149c..13e9151 100644 --- a/VS2008/SDLmain/SDLmain.vcxproj +++ b/VS2008/SDLmain/SDLmain.vcxproj @@ -22,24 +22,24 @@ SDL2main {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A} SDLmain - 8.1 + 10.0 StaticLibrary - v141 + v142 StaticLibrary - v141 + v142 StaticLibrary - v141 + v142 StaticLibrary - v141 + v142 diff --git a/VS2008/SDLnet/SDL_net.vcxproj b/VS2008/SDLnet/SDL_net.vcxproj index 6b1300d..56896e3 100644 --- a/VS2008/SDLnet/SDL_net.vcxproj +++ b/VS2008/SDLnet/SDL_net.vcxproj @@ -22,24 +22,24 @@ SDL2_net {8AB3504F-5E58-4910-AFE8-7A1E595AC3F4} SDL_net - 8.1 + 10.0 DynamicLibrary - v141 + v142 DynamicLibrary - v141 + v142 DynamicLibrary - v141 + v142 DynamicLibrary - v141 + v142 diff --git a/VS2008/Supermodel.vcxproj b/VS2008/Supermodel.vcxproj index 86578d9..9a7eaf4 100644 --- a/VS2008/Supermodel.vcxproj +++ b/VS2008/Supermodel.vcxproj @@ -22,29 +22,29 @@ {B114BBD9-8AEA-4DAE-B367-A66A804CB3DD} Supermodel Win32Proj - 8.1 + 10.0 Application - v141 + v142 Unicode true Application - v141 + v142 Unicode Application - v141 + v142 Unicode true Application - v141 + v142 Unicode diff --git a/VS2008/ZLib/ZLib.vcxproj b/VS2008/ZLib/ZLib.vcxproj index 03516ae..bef52d7 100644 --- a/VS2008/ZLib/ZLib.vcxproj +++ b/VS2008/ZLib/ZLib.vcxproj @@ -25,22 +25,22 @@ StaticLibrary - v141 + v142 false StaticLibrary - v141 + v142 false StaticLibrary - v141 + v142 false StaticLibrary - v141 + v142 false