/** ** Supermodel ** A Sega Model 3 Arcade Emulator. ** Copyright 2011 Bart Trzynadlowski, Nik Henson ** ** This file is part of Supermodel. ** ** Supermodel 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 3 of the License, or (at your option) ** any later version. ** ** Supermodel 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 Supermodel. If not, see . **/ /* * SoundBoard.cpp * * Model 3 sound board. Implementation of the CSoundBoard class. This class can * only be instantiated once because it relies on global variables (the non-OOP * 68K core and an IRQ line). * * Bank Switching * -------------- * Banking is not fully understood yet. It is presumed that the low 2MB of the * sample ROMs are not banked (MAME), but this is not guaranteed. Below are * examples of known situations where banking helps (sound names are as they * appear in the sound test menu). * * sound name * ROM Offsets -> Address * * dayto2pe * -------- * let's hope he does better * A... -> A... (400001=3E) * doing good i'd say you have a .. * E... -> E... (400001=3D) * * From the above, it appears that when (400001)&10, then: * * ROM A00000-DFFFFF -> A00000-DFFFFF * ROM E00000-FFFFFF -> E00000-FFFFFF * * And when that bit is clear (just use default mapping, upper 6MB of ROM): * * ROM 200000-5FFFFF -> A00000-DFFFFF * ROM 600000-7FFFFF -> E00000-FFFFFF */ #include "SoundBoard.h" #include "Supermodel.h" #include "OSD/Audio.h" #include "Sound/SCSP.h" // DEBUG //#define SUPERMODEL_LOG_AUDIO // define this to log all audio to sound.bin #ifdef SUPERMODEL_LOG_AUDIO static FILE *soundFP; #endif // 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 LENGTH_CHANNEL_BUFFER (sizeof(float)*NUM_SAMPLES_PER_FRAME) // 2940 bytes (32 bits x 44.1 KHz x 1/60th second) #define OFFSET_AUDIO_FRONTLEFT 0x200000 // 2940 bytes (32 bits, 44.1 KHz, 1/60th second) left audio channel #define OFFSET_AUDIO_FRONTRIGHT (OFFSET_AUDIO_FRONTLEFT + LENGTH_CHANNEL_BUFFER) // 2940 bytes right audio channel #define OFFSET_AUDIO_REARLEFT (OFFSET_AUDIO_FRONTRIGHT + LENGTH_CHANNEL_BUFFER) // 2940 bytes (32 bits, 44.1 KHz, 1/60th second) left audio channel #define OFFSET_AUDIO_REARRIGHT (OFFSET_AUDIO_REARLEFT + LENGTH_CHANNEL_BUFFER) // 2940 bytes right audio channel #define MEMORY_POOL_SIZE (0x100000 + 0x100000 + 4*LENGTH_CHANNEL_BUFFER) /****************************************************************************** 68K Address Space Handlers ******************************************************************************/ void CSoundBoard::UpdateROMBanks(void) { if ((ctrlReg&0x10)) sampleBank = &sampleROM[0x800000]; else sampleBank = &sampleROM[0x000000]; } UINT8 CSoundBoard::Read8(UINT32 a) { switch ((a>>20)&0xF) { case 0x0: // SCSP RAM 1 (master): 000000-0FFFFF return ram1[a^1]; case 0x1: // SCSP registers (master): 100000-10FFFF (unlike real hardware, we mirror up to 1FFFFF) return SCSP_Master_r8(a); case 0x2: // SCSP RAM 2 (slave): 200000-2FFFFF return ram2[(a&0x0FFFFF)^1]; case 0x3: // SCSP registers (slave): 300000-30FFFF (unlike real hardware, we mirror up to 3FFFFF) return SCSP_Slave_r8(a); case 0x6: // Program ROM: 600000-67FFFF (unlike real hardware, we mirror up to 6FFFFF here) return soundROM[(a&0x07FFFF)^1]; case 0x8: // Sample ROM bank: 800000-FFFFFF case 0x9: case 0xA: case 0xB: case 0xC: case 0xD: case 0xE: case 0xF: return sampleBank[(a&0x7FFFFF)^1]; default: //printf("68K: Unknown read8 %06X\n", a); break; } return 0; } UINT16 CSoundBoard::Read16(UINT32 a) { switch ((a>>20)&0xF) { case 0x0: // SCSP RAM 1 (master): 000000-0FFFFF return *(UINT16 *) &ram1[a]; case 0x1: // SCSP registers (master): 100000-10FFFF return SCSP_Master_r16(a); case 0x2: // SCSP RAM 2 (slave): 200000-2FFFFF return *(UINT16 *) &ram2[a&0x0FFFFF]; case 0x3: // SCSP registers (slave): 300000-30FFFF return SCSP_Slave_r16(a); case 0x6: // Program ROM: 600000-67FFFF return *(UINT16 *) &soundROM[a&0x07FFFF]; case 0x8: // Sample ROM bank: 800000-FFFFFF case 0x9: case 0xA: case 0xB: case 0xC: case 0xD: case 0xE: case 0xF: return *(UINT16 *) &sampleBank[a&0x7FFFFF]; default: //printf("68K: Unknown read16 %06X\n", a); break; } return 0; } UINT32 CSoundBoard::Read32(UINT32 a) { UINT32 hi, lo; switch ((a>>20)&0xF) { case 0x0: // SCSP RAM 1 (master): 000000-0FFFFF hi = *(UINT16 *) &ram1[a]; lo = *(UINT16 *) &ram1[a+2]; // TODO: clamp? Possible bounds hazard. return (hi<<16)|lo; case 0x1: // SCSP registers (master): 100000-10FFFF return SCSP_Master_r32(a); case 0x2: // SCSP RAM 2 (slave): 200000-2FFFFF hi = *(UINT16 *) &ram2[a&0x0FFFFF]; lo = *(UINT16 *) &ram2[(a+2)&0x0FFFFF]; return (hi<<16)|lo; case 0x3: // SCSP registers (slave): 300000-30FFFF return SCSP_Slave_r32(a); case 0x6: // Program ROM: 600000-67FFFF hi = *(UINT16 *) &soundROM[a&0x07FFFF]; lo = *(UINT16 *) &soundROM[(a+2)&0x07FFFF]; return (hi<<16)|lo; case 0x8: // Sample ROM bank: 800000-FFFFFF case 0x9: case 0xA: case 0xB: case 0xC: case 0xD: case 0xE: case 0xF: hi = *(UINT16 *) &sampleBank[a&0x7FFFFF]; lo = *(UINT16 *) &sampleBank[(a+2)&0x7FFFFF]; return (hi<<16)|lo; default: //printf("68K: Unknown read32 %06X\n", a); break; } return 0; } void CSoundBoard::Write8(unsigned int a,unsigned char d) { switch ((a>>20)&0xF) { case 0x0: // SCSP RAM 1 (master): 000000-0FFFFF ram1[a^1] = d; break; case 0x1: // SCSP registers (master): 100000-10FFFF SCSP_Master_w8(a,d); break; case 0x2: // SCSP RAM 2 (slave): 200000-2FFFFF ram2[(a&0x0FFFFF)^1] = d; break; case 0x3: // SCSP registers (slave): 300000-30FFFF SCSP_Slave_w8(a,d); break; default: if (a == 0x400001) { ctrlReg = d; UpdateROMBanks(); } //else // printf("68K: Unknown write8 %06X=%02X\n", a, d); break; } } void CSoundBoard::Write16(unsigned int a,unsigned short d) { switch ((a>>20)&0xF) { case 0x0: // SCSP RAM 1 (master): 000000-0FFFFF *(UINT16 *) &ram1[a] = d; break; case 0x1: // SCSP registers (master): 100000-10FFFF SCSP_Master_w16(a,d); break; case 0x2: // SCSP RAM 2 (slave): 200000-2FFFFF *(UINT16 *) &ram2[a&0x0FFFFF] = d; break; case 0x3: // SCSP registers (slave): 300000-30FFFF SCSP_Slave_w16(a,d); break; default: //printf("68K: Unknown write16 %06X=%04X\n", a, d); break; } } void CSoundBoard::Write32(unsigned int a,unsigned int d) { switch ((a>>20)&0xF) { case 0x0: // SCSP RAM 1 (master): 000000-0FFFFF *(UINT16 *) &ram1[a] = (d>>16); *(UINT16 *) &ram1[a+2] = (d&0xFFFF); break; case 0x1: // SCSP registers (master): 100000-10FFFF SCSP_Master_w32(a,d); break; case 0x2: // SCSP RAM 2 (slave): 200000-2FFFFF *(UINT16 *) &ram2[a&0x0FFFFF] = (d>>16); *(UINT16 *) &ram2[(a+2)&0x0FFFFF] = (d&0xFFFF); break; case 0x3: // SCSP registers (slave): 300000-30FFFF SCSP_Slave_w32(a,d); break; default: //printf("68K: Unknown write32 %06X=%08X\n", a, d); break; } } /****************************************************************************** SCSP 68K Callbacks The SCSP emulator drives the 68K via callbacks. These have to live outside of the CSoundBoard object for now, unfortunately. ******************************************************************************/ // Status of IRQ pins (IPL2-0) on 68K // TODO: can we get rid of this global variable altogether? static int irqLine = 0; // Interrupt acknowledge callback (TODO: don't need this, default behavior in M68K.cpp should be fine) int IRQAck(int irqLevel) { M68KSetIRQ(0); irqLine = 0; return M68K_IRQ_AUTOVECTOR; } // SCSP callback for generating IRQs void SCSP68KIRQCallback(int irqLevel) { /* * IRQ arbitration logic: only allow higher priority IRQs to be asserted or * 0 to clear pending IRQ. */ if ((irqLevel>irqLine) || (0==irqLevel)) { irqLine = irqLevel; } M68KSetIRQ(irqLine); } // SCSP callback for running the 68K int SCSP68KRunCallback(int numCycles) { return M68KRun(numCycles) - numCycles; } /****************************************************************************** Sound Board Interface ******************************************************************************/ void CSoundBoard::WriteMIDIPort(UINT8 data) { SCSP_MidiIn(data); if (NULL != DSB) // DSB receives all commands as well DSB->SendCommand(data); } static INT16 ClampINT16(float x) { INT32 xi = (INT32)x; if (xi > INT16_MAX) { xi = INT16_MAX; } if (xi < INT16_MIN) { xi = INT16_MIN; } return (INT16)xi; } bool CSoundBoard::RunFrame(void) { // Run sound board first to generate SCSP audio if (m_config["EmulateSound"].ValueAs()) { M68KSetContext(&M68K); SCSP_Update(); M68KGetContext(&M68K); } else { memset(audioFL, 0, LENGTH_CHANNEL_BUFFER); memset(audioFR, 0, LENGTH_CHANNEL_BUFFER); memset(audioRL, 0, LENGTH_CHANNEL_BUFFER); memset(audioRR, 0, LENGTH_CHANNEL_BUFFER); } // Compute sound volume as float soundVol = (float)std::max(0,std::min(200,m_config["SoundVolume"].ValueAs())); soundVol = soundVol * (float)(1.0 / 100.0); // Apply sound volume setting to SCSP channels only for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { audioFL[i] *= soundVol; audioFR[i] *= soundVol; audioRL[i] *= soundVol; audioRR[i] *= soundVol; } // Run DSB and mix with existing audio, apply music volume 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(NUM_SAMPLES_PER_FRAME, audioFL, audioFR, audioRL, audioRR, m_config["FlipStereo"].ValueAs()); #ifdef SUPERMODEL_LOG_AUDIO // Output to binary file INT16 s; for (int i = 0; i < NUM_SAMPLES_PER_FRAME; i++) { s = ClampINT16(audioFL[i]); fwrite(&s, sizeof(INT16), 1, soundFP); // left channel s = ClampINT16(audioFR[i]); fwrite(&s, sizeof(INT16), 1, soundFP); // right channel s = ClampINT16(audioRL[i]); fwrite(&s, sizeof(INT16), 1, soundFP); // left channel s = ClampINT16(audioRR[i]); fwrite(&s, sizeof(INT16), 1, soundFP); // right channel } #endif // SUPERMODEL_LOG_AUDIO return bufferFull; } void CSoundBoard::Reset(void) { // Even if SCSP emulation is disabled, we must reset to establish a valid 68K state memcpy(ram1, soundROM, 16); // copy 68K vector table ctrlReg = 0; // set default banks UpdateROMBanks(); M68KSetContext(&M68K); M68KReset(); //printf("SBrd PC=%06X\n", M68KGetPC()); M68KGetContext(&M68K); if (NULL != DSB) DSB->Reset(); DebugLog("Sound Board Reset\n"); //printf("PC=%06X\n", M68KGetPC()); //M68KSetContext(&M68K); //M68KGetContext(&M68K); //printf("PC=%06X\n", M68KGetPC()); } void CSoundBoard::SaveState(CBlockFile *SaveState) { SaveState->NewBlock("Sound Board", __FILE__); SaveState->Write(ram1, 0x100000); SaveState->Write(ram2, 0x100000); SaveState->Write(&ctrlReg, sizeof(ctrlReg)); // All other devices... M68KSetContext(&M68K); M68KSaveState(SaveState, "Sound Board 68K"); SCSP_SaveState(SaveState); if (NULL != DSB) DSB->SaveState(SaveState); } void CSoundBoard::LoadState(CBlockFile *SaveState) { if (OKAY != SaveState->FindBlock("Sound Board")) { ErrorLog("Unable to load sound board state. Save state file is corrupt."); return; } SaveState->Read(ram1, 0x100000); SaveState->Read(ram2, 0x100000); SaveState->Read(&ctrlReg, sizeof(ctrlReg)); UpdateROMBanks(); // All other devices M68KSetContext(&M68K); // so we don't lose callback pointers when copying context back M68KLoadState(SaveState, "Sound Board 68K"); M68KGetContext(&M68K); SCSP_LoadState(SaveState); if (NULL != DSB) DSB->LoadState(SaveState); } /****************************************************************************** Configuration, Initialization, and Shutdown ******************************************************************************/ void CSoundBoard::AttachDSB(CDSB *DSBPtr) { DSB = DSBPtr; DebugLog("Sound Board connected to DSB\n"); } bool CSoundBoard::Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr) { float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000; // Receive sound ROMs soundROM = soundROMPtr; sampleROM = sampleROMPtr; ctrlReg = 0; UpdateROMBanks(); // Allocate all memory for RAM memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE]; if (NULL == memoryPool) return ErrorLog("Insufficient memory for sound board (needs %1.1f MB).", memSizeMB); memset(memoryPool, 0, MEMORY_POOL_SIZE); // Set up memory pointers ram1 = &memoryPool[OFFSET_RAM1]; ram2 = &memoryPool[OFFSET_RAM2]; audioFL = (float*)&memoryPool[OFFSET_AUDIO_FRONTLEFT]; audioFR = (float*)&memoryPool[OFFSET_AUDIO_FRONTRIGHT]; audioRL = (float*)&memoryPool[OFFSET_AUDIO_REARLEFT]; audioRR = (float*)&memoryPool[OFFSET_AUDIO_REARRIGHT]; // Initialize 68K core M68KSetContext(&M68K); M68KInit(); M68KAttachBus(this); M68KSetIRQCallback(IRQAck); M68KGetContext(&M68K); // Initialize SCSPs SCSP_SetBuffers(audioFL, audioFR, audioRL, audioRR, NUM_SAMPLES_PER_FRAME); SCSP_SetCB(SCSP68KRunCallback, SCSP68KIRQCallback); if (OKAY != SCSP_Init(m_config, 2)) return FAIL; SCSP_SetRAM(0, ram1); SCSP_SetRAM(1, ram2); // Binary logging #ifdef SUPERMODEL_LOG_AUDIO soundFP = fopen("sound.bin","wb"); // delete existing file fclose(soundFP); soundFP = fopen("sound.bin","ab"); // append mode #endif return OKAY; } M68KCtx *CSoundBoard::GetM68K(void) { return &M68K; } CDSB *CSoundBoard::GetDSB(void) { return DSB; } CSoundBoard::CSoundBoard(const Util::Config::Node &config) : m_config(config) { DSB = NULL; memoryPool = NULL; ram1 = NULL; ram2 = NULL; audioFL = NULL; audioFR = NULL; audioRL = NULL; audioRR = NULL; soundROM = NULL; sampleROM = NULL; DebugLog("Built Sound Board\n"); } static void Reverse16(UINT8 *buf, unsigned size) { unsigned i; UINT8 tmp; for (i = 0; i < size; i += 2) { tmp = buf[i+0]; buf[i+0] = buf[i+1]; buf[i+1] = tmp; } } CSoundBoard::~CSoundBoard(void) { #ifdef SUPERMODEL_LOG_AUDIO // close binary log file fclose(soundFP); #endif SCSP_Deinit(); DSB = NULL; if (memoryPool != NULL) { delete [] memoryPool; memoryPool = NULL; } ram1 = NULL; ram2 = NULL; audioFL = NULL; audioFR = NULL; audioRL = NULL; audioRR = NULL; soundROM = NULL; sampleROM = NULL; DebugLog("Destroyed Sound Board\n"); }