diff --git a/apu/apu.cpp b/apu/apu.cpp index 4b7c6dc5..c897e90f 100644 --- a/apu/apu.cpp +++ b/apu/apu.cpp @@ -190,6 +190,7 @@ #include #include "snes9x.h" #include "apu.h" +#include "msu1.h" #include "snapshot.h" #include "display.h" #include "hermite_resampler.h" @@ -239,6 +240,13 @@ namespace spc static uint32 ratio_denominator = APU_DENOMINATOR_NTSC; } +namespace msu +{ + static int buffer_size; + static uint8 *landing_buffer = NULL; + static Resampler *resampler = NULL; +} + static void EightBitize (uint8 *, int); static void DeStereo (uint8 *, int); static void ReverseStereo (uint8 *, int); @@ -311,6 +319,9 @@ bool8 S9xMixSamples (uint8 *buffer, int sample_count) memset(dest, 0, sample_count << 1); spc::resampler->clear(); + if(Settings.MSU1) + msu::resampler->clear(); + return (FALSE); } else @@ -320,6 +331,17 @@ bool8 S9xMixSamples (uint8 *buffer, int sample_count) spc::resampler->read((short *) dest, sample_count); if (spc::lag == spc::lag_master) spc::lag = 0; + + if (Settings.MSU1) + { + if (msu::resampler->avail() >= sample_count) + { + uint8 *msu_sample = new uint8[sizeof(dest)]; + msu::resampler->read((short *)msu_sample, sample_count); + for(uint32 i = 0; i < sizeof(dest); ++i) + dest[i] += msu_sample[i]; + } + } } else { @@ -369,6 +391,11 @@ void S9xFinalizeSamples (void) if (Settings.SoundSync && !Settings.TurboMode) return; } + + if (Settings.MSU1 && !msu::resampler->push((short *) msu::landing_buffer, S9xMSU1Samples())) + { + + } } if (!Settings.SoundSync || Settings.TurboMode || Settings.Mute) @@ -380,6 +407,9 @@ void S9xFinalizeSamples (void) spc::sound_in_sync = FALSE; SNES::dsp.spc_dsp.set_output((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size); + + if (Settings.MSU1) + S9xMSU1SetOutput((int16 *)msu::landing_buffer, msu::buffer_size); } void S9xLandSamples (void) @@ -393,6 +423,8 @@ void S9xLandSamples (void) void S9xClearSamples (void) { spc::resampler->clear(); + if (Settings.MSU1) + msu::resampler->clear(); spc::lag = spc::lag_master; } @@ -419,6 +451,12 @@ static void UpdatePlaybackRate (void) double time_ratio = (double) Settings.SoundInputRate * spc::timing_hack_numerator / (Settings.SoundPlaybackRate * spc::timing_hack_denominator); spc::resampler->time_ratio(time_ratio); + + if (Settings.MSU1) + { + time_ratio = 44100.0 / Settings.SoundPlaybackRate; + msu::resampler->time_ratio(time_ratio); + } } bool8 S9xInitSound (int buffer_ms, int lag_ms) @@ -442,6 +480,8 @@ bool8 S9xInitSound (int buffer_ms, int lag_ms) spc::buffer_size <<= 1; if (Settings.SixteenBitSound) spc::buffer_size <<= 1; + if (Settings.MSU1) + msu::buffer_size = buffer_ms * 44100 / 1000; printf("Sound buffer size: %d (%d samples)\n", spc::buffer_size, sample_count); @@ -450,6 +490,11 @@ bool8 S9xInitSound (int buffer_ms, int lag_ms) spc::landing_buffer = new uint8[spc::buffer_size * 2]; if (!spc::landing_buffer) return (FALSE); + if (msu::landing_buffer) + delete[] msu::landing_buffer; + msu::landing_buffer = new uint8[msu::buffer_size * 2]; + if (!msu::landing_buffer) + return (FALSE); /* The resampler and spc unit use samples (16-bit short) as arguments. Use 2x in the resampler for buffer leveling with SoundSync */ @@ -465,6 +510,20 @@ bool8 S9xInitSound (int buffer_ms, int lag_ms) else spc::resampler->resize(spc::buffer_size >> (Settings.SoundSync ? 0 : 1)); + + if (!msu::resampler) + { + msu::resampler = new HermiteResampler(msu::buffer_size); + if (!msu::resampler) + { + delete[] msu::landing_buffer; + return (FALSE); + } + } + else + msu::resampler->resize(msu::buffer_size); + + SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size); UpdatePlaybackRate(); @@ -503,6 +562,7 @@ bool8 S9xInitAPU (void) spc::landing_buffer = NULL; spc::shrink_buffer = NULL; spc::resampler = NULL; + msu::resampler = NULL; return (TRUE); } @@ -526,6 +586,18 @@ void S9xDeinitAPU (void) delete[] spc::shrink_buffer; spc::shrink_buffer = NULL; } + + if (msu::resampler) + { + delete msu::resampler; + msu::resampler = NULL; + } + + if (msu::landing_buffer) + { + delete[] msu::landing_buffer; + msu::landing_buffer = NULL; + } } static inline int S9xAPUGetClock (int32 cpucycles) @@ -572,6 +644,8 @@ void S9xAPUEndScanline (void) S9xAPUExecute(); SNES::dsp.synchronize(); + S9xMSU1Execute(); + if (SNES::dsp.spc_dsp.sample_count() >= APU_MINIMUM_SAMPLE_BLOCK || !spc::sound_in_sync) S9xLandSamples(); } @@ -603,6 +677,9 @@ void S9xResetAPU (void) SNES::dsp.spc_dsp.set_spc_snapshot_callback(SPCSnapshotCallback); spc::resampler->clear(); + + if (Settings.MSU1) + msu::resampler->clear(); } void S9xSoftResetAPU (void) @@ -615,6 +692,9 @@ void S9xSoftResetAPU (void) SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size >> 1); spc::resampler->clear(); + + if (Settings.MSU1) + msu::resampler->clear(); } void S9xAPUSaveState (uint8 *block) diff --git a/cpu.cpp b/cpu.cpp index 545cd116..154b1b34 100644 --- a/cpu.cpp +++ b/cpu.cpp @@ -312,6 +312,8 @@ void S9xReset (void) S9xResetOBC1(); if (Settings.SRTC) S9xResetSRTC(); + if (Settings.MSU1) + S9xMSU1Init(); S9xInitCheatData(); } @@ -346,6 +348,8 @@ void S9xSoftReset (void) S9xResetOBC1(); if (Settings.SRTC) S9xResetSRTC(); + if (Settings.MSU1) + S9xMSU1Init(); S9xInitCheatData(); } diff --git a/getset.h b/getset.h index f72be543..747b5630 100644 --- a/getset.h +++ b/getset.h @@ -199,6 +199,7 @@ #include "obc1.h" #include "seta.h" #include "bsx.h" +#include "msu1.h" #define addCyclesInMemoryAccess \ if (!CPU.InDMAorHDMA) \ diff --git a/globals.cpp b/globals.cpp index f9e18888..25afc1c9 100644 --- a/globals.cpp +++ b/globals.cpp @@ -232,6 +232,7 @@ struct SSPC7110Snapshot s7snap; struct SSRTCSnapshot srtcsnap; struct SRTCData RTCData; struct SBSX BSX; +struct SMSU1 MSU1; struct SMulti Multi; struct SSettings Settings; struct SSNESGameFixes SNESGameFixes; diff --git a/memmap.cpp b/memmap.cpp index 19df95cb..72fc90fb 100644 --- a/memmap.cpp +++ b/memmap.cpp @@ -1270,6 +1270,12 @@ static bool8 is_GNEXT_Add_On (const uint8 *data, uint32 size) return (FALSE); } +static bool8 MsuRomExists (void) +{ + struct stat buf; + return (stat(S9xGetFilename(".msu", ROMFILENAME_DIR), &buf) == 0); +} + int CMemory::ScoreHiROM (bool8 skip_header, int32 romoff) { uint8 *buf = ROM + 0xff00 + romoff + (skip_header ? 0x200 : 0); @@ -2360,6 +2366,7 @@ void CMemory::InitROM (void) Settings.SETA = 0; Settings.SRTC = FALSE; Settings.BS = FALSE; + Settings.MSU1 = FALSE; SuperFX.nRomBanks = CalculatedSize >> 15; @@ -2534,6 +2541,9 @@ void CMemory::InitROM (void) break; } + // MSU1 + Settings.MSU1 = MsuRomExists(); + //// Map memory and calculate checksum Map_Initialize(); @@ -3486,7 +3496,7 @@ const char * CMemory::KartContents (void) static char str[64]; static const char *contents[3] = { "ROM", "ROM+RAM", "ROM+RAM+BAT" }; - char chip[16]; + char chip[20]; if (ROMType == 0 && !Settings.BS) return ("ROM"); @@ -3532,6 +3542,9 @@ const char * CMemory::KartContents (void) else strcpy(chip, ""); + if (Settings.MSU1) + sprintf(chip + strlen(chip), "+MSU-1"); + sprintf(str, "%s%s", contents[(ROMType & 0xf) % 3], chip); return (str); diff --git a/msu1.cpp b/msu1.cpp new file mode 100644 index 00000000..f447a5f9 --- /dev/null +++ b/msu1.cpp @@ -0,0 +1,207 @@ +#include "snes9x.h" +#include "display.h" +#include "msu1.h" +#include + +std::ifstream dataFile, audioFile; +uint32 dataPos, audioPos, audioResumePos, audioLoopPos; +uint16 audioTrack, audioResumeTrack; +char fName[64]; + +// Sample buffer +int16 *bufPos, *bufBegin, *bufEnd; + +void S9xMSU1Init(void) +{ + MSU1.MSU1_STATUS = 0; + MSU1.MSU1_SEEK = 0; + MSU1.MSU1_TRACK = 0; + MSU1.MSU1_VOLUME = 0; + MSU1.MSU1_CONTROL = 0; + + dataPos = 0; + audioPos = 0; + audioResumePos = 0; + + audioTrack = 0; + audioResumeTrack = 0; + + bufPos = 0; + bufBegin = 0; + bufEnd = 0; + + if (dataFile.is_open()) + dataFile.close(); + + if (audioFile.is_open()) + audioFile.close(); + + dataFile.open(S9xGetFilename(".msu", ROMFILENAME_DIR), std::ios::in | std::ios::binary); +} + +void S9xMSU1Execute(void) +{ + static long long hitcount = 0; + //return; // Dummy out for now because it's broken + while ((bufPos < bufEnd) && (MSU1.MSU1_STATUS & AudioPlaying)) + { + hitcount++; + if (audioFile.good()) + { + audioPos += 2; + int16 sample = 0; + ((uint8 *)&sample)[0] = audioFile.get(); + ((uint8 *)&sample)[1] = audioFile.get(); + + sample = (double)sample * (double)MSU1.MSU1_VOLUME / 255.0; + + *bufPos = ((uint8 *)&sample)[0]; + *(bufPos + 1) = ((uint8 *)&sample)[1]; + + bufPos += 2; + + if (audioFile.eof()) + { + if (MSU1.MSU1_STATUS & AudioRepeating) + { + audioPos = audioLoopPos; + audioFile.seekg(audioPos); + } + else + { + MSU1.MSU1_STATUS &= ~(AudioPlaying | AudioRepeating); + audioFile.seekg(8); + return; + } + } + } + else + { + MSU1.MSU1_STATUS &= ~AudioPlaying; + } + } +} + + +uint8 S9xMSU1ReadPort(int port) +{ + switch (port) + { + case 0: + return MSU1.MSU1_STATUS; + case 1: + if (MSU1.MSU1_STATUS & DataBusy) + return 0; + if (dataFile.fail() || dataFile.bad() || dataFile.eof()) + return 0; + return dataFile.get(); + case 2: + return 'S'; + case 3: + return '-'; + case 4: + return 'M'; + case 5: + return 'S'; + case 6: + return 'U'; + case 7: + return '1'; + } +} + + +void S9xMSU1WritePort(int port, uint8 byte) +{ + switch (port) + { + case 0: + ((uint8 *)(&MSU1.MSU1_SEEK))[0] = byte; + break; + case 1: + ((uint8 *)(&MSU1.MSU1_SEEK))[1] = byte; + break; + case 2: + ((uint8 *)(&MSU1.MSU1_SEEK))[2] = byte; + break; + case 3: + ((uint8 *)(&MSU1.MSU1_SEEK))[3] = byte; + dataPos = MSU1.MSU1_SEEK; + if(dataFile.good()) + dataFile.seekg(dataPos); + break; + case 4: + ((uint8 *)(&MSU1.MSU1_TRACK))[0] = byte; + break; + case 5: + ((uint8 *)(&MSU1.MSU1_TRACK))[1] = byte; + audioTrack = MSU1.MSU1_TRACK; + if (audioFile.is_open()) + audioFile.close(); + // This is an ugly hack... need to see if there's a better way to get the base name without extension + sprintf(fName, "%s", S9xGetFilename(".msu", ROMFILENAME_DIR)); + fName[strlen(fName) - 4] = '\0'; + sprintf(fName, "%s-%d.pcm", fName, audioTrack); + + audioFile.open(fName); + if (audioFile.is_open() && audioFile.good()) + { + MSU1.MSU1_STATUS |= AudioError; + + if (audioFile.get() != 'M') + break; + if (audioFile.get() != 'S') + break; + if (audioFile.get() != 'U') + break; + if (audioFile.get() != '1') + break; + + ((uint8 *)(&audioLoopPos))[0] = audioFile.get(); + ((uint8 *)(&audioLoopPos))[1] = audioFile.get(); + ((uint8 *)(&audioLoopPos))[2] = audioFile.get(); + ((uint8 *)(&audioLoopPos))[3] = audioFile.get(); + audioLoopPos += 8; + + MSU1.MSU1_STATUS &= ~AudioPlaying; + MSU1.MSU1_STATUS &= ~AudioRepeating; + if (audioTrack == audioResumeTrack) + { + audioPos = audioResumePos; + audioResumeTrack = 0xFFFF; + audioResumePos = 0; + } + else + audioPos = 8; + + audioFile.seekg(audioPos); + + MSU1.MSU1_STATUS &= ~AudioError; + } + else + MSU1.MSU1_STATUS |= AudioError; + break; + case 6: + MSU1.MSU1_VOLUME = byte; + break; + case 7: + if (MSU1.MSU1_STATUS & (AudioBusy | AudioError)) + break; + + MSU1.MSU1_STATUS = (MSU1.MSU1_STATUS & ~0x30) | ((byte & 0x03) << 4); + if ((byte & 0x05) == 0x05) + audioResumeTrack = audioTrack; + break; + } +} + +uint16 S9xMSU1Samples(void) +{ + return bufPos - bufBegin; +} + +void S9xMSU1SetOutput(int16 * out, int size) +{ + bufPos = bufBegin = out; + bufEnd = out + size; +} diff --git a/msu1.h b/msu1.h new file mode 100644 index 00000000..a846a415 --- /dev/null +++ b/msu1.h @@ -0,0 +1,33 @@ +#ifndef _MSU1_H_ +#define _MSU1_H_ +#include "snes9x.h" + +struct SMSU1 +{ + uint8 MSU1_STATUS; + uint32 MSU1_SEEK; + uint16 MSU1_TRACK; + uint8 MSU1_VOLUME; + uint8 MSU1_CONTROL; +}; + +enum SMSU1_FLAG : uint8 { + Revision = 0x02, //max: 0x07 + AudioResume = 0x04, + AudioError = 0x08, + AudioPlaying = 0x10, + AudioRepeating = 0x20, + AudioBusy = 0x40, + DataBusy = 0x80, +}; + +extern struct SMSU1 MSU1; + +void S9xMSU1Init(void); +void S9xMSU1Execute(void); +uint8 S9xMSU1ReadPort(int port); +void S9xMSU1WritePort(int port, uint8 byte); +uint16 S9xMSU1Samples(void); +void S9xMSU1SetOutput(int16 *out, int size); + +#endif diff --git a/ppu.cpp b/ppu.cpp index ebc99ac8..682fd880 100644 --- a/ppu.cpp +++ b/ppu.cpp @@ -344,6 +344,9 @@ void S9xSetPPU (uint8 Byte, uint16 Address) S9xTraceFormattedMessage("--- HDMA PPU %04X -> %02X", Address, Byte); #endif + if (Settings.MSU1 && (Address & 0xfff8) == 0x2000) // MSU-1 + S9xMSU1WritePort(Address & 7, Byte); + else if ((Address & 0xffc0) == 0x2140) // APUIO0, APUIO1, APUIO2, APUIO3 // write_port will run the APU until given clock before writing value S9xAPUWritePort(Address & 3, Byte); @@ -1095,7 +1098,9 @@ void S9xSetPPU (uint8 Byte, uint16 Address) uint8 S9xGetPPU (uint16 Address) { // MAP_PPU: $2000-$3FFF - + if (Settings.MSU1 && (Address & 0xfff8) == 0x2000) + return (S9xMSU1ReadPort(Address & 7)); + else if (Address < 0x2100) return (OpenBus); @@ -1125,7 +1130,6 @@ uint8 S9xGetPPU (uint16 Address) if ((Address & 0xffc0) == 0x2140) // APUIO0, APUIO1, APUIO2, APUIO3 // read_port will run the APU until given APU time before reading value return (S9xAPUReadPort(Address & 3)); - else if (Address <= 0x2183) { uint8 byte; diff --git a/snes9x.h b/snes9x.h index f7bd5740..fbb4cefe 100644 --- a/snes9x.h +++ b/snes9x.h @@ -377,6 +377,7 @@ struct SSettings bool8 BS; bool8 BSXItself; bool8 BSXBootup; + bool8 MSU1; bool8 MouseMaster; bool8 SuperScopeMaster; bool8 JustifierMaster;