diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c818c8e --- /dev/null +++ b/.clang-format @@ -0,0 +1,121 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortLambdasOnASingleLine: None +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 2 +UseTab: Never +... diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml index eeb53a9..bec9971 100644 --- a/.github/workflows/make.yml +++ b/.github/workflows/make.yml @@ -1,8 +1,6 @@ name: Build and Run Tests on: - push: - branches: [ "main" ] pull_request: branches: [ "main" ] diff --git a/.gitignore b/.gitignore index d726022..4dcaec3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +run-clang-format + # Things .vscode diff --git a/.run-clang-format.sh b/.run-clang-format.sh new file mode 100755 index 0000000..68188ea --- /dev/null +++ b/.run-clang-format.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " + exit 1 +fi +task="${1}"; shift + +function check() +{ + if [ ! $? -eq 0 ]; then + echo "Error fixing style." + exit -1 + fi +} + +function check_syntax() +{ + # If run-clang-format is not installed, clone it + if [ ! -f run-clang-format/run-clang-format.py ]; then + + git clone https://github.com/Sarcasm/run-clang-format.git + if [ ! $? -eq 0 ]; then + echo "Error installing run-clang-format." + exit 1 + fi + fi + + python3 run-clang-format/run-clang-format.py --recursive source --extensions "cpp,hpp" + + if [ ! $? -eq 0 ]; then + echo "Error: C++ Code formatting in source is not normalized." + echo "Solution: Please run this program with the 'fix' argument" + exit -1 + fi +} + +function fix_syntax() +{ + src_files=`find source -type f -name "*.cpp" -o -name "*.hpp"` + echo $src_files | xargs -n6 -P2 clang-format -style=file -i "$@" + check +} + +############################################## +### Testing/fixing C++ Code Style +############################################## +command -v clang-format >/dev/null +if [ ! $? -eq 0 ]; then + echo "Error: please install clang-format on your system." + exit -1 +fi + +if [[ "${task}" == 'check' ]]; then + check_syntax +else + fix_syntax +fi + +exit 0 \ No newline at end of file diff --git a/extern/hqn/hqn.cpp b/extern/hqn/hqn.cpp index bb45f84..fc75347 100644 --- a/extern/hqn/hqn.cpp +++ b/extern/hqn/hqn.cpp @@ -10,7 +10,7 @@ namespace hqn int32_t *_initF_VideoPalette() { static int32_t VideoPalette[512]; - const Nes_Emu::rgb_t *palette = Nes_Emu::nes_colors; + const emulator_t::rgb_t *palette = emulator_t::nes_colors; for (int i = 0; i < 512; i++) { VideoPalette[i] = palette[i].red << 16 | palette[i].green << 8 @@ -25,7 +25,7 @@ const int32_t *HQNState::NES_VIDEO_PALETTE = _initF_VideoPalette(); // Constructor HQNState::HQNState() { - m_emu = new Nes_Emu(); + m_emu = new emulator_t(); joypad[0] = 0x00; joypad[1] = 0x00; @@ -50,7 +50,7 @@ error_t HQNState::setSampleRate(int rate) { const char *ret = m_emu->set_sample_rate(rate); if (!ret) - m_emu->set_equalizer(Nes_Emu::nes_eq); + m_emu->set_equalizer(emulator_t::nes_eq); return ret; } diff --git a/extern/hqn/hqn.h b/extern/hqn/hqn.h index fd48100..d97cad3 100644 --- a/extern/hqn/hqn.h +++ b/extern/hqn/hqn.h @@ -1,12 +1,22 @@ #ifndef __HQN_H__ #define __HQN_H__ -#include #include #include #define BLIT_SIZE 65536 +// Creating emulator instance +#ifdef _USE_QUICKNES + #include + typedef Nes_Emu emulator_t; +#endif + +#ifdef _USE_QUICKERNES + #include + typedef quickerNES::Emu emulator_t; +#endif + namespace hqn { @@ -36,7 +46,7 @@ class HQNState public: /* A reference to the emulator instance. */ - Nes_Emu *m_emu; + emulator_t *m_emu; static const int32_t *NES_VIDEO_PALETTE; @@ -46,7 +56,7 @@ public: HQNState(); ~HQNState(); - void setEmulatorPointer(void* const emuPtr) { m_emu = (Nes_Emu*)emuPtr; } + void setEmulatorPointer(void* const emuPtr) { m_emu = (emulator_t*)emuPtr; } /* The joypad data for the two joypads available to an NES. @@ -55,7 +65,7 @@ public: uint32_t joypad[2]; /* Get the emulator this state uses. */ - inline Nes_Emu *emu() const + inline emulator_t *emu() const { return m_emu; } @@ -146,7 +156,7 @@ inline void saveBlit(const void *ePtr, int32_t *dest, const int32_t *colors, int { // what is the point of the 256 color bitmap and the dynamic color allocation to it? // why not just render directly to a 512 color bitmap with static palette positions? -// Nes_Emu *e = m_emu; // e was a parameter but since this is now part of a class, it's just in here +// emulator_t *e = m_emu; // e was a parameter but since this is now part of a class, it's just in here // const int srcpitch = e->frame().pitch; // const unsigned char *src = e->frame().pixels; // const unsigned char *const srcend = src + (e->image_height - cropbottom) * srcpitch; @@ -166,16 +176,16 @@ inline void saveBlit(const void *ePtr, int32_t *dest, const int32_t *colors, int // } // } - const Nes_Emu *e = (Nes_Emu*) ePtr; + const emulator_t *e = (emulator_t*) ePtr; const unsigned char *in_pixels = e->frame().pixels; if (in_pixels == NULL) return; int32_t *out_pixels = dest; - for (unsigned h = 0; h < Nes_Emu::image_height; h++, in_pixels += e->frame().pitch, out_pixels += Nes_Emu::image_width) - for (unsigned w = 0; w < Nes_Emu::image_width; w++) + for (unsigned h = 0; h < emulator_t::image_height; h++, in_pixels += e->frame().pitch, out_pixels += emulator_t::image_width) + for (unsigned w = 0; w < emulator_t::image_width; w++) { unsigned col = e->frame().palette[in_pixels[w]]; - const Nes_Emu::rgb_t& rgb = e->nes_colors[col]; + const emulator_t::rgb_t& rgb = e->nes_colors[col]; unsigned r = rgb.red; unsigned g = rgb.green; unsigned b = rgb.blue; diff --git a/source/emuInstance.hpp b/source/emuInstance.hpp index 28b4efd..028978e 100644 --- a/source/emuInstance.hpp +++ b/source/emuInstance.hpp @@ -1,183 +1,204 @@ #pragma once -#include #include "sha1/sha1.hpp" +#include #define _LOW_MEM_SIZE 0x800 #define _HIGH_MEM_SIZE 0x2000 #define _NAMETABLES_MEM_SIZE 0x1000 // Size of image generated in graphics buffer -static const uint16_t image_width = 256; -static const uint16_t image_height = 240; +static const uint16_t image_width = 256; +static const uint16_t image_height = 240; class EmuInstance { - public: + public: + typedef uint8_t inputType; - typedef uint8_t inputType; + // Deleting default constructors + virtual ~EmuInstance() = default; - // Deleting default constructors - virtual ~EmuInstance() = default; + // Controller input bits + // 0 - A / 1 + // 1 - B / 2 + // 2 - Select / 4 + // 3 - Start / 8 + // 4 - Up / 16 + // 5 - Down / 32 + // 6 - Left / 64 + // 7 - Right / 128 - // Controller input bits - // 0 - A / 1 - // 1 - B / 2 - // 2 - Select / 4 - // 3 - Start / 8 - // 4 - Up / 16 - // 5 - Down / 32 - // 6 - Left / 64 - // 7 - Right / 128 + // Move Format: + // RLDUTSBA + // ........ - // Move Format: - // RLDUTSBA - // ........ - - static inline inputType moveStringToCode(const std::string& move) - { - inputType moveCode = 0; - - for (size_t i = 0; i < move.size(); i++) switch(move[i]) + static inline inputType moveStringToCode(const std::string &move) { - case 'U': moveCode |= 0b00010000; break; - case 'D': moveCode |= 0b00100000; break; - case 'L': moveCode |= 0b01000000; break; - case 'R': moveCode |= 0b10000000; break; - case 'S': moveCode |= 0b00001000; break; - case 's': moveCode |= 0b00000100; break; - case 'B': moveCode |= 0b00000010; break; - case 'A': moveCode |= 0b00000001; break; - case 'r': break; - case '.': break; - case '|': break; - default: EXIT_WITH_ERROR("Move provided cannot be parsed: '%s', unrecognized character: '%c'\n", move.c_str(), move[i]); + inputType moveCode = 0; + + for (size_t i = 0; i < move.size(); i++) switch (move[i]) + { + case 'U': moveCode |= 0b00010000; break; + case 'D': moveCode |= 0b00100000; break; + case 'L': moveCode |= 0b01000000; break; + case 'R': moveCode |= 0b10000000; break; + case 'S': moveCode |= 0b00001000; break; + case 's': moveCode |= 0b00000100; break; + case 'B': moveCode |= 0b00000010; break; + case 'A': moveCode |= 0b00000001; break; + case 'r': break; + case '.': break; + case '|': break; + default: EXIT_WITH_ERROR("Move provided cannot be parsed: '%s', unrecognized character: '%c'\n", move.c_str(), move[i]); + } + + return moveCode; } - return moveCode; - } - - static inline std::string moveCodeToString(const inputType move) - { + static inline std::string moveCodeToString(const inputType move) + { #ifndef _NES_PLAYER_2 - std::string moveString = "|..|"; + std::string moveString = "|..|"; #else - std::string moveString = "|..|........|"; + std::string moveString = "|..|........|"; #endif - if (move & 0b00010000) moveString += 'U'; else moveString += '.'; - if (move & 0b00100000) moveString += 'D'; else moveString += '.'; - if (move & 0b01000000) moveString += 'L'; else moveString += '.'; - if (move & 0b10000000) moveString += 'R'; else moveString += '.'; - if (move & 0b00001000) moveString += 'S'; else moveString += '.'; - if (move & 0b00000100) moveString += 's'; else moveString += '.'; - if (move & 0b00000010) moveString += 'B'; else moveString += '.'; - if (move & 0b00000001) moveString += 'A'; else moveString += '.'; + if (move & 0b00010000) + moveString += 'U'; + else + moveString += '.'; + if (move & 0b00100000) + moveString += 'D'; + else + moveString += '.'; + if (move & 0b01000000) + moveString += 'L'; + else + moveString += '.'; + if (move & 0b10000000) + moveString += 'R'; + else + moveString += '.'; + if (move & 0b00001000) + moveString += 'S'; + else + moveString += '.'; + if (move & 0b00000100) + moveString += 's'; + else + moveString += '.'; + if (move & 0b00000010) + moveString += 'B'; + else + moveString += '.'; + if (move & 0b00000001) + moveString += 'A'; + else + moveString += '.'; - moveString += "|"; - return moveString; - } - - inline void advanceState(const std::string& move) - { - if (move.find("r") != std::string::npos) doSoftReset(); + moveString += "|"; + return moveString; + } - advanceStateImpl(moveStringToCode(move), 0); - } + inline void advanceState(const std::string &move) + { + if (move.find("r") != std::string::npos) doSoftReset(); - inline size_t getStateSize() const { return _stateSize; } - inline size_t getLiteStateSize() const { return _liteStateSize; } - inline std::string getRomSHA1() const { return _romSHA1String; } + advanceStateImpl(moveStringToCode(move), 0); + } - inline hash_t getStateHash() const - { - MetroHash128 hash; + inline size_t getStateSize() const { return _stateSize; } + inline size_t getLiteStateSize() const { return _liteStateSize; } + inline std::string getRomSHA1() const { return _romSHA1String; } - hash.Update(getLowMem(), _LOW_MEM_SIZE); - hash.Update(getHighMem(), _HIGH_MEM_SIZE); - hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE); - hash.Update(getChrMem(), getChrMemSize()); + inline hash_t getStateHash() const + { + MetroHash128 hash; - hash_t result; - hash.Finalize(reinterpret_cast(&result)); - return result; - } + hash.Update(getLowMem(), _LOW_MEM_SIZE); + hash.Update(getHighMem(), _HIGH_MEM_SIZE); + hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE); + hash.Update(getChrMem(), getChrMemSize()); - inline void enableRendering() { _doRendering = true; }; - inline void disableRendering() { _doRendering = false; }; + hash_t result; + hash.Finalize(reinterpret_cast(&result)); + return result; + } - inline void loadStateFile(const std::string& stateFilePath) - { - std::string stateData; - bool status = loadStringFromFile(stateData, stateFilePath); - if (status == false) EXIT_WITH_ERROR("Could not find/read state file: %s\n", stateFilePath.c_str()); - deserializeState((uint8_t*)stateData.data()); - } + inline void enableRendering() { _doRendering = true; }; + inline void disableRendering() { _doRendering = false; }; - inline void saveStateFile(const std::string& stateFilePath) const - { - std::string stateData; - stateData.resize(_stateSize); - serializeState((uint8_t*)stateData.data()); - saveStringToFile(stateData, stateFilePath.c_str()); - } + inline void loadStateFile(const std::string &stateFilePath) + { + std::string stateData; + bool status = loadStringFromFile(stateData, stateFilePath); + if (status == false) EXIT_WITH_ERROR("Could not find/read state file: %s\n", stateFilePath.c_str()); + deserializeState((uint8_t *)stateData.data()); + } -inline void loadROMFile(const std::string& romFilePath) -{ - // Loading ROM data - bool status = loadStringFromFile(_romData, romFilePath); - if (status == false) EXIT_WITH_ERROR("Could not find/read ROM file: %s\n", romFilePath.c_str()); + inline void saveStateFile(const std::string &stateFilePath) const + { + std::string stateData; + stateData.resize(_stateSize); + serializeState((uint8_t *)stateData.data()); + saveStringToFile(stateData, stateFilePath.c_str()); + } - // Calculating ROM hash value - _romSHA1String = SHA1::GetHash((uint8_t*)_romData.data(), _romData.size()); + inline void loadROMFile(const std::string &romFilePath) + { + // Loading ROM data + bool status = loadStringFromFile(_romData, romFilePath); + if (status == false) EXIT_WITH_ERROR("Could not find/read ROM file: %s\n", romFilePath.c_str()); - // Actually loading rom file - status = loadROMFileImpl(_romData); - if (status == false) EXIT_WITH_ERROR("Could not process ROM file: %s\n", romFilePath.c_str()); + // Calculating ROM hash value + _romSHA1String = SHA1::GetHash((uint8_t *)_romData.data(), _romData.size()); - // Detecting full state size - _stateSize = getStateSizeImpl(); + // Actually loading rom file + status = loadROMFileImpl(_romData); + if (status == false) EXIT_WITH_ERROR("Could not process ROM file: %s\n", romFilePath.c_str()); - // Detecting lite state size - _liteStateSize = getLiteStateSizeImpl(); -} + // Detecting full state size + _stateSize = getStateSizeImpl(); - // Virtual functions + // Detecting lite state size + _liteStateSize = getLiteStateSizeImpl(); + } - virtual bool loadROMFileImpl(const std::string& romFilePath) = 0; - virtual void advanceStateImpl(const inputType controller1, const inputType controller2) = 0; - virtual uint8_t* getLowMem() const = 0; - virtual uint8_t* getNametableMem() const = 0; - virtual uint8_t* getHighMem() const = 0; - virtual const uint8_t* getChrMem() const = 0; - virtual size_t getChrMemSize() const = 0; - virtual void serializeState(uint8_t* state) const = 0; - virtual void deserializeState(const uint8_t* state) = 0; - virtual size_t getStateSizeImpl() const = 0; - virtual size_t getLiteStateSizeImpl() const { return getStateSizeImpl(); }; - virtual void doSoftReset() = 0; - virtual void doHardReset() = 0; - virtual std::string getCoreName() const = 0; - virtual void* getInternalEmulatorPointer() const = 0; + // Virtual functions - protected: + virtual bool loadROMFileImpl(const std::string &romFilePath) = 0; + virtual void advanceStateImpl(const inputType controller1, const inputType controller2) = 0; + virtual uint8_t *getLowMem() const = 0; + virtual uint8_t *getNametableMem() const = 0; + virtual uint8_t *getHighMem() const = 0; + virtual const uint8_t *getChrMem() const = 0; + virtual size_t getChrMemSize() const = 0; + virtual void serializeState(uint8_t *state) const = 0; + virtual void deserializeState(const uint8_t *state) = 0; + virtual size_t getStateSizeImpl() const = 0; + virtual size_t getLiteStateSizeImpl() const { return getStateSizeImpl(); }; + virtual void doSoftReset() = 0; + virtual void doHardReset() = 0; + virtual std::string getCoreName() const = 0; + virtual void *getInternalEmulatorPointer() const = 0; - EmuInstance() = default; + protected: + EmuInstance() = default; - // Storage for the light state size - size_t _liteStateSize; + // Storage for the light state size + size_t _liteStateSize; - // Storage for the full state size - size_t _stateSize; + // Storage for the full state size + size_t _stateSize; - // Flag to determine whether to enable/disable rendering - bool _doRendering = true; - - private: + // Flag to determine whether to enable/disable rendering + bool _doRendering = true; - // Storage for the ROM data - std::string _romData; + private: + // Storage for the ROM data + std::string _romData; - // SHA1 rom hash - std::string _romSHA1String; + // SHA1 rom hash + std::string _romSHA1String; }; diff --git a/source/playbackInstance.hpp b/source/playbackInstance.hpp index 95db6ab..3f26f9b 100644 --- a/source/playbackInstance.hpp +++ b/source/playbackInstance.hpp @@ -1,34 +1,40 @@ #pragma once -#include -#include +#include "emuInstance.hpp" #include #include -#include -#include +#include #include -#include "emuInstance.hpp" +#include +#include +#include #define _INVERSE_FRAME_RATE 16667 -class Nes_Emu; +// Creating emulator instance +#ifdef _USE_QUICKNES + typedef Nes_Emu emulator_t; +#endif + +#ifdef _USE_QUICKERNES + typedef quickerNES::Emu emulator_t; +#endif struct stepData_t { std::string input; - uint8_t* stateData; + uint8_t *stateData; hash_t hash; }; class PlaybackInstance { - public: - - void addStep(const std::string& input) + public: + void addStep(const std::string &input) { stepData_t step; step.input = input; - step.stateData = (uint8_t*) malloc(_emu->getStateSize()); + step.stateData = (uint8_t *)malloc(_emu->getStateSize()); _emu->serializeState(step.stateData); step.hash = _emu->getStateHash(); @@ -37,219 +43,217 @@ class PlaybackInstance } // Initializes the playback module instance - PlaybackInstance(EmuInstance* emu, const std::vector& sequence, const std::string& overlayPath = "") : _emu(emu) - { - // Enabling emulation rendering - _emu->enableRendering(); - - // Loading Emulator instance HQN - _hqnState.setEmulatorPointer(_emu->getInternalEmulatorPointer()); - static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height]; - _hqnState.m_emu->set_pixels(video_buffer, Nes_Emu::image_width+8); - - // Building sequence information - for (const auto& input : sequence) + PlaybackInstance(EmuInstance *emu, const std::vector &sequence, const std::string &overlayPath = "") : _emu(emu) { - // Adding new step - addStep(input); + // Enabling emulation rendering + _emu->enableRendering(); - // Advance state based on the input received - _emu->advanceState(input); + // Loading Emulator instance HQN + _hqnState.setEmulatorPointer(_emu->getInternalEmulatorPointer()); + static uint8_t video_buffer[emulator_t::image_width * emulator_t::image_height]; + _hqnState.m_emu->set_pixels(video_buffer, emulator_t::image_width + 8); + + // Building sequence information + for (const auto &input : sequence) + { + // Adding new step + addStep(input); + + // Advance state based on the input received + _emu->advanceState(input); + } + + // Adding last step with no input + addStep(""); + + // Loading overlay, if provided + if (overlayPath != "") + { + // Using overlay + _useOverlay = true; + + // Loading overlay images + std::string imagePath; + + imagePath = _overlayPath + std::string("/base.png"); + _overlayBaseSurface = IMG_Load(imagePath.c_str()); + if (_overlayBaseSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_a.png"); + _overlayButtonASurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonASurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_b.png"); + _overlayButtonBSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonBSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_select.png"); + _overlayButtonSelectSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonSelectSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_start.png"); + _overlayButtonStartSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonStartSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_left.png"); + _overlayButtonLeftSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonLeftSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_right.png"); + _overlayButtonRightSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonRightSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_up.png"); + _overlayButtonUpSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonUpSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + + imagePath = _overlayPath + std::string("/button_down.png"); + _overlayButtonDownSurface = IMG_Load(imagePath.c_str()); + if (_overlayButtonDownSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + } + + // Opening rendering window + SDL_SetMainReady(); + + // We can only call SDL_InitSubSystem once + if (!SDL_WasInit(SDL_INIT_VIDEO)) + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) + EXIT_WITH_ERROR("Failed to initialize video: %s", SDL_GetError()); + + // Creating HQN GUI + _hqnGUI = hqn::GUIController::create(_hqnState); + _hqnGUI->setScale(1); } - // Adding last step with no input - addStep(""); - - // Loading overlay, if provided - if (overlayPath != "") + // Function to render frame + void renderFrame(const size_t stepId) { - // Using overlay - _useOverlay = true; + // Checking the required step id does not exceed contents of the sequence + if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - // Loading overlay images - std::string imagePath; + // Getting step information + const auto &step = _stepSequence[stepId]; - imagePath = _overlayPath + std::string("/base.png"); - _overlayBaseSurface = IMG_Load(imagePath.c_str()); - if (_overlayBaseSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + // Pointer to overlay images (NULL if unused) + SDL_Surface *overlayButtonASurface = NULL; + SDL_Surface *overlayButtonBSurface = NULL; + SDL_Surface *overlayButtonSelectSurface = NULL; + SDL_Surface *overlayButtonStartSurface = NULL; + SDL_Surface *overlayButtonLeftSurface = NULL; + SDL_Surface *overlayButtonRightSurface = NULL; + SDL_Surface *overlayButtonUpSurface = NULL; + SDL_Surface *overlayButtonDownSurface = NULL; - imagePath = _overlayPath + std::string("/button_a.png"); - _overlayButtonASurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonASurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + // Load correct overlay images, if using overlay + if (_useOverlay == true) + { + if (step.input.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface; + if (step.input.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface; + if (step.input.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface; + if (step.input.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface; + if (step.input.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface; + if (step.input.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface; + if (step.input.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface; + if (step.input.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface; + } - imagePath = _overlayPath + std::string("/button_b.png"); - _overlayButtonBSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonBSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + // Since we do not store the blit information (too much memory), we need to load the previous frame and re-run the input - imagePath = _overlayPath + std::string("/button_select.png"); - _overlayButtonSelectSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonSelectSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + // If its the first step, then simply reset + if (stepId == 0) _emu->doHardReset(); - imagePath = _overlayPath + std::string("/button_start.png"); - _overlayButtonStartSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonStartSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + // Else we load the previous frame + if (stepId > 0) + { + const auto stateData = getStateData(stepId - 1); + _emu->deserializeState(stateData); + _emu->advanceState(getStateInput(stepId - 1)); + } - imagePath = _overlayPath + std::string("/button_left.png"); - _overlayButtonLeftSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonLeftSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); - - imagePath = _overlayPath + std::string("/button_right.png"); - _overlayButtonRightSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonRightSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); - - imagePath = _overlayPath + std::string("/button_up.png"); - _overlayButtonUpSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonUpSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); - - imagePath = _overlayPath + std::string("/button_down.png"); - _overlayButtonDownSurface = IMG_Load(imagePath.c_str()); - if (_overlayButtonDownSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError()); + // Updating image + int32_t curBlit[BLIT_SIZE]; + saveBlit(_emu->getInternalEmulatorPointer(), curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0); + _hqnGUI->update_blit(curBlit, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface); } - // Opening rendering window - SDL_SetMainReady(); - - // We can only call SDL_InitSubSystem once - if (!SDL_WasInit(SDL_INIT_VIDEO)) - if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) - EXIT_WITH_ERROR("Failed to initialize video: %s", SDL_GetError()); - - // Creating HQN GUI - _hqnGUI = hqn::GUIController::create(_hqnState); - _hqnGUI->setScale(1); - } - - // Function to render frame - void renderFrame(const size_t stepId) - { - // Checking the required step id does not exceed contents of the sequence - if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - - // Getting step information - const auto& step = _stepSequence[stepId]; - - // Pointer to overlay images (NULL if unused) - SDL_Surface* overlayButtonASurface = NULL; - SDL_Surface* overlayButtonBSurface = NULL; - SDL_Surface* overlayButtonSelectSurface = NULL; - SDL_Surface* overlayButtonStartSurface = NULL; - SDL_Surface* overlayButtonLeftSurface = NULL; - SDL_Surface* overlayButtonRightSurface = NULL; - SDL_Surface* overlayButtonUpSurface = NULL; - SDL_Surface* overlayButtonDownSurface = NULL; - - // Load correct overlay images, if using overlay - if (_useOverlay == true) + size_t getSequenceLength() const { - if (step.input.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface; - if (step.input.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface; - if (step.input.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface; - if (step.input.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface; - if (step.input.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface; - if (step.input.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface; - if (step.input.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface; - if (step.input.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface; + return _stepSequence.size(); } - // Since we do not store the blit information (too much memory), we need to load the previous frame and re-run the input - - // If its the first step, then simply reset - if (stepId == 0) _emu->doHardReset(); - - // Else we load the previous frame - if (stepId > 0) + const std::string getInput(const size_t stepId) const { - const auto stateData = getStateData(stepId-1); - _emu->deserializeState(stateData); - _emu->advanceState(getStateInput(stepId-1)); + // Checking the required step id does not exceed contents of the sequence + if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); + + // Getting step information + const auto &step = _stepSequence[stepId]; + + // Returning step input + return step.input; } - - // Updating image - int32_t curBlit[BLIT_SIZE]; - saveBlit(_emu->getInternalEmulatorPointer(), curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0); - _hqnGUI->update_blit(curBlit, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface); - } - size_t getSequenceLength() const - { - return _stepSequence.size(); - } + const uint8_t *getStateData(const size_t stepId) const + { + // Checking the required step id does not exceed contents of the sequence + if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - const std::string getInput(const size_t stepId) const - { - // Checking the required step id does not exceed contents of the sequence - if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - - // Getting step information - const auto& step = _stepSequence[stepId]; + // Getting step information + const auto &step = _stepSequence[stepId]; - // Returning step input - return step.input; - } + // Returning step input + return step.stateData; + } - const uint8_t* getStateData(const size_t stepId) const - { - // Checking the required step id does not exceed contents of the sequence - if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - - // Getting step information - const auto& step = _stepSequence[stepId]; + const hash_t getStateHash(const size_t stepId) const + { + // Checking the required step id does not exceed contents of the sequence + if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - // Returning step input - return step.stateData; - } + // Getting step information + const auto &step = _stepSequence[stepId]; - const hash_t getStateHash(const size_t stepId) const - { - // Checking the required step id does not exceed contents of the sequence - if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - - // Getting step information - const auto& step = _stepSequence[stepId]; + // Returning step input + return step.hash; + } - // Returning step input - return step.hash; - } + const std::string getStateInput(const size_t stepId) const + { + // Checking the required step id does not exceed contents of the sequence + if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); -const std::string getStateInput(const size_t stepId) const - { - // Checking the required step id does not exceed contents of the sequence - if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence"); - - // Getting step information - const auto& step = _stepSequence[stepId]; + // Getting step information + const auto &step = _stepSequence[stepId]; - // Returning step input - return step.input; - } + // Returning step input + return step.input; + } + private: + // Internal sequence information + std::vector _stepSequence; - private: + // Storage for the HQN state + hqn::HQNState _hqnState; - // Internal sequence information - std::vector _stepSequence; + // Storage for the HQN GUI controller + hqn::GUIController *_hqnGUI; - // Storage for the HQN state - hqn::HQNState _hqnState; + // Pointer to the contained emulator instance + EmuInstance *const _emu; - // Storage for the HQN GUI controller - hqn::GUIController* _hqnGUI; + // Flag to store whether to use the button overlay + bool _useOverlay = false; - // Pointer to the contained emulator instance - EmuInstance* const _emu; - - // Flag to store whether to use the button overlay - bool _useOverlay = false; - - // Overlay info - std::string _overlayPath; - SDL_Surface* _overlayBaseSurface = NULL; - SDL_Surface* _overlayButtonASurface; - SDL_Surface* _overlayButtonBSurface; - SDL_Surface* _overlayButtonSelectSurface; - SDL_Surface* _overlayButtonStartSurface; - SDL_Surface* _overlayButtonLeftSurface; - SDL_Surface* _overlayButtonRightSurface; - SDL_Surface* _overlayButtonUpSurface; - SDL_Surface* _overlayButtonDownSurface; + // Overlay info + std::string _overlayPath; + SDL_Surface *_overlayBaseSurface = NULL; + SDL_Surface *_overlayButtonASurface; + SDL_Surface *_overlayButtonBSurface; + SDL_Surface *_overlayButtonSelectSurface; + SDL_Surface *_overlayButtonStartSurface; + SDL_Surface *_overlayButtonLeftSurface; + SDL_Surface *_overlayButtonRightSurface; + SDL_Surface *_overlayButtonUpSurface; + SDL_Surface *_overlayButtonDownSurface; }; diff --git a/source/player.cpp b/source/player.cpp index c353f04..6ddae77 100644 --- a/source/player.cpp +++ b/source/player.cpp @@ -1,15 +1,15 @@ -#include #include "argparse/argparse.hpp" -#include "utils.hpp" #include "emuInstance.hpp" #include "playbackInstance.hpp" +#include "utils.hpp" +#include #ifdef _USE_QUICKNES -#include "quickNESInstance.hpp" + #include "quickNESInstance.hpp" #endif #ifdef _USE_QUICKERNES -#include "quickerNESInstance.hpp" + #include "quickerNESInstance.hpp" #endif int main(int argc, char *argv[]) @@ -26,8 +26,8 @@ int main(int argc, char *argv[]) .required(); program.add_argument("stateFile") - .help("(Optional) Path to the initial state file to load.") - .default_value(std::string("")); + .help("(Optional) Path to the initial state file to load.") + .default_value(std::string("")); program.add_argument("--reproduce") .help("Plays the entire sequence without interruptions and exit at the end.") @@ -40,8 +40,14 @@ int main(int argc, char *argv[]) .implicit_value(true); // Try to parse arguments - try { program.parse_args(argc, argv); } - catch (const std::runtime_error &err) { EXIT_WITH_ERROR("%s\n%s", err.what(), program.help().str().c_str()); } + try + { + program.parse_args(argc, argv); + } + catch (const std::runtime_error &err) + { + EXIT_WITH_ERROR("%s\n%s", err.what(), program.help().str().c_str()); + } // Getting ROM file path std::string romFilePath = program.get("romFile"); @@ -78,14 +84,14 @@ int main(int argc, char *argv[]) refreshTerminal(); - // Creating emulator instance - #ifdef _USE_QUICKNES +// Creating emulator instance +#ifdef _USE_QUICKNES auto e = QuickNESInstance(); - #endif +#endif - #ifdef _USE_QUICKERNES - auto e = QuickerNESInstance(); - #endif +#ifdef _USE_QUICKERNES + auto e = quickerNES::QuickerNESInstance(); +#endif // Loading ROM File e.loadROMFile(romFilePath); @@ -110,13 +116,13 @@ int main(int argc, char *argv[]) bool showFrameInfo = true; // Interactive section - while(continueRunning) + while (continueRunning) { // Updating display if (disableRender == false) p.renderFrame(currentStep); // Getting input - const auto& input = p.getStateInput(currentStep); + const auto &input = p.getStateInput(currentStep); // Getting state hash const auto hash = p.getStateHash(currentStep); @@ -158,12 +164,12 @@ int main(int argc, char *argv[]) // Correct current step if requested more than possible if (currentStep < 0) currentStep = 0; - if (currentStep >= sequenceLength) currentStep = sequenceLength-1; + if (currentStep >= sequenceLength) currentStep = sequenceLength - 1; // Quicksave creation command if (command == 's') { - // Storing state file + // Storing state file std::string saveFileName = "quicksave.state"; std::string saveData; @@ -186,4 +192,3 @@ int main(int argc, char *argv[]) // Ending ncurses window finalizeTerminal(); } - diff --git a/source/quickNES/Mapper_70.cpp b/source/quickNES/Mapper_70.cpp index deb364a..00abf8d 100644 --- a/source/quickNES/Mapper_70.cpp +++ b/source/quickNES/Mapper_70.cpp @@ -23,58 +23,59 @@ #include "Nes_Mapper.h" -template < bool _is152 > -class Mapper_74x161x162x32 : public Nes_Mapper { -public: - Mapper_74x161x162x32() - { - register_state( &bank, 1 ); - } +template +class Mapper_74x161x162x32 : public Nes_Mapper +{ + public: + Mapper_74x161x162x32() + { + register_state(&bank, 1); + } - virtual void reset_state() - { - if ( _is152 == 0 ) - bank = ~0; - } + virtual void reset_state() + { + if (_is152 == 0) + bank = ~0; + } - virtual void apply_mapping() - { - if ( _is152 ) - write( 0, 0, bank ); - else - { - intercept_writes( 0x6000, 1 ); - write_intercepted( 0, 0x6000, bank ); - } - } + virtual void apply_mapping() + { + if (_is152) + write(0, 0, bank); + else + { + intercept_writes(0x6000, 1); + write_intercepted(0, 0x6000, bank); + } + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( ( addr != 0x6000 ) || _is152 ) - return false; + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if ((addr != 0x6000) || _is152) + return false; - bank = data; - set_prg_bank( 0x8000, bank_32k, ( bank >> 4 ) & 0x03 ); - set_chr_bank( 0x0000, bank_8k, ( ( bank >> 4 ) & 0x04 ) | ( bank & 0x03 ) ); + bank = data; + set_prg_bank(0x8000, bank_32k, (bank >> 4) & 0x03); + set_chr_bank(0x0000, bank_8k, ((bank >> 4) & 0x04) | (bank & 0x03)); - return true; - } + return true; + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( _is152 == 0) return; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (_is152 == 0) return; - bank = handle_bus_conflict (addr, data ); - set_prg_bank( 0x8000, bank_16k, ( bank >> 4 ) & 0x07 ); - set_chr_bank( 0x0000, bank_8k, bank & 0x0F ); - mirror_single( ( bank >> 7) & 0x01 ); - } + bank = handle_bus_conflict(addr, data); + set_prg_bank(0x8000, bank_16k, (bank >> 4) & 0x07); + set_chr_bank(0x0000, bank_8k, bank & 0x0F); + mirror_single((bank >> 7) & 0x01); + } - uint8_t bank; + uint8_t bank; }; void register_mapper_70(); void register_mapper_70() { - register_mapper< Mapper_74x161x162x32 > ( 70 ); + register_mapper>(70); } diff --git a/source/quickNES/quickNESInstance.hpp b/source/quickNES/quickNESInstance.hpp index c35f4fc..46d5c30 100644 --- a/source/quickNES/quickNESInstance.hpp +++ b/source/quickNES/quickNESInstance.hpp @@ -12,81 +12,79 @@ extern void register_mapper_70(); class QuickNESInstance : public EmuInstance { - public: + public: + QuickNESInstance() : EmuInstance() + { + // Creating new emulator + _nes = new Nes_Emu; - QuickNESInstance() : EmuInstance() - { - // Creating new emulator - _nes = new Nes_Emu; + // Allocating video buffer + video_buffer = (uint8_t *)malloc(image_width * image_height); - // Allocating video buffer - video_buffer = (uint8_t*) malloc(image_width * image_height); + // Setting video buffer + _nes->set_pixels(video_buffer, image_width + 8); - // Setting video buffer - _nes->set_pixels(video_buffer, image_width+8); + // If running the original QuickNES, register extra mappers now + register_misc_mappers(); + register_extra_mappers(); + register_mapper_70(); + } - // If running the original QuickNES, register extra mappers now - register_misc_mappers(); - register_extra_mappers(); - register_mapper_70(); - } + virtual bool loadROMFileImpl(const std::string &romData) override + { + // Loading rom data + Mem_File_Reader romReader(romData.data(), (int)romData.size()); + Auto_File_Reader romFile(romReader); + auto result = _nes->load_ines(romFile); + return result == 0; + } - virtual bool loadROMFileImpl(const std::string& romData) override - { - // Loading rom data - Mem_File_Reader romReader(romData.data(), (int)romData.size()); - Auto_File_Reader romFile(romReader); - auto result = _nes->load_ines(romFile); - return result == 0; - } + uint8_t *getLowMem() const override { return _nes->low_mem(); }; + uint8_t *getNametableMem() const override { return _nes->nametable_mem(); }; + uint8_t *getHighMem() const override { return _nes->high_mem(); }; + const uint8_t *getChrMem() const override { return _nes->chr_mem(); }; + size_t getChrMemSize() const override { return _nes->chr_size(); }; - uint8_t* getLowMem() const override { return _nes->low_mem(); }; - uint8_t* getNametableMem() const override { return _nes->nametable_mem(); }; - uint8_t* getHighMem() const override { return _nes->high_mem();}; - const uint8_t* getChrMem() const override { return _nes->chr_mem();}; - size_t getChrMemSize() const override { return _nes->chr_size();}; + void serializeState(uint8_t *state) const override + { + Mem_Writer w(state, _stateSize, 0); + Auto_File_Writer a(w); + _nes->save_state(a); + } - void serializeState(uint8_t* state) const override - { - Mem_Writer w(state, _stateSize, 0); - Auto_File_Writer a(w); - _nes->save_state(a); - } + void deserializeState(const uint8_t *state) override + { + Mem_File_Reader r(state, _stateSize); + Auto_File_Reader a(r); + _nes->load_state(a); + } - void deserializeState(const uint8_t* state) override - { - Mem_File_Reader r(state, _stateSize); - Auto_File_Reader a(r); - _nes->load_state(a); - } + void advanceStateImpl(const inputType controller1, const inputType controller2) override + { + if (_doRendering == true) _nes->emulate_frame(controller1, controller2); + if (_doRendering == false) _nes->emulate_skip_frame(controller1, controller2); + } - void advanceStateImpl(const inputType controller1, const inputType controller2) override - { - if (_doRendering == true) _nes->emulate_frame(controller1, controller2); - if (_doRendering == false) _nes->emulate_skip_frame(controller1, controller2); - } + std::string getCoreName() const override { return "QuickNES"; } + void doSoftReset() override { _nes->reset(false); } + void doHardReset() override { _nes->reset(true); } - std::string getCoreName() const override { return "QuickNES"; } - void doSoftReset() override { _nes->reset(false); } - void doHardReset() override { _nes->reset(true); } + void *getInternalEmulatorPointer() const override { return _nes; } - void* getInternalEmulatorPointer() const override { return _nes; } + private: + inline size_t getStateSizeImpl() const override + { + uint8_t *data = (uint8_t *)malloc(_DUMMY_SIZE); + Mem_Writer w(data, _DUMMY_SIZE); + Auto_File_Writer a(w); + _nes->save_state(a); + free(data); + return w.size(); + } - private: + // Video buffer + uint8_t *video_buffer; - inline size_t getStateSizeImpl() const override - { - uint8_t* data = (uint8_t*) malloc (_DUMMY_SIZE); - Mem_Writer w(data, _DUMMY_SIZE); - Auto_File_Writer a(w); - _nes->save_state(a); - free(data); - return w.size(); - } - - // Video buffer - uint8_t* video_buffer; - - // Emulator instance - Nes_Emu* _nes; + // Emulator instance + Nes_Emu *_nes; }; diff --git a/source/quickerNES/Nes_Cart.h b/source/quickerNES/Nes_Cart.h deleted file mode 100644 index 1957cae..0000000 --- a/source/quickerNES/Nes_Cart.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -// NES cartridge data (PRG, CHR, mapper) - -/* Copyright (C) 2004-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ - -#include -#include -#include - -class Nes_Cart { -public: - - Nes_Cart() = default; - - struct ines_header_t { - uint8_t signature [4]; - uint8_t prg_count; // number of 16K PRG banks - uint8_t chr_count; // number of 8K CHR banks - uint8_t flags; // MMMM FTBV Mapper low, Four-screen, Trainer, Battery, V mirror - uint8_t flags2; // MMMM --XX Mapper high 4 bits - uint8_t zero [8]; // if zero [7] is non-zero, treat flags2 as zero - }; - static_assert( sizeof (ines_header_t) == 16 ); - - // Load iNES file -void load_ines( const uint8_t* buffer ) - { - ines_header_t h; - - size_t bufferPos = 0; - { size_t copySize = sizeof(ines_header_t); memcpy(&h, &buffer[bufferPos], copySize); bufferPos += copySize; } - if ( h.zero [7] ) h.flags2 = 0; - set_mapper( h.flags, h.flags2 ); - - // skip trainer - if ( h.flags & 0x04 ) bufferPos += 512; - - // Allocating memory for prg and chr - prg_size_ = h.prg_count * 16 * 1024L; - chr_size_ = h.chr_count * 8 * 1024L; - - auto p = malloc(prg_size_ + chr_size_); - prg_ = (uint8_t*)p; - chr_ = &prg_[prg_size_]; - - { size_t copySize = prg_size(); memcpy(prg(), &buffer[bufferPos], copySize); bufferPos += copySize; } - { size_t copySize = chr_size(); memcpy(chr(), &buffer[bufferPos], copySize); bufferPos += copySize; } - } - - inline bool has_battery_ram() const { return mapper & 0x02; } - - // Set mapper and information bytes. LSB and MSB are the standard iNES header - // bytes at offsets 6 and 7. - inline void set_mapper( int mapper_lsb, int mapper_msb ) - { - mapper = mapper_msb * 0x100 + mapper_lsb; - } - - inline int mapper_code() const { return ((mapper >> 8) & 0xf0) | ((mapper >> 4) & 0x0f); } - - // Size of PRG data - long prg_size() const { return prg_size_; } - - // Size of CHR data - long chr_size() const { return chr_size_; } - - unsigned mapper_data() const { return mapper; } - - // Initial mirroring setup - int mirroring() const { return mapper & 0x09; } - - // Pointer to beginning of PRG data - inline uint8_t * prg() { return prg_; } - inline uint8_t const* prg() const { return prg_; } - - // Pointer to beginning of CHR data - inline uint8_t * chr() { return chr_; } - inline uint8_t const* chr() const { return chr_; } - - // End of public interface -private: - uint8_t *prg_; - uint8_t *chr_; - long prg_size_; - long chr_size_; - unsigned mapper; -}; - diff --git a/source/quickerNES/Nes_Core.h b/source/quickerNES/Nes_Core.h deleted file mode 100644 index c4f960c..0000000 --- a/source/quickerNES/Nes_Core.h +++ /dev/null @@ -1,986 +0,0 @@ -#pragma once - -// Internal NES emulator - -/* Copyright (C) 2004-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -// Nes_Emu 0.7.0 - - -#include -#include -#include "apu/apu.h" -#include "Nes_Cpu.h" -#include "ppu/Nes_Ppu.h" -#include "mappers/mapper.h" - -class Nes_Cart; - -#undef NES_EMU_CPU_HOOK -#ifndef NES_EMU_CPU_HOOK - #define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time ) -#endif - -bool const wait_states_enabled = true; -bool const single_instruction_mode = false; // for debugging irq/nmi timing issues -const int unmapped_fill = Nes_Cpu::page_wrap_opcode; -unsigned const low_ram_size = 0x800; -unsigned const low_ram_end = 0x2000; -unsigned const sram_end = 0x8000; -const int irq_inhibit_mask = 0x04; - -struct nes_state_t -{ - uint16_t timestamp; // CPU clocks * 15 (for NTSC) - uint8_t pal; - uint8_t unused [1]; - uint32_t frame_count; // number of frames emulated since power-up -}; - -struct joypad_state_t -{ - uint32_t joypad_latches [2]; // joypad 1 & 2 shift registers - uint8_t w4016; // strobe - uint8_t unused [3]; -}; -static_assert( sizeof (joypad_state_t) == 12 ); - -struct cpu_state_t -{ - uint16_t pc; - uint8_t s; - uint8_t p; - uint8_t a; - uint8_t x; - uint8_t y; - uint8_t unused [1]; -}; -static_assert( sizeof (cpu_state_t) == 8 ); - -class Nes_Core : private Nes_Cpu { - typedef Nes_Cpu cpu; -public: - - Nes_Core() : ppu( this ) - { - cart = NULL; - impl = NULL; - mapper = NULL; - memset( &nes, 0, sizeof nes ); - memset( &joypad, 0, sizeof joypad ); - } - - ~Nes_Core() - { - close(); - delete impl; - } - - const char * init() - { - if ( !impl ) - { - impl = new impl_t; - impl->apu.dmc_reader( read_dmc, this ); - impl->apu.irq_notifier( apu_irq_changed, this ); - } - - return 0; - } - - void open( Nes_Cart const* new_cart ) - { - close(); - init(); - - // Getting cartdrige mapper code - auto mapperCode = new_cart->mapper_code(); - - // Getting mapper corresponding to that code - mapper = Nes_Mapper::getMapperFromCode(mapperCode); - - // If no mapper was found, return null (error) now - if (mapper == nullptr) - { - fprintf(stderr, "Could not find mapper for code: %u\n", mapperCode); - exit(-1); - } - - // Assigning backwards pointers to cartdrige and emulator now - mapper->cart_ = new_cart; - mapper->emu_ = this; - - ppu.open_chr( new_cart->chr(), new_cart->chr_size() ); - - cart = new_cart; - memset( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page ); - reset( true, true ); - } - - size_t getLiteStateSize() const - { - size_t size = 0; - - size += sizeof(nes_state_t); - size += sizeof(registers_t); - size += sizeof(ppu_state_t); - size += sizeof(Nes_Apu::apu_state_t); - size += sizeof(joypad_state_t); - size += mapper->state_size; - size += low_ram_size; - size += Nes_Ppu::spr_ram_size; - size_t nametable_size = 0x800; - if (ppu.nt_banks [3] >= &ppu.impl->nt_ram [0xC00] ) nametable_size = 0x1000; - size += nametable_size; - if ( ppu.chr_is_writable ) size += ppu.chr_size; - if ( sram_present ) size += impl->sram_size; - - return size; - } - - size_t getStateSize() const - { - size_t size = 0; - - size += sizeof(char[4]); // NESS Block - size += sizeof(uint32_t); // Block Size - - size += sizeof(char[4]); // TIME Block - size += sizeof(uint32_t); // Block Size - size += sizeof(nes_state_t); - - size += sizeof(char[4]); // CPUR Block - size += sizeof(uint32_t); // Block Size - size += sizeof(registers_t); - - size += sizeof(char[4]); // PPUR Block - size += sizeof(uint32_t); // Block Size - size += sizeof(ppu_state_t); - - size += sizeof(char[4]); // APUR Block - size += sizeof(uint32_t); // Block Size - size += sizeof(Nes_Apu::apu_state_t); - - size += sizeof(char[4]); // CTRL Block - size += sizeof(uint32_t); // Block Size - size += sizeof(joypad_state_t); - - size += sizeof(char[4]); // MAPR Block - size += sizeof(uint32_t); // Block Size - size += mapper->state_size; - - size += sizeof(char[4]); // LRAM Block - size += sizeof(uint32_t); // Block Size - size += low_ram_size; - - size += sizeof(char[4]); // SPRT Block - size += sizeof(uint32_t); // Block Size - size += Nes_Ppu::spr_ram_size; - - size += sizeof(char[4]); // NTAB Block - size += sizeof(uint32_t); // Block Size - size_t nametable_size = 0x800; - if (ppu.nt_banks [3] >= &ppu.impl->nt_ram [0xC00] ) nametable_size = 0x1000; - size += nametable_size; - - if ( ppu.chr_is_writable ) - { - size += sizeof(char[4]); // CHRR Block - size += sizeof(uint32_t); // Block Size - size += ppu.chr_size; - } - - if ( sram_present ) - { - size += sizeof(char[4]); // SRAM Block - size += sizeof(uint32_t); // Block Size - size += impl->sram_size; - } - - size += sizeof(char[4]); // gend Block - size += sizeof(uint32_t); // Block Size - - return size; - } - - size_t serializeState(uint8_t* buffer) const - { - size_t pos = 0; - std::string headerCode; - const uint32_t headerSize = sizeof(char) * 4; - uint32_t blockSize = 0; - void* dataSource; - - headerCode = "NESS"; // NESS Block - blockSize = 0xFFFFFFFF; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - - headerCode = "TIME"; // TIME Block - nes_state_t state = nes; - state.timestamp *= 5; - blockSize = sizeof(nes_state_t); - dataSource = (void*) &state; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "CPUR"; // CPUR Block - cpu_state_t s; - memset( &s, 0, sizeof s ); - s.pc = r.pc; - s.s = r.sp; - s.a = r.a; - s.x = r.x; - s.y = r.y; - s.p = r.status; - blockSize = sizeof(cpu_state_t); - dataSource = (void*) &s; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "PPUR"; // PPUR Block - blockSize = sizeof(ppu_state_t); - dataSource = (void*) &ppu; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "APUR"; // APUR Block - Nes_Apu::apu_state_t apuState; - impl->apu.save_state(&apuState); - blockSize = sizeof(Nes_Apu::apu_state_t); - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], &apuState, blockSize); pos += blockSize; - - headerCode = "CTRL"; // CTRL Block - blockSize = sizeof(joypad_state_t); - dataSource = (void*) &joypad; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "MAPR"; // MAPR Block - blockSize = mapper->state_size; - dataSource = (void*) mapper->state; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "LRAM"; // LRAM Block - blockSize = low_ram_size; - dataSource = (void*) low_mem; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "SPRT"; // SPRT Block - blockSize = Nes_Ppu::spr_ram_size; - dataSource = (void*) ppu.spr_ram; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - headerCode = "NTAB"; // NTAB Block - size_t nametable_size = 0x800; - if (ppu.nt_banks [3] >= &ppu.impl->nt_ram [0xC00] ) nametable_size = 0x1000; - blockSize = nametable_size; - dataSource = (void*) ppu.impl->nt_ram; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - - if ( ppu.chr_is_writable ) - { - headerCode = "CHRR"; // CHRR Block - blockSize = ppu.chr_size; - dataSource = (void*) ppu.impl->chr_ram; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - } - - if ( sram_present ) - { - headerCode = "SRAM"; // SRAM Block - blockSize = impl->sram_size; - dataSource = (void*) impl->sram; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - memcpy(&buffer[pos], dataSource, blockSize); pos += blockSize; - } - - headerCode = "gend"; // gend Block - blockSize = 0; - memcpy(&buffer[pos], headerCode.data(), headerSize); pos += headerSize; - memcpy(&buffer[pos], &blockSize, headerSize); pos += headerSize; - - return pos; // Bytes written - } - - size_t deserializeState(const uint8_t* buffer) - { - disable_rendering(); - error_count = 0; - ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over - - size_t pos = 0; - const uint32_t headerSize = sizeof(char) * 4; - uint32_t blockSize = 0; - - // NESS Block - pos += headerSize; - pos += headerSize; - - // TIME Block - nes_state_t nesState; - pos += headerSize; - pos += headerSize; - blockSize = sizeof(nes_state_t); - memcpy(&nesState, &buffer[pos], blockSize); pos += blockSize; - nes = nesState; - nes.timestamp /= 5; - - // CPUR Block - cpu_state_t s; - blockSize = sizeof(cpu_state_t); - pos += headerSize; - pos += headerSize; - memcpy((void*) &s, &buffer[pos], blockSize); pos += blockSize; - r.pc = s.pc; - r.sp = s.s; - r.a = s.a; - r.x = s.x; - r.y = s.y; - r.status = s.p; - - // PPUR Block - blockSize = sizeof(ppu_state_t); - pos += headerSize; - pos += headerSize; - memcpy((void*) &ppu, &buffer[pos], blockSize); pos += blockSize; - - // APUR Block - Nes_Apu::apu_state_t apuState; - blockSize = sizeof(Nes_Apu::apu_state_t); - pos += headerSize; - pos += headerSize; - memcpy(&apuState, &buffer[pos], blockSize); - pos += blockSize; - impl->apu.load_state(apuState); - impl->apu.end_frame( -(int) nes.timestamp / ppu_overclock ); - - // CTRL Block - blockSize = sizeof(joypad_state_t); - pos += headerSize; - pos += headerSize; - memcpy((void*) &joypad, &buffer[pos], blockSize); pos += blockSize; - - // MAPR Block - mapper->default_reset_state(); - blockSize = mapper->state_size; - pos += headerSize; - pos += headerSize; - memcpy((void*) mapper->state, &buffer[pos], blockSize); pos += blockSize; - mapper->apply_mapping(); - - // LRAM Block - blockSize = low_ram_size; - pos += headerSize; - pos += headerSize; - memcpy((void*) low_mem, &buffer[pos], blockSize); pos += blockSize; - - // SPRT Block - blockSize = Nes_Ppu::spr_ram_size; - pos += headerSize; - pos += headerSize; - memcpy((void*) ppu.spr_ram, &buffer[pos], blockSize); pos += blockSize; - - // NTAB Block - size_t nametable_size = 0x800; - if (ppu.nt_banks [3] >= &ppu.impl->nt_ram [0xC00] ) nametable_size = 0x1000; - blockSize = nametable_size; - pos += headerSize; - pos += headerSize; - memcpy((void*) ppu.impl->nt_ram, &buffer[pos], blockSize); pos += blockSize; - - if ( ppu.chr_is_writable ) - { - // CHRR Block - blockSize = ppu.chr_size; - pos += headerSize; - pos += headerSize; - memcpy((void*) ppu.impl->chr_ram, &buffer[pos], blockSize); pos += blockSize; - } - - if ( sram_present ) - { - // SRAM Block - blockSize = impl->sram_size; - pos += headerSize; - pos += headerSize; - memcpy((void*) impl->sram, &buffer[pos], blockSize); pos += blockSize; - enable_sram(true); - } - - // headerCode = "gend"; // gend Block - pos += headerSize; - pos += headerSize; - - return pos; // Bytes read - } - - void reset( bool full_reset, bool erase_battery_ram ) - { - if ( full_reset ) - { - cpu::reset( impl->unmapped_page ); - cpu_time_offset = -1; - clock_ = 0; - - // Low RAM - memset( cpu::low_mem, 0xFF, low_ram_size ); - cpu::low_mem [8] = 0xf7; - cpu::low_mem [9] = 0xef; - cpu::low_mem [10] = 0xdf; - cpu::low_mem [15] = 0xbf; - - // SRAM - lrom_readable = 0; - sram_present = true; - enable_sram( false ); - if ( !cart->has_battery_ram() || erase_battery_ram ) - memset( impl->sram, 0xFF, impl->sram_size ); - - joypad.joypad_latches [0] = 0; - joypad.joypad_latches [1] = 0; - - nes.frame_count = 0; - } - - // to do: emulate partial reset - - ppu.reset( full_reset ); - impl->apu.reset(); - - mapper->reset(); - - cpu::r.pc = read_vector( 0xFFFC ); - cpu::r.sp = 0xfd; - cpu::r.a = 0; - cpu::r.x = 0; - cpu::r.y = 0; - cpu::r.status = irq_inhibit_mask; - nes.timestamp = 0; - error_count = 0; - } - - nes_time_t emulate_frame(int joypad1, int joypad2) - { - current_joypad [0] = (joypad1 |= ~0xFF); - current_joypad [1] = (joypad2 |= ~0xFF); - - cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1; - ppu_2002_time = 0; - clock_ = cpu_time_offset; - - // TODO: clean this fucking mess up - auto t0 = emulate_frame_(); - impl->apu.run_until_( t0 ); - clock_ = cpu_time_offset; - auto t1 = cpu_time(); - impl->apu.run_until_( t1 ); - - nes_time_t ppu_frame_length = ppu.frame_length(); - nes_time_t length = cpu_time(); - nes.timestamp = ppu.end_frame( length ); - mapper->end_frame( length ); - - impl->apu.end_frame( ppu_frame_length ); - - disable_rendering(); - nes.frame_count++; - - return ppu_frame_length; - } - - void close() - { - cart = NULL; - delete mapper; - mapper = NULL; - - ppu.close_chr(); - - disable_rendering(); - } - - - void irq_changed() - { - cpu_set_irq_time( earliest_irq( cpu_time() ) ); - } - - void event_changed() - { - cpu_set_end_time( earliest_event( cpu_time() ) ); - } - -public: private: friend class Nes_Emu; - - struct impl_t - { - enum { sram_size = 0x2000 }; - uint8_t sram [sram_size]; - Nes_Apu apu; - - // extra byte allows CPU to always read operand of instruction, which - // might go past end of data - uint8_t unmapped_page [::Nes_Cpu::page_size + 1]; - }; - impl_t* impl; // keep large arrays separate - unsigned long error_count; - bool sram_present; - -public: - unsigned long current_joypad [2]; - Nes_Cart const* cart; - Nes_Mapper* mapper; - nes_state_t nes; - Nes_Ppu ppu; - -private: - // noncopyable - Nes_Core( const Nes_Core& ); - Nes_Core& operator = ( const Nes_Core& ); - - // Timing - nes_time_t ppu_2002_time; - void disable_rendering() { clock_ = 0; } - - inline nes_time_t earliest_irq( nes_time_t present ) - { - return std::min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) ); - } - - inline nes_time_t ppu_frame_length( nes_time_t present ) - { - nes_time_t t = ppu.frame_length(); - if ( t > present ) - return t; - - ppu.render_bg_until( clock() ); // to do: why this call to clock() rather than using present? - return ppu.frame_length(); - } - - - inline nes_time_t earliest_event( nes_time_t present ) - { - // PPU frame - nes_time_t t = ppu_frame_length( present ); - - // DMC - if ( wait_states_enabled ) - t = std::min( t, impl->apu.next_dmc_read_time() + 1 ); - - // NMI - t = std::min( t, ppu.nmi_time() ); - - if ( single_instruction_mode ) - t = std::min( t, present + 1 ); - - return t; - } - - // APU and Joypad - joypad_state_t joypad; - - int read_io( nes_addr_t addr ) - { - if ( (addr & 0xFFFE) == 0x4016 ) - { - // to do: to aid with recording, doesn't emulate transparent latch, - // so a game that held strobe at 1 and read $4016 or $4017 would not get - // the current A status as occurs on a NES - unsigned long result = joypad.joypad_latches [addr & 1]; - if ( !(joypad.w4016 & 1) ) - joypad.joypad_latches [addr & 1] = (result >> 1) | 0x80000000; - return result & 1; - } - - if ( addr == Nes_Apu::status_addr ) - return impl->apu.read_status( clock() ); - - return addr >> 8; // simulate open bus - } - - void write_io( nes_addr_t addr, int data ) - { - // sprite dma - if ( addr == 0x4014 ) - { - ppu.dma_sprites( clock(), cpu::get_code( data * 0x100 ) ); - cpu_adjust_time( 513 ); - return; - } - - // joypad strobe - if ( addr == 0x4016 ) - { - // if strobe goes low, latch data - if ( joypad.w4016 & 1 & ~data ) - { - joypad.joypad_latches [0] = current_joypad [0]; - joypad.joypad_latches [1] = current_joypad [1]; - } - joypad.w4016 = data; - return; - } - - // apu - if ( unsigned (addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr ) - { - impl->apu.write_register( clock(), addr, data ); - if ( wait_states_enabled ) - { - if ( addr == 0x4010 || (addr == 0x4015 && (data & 0x10)) ) - { - impl->apu.run_until( clock() + 1 ); - event_changed(); - } - } - return; - } - } - - static inline int read_dmc( void* data, nes_addr_t addr ) - { - Nes_Core* emu = (Nes_Core*) data; - int result = *emu->cpu::get_code( addr ); - if ( wait_states_enabled ) - emu->cpu_adjust_time( 4 ); - return result; - } - - static inline void apu_irq_changed( void* emu ) - { - ((Nes_Core*) emu)->irq_changed(); - } - - - // CPU - unsigned sram_readable; - unsigned sram_writable; - unsigned lrom_readable; - nes_time_t clock_; - nes_time_t cpu_time_offset; - - nes_time_t emulate_frame_() - { - Nes_Cpu::result_t last_result = cpu::result_cycles; - int extra_instructions = 0; - while ( true ) - { - // Add DMC wait-states to CPU time - if ( wait_states_enabled ) - { - impl->apu.run_until( cpu_time() ); - clock_ = cpu_time_offset; - } - - nes_time_t present = cpu_time(); - if ( present >= ppu_frame_length( present ) ) - { - if ( ppu.nmi_time() <= present ) - { - // NMI will occur next, so delayed CLI and SEI don't need to be handled. - // If NMI will occur normally ($2000.7 and $2002.7 set), let it occur - // next frame, otherwise vector it now. - - if ( !(ppu.w2000 & 0x80 & ppu.r2002) ) - { - /* vectored NMI at end of frame */ - vector_interrupt( 0xFFFA ); - present += 7; - } - return present; - } - - if ( extra_instructions > 2 ) - { - return present; - } - - if ( last_result != cpu::result_cli && last_result != cpu::result_sei && - (ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) ) - return present; - - /* Executing extra instructions for frame */ - extra_instructions++; // execute one more instruction - } - - // NMI - if ( present >= ppu.nmi_time() ) - { - ppu.acknowledge_nmi(); - vector_interrupt( 0xFFFA ); - last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now - } - - // IRQ - nes_time_t irq_time = earliest_irq( present ); - cpu_set_irq_time( irq_time ); - if ( present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) || - last_result == cpu::result_sei) ) - { - if ( last_result != cpu::result_cli ) - { - /* IRQ vectored */ - mapper->run_until( present ); - vector_interrupt( 0xFFFE ); - } - else - { - // CLI delays IRQ - cpu_set_irq_time( present + 1 ); - } - } - - // CPU - nes_time_t end_time = earliest_event( present ); - if ( extra_instructions ) - end_time = present + 1; - unsigned long cpu_error_count = cpu::error_count(); - last_result = NES_EMU_CPU_HOOK( cpu, end_time - cpu_time_offset - 1 ); - cpu_adjust_time( cpu::time() ); - clock_ = cpu_time_offset; - error_count += cpu::error_count() - cpu_error_count; - } - } - - nes_addr_t read_vector( nes_addr_t addr ) - { - uint8_t const* p = cpu::get_code( addr ); - return p [1] * 0x100 + p [0]; - } - - void vector_interrupt( nes_addr_t vector ) - { - cpu::push_byte( cpu::r.pc >> 8 ); - cpu::push_byte( cpu::r.pc & 0xFF ); - cpu::push_byte( cpu::r.status | 0x20 ); // reserved bit is set - - cpu_adjust_time( 7 ); - cpu::r.status |= irq_inhibit_mask; - cpu::r.pc = read_vector( vector ); - } - - static void log_unmapped( nes_addr_t addr, int data = -1 ); - void cpu_set_irq_time( nes_time_t t ) { cpu::set_irq_time_( t - 1 - cpu_time_offset ); } - void cpu_set_end_time( nes_time_t t ) { cpu::set_end_time_( t - 1 - cpu_time_offset ); } - nes_time_t cpu_time() const { return clock_ + 1; } - - inline void cpu_adjust_time( int n ) - { - ppu_2002_time -= n; - cpu_time_offset += n; - cpu::reduce_limit( n ); - } - -public: private: friend class Nes_Ppu; - void set_ppu_2002_time( nes_time_t t ) { ppu_2002_time = t - 1 - cpu_time_offset; } - -public: private: friend class Nes_Mapper; - - void enable_prg_6000() - { - sram_writable = 0; - sram_readable = 0; - lrom_readable = 0x8000; - } - - void enable_sram( bool b, bool read_only = false) - { - sram_writable = 0; - if ( b ) - { - if ( !sram_present ) - { - sram_present = true; - memset( impl->sram, 0xFF, impl->sram_size ); - } - sram_readable = sram_end; - if ( !read_only ) - sram_writable = sram_end; - cpu::map_code( 0x6000, impl->sram_size, impl->sram ); - } - else - { - sram_readable = 0; - for ( int i = 0; i < impl->sram_size; i += cpu::page_size ) - cpu::map_code( 0x6000 + i, cpu::page_size, impl->unmapped_page ); - } - } - - nes_time_t clock() const { return clock_; } - - void add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write ) -{ - int end = (addr + size + (page_size - 1)) >> page_bits; - for ( int page = addr >> page_bits; page < end; page++ ) - { - data_reader_mapped [page] |= read; - data_writer_mapped [page] |= write; - } -} - -public: private: friend class Nes_Cpu; - int cpu_read_ppu( nes_addr_t, nes_time_t ); - int cpu_read( nes_addr_t, nes_time_t ); - void cpu_write( nes_addr_t, int data, nes_time_t ); - void cpu_write_2007( int data ); - -private: - unsigned char data_reader_mapped [page_count + 1]; // extra entry for overflow - unsigned char data_writer_mapped [page_count + 1]; -}; - -inline int Nes_Core::cpu_read( nes_addr_t addr, nes_time_t time ) -{ - { - int result = cpu::low_mem [addr & 0x7FF]; - if ( !(addr & 0xE000) ) - return result; - } - - { - int result = *cpu::get_code( addr ); - if ( addr > 0x7FFF ) - return result; - } - - time += cpu_time_offset; - if ( addr < 0x4000 ) - return ppu.read( addr, time ); - - clock_ = time; - if ( data_reader_mapped [addr >> page_bits] ) - { - int result = mapper->read( time, addr ); - if ( result >= 0 ) - return result; - } - - if ( addr < 0x6000 ) - return read_io( addr ); - - if ( addr < sram_readable ) - return impl->sram [addr & (impl_t::sram_size - 1)]; - - if ( addr < lrom_readable ) - return *cpu::get_code( addr ); - - return addr >> 8; // simulate open bus -} - -inline int Nes_Core::cpu_read_ppu( nes_addr_t addr, nes_time_t time ) -{ - //LOG_FREQ( "cpu_read_ppu", 16, addr >> 12 ); - - // Read of status register (0x2002) is heavily optimized since many games - // poll it hundreds of times per frame. - nes_time_t next = ppu_2002_time; - int result = ppu.r2002; - if ( addr == 0x2002 ) - { - ppu.second_write = false; - if ( time >= next ) - result = ppu.read_2002( time + cpu_time_offset ); - } - else - { - result = cpu::low_mem [addr & 0x7FF]; - if ( addr >= 0x2000 ) - result = cpu_read( addr, time ); - } - - return result; -} - -inline void Nes_Core::cpu_write_2007( int data ) -{ - // ppu.write_2007() is inlined - if ( ppu.write_2007( data ) & Nes_Ppu::vaddr_clock_mask ) - mapper->a12_clocked(); -} - -inline void Nes_Core::cpu_write( nes_addr_t addr, int data, nes_time_t time ) -{ - //LOG_FREQ( "cpu_write", 16, addr >> 12 ); - - if ( !(addr & 0xE000) ) - { - cpu::low_mem [addr & 0x7FF] = data; - return; - } - - time += cpu_time_offset; - if ( addr < 0x4000 ) - { - if ( (addr & 7) == 7 ) - cpu_write_2007( data ); - else - ppu.write( time, addr, data ); - return; - } - - clock_ = time; - if ( data_writer_mapped [addr >> page_bits] && mapper->write_intercepted( time, addr, data ) ) - return; - - if ( addr < 0x6000 ) - { - write_io( addr, data ); - return; - } - - if ( addr < sram_writable ) - { - impl->sram [addr & (impl_t::sram_size - 1)] = data; - return; - } - - if ( addr > 0x7FFF ) - { - mapper->write( clock_, addr, data ); - return; - } -} - -#define NES_CPU_READ_PPU( cpu, addr, time ) \ - static_cast(*cpu).cpu_read_ppu( addr, time ) - -#define NES_CPU_READ( cpu, addr, time ) \ - static_cast(*cpu).cpu_read( addr, time ) - -#define NES_CPU_WRITEX( cpu, addr, data, time ){\ - static_cast(*cpu).cpu_write( addr, data, time );\ -} - -#define NES_CPU_WRITE( cpu, addr, data, time ){\ - if ( addr < 0x800 ) cpu->low_mem [addr] = data;\ - else if ( addr == 0x2007 ) static_cast(*cpu).cpu_write_2007( data );\ - else static_cast(*cpu).cpu_write( addr, data, time );\ -} - diff --git a/source/quickerNES/Nes_Cpu.h b/source/quickerNES/Nes_Cpu.h deleted file mode 100644 index 7e65a4e..0000000 --- a/source/quickerNES/Nes_Cpu.h +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -// NES 6502 CPU emulator -// Nes_Emu 0.7.0 - -#include - -typedef long nes_time_t; // clock cycle count -typedef unsigned nes_addr_t; // 16-bit address - -class Nes_Cpu { -public: - - // NES 6502 registers. *Not* kept updated during a call to run(). - struct registers_t { - uint16_t pc; // Should be more than 16 bits to allow overflow detection -- but I (eien86) removed it to maximize performance. - uint8_t a; - uint8_t x; - uint8_t y; - uint8_t status; - uint8_t sp; - }; - - // Map code memory (memory accessed via the program counter). Start and size - // must be multiple of page_size. - enum { page_bits = 11 }; - enum { page_count = 0x10000 >> page_bits }; - enum { page_size = 1L << page_bits }; - - // Clear registers, unmap memory, and map code pages to unmapped_page. - void reset( void const* unmapped_page = 0 ); - - inline void map_code( nes_addr_t start, unsigned size, const void* data ) - { - unsigned first_page = start / page_size; - const uint8_t* newPtr = (uint8_t*) data - start; - for ( unsigned i = size / page_size; i--; ) code_map [first_page + i] = newPtr; - } - - // Access memory as the emulated CPU does. - int read( nes_addr_t ); - void write( nes_addr_t, int data ); - - // Push a byte on the stack - inline void push_byte( int data ) - { - int sp = r.sp; - r.sp = (sp - 1) & 0xFF; - low_mem [0x100 + sp] = data; - } - - // Reasons that run() returns - enum result_t { - result_cycles, // Requested number of cycles (or more) were executed - result_sei, // I flag just set and IRQ time would generate IRQ now - result_cli, // I flag just cleared but IRQ should occur *after* next instr - result_badop // unimplemented/illegal instruction - }; - - result_t run( nes_time_t end_time ); - - nes_time_t time() const { return clock_count; } - - inline void reduce_limit( int offset ) - { - clock_limit -= offset; - end_time_ -= offset; - irq_time_ -= offset; - } - - inline void set_end_time_( nes_time_t t ) - { - end_time_ = t; - update_clock_limit(); - } - - inline void set_irq_time_( nes_time_t t ) - { - irq_time_ = t; - update_clock_limit(); - } - - unsigned long error_count() const { return error_count_; } - - // If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped. - enum { page_wrap_opcode = 0xF2 }; - - // One of the many opcodes that are undefined and stop CPU emulation. - enum { bad_opcode = 0xD2 }; - - uint8_t const* code_map [page_count + 1]; - nes_time_t clock_limit; - nes_time_t clock_count; - nes_time_t irq_time_; - nes_time_t end_time_; - unsigned long error_count_; - - enum { irq_inhibit = 0x04 }; - - inline void update_clock_limit() - { - nes_time_t t = end_time_; - if ( t > irq_time_ && !(r.status & irq_inhibit) ) - t = irq_time_; - clock_limit = t; - } - - registers_t r; - bool isCorrectExecution = true; - - // low_mem is a full page size so it can be mapped with code_map - uint8_t low_mem [page_size > 0x800 ? page_size : 0x800]; - - inline uint8_t* get_code( nes_addr_t addr ) - { - return (uint8_t*) code_map [addr >> page_bits] + addr; - } - - -}; - - - diff --git a/source/quickerNES/Nes_Emu.h b/source/quickerNES/Nes_Emu.h deleted file mode 100644 index 0d725c1..0000000 --- a/source/quickerNES/Nes_Emu.h +++ /dev/null @@ -1,262 +0,0 @@ -#pragma once - -// NES video game console emulator with snapshot support - -// Nes_Emu 0.7.0 - -#include "apu/Multi_Buffer.h" -#include "Nes_Cart.h" -#include "Nes_Core.h" - -class Nes_State; - -class Nes_Emu { -public: - Nes_Emu(); - virtual ~Nes_Emu(); - -// Basic setup - - // Load iNES file into emulator and clear recording - void load_ines( const uint8_t* buffer ); - - // Set sample rate for sound generation - const char * set_sample_rate( long ); - - // Size and depth of graphics buffer required for rendering. Note that this - // is larger than the actual image, with a temporary area around the edge - // that gets filled with junk. - static const uint16_t buffer_width = Nes_Ppu::buffer_width; - uint16_t buffer_height() const { return buffer_height_; } - static const uint8_t bits_per_pixel = 8; - - // Set graphics buffer to render pixels to. Pixels points to top-left pixel and - // row_bytes is the number of bytes to get to the next line (positive or negative). - void set_pixels( void* pixels, long row_bytes ); - - // Size of image generated in graphics buffer - static const uint16_t image_width = 256; - static const uint16_t image_height = 240; - - const uint8_t* getHostPixels () const { return emu.ppu.host_pixels; } - - size_t getLiteStateSize() const { return emu.getLiteStateSize(); } - size_t getStateSize() const { return emu.getStateSize(); } - -// Basic emulation - - // Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image - // and sound are available for output using the accessors below. - virtual const char * emulate_frame( int joypad1, int joypad2 = 0 ); - - // Emulate one video frame using joypad1 and joypad2 as input, but skips drawing. - // Afterwards, audio is available for output using the accessors below. - virtual const char * emulate_skip_frame( int joypad1, int joypad2 = 0 ); - - // Maximum size of palette that can be generated - static const uint16_t max_palette_size = 256; - - // Result of current frame - struct frame_t - { - static const uint8_t left = 8; - - int joypad_read_count; // number of times joypads were strobed (read) - int burst_phase; // NTSC burst phase for frame (0, 1, or 2) - - int sample_count; // number of samples (always a multiple of chan_count) - int chan_count; // 1: mono, 2: stereo - - int top; // top-left position of image in graphics buffer - unsigned char* pixels; // pointer to top-left pixel of image - long pitch; // number of bytes to get to next row of image - - int palette_begin; // first host palette entry, as set by set_palette_range() - int palette_size; // number of entries used for current frame - short palette [max_palette_size]; // [palette_begin to palette_begin+palette_size-1] - }; - frame_t const& frame() const { return *frame_; } - - // Read samples for the current frame. Returns number of samples read into buffer. - // Currently all samples must be read in one call. - virtual long read_samples( short* out, long max_samples ); - -// Additional features - - // Use already-loaded cartridge. Retains pointer, so it must be kept around until - // closed. A cartridge can be shared among multiple emulators. After opening, - // cartridge's CHR data shouldn't be modified since a copy is cached internally. - void set_cart( Nes_Cart const* ); - - // Pointer to current cartridge, or NULL if none is loaded - Nes_Cart const* cart() const { return emu.cart; } - - // Emulate powering NES off and then back on. If full_reset is false, emulates - // pressing the reset button only, which doesn't affect memory, otherwise - // emulates powering system off then on. - virtual void reset( bool full_reset = true, bool erase_battery_ram = false ); - - // Number of undefined CPU instructions encountered. Cleared after reset() and - // load_state(). A non-zero value indicates that cartridge is probably - // incompatible. - unsigned long error_count() const { return emu.error_count; } - -// Sound - - // Set sample rate and use a custom sound buffer instead of the default - // mono buffer, i.e. Nes_Buffer, Effects_Buffer, etc.. - const char * set_sample_rate( long rate, Multi_Buffer* ); - - // Adjust effective frame rate by changing how many samples are generated each frame. - // Allows fine tuning of frame rate to improve synchronization. - void set_frame_rate( double rate ); - - // Number of sound channels for current cartridge - int channel_count() const { return channel_count_; } - - // Frequency equalizer parameters - struct equalizer_t { - double treble; // 5.0 = extra-crisp, -200.0 = muffled - long bass; // 0 = deep, 20000 = tinny - }; - - // Current frequency equalization - equalizer_t const& equalizer() const { return equalizer_; } - - // Change frequency equalization - void set_equalizer( equalizer_t const& ); - - // Equalizer presets - static equalizer_t const nes_eq; // NES - static equalizer_t const famicom_eq; // Famicom - static equalizer_t const tv_eq; // TV speaker - static equalizer_t const flat_eq; // Flat EQ - static equalizer_t const crisp_eq; // Crisp EQ (Treble boost) - static equalizer_t const tinny_eq; // Tinny EQ (Like a handheld speaker) - -// File save/load - - // Save emulator state - size_t serializeState (uint8_t* buffer) const { return emu.serializeState(buffer); } - size_t deserializeState (const uint8_t* buffer) { return emu.deserializeState(buffer); } - - // True if current cartridge claims it uses battery-backed memory - bool has_battery_ram() const { return cart()->has_battery_ram(); } - -// Graphics - - // Number of frames generated per second - enum { frame_rate = 60 }; - - // Size of fixed NES color table (including the 8 color emphasis modes) - enum { color_table_size = 8 * 64 }; - - // NES color lookup table based on standard NTSC TV decoder. Use nes_ntsc.h to - // generate a palette with custom parameters. - struct rgb_t { unsigned char red, green, blue; }; - static rgb_t const nes_colors [color_table_size]; - - // Hide/show/enhance sprites. Sprite mode does not affect emulation accuracy. - enum sprite_mode_t { - sprites_hidden = 0, - sprites_visible = 8, // limit of 8 sprites per scanline as on NES (default) - sprites_enhanced = 64 // unlimited sprites per scanline (no flickering) - }; - void set_sprite_mode( sprite_mode_t n ) { emu.ppu.sprite_limit = n; } - - // Set range of host palette entries to use in graphics buffer; default uses - // all of them. Begin will be rounded up to next multiple of palette_alignment. - // Use frame().palette_begin to find the adjusted beginning entry used. - enum { palette_alignment = 64 }; - void set_palette_range( int begin, int end = 256 ); - -// Access to emulated memory, for viewer/cheater/debugger - - // CHR - uint8_t const* chr_mem(); - long chr_size() const; - void write_chr( void const*, long count, long offset ); - - // Nametable - uint8_t* nametable_mem() { return emu.ppu.impl->nt_ram; } - long nametable_size() const { return 0x1000; } - - // Built-in 2K memory - enum { low_mem_size = 0x800 }; - uint8_t* low_mem() { return emu.low_mem; } - - // Optional 8K memory - enum { high_mem_size = 0x2000 }; - uint8_t* high_mem() { return emu.impl->sram; } - - // Sprite memory - uint8_t* spr_mem() { return emu.ppu.getSpriteRAM(); } - uint16_t spr_mem_size() { return emu.ppu.getSpriteRAMSize(); } - - // End of public interface -public: - const char * set_sample_rate( long rate, class Nes_Buffer* ); - const char * set_sample_rate( long rate, class Nes_Effects_Buffer* ); - void irq_changed() { emu.irq_changed(); } -private: - - frame_t* frame_; - int buffer_height_; - bool fade_sound_in; - bool fade_sound_out; - virtual const char * init_(); - - virtual void loading_state( Nes_State const& ) { } - long timestamp() const { return emu.nes.frame_count; } - void set_timestamp( long t ) { emu.nes.frame_count = t; } - -private: - // noncopyable - Nes_Emu( const Nes_Emu& ); - Nes_Emu& operator = ( const Nes_Emu& ); - - // sound - Multi_Buffer* default_sound_buf; - Multi_Buffer* sound_buf; - unsigned sound_buf_changed_count; - Silent_Buffer silent_buffer; - equalizer_t equalizer_; - int channel_count_; - bool sound_enabled; - void enable_sound( bool ); - void clear_sound_buf(); - void fade_samples( blip_sample_t*, int size, int step ); - - char* host_pixels; - int host_palette_size; - frame_t single_frame; - Nes_Cart private_cart; - Nes_Core emu; // large; keep at end - - bool init_called; - const char * auto_init(); - - bool extra_fade_sound_in; - bool extra_fade_sound_out; - unsigned extra_sound_buf_changed_count; -public: - void SaveAudioBufferState(); - void RestoreAudioBufferState(); -}; - -inline void Nes_Emu::set_pixels( void* p, long n ) -{ - host_pixels = (char*) p + n; - emu.ppu.host_row_bytes = n; -} - -inline uint8_t const* Nes_Emu::chr_mem() -{ - return cart()->chr_size() ? (uint8_t*) cart()->chr() : emu.ppu.impl->chr_ram; -} - -inline long Nes_Emu::chr_size() const -{ - return cart()->chr_size() ? cart()->chr_size() : emu.ppu.chr_addr_size; -} - diff --git a/source/quickerNES/apu/Blip_Buffer.cpp b/source/quickerNES/apu/Blip_Buffer.cpp index 9288921..7252d7b 100644 --- a/source/quickerNES/apu/Blip_Buffer.cpp +++ b/source/quickerNES/apu/Blip_Buffer.cpp @@ -1,12 +1,11 @@ // Blip_Buffer 0.4.0. http://www.slack.net/~ant/ -#include "Blip_Buffer.h" - +#include "Blip_Buffer.hpp" #include -#include -#include #include +#include +#include /* Copyright (C) 2003-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -19,405 +18,405 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifdef BLARGG_ENABLE_OPTIMIZER - #include BLARGG_ENABLE_OPTIMIZER -#endif +namespace quickerNES +{ int const buffer_extra = blip_widest_impulse_ + 2; Blip_Buffer::Blip_Buffer() { - factor_ = LONG_MAX; - offset_ = 0; - buffer_ = 0; - buffer_size_ = 0; - sample_rate_ = 0; - reader_accum = 0; - bass_shift = 0; - clock_rate_ = 0; - bass_freq_ = 16; - length_ = 0; + factor_ = LONG_MAX; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum = 0; + bass_shift = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; - extra_length = length_; - extra_offset = offset_; - extra_reader_accum = reader_accum; - memset(extra_buffer, 0, sizeof(extra_buffer)); + extra_length = length_; + extra_offset = offset_; + extra_reader_accum = reader_accum; + memset(extra_buffer, 0, sizeof(extra_buffer)); } Blip_Buffer::~Blip_Buffer() { - if ( buffer_ ) - free( buffer_ ); + if (buffer_) + free(buffer_); } -void Blip_Buffer::clear( int entire_buffer ) +void Blip_Buffer::clear(int entire_buffer) { - offset_ = 0; - reader_accum = 0; - if ( buffer_ ) - { - long count = (entire_buffer ? buffer_size_ : samples_avail()); - memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) ); - } + offset_ = 0; + reader_accum = 0; + if (buffer_) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset(buffer_, 0, (count + buffer_extra) * sizeof(buf_t_)); + } } -const char *Blip_Buffer::set_sample_rate( long new_rate, int msec ) +const char *Blip_Buffer::set_sample_rate(long new_rate, int msec) { - // start with maximum length that resampled time can represent - long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64; - if ( msec != blip_max_length ) - { - long s = (new_rate * (msec + 1) + 999) / 1000; - if ( s < new_size ) - new_size = s; - } - - if ( buffer_size_ != new_size ) - { - void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ ); - if ( !p ) - return "Out of memory"; - buffer_ = (buf_t_*) p; - } - - buffer_size_ = new_size; - // update things based on the sample rate - sample_rate_ = new_rate; - length_ = new_size * 1000 / new_rate - 1; - if ( clock_rate_ ) - clock_rate( clock_rate_ ); - bass_freq( bass_freq_ ); - - clear(); - - return 0; // success + // start with maximum length that resampled time can represent + long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64; + if (msec != blip_max_length) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if (s < new_size) + new_size = s; + } + + if (buffer_size_ != new_size) + { + void *p = realloc(buffer_, (new_size + buffer_extra) * sizeof *buffer_); + if (!p) + return "Out of memory"; + buffer_ = (buf_t_ *)p; + } + + buffer_size_ = new_size; + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = new_size * 1000 / new_rate - 1; + if (clock_rate_) + clock_rate(clock_rate_); + bass_freq(bass_freq_); + + clear(); + + return 0; // success } -blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const +blip_resampled_time_t Blip_Buffer::clock_rate_factor(long clock_rate) const { - double ratio = (double) sample_rate_ / clock_rate; - long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); - return (blip_resampled_time_t) factor; + double ratio = (double)sample_rate_ / clock_rate; + long factor = (long)floor(ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5); + return (blip_resampled_time_t)factor; } -void Blip_Buffer::bass_freq( int freq ) +void Blip_Buffer::bass_freq(int freq) { - bass_freq_ = freq; - int shift = 31; - if ( freq > 0 ) - { - shift = 13; - long f = (freq << 16) / sample_rate_; - while ( (f >>= 1) && --shift ) { } - } - bass_shift = shift; + bass_freq_ = freq; + int shift = 31; + if (freq > 0) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ((f >>= 1) && --shift) {} + } + bass_shift = shift; } -void Blip_Buffer::end_frame( blip_time_t t ) +void Blip_Buffer::end_frame(blip_time_t t) { - offset_ += t * factor_; + offset_ += t * factor_; } -void Blip_Buffer::remove_silence( long count ) +void Blip_Buffer::remove_silence(long count) { - offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + offset_ -= (blip_resampled_time_t)count << BLIP_BUFFER_ACCURACY; } -long Blip_Buffer::count_samples( blip_time_t t ) const +long Blip_Buffer::count_samples(blip_time_t t) const { - unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; - unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; - return (long) (last_sample - first_sample); + unsigned long last_sample = resampled_time(t) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return (long)(last_sample - first_sample); } -blip_time_t Blip_Buffer::count_clocks( long count ) const +blip_time_t Blip_Buffer::count_clocks(long count) const { - if ( count > buffer_size_ ) - count = buffer_size_; - blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; - return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); + if (count > buffer_size_) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t)count << BLIP_BUFFER_ACCURACY; + return (blip_time_t)((time - offset_ + factor_ - 1) / factor_); } -void Blip_Buffer::remove_samples( long count ) +void Blip_Buffer::remove_samples(long count) { - if ( count ) - { - remove_silence( count ); - - // copy remaining samples to beginning and clear old samples - long remain = samples_avail() + buffer_extra; - memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); - memset( buffer_ + remain, 0, count * sizeof *buffer_ ); - } + if (count) + { + remove_silence(count); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + buffer_extra; + memmove(buffer_, buffer_ + count, remain * sizeof *buffer_); + memset(buffer_ + remain, 0, count * sizeof *buffer_); + } } // Blip_Synth_ -Blip_Synth_::Blip_Synth_( short* p, int w ) : - impulses( p ), - width( w ) +Blip_Synth_::Blip_Synth_(short *p, int w) : impulses(p), + width(w) { - volume_unit_ = 0.0; - kernel_unit = 0; - buf = 0; - last_amp = 0; - delta_factor = 0; + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; } // TODO: apparently this is defined elsewhere too #define pi my_pi static double const pi = 3.1415926535897932384626433832795029; -static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +static void gen_sinc(float *out, int count, double oversample, double treble, double cutoff) { - if ( cutoff >= 0.999 ) - cutoff = 0.999; - - if ( treble < -300.0 ) - treble = -300.0; - if ( treble > 5.0 ) - treble = 5.0; - - double const maxh = 4096.0; - double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); - double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); - double const to_angle = pi / 2 / maxh / oversample; - for ( int i = 0; i < count; i++ ) - { - double angle = ((i - count) * 2 + 1) * to_angle; - double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); - double cos_nc_angle = cos( maxh * cutoff * angle ); - double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); - double cos_angle = cos( angle ); - - c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; - double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); - double b = 2.0 - cos_angle - cos_angle; - double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; - - out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d - } + if (cutoff >= 0.999) + cutoff = 0.999; + + if (treble < -300.0) + treble = -300.0; + if (treble > 5.0) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow(10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff)); + double const pow_a_n = pow(rolloff, maxh - maxh * cutoff); + double const to_angle = pi / 2 / maxh / oversample; + for (int i = 0; i < count; i++) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos((maxh - 1.0) * angle) - cos(maxh * angle); + double cos_nc_angle = cos(maxh * cutoff * angle); + double cos_nc1_angle = cos((maxh * cutoff - 1.0) * angle); + double cos_angle = cos(angle); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out[i] = (float)((a * d + c * b) / (b * d)); // a / b + c / d + } } -void blip_eq_t::generate( float* out, int count ) const +void blip_eq_t::generate(float *out, int count) const { - // lower cutoff freq for narrow kernels with their wider transition band - // (8 points->1.49, 16 points->1.15) - double oversample = blip_res * 2.25 / count + 0.85; - double half_rate = sample_rate * 0.5; - if ( cutoff_freq ) - oversample = half_rate / cutoff_freq; - double cutoff = rolloff_freq * oversample / half_rate; - - gen_sinc( out, count, blip_res * oversample, treble, cutoff ); - - // apply (half of) hamming window - double to_fraction = pi / (count - 1); - for ( int i = count; i--; ) - out [i] *= 0.54 - 0.46 * cos( i * to_fraction ); + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if (cutoff_freq) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc(out, count, blip_res * oversample, treble, cutoff); + + // apply (half of) hamming window + double to_fraction = pi / (count - 1); + for (int i = count; i--;) + out[i] *= 0.54 - 0.46 * cos(i * to_fraction); } void Blip_Synth_::adjust_impulse() { - // sum pairs for each phase and add error correction to end of first half - int const size = impulses_size(); - for ( int p = blip_res; p-- >= blip_res / 2; ) - { - int p2 = blip_res - 2 - p; - long error = kernel_unit; - for ( int i = 1; i < size; i += blip_res ) - { - error -= impulses [i + p ]; - error -= impulses [i + p2]; - } - if ( p == p2 ) - error /= 2; // phase = 0.5 impulse uses same half for both sides - impulses [size - blip_res + p] += error; - //printf( "error: %ld\n", error ); - } - - //for ( int i = blip_res; i--; printf( "\n" ) ) - // for ( int j = 0; j < width / 2; j++ ) - // printf( "%5ld,", impulses [j * blip_res + i + 1] ); + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for (int p = blip_res; p-- >= blip_res / 2;) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for (int i = 1; i < size; i += blip_res) + { + error -= impulses[i + p]; + error -= impulses[i + p2]; + } + if (p == p2) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses[size - blip_res + p] += error; + // printf( "error: %ld\n", error ); + } + + // for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); } -void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +void Blip_Synth_::treble_eq(blip_eq_t const &eq) { - float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; - - int const half_size = blip_res / 2 * (width - 1); - eq.generate( &fimpulse [blip_res], half_size ); - - int i; - - // need mirror slightly past center for calculation - for ( i = blip_res; i--; ) - fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; - - // starts at 0 - for ( i = 0; i < blip_res; i++ ) - fimpulse [i] = 0.0f; - - // find rescale factor - double total = 0.0; - for ( i = 0; i < half_size; i++ ) - total += fimpulse [blip_res + i]; - - //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB - //double const base_unit = 37888.0; // allows treble to +5 dB - double const base_unit = 32768.0; // necessary for blip_unscaled to work - double rescale = base_unit / 2 / total; - kernel_unit = (long) base_unit; - - // integrate, first difference, rescale, convert to int - double sum = 0.0; - double next = 0.0; - int const impulses_size = this->impulses_size(); - for ( i = 0; i < impulses_size; i++ ) - { - impulses [i] = (short) floor( (next - sum) * rescale + 0.5 ); - sum += fimpulse [i]; - next += fimpulse [i + blip_res]; - } - adjust_impulse(); - - // volume might require rescaling - double vol = volume_unit_; - if ( vol ) - { - volume_unit_ = 0.0; - volume_unit( vol ); - } + float fimpulse[blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate(&fimpulse[blip_res], half_size); + + int i; + + // need mirror slightly past center for calculation + for (i = blip_res; i--;) + fimpulse[blip_res + half_size + i] = fimpulse[blip_res + half_size - 1 - i]; + + // starts at 0 + for (i = 0; i < blip_res; i++) + fimpulse[i] = 0.0f; + + // find rescale factor + double total = 0.0; + for (i = 0; i < half_size; i++) + total += fimpulse[blip_res + i]; + + // double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + // double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long)base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const impulses_size = this->impulses_size(); + for (i = 0; i < impulses_size; i++) + { + impulses[i] = (short)floor((next - sum) * rescale + 0.5); + sum += fimpulse[i]; + next += fimpulse[i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if (vol) + { + volume_unit_ = 0.0; + volume_unit(vol); + } } -void Blip_Synth_::volume_unit( double new_unit ) +void Blip_Synth_::volume_unit(double new_unit) { - if ( new_unit != volume_unit_ ) - { - // use default eq if it hasn't been set yet - if ( !kernel_unit ) - treble_eq( -8.0 ); - - volume_unit_ = new_unit; - double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; - - if ( factor > 0.0 ) - { - int shift = 0; - - // if unit is really small, might need to attenuate kernel - while ( factor < 2.0 ) - { - shift++; - factor *= 2.0; - } - - if ( shift ) - { - kernel_unit >>= shift; - - // keep values positive to avoid round-towards-zero of sign-preserving - // right shift for negative values - long offset = 0x8000 + (1 << (shift - 1)); - long offset2 = 0x8000 >> shift; - for ( int i = impulses_size(); i--; ) - impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2); - adjust_impulse(); - } - } - delta_factor = (int) floor( factor + 0.5 ); - //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); - } + if (new_unit != volume_unit_) + { + // use default eq if it hasn't been set yet + if (!kernel_unit) + treble_eq(-8.0); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if (factor > 0.0) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while (factor < 2.0) + { + shift++; + factor *= 2.0; + } + + if (shift) + { + kernel_unit >>= shift; + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for (int i = impulses_size(); i--;) + impulses[i] = (short)(((impulses[i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int)floor(factor + 0.5); + // printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } } -long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo ) +long Blip_Buffer::read_samples(blip_sample_t *out, long max_samples, int stereo) { - long count = samples_avail(); - if ( count > max_samples ) - count = max_samples; - - if ( count ) - { - int const sample_shift = blip_sample_bits - 16; - int const bass_shift = this->bass_shift; - long accum = reader_accum; - buf_t_* in = buffer_; - - if (out != NULL) - { - if ( !stereo ) - { - for ( long n = count; n--; ) - { - long s = accum >> sample_shift; - accum -= accum >> bass_shift; - accum += *in++; - *out++ = (blip_sample_t) s; - - // clamp sample - if ( (blip_sample_t) s != s ) - out [-1] = (blip_sample_t) (0x7FFF - (s >> 24)); - } - } - else - { - for ( long n = count; n--; ) - { - long s = accum >> sample_shift; - accum -= accum >> bass_shift; - accum += *in++; - *out = (blip_sample_t) s; - out += 2; - - // clamp sample - if ( (blip_sample_t) s != s ) - out [-2] = (blip_sample_t) (0x7FFF - (s >> 24)); - } - } - } - else - { - //only run accumulator, do not output anything - for (long n = count; n--; ) - { - accum -= accum >> bass_shift; - accum += *in++; - } - } - - reader_accum = accum; - remove_samples( count ); - } - return count; + long count = samples_avail(); + if (count > max_samples) + count = max_samples; + + if (count) + { + int const sample_shift = blip_sample_bits - 16; + int const bass_shift = this->bass_shift; + long accum = reader_accum; + buf_t_ *in = buffer_; + + if (out != NULL) + { + if (!stereo) + { + for (long n = count; n--;) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out++ = (blip_sample_t)s; + + // clamp sample + if ((blip_sample_t)s != s) + out[-1] = (blip_sample_t)(0x7FFF - (s >> 24)); + } + } + else + { + for (long n = count; n--;) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out = (blip_sample_t)s; + out += 2; + + // clamp sample + if ((blip_sample_t)s != s) + out[-2] = (blip_sample_t)(0x7FFF - (s >> 24)); + } + } + } + else + { + // only run accumulator, do not output anything + for (long n = count; n--;) + { + accum -= accum >> bass_shift; + accum += *in++; + } + } + + reader_accum = accum; + remove_samples(count); + } + return count; } -void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +void Blip_Buffer::mix_samples(blip_sample_t const *in, long count) { - buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; - - int const sample_shift = blip_sample_bits - 16; - int prev = 0; - while ( count-- ) - { - long s = (long) *in++ << sample_shift; - *out += s - prev; - prev = s; - ++out; - } - *out -= prev; + buf_t_ *out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while (count--) + { + long s = (long)*in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; } void Blip_Buffer::SaveAudioBufferState() { - extra_length = length_; - extra_offset = offset_; - extra_reader_accum = reader_accum; - memcpy(extra_buffer, buffer_, sizeof(extra_buffer)); + extra_length = length_; + extra_offset = offset_; + extra_reader_accum = reader_accum; + memcpy(extra_buffer, buffer_, sizeof(extra_buffer)); } void Blip_Buffer::RestoreAudioBufferState() { - length_ = extra_length; - offset_ = extra_offset; - reader_accum = extra_reader_accum; - memcpy(buffer_, extra_buffer, sizeof(extra_buffer)); + length_ = extra_length; + offset_ = extra_offset; + reader_accum = extra_reader_accum; + memcpy(buffer_, extra_buffer, sizeof(extra_buffer)); } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/Blip_Buffer.h b/source/quickerNES/apu/Blip_Buffer.h deleted file mode 100644 index 1c4c1cf..0000000 --- a/source/quickerNES/apu/Blip_Buffer.h +++ /dev/null @@ -1,358 +0,0 @@ - -// Band-limited sound synthesis and buffering - -// Blip_Buffer 0.4.0 - -#ifndef BLIP_BUFFER_H -#define BLIP_BUFFER_H - -// Time unit at source clock rate -typedef long blip_time_t; - -// Output samples are 16-bit signed, with a range of -32768 to 32767 -typedef short blip_sample_t; -enum { blip_sample_max = 32767 }; - -class Blip_Buffer { -public: - // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults - // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there - // isn't enough memory, returns error without affecting current buffer setup. - const char *set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); - - // Set number of source time units per second - void clock_rate( long ); - - // End current time frame of specified duration and make its samples available - // (along with any still-unread samples) for reading with read_samples(). Begins - // a new time frame at the end of the current frame. - void end_frame( blip_time_t time ); - - // Read at most 'max_samples' out of buffer into 'dest', removing them from from - // the buffer. Returns number of samples actually read and removed. If stereo is - // true, increments 'dest' one extra time after writing each sample, to allow - // easy interleving of two channels into a stereo output buffer. - long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); - -// Additional optional features - - // Current output sample rate - long sample_rate() const; - - // Length of buffer, in milliseconds - int length() const; - - // Number of source time units per second - long clock_rate() const; - - // Set frequency high-pass filter frequency, where higher values reduce bass more - void bass_freq( int frequency ); - - // Number of samples delay from synthesis to samples read out - int output_latency() const; - - // Remove all available samples and clear buffer to silence. If 'entire_buffer' is - // false, just clears out any samples waiting rather than the entire buffer. - void clear( int entire_buffer = 1 ); - - // Number of samples available for reading with read_samples() - long samples_avail() const; - - // Remove 'count' samples from those waiting to be read - void remove_samples( long count ); - -// Experimental features - - // Number of raw samples that can be mixed within frame of specified duration. - long count_samples( blip_time_t duration ) const; - - // Mix 'count' samples from 'buf' into buffer. - void mix_samples( blip_sample_t const* buf, long count ); - - // Count number of clocks needed until 'count' samples will be available. - // If buffer can't even hold 'count' samples, returns number of clocks until - // buffer becomes full. - blip_time_t count_clocks( long count ) const; - - // not documented yet - typedef unsigned long blip_resampled_time_t; - void remove_silence( long count ); - blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } - blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } - blip_resampled_time_t clock_rate_factor( long clock_rate ) const; -public: - Blip_Buffer(); - ~Blip_Buffer(); - - // Deprecated - typedef blip_resampled_time_t resampled_time_t; - const char *sample_rate( long r ) { return set_sample_rate( r ); } - const char *sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } -private: - // noncopyable - Blip_Buffer( const Blip_Buffer& ); - Blip_Buffer& operator = ( const Blip_Buffer& ); -public: - typedef long buf_t_; - unsigned long factor_; - blip_resampled_time_t offset_; - buf_t_* buffer_; - long buffer_size_; -private: - long reader_accum; - int bass_shift; - long sample_rate_; - long clock_rate_; - int bass_freq_; - int length_; - friend class Blip_Reader; - -private: - //extra information necessary to load state to an exact sample - buf_t_ extra_buffer[32]; - int extra_length; - long extra_reader_accum; - blip_resampled_time_t extra_offset; -public: - void SaveAudioBufferState(); - void RestoreAudioBufferState(); -}; - -#ifdef HAVE_CONFIG_H - #include "config.h" -#endif - -// Number of bits in resample ratio fraction. Higher values give a more accurate ratio -// but reduce maximum buffer size. -#ifndef BLIP_BUFFER_ACCURACY - #define BLIP_BUFFER_ACCURACY 16 -#endif - -// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in -// noticeable broadband noise when synthesizing high frequency square waves. -// Affects size of Blip_Synth objects since they store the waveform directly. -#ifndef BLIP_PHASE_BITS - #define BLIP_PHASE_BITS 6 -#endif - - // Internal - typedef unsigned long blip_resampled_time_t; - int const blip_widest_impulse_ = 16; - int const blip_res = 1 << BLIP_PHASE_BITS; - class blip_eq_t; - - class Blip_Synth_ { - double volume_unit_; - short* const impulses; - int const width; - long kernel_unit; - int impulses_size() const { return blip_res / 2 * width + 1; } - void adjust_impulse(); - public: - Blip_Buffer* buf; - int last_amp; - int delta_factor; - - Blip_Synth_( short* impulses, int width ); - void treble_eq( blip_eq_t const& ); - void volume_unit( double ); - }; - -// Quality level. Start with blip_good_quality. -const int blip_med_quality = 8; -const int blip_good_quality = 12; -const int blip_high_quality = 16; - -// Range specifies the greatest expected change in amplitude. Calculate it -// by finding the difference between the maximum and minimum expected -// amplitudes (max - min). -template -class Blip_Synth { -public: - // Set overall volume of waveform - void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } - - // Configure low-pass filter (see notes.txt) - void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } - - // Get/set Blip_Buffer used for output - Blip_Buffer* output() const { return impl.buf; } - void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } - - // Update amplitude of waveform at given time. Using this requires a separate - // Blip_Synth for each waveform. - void update( blip_time_t time, int amplitude ); - -// Low-level interface - - // Add an amplitude transition of specified delta, optionally into specified buffer - // rather than the one set with output(). Delta can be positive or negative. - // The actual change in amplitude is delta * (volume / range) - void offset( blip_time_t, int delta, Blip_Buffer* ) const; - void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } - - // Works directly in terms of fractional output samples. Contact author for more. - void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; - - // Same as offset(), except code is inlined for higher performance - void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { - offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); - } - void offset_inline( blip_time_t t, int delta ) const { - offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); - } - -public: - Blip_Synth() : impl( impulses, quality ) { } -private: - typedef short imp_t; - imp_t impulses [blip_res * (quality / 2) + 1]; - Blip_Synth_ impl; -}; - -// Low-pass equalization parameters -class blip_eq_t { -public: - // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce - // treble, small positive values (0 to 5.0) increase treble. - blip_eq_t( double treble_db = 0 ); - - // See notes.txt - blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); - -private: - double treble; - long rolloff_freq; - long sample_rate; - long cutoff_freq; - void generate( float* out, int count ) const; - friend class Blip_Synth_; -}; - -int const blip_sample_bits = 30; - -// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples -class Blip_Reader { -public: - // Begin reading samples from buffer. Returns value to pass to next() (can - // be ignored if default bass_freq is acceptable). - int begin( Blip_Buffer& ); - - // Current sample - long read() const { return accum >> (blip_sample_bits - 16); } - - // Current raw sample in full internal resolution - long read_raw() const { return accum; } - - // Advance to next sample - void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } - - // End reading samples from buffer. The number of samples read must now be removed - // using Blip_Buffer::remove_samples(). - void end( Blip_Buffer& b ) { b.reader_accum = accum; } - -private: - const Blip_Buffer::buf_t_* buf; - long accum; -}; - - -// End of public interface - - -// Compatibility with older version -const long blip_unscaled = 65535; -const int blip_low_quality = blip_med_quality; -const int blip_best_quality = blip_high_quality; - -#define BLIP_FWD( i ) { \ - long t0 = i0 * delta + buf [fwd + i]; \ - long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \ - i0 = imp [blip_res * (i + 2)]; \ - buf [fwd + i] = t0; \ - buf [fwd + 1 + i] = t1; } - -#define BLIP_REV( r ) { \ - long t0 = i0 * delta + buf [rev - r]; \ - long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \ - i0 = imp [blip_res * (r - 1)]; \ - buf [rev - r] = t0; \ - buf [rev + 1 - r] = t1; } - -template -inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, - int delta, Blip_Buffer* blip_buf ) const -{ - // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the - // need for a longer buffer as set by set_sample_rate(). - delta *= impl.delta_factor; - int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); - imp_t const* imp = impulses + blip_res - phase; - long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); - long i0 = *imp; - - int const fwd = (blip_widest_impulse_ - quality) / 2; - int const rev = fwd + quality - 2; - - BLIP_FWD( 0 ) - if ( quality > 8 ) BLIP_FWD( 2 ) - if ( quality > 12 ) BLIP_FWD( 4 ) - { - int const mid = quality / 2 - 1; - long t0 = i0 * delta + buf [fwd + mid - 1]; - long t1 = imp [blip_res * mid] * delta + buf [fwd + mid]; - imp = impulses + phase; - i0 = imp [blip_res * mid]; - buf [fwd + mid - 1] = t0; - buf [fwd + mid] = t1; - } - if ( quality > 12 ) BLIP_REV( 6 ) - if ( quality > 8 ) BLIP_REV( 4 ) - BLIP_REV( 2 ) - - long t0 = i0 * delta + buf [rev]; - long t1 = *imp * delta + buf [rev + 1]; - buf [rev] = t0; - buf [rev + 1] = t1; -} - -#undef BLIP_FWD -#undef BLIP_REV - -template -void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const -{ - offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); -} - -template -void Blip_Synth::update( blip_time_t t, int amp ) -{ - int delta = amp - impl.last_amp; - impl.last_amp = amp; - offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); -} - -inline blip_eq_t::blip_eq_t( double t ) : - treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } -inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : - treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } - -inline int Blip_Buffer::length() const { return length_; } -inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } -inline long Blip_Buffer::sample_rate() const { return sample_rate_; } -inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } -inline long Blip_Buffer::clock_rate() const { return clock_rate_; } -inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } - -inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) -{ - buf = blip_buf.buffer_; - accum = blip_buf.reader_accum; - return blip_buf.bass_shift; -} - -int const blip_max_length = 0; -int const blip_default_length = 250; - -#endif diff --git a/source/quickerNES/apu/Blip_Buffer.hpp b/source/quickerNES/apu/Blip_Buffer.hpp new file mode 100644 index 0000000..3cc9c50 --- /dev/null +++ b/source/quickerNES/apu/Blip_Buffer.hpp @@ -0,0 +1,380 @@ +#pragma once + +// Band-limited sound synthesis and buffering +// Blip_Buffer 0.4.0 + +namespace quickerNES +{ + +// Time unit at source clock rate +typedef long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum +{ + blip_sample_max = 32767 +}; + +class Blip_Buffer +{ + public: + // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there + // isn't enough memory, returns error without affecting current buffer setup. + const char *set_sample_rate(long samples_per_sec, int msec_length = 1000 / 4); + + // Set number of source time units per second + void clock_rate(long); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame(blip_time_t time); + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples(blip_sample_t *dest, long max_samples, int stereo = 0); + + // Additional optional features + + // Current output sample rate + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + + // Set frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq(int frequency); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear(int entire_buffer = 1); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Remove 'count' samples from those waiting to be read + void remove_samples(long count); + + // Experimental features + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples(blip_time_t duration) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples(blip_sample_t const *buf, long count); + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks(long count) const; + + // not documented yet + typedef unsigned long blip_resampled_time_t; + void remove_silence(long count); + blip_resampled_time_t resampled_duration(int t) const { return t * factor_; } + blip_resampled_time_t resampled_time(blip_time_t t) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor(long clock_rate) const; + + public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + const char *sample_rate(long r) { return set_sample_rate(r); } + const char *sample_rate(long r, int msec) { return set_sample_rate(r, msec); } + + private: + // noncopyable + Blip_Buffer(const Blip_Buffer &); + Blip_Buffer &operator=(const Blip_Buffer &); + + public: + typedef long buf_t_; + unsigned long factor_; + blip_resampled_time_t offset_; + buf_t_ *buffer_; + long buffer_size_; + + private: + long reader_accum; + int bass_shift; + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + friend class Blip_Reader; + + private: + // extra information necessary to load state to an exact sample + buf_t_ extra_buffer[32]; + int extra_length; + long extra_reader_accum; + blip_resampled_time_t extra_offset; + + public: + void SaveAudioBufferState(); + void RestoreAudioBufferState(); +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 6 +#endif + +// Internal +typedef unsigned long blip_resampled_time_t; +int const blip_widest_impulse_ = 16; +int const blip_res = 1 << BLIP_PHASE_BITS; +class blip_eq_t; + +class Blip_Synth_ +{ + double volume_unit_; + short *const impulses; + int const width; + long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + + public: + Blip_Buffer *buf; + int last_amp; + int delta_factor; + + Blip_Synth_(short *impulses, int width); + void treble_eq(blip_eq_t const &); + void volume_unit(double); +}; + +// Quality level. Start with blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template +class Blip_Synth +{ + public: + // Set overall volume of waveform + void volume(double v) { impl.volume_unit(v * (1.0 / (range < 0 ? -range : range))); } + + // Configure low-pass filter (see notes.txt) + void treble_eq(blip_eq_t const &eq) { impl.treble_eq(eq); } + + // Get/set Blip_Buffer used for output + Blip_Buffer *output() const { return impl.buf; } + void output(Blip_Buffer *b) + { + impl.buf = b; + impl.last_amp = 0; + } + + // Update amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update(blip_time_t time, int amplitude); + + // Low-level interface + + // Add an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset(blip_time_t, int delta, Blip_Buffer *) const; + void offset(blip_time_t t, int delta) const { offset(t, delta, impl.buf); } + + // Works directly in terms of fractional output samples. Contact author for more. + void offset_resampled(blip_resampled_time_t, int delta, Blip_Buffer *) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline(blip_time_t t, int delta, Blip_Buffer *buf) const + { + offset_resampled(t * buf->factor_ + buf->offset_, delta, buf); + } + void offset_inline(blip_time_t t, int delta) const + { + offset_resampled(t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf); + } + + public: + Blip_Synth() : impl(impulses, quality) {} + + private: + typedef short imp_t; + imp_t impulses[blip_res * (quality / 2) + 1]; + Blip_Synth_ impl; +}; + +// Low-pass equalization parameters +class blip_eq_t +{ + public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t(double treble_db = 0); + + // See notes.txt + blip_eq_t(double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0); + + private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate(float *out, int count) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples +class Blip_Reader +{ + public: + // Begin reading samples from buffer. Returns value to pass to next() (can + // be ignored if default bass_freq is acceptable). + int begin(Blip_Buffer &); + + // Current sample + long read() const { return accum >> (blip_sample_bits - 16); } + + // Current raw sample in full internal resolution + long read_raw() const { return accum; } + + // Advance to next sample + void next(int bass_shift = 9) { accum += *buf++ - (accum >> bass_shift); } + + // End reading samples from buffer. The number of samples read must now be removed + // using Blip_Buffer::remove_samples(). + void end(Blip_Buffer &b) { b.reader_accum = accum; } + + private: + const Blip_Buffer::buf_t_ *buf; + long accum; +}; + +// End of public interface + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +#define BLIP_FWD(i) \ + { \ + long t0 = i0 * delta + buf[fwd + i]; \ + long t1 = imp[blip_res * (i + 1)] * delta + buf[fwd + 1 + i]; \ + i0 = imp[blip_res * (i + 2)]; \ + buf[fwd + i] = t0; \ + buf[fwd + 1 + i] = t1; \ + } + +#define BLIP_REV(r) \ + { \ + long t0 = i0 * delta + buf[rev - r]; \ + long t1 = imp[blip_res * r] * delta + buf[rev + 1 - r]; \ + i0 = imp[blip_res * (r - 1)]; \ + buf[rev - r] = t0; \ + buf[rev + 1 - r] = t1; \ + } + +template +inline void Blip_Synth::offset_resampled(blip_resampled_time_t time, + int delta, + Blip_Buffer *blip_buf) const +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + delta *= impl.delta_factor; + int phase = (int)(time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + imp_t const *imp = impulses + blip_res - phase; + long *buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + long i0 = *imp; + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + + BLIP_FWD(0) + if (quality > 8) BLIP_FWD(2) + if (quality > 12) BLIP_FWD(4) + { + int const mid = quality / 2 - 1; + long t0 = i0 * delta + buf[fwd + mid - 1]; + long t1 = imp[blip_res * mid] * delta + buf[fwd + mid]; + imp = impulses + phase; + i0 = imp[blip_res * mid]; + buf[fwd + mid - 1] = t0; + buf[fwd + mid] = t1; + } + if (quality > 12) BLIP_REV(6) + if (quality > 8) BLIP_REV(4) + BLIP_REV(2) + + long t0 = i0 * delta + buf[rev]; + long t1 = *imp * delta + buf[rev + 1]; + buf[rev] = t0; + buf[rev + 1] = t1; +} + +#undef BLIP_FWD +#undef BLIP_REV + +template +void Blip_Synth::offset(blip_time_t t, int delta, Blip_Buffer *buf) const +{ + offset_resampled(t * buf->factor_ + buf->offset_, delta, buf); +} + +template +void Blip_Synth::update(blip_time_t t, int amp) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled(t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf); +} + +inline blip_eq_t::blip_eq_t(double t) : treble(t), rolloff_freq(0), sample_rate(44100), cutoff_freq(0) {} +inline blip_eq_t::blip_eq_t(double t, long rf, long sr, long cf) : treble(t), rolloff_freq(rf), sample_rate(sr), cutoff_freq(cf) {} + +inline int Blip_Buffer::length() const { return length_; } +inline long Blip_Buffer::samples_avail() const { return (long)(offset_ >> BLIP_BUFFER_ACCURACY); } +inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate(long cps) { factor_ = clock_rate_factor(clock_rate_ = cps); } + +inline int Blip_Reader::begin(Blip_Buffer &blip_buf) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum; + return blip_buf.bass_shift; +} + +int const blip_max_length = 0; +int const blip_default_length = 250; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/Effects_Buffer.cpp b/source/quickerNES/apu/Effects_Buffer.cpp deleted file mode 100644 index 0a8ef9a..0000000 --- a/source/quickerNES/apu/Effects_Buffer.cpp +++ /dev/null @@ -1,505 +0,0 @@ - -// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ - -#include "Effects_Buffer.h" -#include - -/* Copyright (C) 2003-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -typedef long fixed_t; - -#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) -#define FMUL( x, y ) (((x) * (y)) >> 15) - -const unsigned echo_size = 4096; -const unsigned echo_mask = echo_size - 1; -static_assert( (echo_size & echo_mask) == 0 ); // must be power of 2 - -const unsigned reverb_size = 8192 * 2; -const unsigned reverb_mask = reverb_size - 1; -static_assert( (reverb_size & reverb_mask) == 0 ); // must be power of 2 - -Effects_Buffer::config_t::config_t() -{ - pan_1 = -0.15f; - pan_2 = 0.15f; - reverb_delay = 88.0f; - reverb_level = 0.12f; - echo_delay = 61.0f; - echo_level = 0.10f; - delay_variance = 18.0f; - effects_enabled = false; -} - -void Effects_Buffer::set_depth( double d ) -{ - float f = (float) d; - config_t c; - c.pan_1 = -0.6f * f; - c.pan_2 = 0.6f * f; - c.reverb_delay = 880 * 0.1f; - c.echo_delay = 610 * 0.1f; - if ( f > 0.5 ) - f = 0.5; // TODO: more linear reduction of extreme reverb/echo - c.reverb_level = 0.5f * f; - c.echo_level = 0.30f * f; - c.delay_variance = 180 * 0.1f; - c.effects_enabled = (d > 0.0f); - config( c ); -} - -Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 ) -{ - buf_count = center_only ? max_buf_count - 4 : max_buf_count; - - echo_buf = NULL; - echo_pos = 0; - - reverb_buf = NULL; - reverb_pos = 0; - - stereo_remain = 0; - effect_remain = 0; - effects_enabled = false; - set_depth( 0 ); -} - -Effects_Buffer::~Effects_Buffer() -{ - delete [] echo_buf; - delete [] reverb_buf; -} - -const char *Effects_Buffer::set_sample_rate( long rate, int msec ) -{ - if ( !echo_buf ) - { - echo_buf = new blip_sample_t [echo_size]; - } - - if ( !reverb_buf ) - { - reverb_buf = new blip_sample_t [reverb_size]; - } - - for ( int i = 0; i < buf_count; i++ ) bufs [i].set_sample_rate( rate, msec ); - - config( config_ ); - clear(); - - return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); -} - -void Effects_Buffer::clock_rate( long rate ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clock_rate( rate ); -} - -void Effects_Buffer::bass_freq( int freq ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].bass_freq( freq ); -} - -void Effects_Buffer::clear() -{ - stereo_remain = 0; - effect_remain = 0; - if ( echo_buf ) - memset( echo_buf, 0, echo_size * sizeof *echo_buf ); - if ( reverb_buf ) - memset( reverb_buf, 0, reverb_size * sizeof *reverb_buf ); - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clear(); -} - -inline int pin_range( int n, int max, int min = 0 ) -{ - if ( n < min ) - return min; - if ( n > max ) - return max; - return n; -} - -void Effects_Buffer::config( const config_t& cfg ) -{ - channels_changed(); - - // clear echo and reverb buffers - if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf ) - { - memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); - memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); - } - - config_ = cfg; - - if ( config_.effects_enabled ) - { - // convert to internal format - - chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); - chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; - - chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); - chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; - - chans.reverb_level = TO_FIXED( config_.reverb_level ); - chans.echo_level = TO_FIXED( config_.echo_level ); - - int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate()); - - int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate()); - chans.reverb_delay_l = pin_range( reverb_size - - (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); - chans.reverb_delay_r = pin_range( reverb_size + 1 - - (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); - - int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate()); - chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), - echo_size - 1 ); - chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), - echo_size - 1 ); - - // set up outputs - for ( unsigned i = 0; i < chan_count; i++ ) - { - channel_t& o = channels [i]; - if ( i < 2 ) - { - o.center = &bufs [i]; - o.left = &bufs [3]; - o.right = &bufs [4]; - } - else - { - o.center = &bufs [2]; - o.left = &bufs [5]; - o.right = &bufs [6]; - } - } - - } - else - { - // set up outputs - for ( unsigned i = 0; i < chan_count; i++ ) - { - channel_t& o = channels [i]; - o.center = &bufs [0]; - o.left = &bufs [1]; - o.right = &bufs [2]; - } - } - - if ( buf_count < max_buf_count ) - { - for ( unsigned i = 0; i < chan_count; i++ ) - { - channel_t& o = channels [i]; - o.left = o.center; - o.right = o.center; - } - } -} - -void Effects_Buffer::end_frame( blip_time_t clock_count, bool stereo ) -{ - for ( int i = 0; i < buf_count; i++ ) - bufs [i].end_frame( clock_count ); - - if ( stereo && buf_count == max_buf_count ) - stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); - - if ( effects_enabled || config_.effects_enabled ) - effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); - - effects_enabled = config_.effects_enabled; -} - -long Effects_Buffer::samples_avail() const -{ - return bufs [0].samples_avail() * 2; -} - -long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) -{ - long remain = bufs [0].samples_avail(); - if ( remain > (total_samples >> 1) ) - remain = (total_samples >> 1); - total_samples = remain; - while ( remain ) - { - int active_bufs = buf_count; - long count = remain; - - // optimizing mixing to skip any channels which had nothing added - if ( effect_remain ) - { - if ( count > effect_remain ) - count = effect_remain; - - if ( stereo_remain ) - { - mix_enhanced( out, count ); - } - else - { - mix_mono_enhanced( out, count ); - active_bufs = 3; - } - } - else if ( stereo_remain ) - { - mix_stereo( out, count ); - active_bufs = 3; - } - else - { - mix_mono( out, count ); - active_bufs = 1; - } - - out += count * 2; - remain -= count; - - stereo_remain -= count; - if ( stereo_remain < 0 ) - stereo_remain = 0; - - effect_remain -= count; - if ( effect_remain < 0 ) - effect_remain = 0; - - for ( int i = 0; i < buf_count; i++ ) - { - if ( i < active_bufs ) - bufs [i].remove_samples( count ); - else - bufs [i].remove_silence( count ); // keep time synchronized - } - } - - return total_samples * 2; -} - -void Effects_Buffer::mix_mono( blip_sample_t* out, long count ) -{ - Blip_Reader c; - int shift = c.begin( bufs [0] ); - - // unrolled loop - for ( long n = count >> 1; n--; ) - { - long cs0 = c.read(); - c.next( shift ); - - long cs1 = c.read(); - c.next( shift ); - - if ( (int16_t) cs0 != cs0 ) - cs0 = 0x7FFF - (cs0 >> 24); - ((uint32_t*) out) [0] = ((uint16_t) cs0) | (cs0 << 16); - - if ( (int16_t) cs1 != cs1 ) - cs1 = 0x7FFF - (cs1 >> 24); - ((uint32_t*) out) [1] = ((uint16_t) cs1) | (cs1 << 16); - out += 4; - } - - if ( count & 1 ) - { - int s = c.read(); - c.next( shift ); - out [0] = s; - out [1] = s; - if ( (int16_t) s != s ) - { - s = 0x7FFF - (s >> 24); - out [0] = s; - out [1] = s; - } - } - - c.end( bufs [0] ); -} - -void Effects_Buffer::mix_stereo( blip_sample_t* out, long count ) -{ - Blip_Reader l; l.begin( bufs [1] ); - Blip_Reader r; r.begin( bufs [2] ); - Blip_Reader c; - int shift = c.begin( bufs [0] ); - - while ( count-- ) - { - int cs = c.read(); - c.next( shift ); - int left = cs + l.read(); - int right = cs + r.read(); - l.next( shift ); - r.next( shift ); - - if ( (int16_t) left != left ) - left = 0x7FFF - (left >> 24); - - out [0] = left; - out [1] = right; - - out += 2; - - if ( (int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); - } - - c.end( bufs [0] ); - r.end( bufs [2] ); - l.end( bufs [1] ); -} - -void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out, long count ) -{ - Blip_Reader sq1; sq1.begin( bufs [0] ); - Blip_Reader sq2; sq2.begin( bufs [1] ); - Blip_Reader center; - int shift = center.begin( bufs [2] ); - - int echo_pos = this->echo_pos; - int reverb_pos = this->reverb_pos; - - while ( count-- ) - { - int sum1_s = sq1.read(); - int sum2_s = sq2.read(); - - sq1.next( shift ); - sq2.next( shift ); - - int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + - FMUL( sum2_s, chans.pan_2_levels [0] ) + - reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; - - int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + - FMUL( sum2_s, chans.pan_2_levels [1] ) + - reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; - - fixed_t reverb_level = chans.reverb_level; - reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); - reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); - reverb_pos = (reverb_pos + 2) & reverb_mask; - - int sum3_s = center.read(); - center.next( shift ); - - int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); - int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); - - echo_buf [echo_pos] = sum3_s; - echo_pos = (echo_pos + 1) & echo_mask; - - if ( (int16_t) left != left ) - left = 0x7FFF - (left >> 24); - - out [0] = left; - out [1] = right; - - out += 2; - - if ( (int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); - } - this->reverb_pos = reverb_pos; - this->echo_pos = echo_pos; - - sq1.end( bufs [0] ); - sq2.end( bufs [1] ); - center.end( bufs [2] ); -} - -void Effects_Buffer::mix_enhanced( blip_sample_t* out, long count ) -{ - Blip_Reader l1; l1.begin( bufs [3] ); - Blip_Reader r1; r1.begin( bufs [4] ); - Blip_Reader l2; l2.begin( bufs [5] ); - Blip_Reader r2; r2.begin( bufs [6] ); - Blip_Reader sq1; sq1.begin( bufs [0] ); - Blip_Reader sq2; sq2.begin( bufs [1] ); - Blip_Reader center; - int shift = center.begin( bufs [2] ); - - int echo_pos = this->echo_pos; - int reverb_pos = this->reverb_pos; - - while ( count-- ) - { - int sum1_s = sq1.read(); - int sum2_s = sq2.read(); - - sq1.next( shift ); - sq2.next( shift ); - - int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + - FMUL( sum2_s, chans.pan_2_levels [0] ) + l1.read() + - reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; - - int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + - FMUL( sum2_s, chans.pan_2_levels [1] ) + r1.read() + - reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; - - l1.next( shift ); - r1.next( shift ); - - fixed_t reverb_level = chans.reverb_level; - reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); - reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); - reverb_pos = (reverb_pos + 2) & reverb_mask; - - int sum3_s = center.read(); - center.next( shift ); - - int left = new_reverb_l + sum3_s + l2.read() + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); - int right = new_reverb_r + sum3_s + r2.read() + FMUL( chans.echo_level, - echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); - - l2.next( shift ); - r2.next( shift ); - - echo_buf [echo_pos] = sum3_s; - echo_pos = (echo_pos + 1) & echo_mask; - - if ( (int16_t) left != left ) - left = 0x7FFF - (left >> 24); - - out [0] = left; - out [1] = right; - - out += 2; - - if ( (int16_t) right != right ) - out [-1] = 0x7FFF - (right >> 24); - } - this->reverb_pos = reverb_pos; - this->echo_pos = echo_pos; - - sq1.end( bufs [0] ); - sq2.end( bufs [1] ); - center.end( bufs [2] ); - l1.end( bufs [3] ); - r1.end( bufs [4] ); - l2.end( bufs [5] ); - r2.end( bufs [6] ); -} diff --git a/source/quickerNES/apu/Effects_Buffer.h b/source/quickerNES/apu/Effects_Buffer.h deleted file mode 100644 index 7ad1ff7..0000000 --- a/source/quickerNES/apu/Effects_Buffer.h +++ /dev/null @@ -1,93 +0,0 @@ - -// Multi-channel effects buffer with panning, echo and reverb - -// Game_Music_Emu 0.3.0 - -#ifndef EFFECTS_BUFFER_H -#define EFFECTS_BUFFER_H - -#include -#include "Multi_Buffer.h" - -// Effects_Buffer uses several buffers and outputs stereo sample pairs. -class Effects_Buffer : public Multi_Buffer { -public: - // If center_only is true, only center buffers are created and - // less memory is used. - Effects_Buffer( bool center_only = false ); - - // Channel Effect Center Pan - // --------------------------------- - // 0,5 reverb pan_1 - // 1,6 reverb pan_2 - // 2,7 echo - - // 3 echo - - // 4 echo - - - // Channel configuration - struct config_t { - double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right - double pan_2; - double echo_delay; // msec - double echo_level; // 0.0 to 1.0 - double reverb_delay; // msec - double delay_variance; // difference between left/right delays (msec) - double reverb_level; // 0.0 to 1.0 - bool effects_enabled; // if false, use optimized simple mixer - config_t(); - }; - - // Set configuration of buffer - virtual void config( const config_t& ); - void set_depth( double ); - -public: - ~Effects_Buffer(); - const char *set_sample_rate( long samples_per_sec, int msec = blip_default_length ); - void clock_rate( long ); - void bass_freq( int ); - void clear(); - channel_t channel( int ); - void end_frame( blip_time_t, bool was_stereo = true ); - long read_samples( blip_sample_t*, long ); - long samples_avail() const; -private: - typedef long fixed_t; - - enum { max_buf_count = 7 }; - Blip_Buffer bufs [max_buf_count]; - enum { chan_count = 5 }; - channel_t channels [chan_count]; - config_t config_; - long stereo_remain; - long effect_remain; - int buf_count; - bool effects_enabled; - - blip_sample_t* reverb_buf; - blip_sample_t* echo_buf; - int reverb_pos; - int echo_pos; - - struct { - fixed_t pan_1_levels [2]; - fixed_t pan_2_levels [2]; - int echo_delay_l; - int echo_delay_r; - fixed_t echo_level; - int reverb_delay_l; - int reverb_delay_r; - fixed_t reverb_level; - } chans; - - void mix_mono( blip_sample_t*, long ); - void mix_stereo( blip_sample_t*, long ); - void mix_enhanced( blip_sample_t*, long ); - void mix_mono_enhanced( blip_sample_t*, long ); -}; - - inline Effects_Buffer::channel_t Effects_Buffer::channel( int i ) { - return channels [i % chan_count]; - } - -#endif diff --git a/source/quickerNES/apu/Multi_Buffer.cpp b/source/quickerNES/apu/Multi_Buffer.cpp index 9c2746f..e9a2353 100644 --- a/source/quickerNES/apu/Multi_Buffer.cpp +++ b/source/quickerNES/apu/Multi_Buffer.cpp @@ -1,7 +1,7 @@ // Blip_Buffer 0.4.0. http://www.slack.net/~ant/ -#include "Multi_Buffer.h" +#include "Multi_Buffer.hpp" #include /* Copyright (C) 2003-2006 Shay Green. This module is free software; you @@ -15,30 +15,33 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +namespace quickerNES { - length_ = 0; - sample_rate_ = 0; - channels_changed_count_ = 1; - channels_changed_count_save_ = 1; + +Multi_Buffer::Multi_Buffer(int spf) : samples_per_frame_(spf) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; + channels_changed_count_save_ = 1; } -const char * Multi_Buffer::set_channel_count( int ) +const char *Multi_Buffer::set_channel_count(int) { - return 0; + return 0; } void Multi_Buffer::SaveAudioBufferStatePrivate() { - channels_changed_count_save_ = channels_changed_count_; + channels_changed_count_save_ = channels_changed_count_; } void Multi_Buffer::RestoreAudioBufferStatePrivate() { - channels_changed_count_ = channels_changed_count_save_; + channels_changed_count_ = channels_changed_count_save_; } -Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +Mono_Buffer::Mono_Buffer() : Multi_Buffer(1) { } @@ -46,235 +49,239 @@ Mono_Buffer::~Mono_Buffer() { } -const char * Mono_Buffer::set_sample_rate( long rate, int msec ) +const char *Mono_Buffer::set_sample_rate(long rate, int msec) { - buf.set_sample_rate( rate, msec ); - return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); + buf.set_sample_rate(rate, msec); + return Multi_Buffer::set_sample_rate(buf.sample_rate(), buf.length()); } void Mono_Buffer::SaveAudioBufferState() { - SaveAudioBufferStatePrivate(); - center()->SaveAudioBufferState(); + SaveAudioBufferStatePrivate(); + center()->SaveAudioBufferState(); } void Mono_Buffer::RestoreAudioBufferState() { - RestoreAudioBufferStatePrivate(); - center()->RestoreAudioBufferState(); + RestoreAudioBufferStatePrivate(); + center()->RestoreAudioBufferState(); } // Silent_Buffer -Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +Silent_Buffer::Silent_Buffer() : Multi_Buffer(1) // 0 channels would probably confuse { - chan.left = 0; - chan.center = 0; - chan.right = 0; + chan.left = 0; + chan.center = 0; + chan.right = 0; } void Silent_Buffer::SaveAudioBufferState() { - SaveAudioBufferStatePrivate(); + SaveAudioBufferStatePrivate(); } void Silent_Buffer::RestoreAudioBufferState() { - RestoreAudioBufferStatePrivate(); + RestoreAudioBufferStatePrivate(); } // Mono_Buffer -Mono_Buffer::channel_t Mono_Buffer::channel( int ) +Mono_Buffer::channel_t Mono_Buffer::channel(int) { - channel_t ch; - ch.center = &buf; - ch.left = &buf; - ch.right = &buf; - return ch; + channel_t ch; + ch.center = &buf; + ch.left = &buf; + ch.right = &buf; + return ch; } -void Mono_Buffer::end_frame( blip_time_t t, bool ) +void Mono_Buffer::end_frame(blip_time_t t, bool) { - buf.end_frame( t ); + buf.end_frame(t); } // Stereo_Buffer -Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer(2) { - chan.center = &bufs [0]; - chan.left = &bufs [1]; - chan.right = &bufs [2]; + chan.center = &bufs[0]; + chan.left = &bufs[1]; + chan.right = &bufs[2]; } Stereo_Buffer::~Stereo_Buffer() { } -const char * Stereo_Buffer::set_sample_rate( long rate, int msec ) +const char *Stereo_Buffer::set_sample_rate(long rate, int msec) { - for ( int i = 0; i < buf_count; i++ ) bufs [i].set_sample_rate( rate, msec ) ; - return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); + for (int i = 0; i < buf_count; i++) bufs[i].set_sample_rate(rate, msec); + return Multi_Buffer::set_sample_rate(bufs[0].sample_rate(), bufs[0].length()); } -void Stereo_Buffer::clock_rate( long rate ) +void Stereo_Buffer::clock_rate(long rate) { - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clock_rate( rate ); + for (int i = 0; i < buf_count; i++) + bufs[i].clock_rate(rate); } -void Stereo_Buffer::bass_freq( int bass ) +void Stereo_Buffer::bass_freq(int bass) { - for ( unsigned i = 0; i < buf_count; i++ ) - bufs [i].bass_freq( bass ); + for (unsigned i = 0; i < buf_count; i++) + bufs[i].bass_freq(bass); } void Stereo_Buffer::clear() { - stereo_added = false; - was_stereo = false; - for ( int i = 0; i < buf_count; i++ ) - bufs [i].clear(); + stereo_added = false; + was_stereo = false; + for (int i = 0; i < buf_count; i++) + bufs[i].clear(); } -void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +void Stereo_Buffer::end_frame(blip_time_t clock_count, bool stereo) { - for ( unsigned i = 0; i < buf_count; i++ ) - bufs [i].end_frame( clock_count ); - - stereo_added |= stereo; + for (unsigned i = 0; i < buf_count; i++) + bufs[i].end_frame(clock_count); + + stereo_added |= stereo; } -long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +long Stereo_Buffer::read_samples(blip_sample_t *out, long count) { - count = (unsigned) count / 2; - - long avail = bufs [0].samples_avail(); - if ( count > avail ) - count = avail; - if ( count ) - { - if ( stereo_added || was_stereo ) - { - mix_stereo( out, count ); - - bufs [0].remove_samples( count ); - bufs [1].remove_samples( count ); - bufs [2].remove_samples( count ); - } - else - { - mix_mono( out, count ); - - bufs [0].remove_samples( count ); - - bufs [1].remove_silence( count ); - bufs [2].remove_silence( count ); - } - - // to do: this might miss opportunities for optimization - if ( !bufs [0].samples_avail() ) { - was_stereo = stereo_added; - stereo_added = false; - } - } - - return count * 2; + count = (unsigned)count / 2; + + long avail = bufs[0].samples_avail(); + if (count > avail) + count = avail; + if (count) + { + if (stereo_added || was_stereo) + { + mix_stereo(out, count); + + bufs[0].remove_samples(count); + bufs[1].remove_samples(count); + bufs[2].remove_samples(count); + } + else + { + mix_mono(out, count); + + bufs[0].remove_samples(count); + + bufs[1].remove_silence(count); + bufs[2].remove_silence(count); + } + + // to do: this might miss opportunities for optimization + if (!bufs[0].samples_avail()) + { + was_stereo = stereo_added; + stereo_added = false; + } + } + + return count * 2; } -void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +void Stereo_Buffer::mix_stereo(blip_sample_t *out, long count) { - Blip_Reader left; - Blip_Reader right; - Blip_Reader center; - - left.begin( bufs [1] ); - right.begin( bufs [2] ); - int bass = center.begin( bufs [0] ); - - if (out != 0) - { - while ( count-- ) - { - int c = center.read(); - long l = c + left.read(); - long r = c + right.read(); - center.next( bass ); - out [0] = l; - out [1] = r; - out += 2; - - if ( (int16_t) l != l ) - out [-2] = 0x7FFF - (l >> 24); - - left.next( bass ); - right.next( bass ); - - if ( (int16_t) r != r ) - out [-1] = 0x7FFF - (r >> 24); - } - } - else - { - //only run accumulators, do not output any audio - while (count--) - { - center.next(bass); - left.next(bass); - right.next(bass); - } - } - - center.end( bufs [0] ); - right.end( bufs [2] ); - left.end( bufs [1] ); + Blip_Reader left; + Blip_Reader right; + Blip_Reader center; + + left.begin(bufs[1]); + right.begin(bufs[2]); + int bass = center.begin(bufs[0]); + + if (out != 0) + { + while (count--) + { + int c = center.read(); + long l = c + left.read(); + long r = c + right.read(); + center.next(bass); + out[0] = l; + out[1] = r; + out += 2; + + if ((int16_t)l != l) + out[-2] = 0x7FFF - (l >> 24); + + left.next(bass); + right.next(bass); + + if ((int16_t)r != r) + out[-1] = 0x7FFF - (r >> 24); + } + } + else + { + // only run accumulators, do not output any audio + while (count--) + { + center.next(bass); + left.next(bass); + right.next(bass); + } + } + + center.end(bufs[0]); + right.end(bufs[2]); + left.end(bufs[1]); } -void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +void Stereo_Buffer::mix_mono(blip_sample_t *out, long count) { - Blip_Reader in; - int bass = in.begin( bufs [0] ); - - if (out != 0) - { - while ( count-- ) - { - long s = in.read(); - in.next( bass ); - out [0] = s; - out [1] = s; - out += 2; - - if ( (int16_t) s != s ) { - s = 0x7FFF - (s >> 24); - out [-2] = s; - out [-1] = s; - } - } - } - else - { - while (count--) - { - in.next(bass); - } - } - - in.end( bufs [0] ); + Blip_Reader in; + int bass = in.begin(bufs[0]); + + if (out != 0) + { + while (count--) + { + long s = in.read(); + in.next(bass); + out[0] = s; + out[1] = s; + out += 2; + + if ((int16_t)s != s) + { + s = 0x7FFF - (s >> 24); + out[-2] = s; + out[-1] = s; + } + } + } + else + { + while (count--) + { + in.next(bass); + } + } + + in.end(bufs[0]); } void Stereo_Buffer::SaveAudioBufferState() { - SaveAudioBufferStatePrivate(); - left()->SaveAudioBufferState(); - center()->SaveAudioBufferState(); - right()->SaveAudioBufferState(); + SaveAudioBufferStatePrivate(); + left()->SaveAudioBufferState(); + center()->SaveAudioBufferState(); + right()->SaveAudioBufferState(); } void Stereo_Buffer::RestoreAudioBufferState() { - RestoreAudioBufferStatePrivate(); - left()->RestoreAudioBufferState(); - center()->RestoreAudioBufferState(); - right()->RestoreAudioBufferState(); + RestoreAudioBufferStatePrivate(); + left()->RestoreAudioBufferState(); + center()->RestoreAudioBufferState(); + right()->RestoreAudioBufferState(); } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/Multi_Buffer.h b/source/quickerNES/apu/Multi_Buffer.h deleted file mode 100644 index 670a896..0000000 --- a/source/quickerNES/apu/Multi_Buffer.h +++ /dev/null @@ -1,189 +0,0 @@ - -// Multi-channel sound buffer interface, and basic mono and stereo buffers - -// Blip_Buffer 0.4.0 - -#ifndef MULTI_BUFFER_H -#define MULTI_BUFFER_H - -#include "Blip_Buffer.h" - -// Interface to one or more Blip_Buffers mapped to one or more channels -// consisting of left, center, and right buffers. -class Multi_Buffer { -public: - Multi_Buffer( int samples_per_frame ); - virtual ~Multi_Buffer() { } - - // Set the number of channels available - virtual const char* set_channel_count( int ); - - // Get indexed channel, from 0 to channel count - 1 - struct channel_t { - Blip_Buffer* center; - Blip_Buffer* left; - Blip_Buffer* right; - }; - virtual channel_t channel( int index ) = 0; - - // See Blip_Buffer.h - virtual const char* set_sample_rate( long rate, int msec = blip_default_length ) = 0; - virtual void clock_rate( long ) = 0; - virtual void bass_freq( int ) = 0; - virtual void clear() = 0; - long sample_rate() const; - - // Length of buffer, in milliseconds - int length() const; - - // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' - // if nothing was added to the left and right buffers of any channel for - // this time frame. - virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; - - // Number of samples per output frame (1 = mono, 2 = stereo) - int samples_per_frame() const; - - // Count of changes to channel configuration. Incremented whenever - // a change is made to any of the Blip_Buffers for any channel. - unsigned channels_changed_count() { return channels_changed_count_; } - - // See Blip_Buffer.h - virtual long read_samples( blip_sample_t*, long ) = 0; - virtual long samples_avail() const = 0; - -protected: - void channels_changed() { channels_changed_count_++; } -private: - // noncopyable - Multi_Buffer( const Multi_Buffer& ); - Multi_Buffer& operator = ( const Multi_Buffer& ); - - unsigned channels_changed_count_; - long sample_rate_; - int length_; - int const samples_per_frame_; - unsigned channels_changed_count_save_; -protected: - void SaveAudioBufferStatePrivate(); - void RestoreAudioBufferStatePrivate(); -public: - virtual void SaveAudioBufferState() = 0; - virtual void RestoreAudioBufferState() = 0; -}; - -// Uses a single buffer and outputs mono samples. -class Mono_Buffer : public Multi_Buffer { - Blip_Buffer buf; -public: - Mono_Buffer(); - ~Mono_Buffer(); - - // Buffer used for all channels - Blip_Buffer* center() { return &buf; } - - // See Multi_Buffer - const char* set_sample_rate( long rate, int msec = blip_default_length ); - void clock_rate( long ); - void bass_freq( int ); - void clear(); - channel_t channel( int ); - void end_frame( blip_time_t, bool unused = true ); - long samples_avail() const; - long read_samples( blip_sample_t*, long ); - - virtual void SaveAudioBufferState(); - virtual void RestoreAudioBufferState(); -}; - -// Uses three buffers (one for center) and outputs stereo sample pairs. -class Stereo_Buffer : public Multi_Buffer { -public: - Stereo_Buffer(); - ~Stereo_Buffer(); - - // Buffers used for all channels - Blip_Buffer* center() { return &bufs [0]; } - Blip_Buffer* left() { return &bufs [1]; } - Blip_Buffer* right() { return &bufs [2]; } - - // See Multi_Buffer - const char* set_sample_rate( long, int msec = blip_default_length ); - void clock_rate( long ); - void bass_freq( int ); - void clear(); - channel_t channel( int index ); - void end_frame( blip_time_t, bool added_stereo = true ); - - long samples_avail() const; - long read_samples( blip_sample_t*, long ); - -private: - enum { buf_count = 3 }; - Blip_Buffer bufs [buf_count]; - channel_t chan; - bool stereo_added; - bool was_stereo; - - void mix_stereo( blip_sample_t*, long ); - void mix_mono( blip_sample_t*, long ); - - virtual void SaveAudioBufferState(); - virtual void RestoreAudioBufferState(); -}; - -// Silent_Buffer generates no samples, useful where no sound is wanted -class Silent_Buffer : public Multi_Buffer { - channel_t chan; -public: - Silent_Buffer(); - - const char* set_sample_rate( long rate, int msec = blip_default_length ); - void clock_rate( long ) { } - void bass_freq( int ) { } - void clear() { } - channel_t channel( int ) { return chan; } - void end_frame( blip_time_t, bool unused = true ) { } - long samples_avail() const { return 0; } - long read_samples( blip_sample_t*, long ) { return 0; } - - virtual void SaveAudioBufferState(); - virtual void RestoreAudioBufferState(); -}; - - -// End of public interface - -inline const char* Multi_Buffer::set_sample_rate( long rate, int msec ) -{ - sample_rate_ = rate; - length_ = msec; - return 0; -} - -inline const char* Silent_Buffer::set_sample_rate( long rate, int msec ) -{ - return Multi_Buffer::set_sample_rate( rate, msec ); -} - -inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } - -inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; } - -inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; } - -inline long Multi_Buffer::sample_rate() const { return sample_rate_; } - -inline int Multi_Buffer::length() const { return length_; } - -inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); } - -inline void Mono_Buffer::clear() { buf.clear(); } - -inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); } - -inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } - -inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } - -#endif diff --git a/source/quickerNES/apu/Multi_Buffer.hpp b/source/quickerNES/apu/Multi_Buffer.hpp new file mode 100644 index 0000000..20a1ff3 --- /dev/null +++ b/source/quickerNES/apu/Multi_Buffer.hpp @@ -0,0 +1,202 @@ + +#pragma once + +// Multi-channel sound buffer interface, and basic mono and stereo buffers +// Blip_Buffer 0.4.0 + +#include "Blip_Buffer.hpp" + +namespace quickerNES +{ + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer +{ + public: + Multi_Buffer(int samples_per_frame); + virtual ~Multi_Buffer() {} + + // Set the number of channels available + virtual const char *set_channel_count(int); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t + { + Blip_Buffer *center; + Blip_Buffer *left; + Blip_Buffer *right; + }; + virtual channel_t channel(int index) = 0; + + // See Blip_Buffer.h + virtual const char *set_sample_rate(long rate, int msec = blip_default_length) = 0; + virtual void clock_rate(long) = 0; + virtual void bass_freq(int) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' + // if nothing was added to the left and right buffers of any channel for + // this time frame. + virtual void end_frame(blip_time_t, bool added_stereo = true) = 0; + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples(blip_sample_t *, long) = 0; + virtual long samples_avail() const = 0; + + protected: + void channels_changed() { channels_changed_count_++; } + + private: + // noncopyable + Multi_Buffer(const Multi_Buffer &); + Multi_Buffer &operator=(const Multi_Buffer &); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int const samples_per_frame_; + unsigned channels_changed_count_save_; + + protected: + void SaveAudioBufferStatePrivate(); + void RestoreAudioBufferStatePrivate(); + + public: + virtual void SaveAudioBufferState() = 0; + virtual void RestoreAudioBufferState() = 0; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer +{ + Blip_Buffer buf; + + public: + Mono_Buffer(); + ~Mono_Buffer(); + + // Buffer used for all channels + Blip_Buffer *center() { return &buf; } + + // See Multi_Buffer + const char *set_sample_rate(long rate, int msec = blip_default_length); + void clock_rate(long); + void bass_freq(int); + void clear(); + channel_t channel(int); + void end_frame(blip_time_t, bool unused = true); + long samples_avail() const; + long read_samples(blip_sample_t *, long); + + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer +{ + public: + Stereo_Buffer(); + ~Stereo_Buffer(); + + // Buffers used for all channels + Blip_Buffer *center() { return &bufs[0]; } + Blip_Buffer *left() { return &bufs[1]; } + Blip_Buffer *right() { return &bufs[2]; } + + // See Multi_Buffer + const char *set_sample_rate(long, int msec = blip_default_length); + void clock_rate(long); + void bass_freq(int); + void clear(); + channel_t channel(int index); + void end_frame(blip_time_t, bool added_stereo = true); + + long samples_avail() const; + long read_samples(blip_sample_t *, long); + + private: + enum + { + buf_count = 3 + }; + Blip_Buffer bufs[buf_count]; + channel_t chan; + bool stereo_added; + bool was_stereo; + + void mix_stereo(blip_sample_t *, long); + void mix_mono(blip_sample_t *, long); + + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer +{ + channel_t chan; + + public: + Silent_Buffer(); + + const char *set_sample_rate(long rate, int msec = blip_default_length); + void clock_rate(long) {} + void bass_freq(int) {} + void clear() {} + channel_t channel(int) { return chan; } + void end_frame(blip_time_t, bool unused = true) {} + long samples_avail() const { return 0; } + long read_samples(blip_sample_t *, long) { return 0; } + + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +// End of public interface + +inline const char *Multi_Buffer::set_sample_rate(long rate, int msec) +{ + sample_rate_ = rate; + length_ = msec; + return 0; +} + +inline const char *Silent_Buffer::set_sample_rate(long rate, int msec) +{ + return Multi_Buffer::set_sample_rate(rate, msec); +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Stereo_Buffer::samples_avail() const { return bufs[0].samples_avail() * 2; } + +inline Stereo_Buffer::channel_t Stereo_Buffer::channel(int) { return chan; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +inline void Mono_Buffer::clock_rate(long rate) { buf.clock_rate(rate); } + +inline void Mono_Buffer::clear() { buf.clear(); } + +inline void Mono_Buffer::bass_freq(int freq) { buf.bass_freq(freq); } + +inline long Mono_Buffer::read_samples(blip_sample_t *p, long s) { return buf.read_samples(p, s); } + +inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/NESEffectsBuffer.cpp b/source/quickerNES/apu/NESEffectsBuffer.cpp new file mode 100644 index 0000000..10b3d1b --- /dev/null +++ b/source/quickerNES/apu/NESEffectsBuffer.cpp @@ -0,0 +1,91 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "apu/NESEffectsBuffer.hpp" +#include "apu/apu.hpp" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +namespace quickerNES +{ + +Nes_Effects_Buffer::Nes_Effects_Buffer() : Effects_Buffer(true) // nes never uses stereo channels +{ + config_t c; + c.effects_enabled = false; + config(c); +} + +Nes_Effects_Buffer::~Nes_Effects_Buffer() {} + +Multi_Buffer *set_apu(Nes_Effects_Buffer *buf, Apu *apu) +{ + buf->set_apu(apu); + return buf; +} + +void Nes_Effects_Buffer::enable_nonlinearity(bool b) +{ + if (b) clear(); + Apu *apu = nonlin.enable(b, channel(2).center); + apu->osc_output(0, channel(0).center); + apu->osc_output(1, channel(1).center); +} + +void Nes_Effects_Buffer::config(const config_t &in) +{ + config_t c = in; + if (!c.effects_enabled) + { + // effects must always be enabled to keep separate buffers, so + // set parameters to be equivalent to disabled + c.pan_1 = 0; + c.pan_2 = 0; + c.echo_level = 0; + c.reverb_level = 0; + c.effects_enabled = true; + } + Effects_Buffer::config(c); +} + +const char *Nes_Effects_Buffer::set_sample_rate(long rate, int msec) +{ + enable_nonlinearity(nonlin.enabled); // reapply + return Effects_Buffer::set_sample_rate(rate, msec); +} + +void Nes_Effects_Buffer::clear() +{ + nonlin.clear(); + Effects_Buffer::clear(); +} + +Nes_Effects_Buffer::channel_t Nes_Effects_Buffer::channel(int i) +{ + return Effects_Buffer::channel((2 <= i && i <= 4) ? 2 : i & 1); +} + +long Nes_Effects_Buffer::read_samples(blip_sample_t *out, long count) +{ + count = 2 * nonlin.make_nonlinear(*channel(2).center, count / 2); + return Effects_Buffer::read_samples(out, count); +} + +void Nes_Effects_Buffer::SaveAudioBufferState() +{ +} + +void Nes_Effects_Buffer::RestoreAudioBufferState() +{ +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/NESEffectsBuffer.hpp b/source/quickerNES/apu/NESEffectsBuffer.hpp new file mode 100644 index 0000000..7d5e539 --- /dev/null +++ b/source/quickerNES/apu/NESEffectsBuffer.hpp @@ -0,0 +1,41 @@ +#pragma once + +// Effects_Buffer with non-linear sound +// Nes_Emu 0.7.0 + +#include "effectsBuffer.hpp" +#include "buffer.hpp" + +namespace quickerNES +{ + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Nes_Effects_Buffer : public Effects_Buffer +{ + public: + Nes_Effects_Buffer(); + ~Nes_Effects_Buffer(); + + // Setup APU for use with buffer, including setting its output to this buffer. + // If you're using Nes_Emu, this is automatically called for you. + void set_apu(Apu *apu) { nonlin.set_apu(apu); } + + // Enable/disable non-linear output + void enable_nonlinearity(bool = true); + + // See Effects_Buffer.h for reference + const char *set_sample_rate(long rate, int msec = blip_default_length); + void config(const config_t &); + void clear(); + channel_t channel(int); + long read_samples(blip_sample_t *, long); + + void SaveAudioBufferState(); + void RestoreAudioBufferState(); + + private: + Nonlinearizer nonlin; + friend Multi_Buffer *set_apu(Nes_Effects_Buffer *, Apu *); +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/Nes_Buffer.cpp b/source/quickerNES/apu/Nes_Buffer.cpp deleted file mode 100644 index 0f76336..0000000 --- a/source/quickerNES/apu/Nes_Buffer.cpp +++ /dev/null @@ -1,233 +0,0 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ - -#include "apu/Nes_Buffer.h" -#include "apu/apu.h" - -/* Library Copyright (C) 2003-2006 Shay Green. This library is free software; -you can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - - -// Nes_Buffer - -Nes_Buffer::Nes_Buffer() : Multi_Buffer( 1 ) { } - -Nes_Buffer::~Nes_Buffer() { } - -Multi_Buffer* set_apu( Nes_Buffer* buf, Nes_Apu* apu ) -{ - buf->set_apu( apu ); - return buf; -} - -void Nes_Buffer::enable_nonlinearity( bool b ) -{ - if ( b ) - clear(); - - Nes_Apu* apu = nonlin.enable( b, &tnd ); - apu->osc_output( 0, &buf ); - apu->osc_output( 1, &buf ); -} - -const char * Nes_Buffer::set_sample_rate( long rate, int msec ) -{ - enable_nonlinearity( nonlin.enabled ); // reapply - buf.set_sample_rate( rate, msec ); - tnd.set_sample_rate( rate, msec ); - return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); -} - -void Nes_Buffer::clock_rate( long rate ) -{ - buf.clock_rate( rate ); - tnd.clock_rate( rate ); -} - -void Nes_Buffer::bass_freq( int freq ) -{ - buf.bass_freq( freq ); - tnd.bass_freq( freq ); -} - -void Nes_Buffer::clear() -{ - nonlin.clear(); - buf.clear(); - tnd.clear(); -} - -Nes_Buffer::channel_t Nes_Buffer::channel( int i ) -{ - channel_t c; - c.center = &buf; - if ( 2 <= i && i <= 4 ) - c.center = &tnd; // only use for triangle, noise, and dmc - c.left = c.center; - c.right = c.center; - return c; -} - -void Nes_Buffer::end_frame( blip_time_t length, bool ) -{ - buf.end_frame( length ); - tnd.end_frame( length ); -} - -long Nes_Buffer::samples_avail() const -{ - return buf.samples_avail(); -} - -long Nes_Buffer::read_samples( blip_sample_t* out, long count ) -{ - count = nonlin.make_nonlinear( tnd, count ); - if ( count ) - { - Blip_Reader lin; - Blip_Reader nonlin; - - int lin_bass = lin.begin( buf ); - int nonlin_bass = nonlin.begin( tnd ); - - if (out != 0) - { - for ( int n = count; n--; ) - { - int s = lin.read() + nonlin.read(); - lin.next( lin_bass ); - nonlin.next( nonlin_bass ); - *out++ = s; - - if ( (int16_t) s != s ) - out [-1] = 0x7FFF - (s >> 24); - } - } - else - { - //only run accumulators, do not output audio - for (int n = count; n--; ) - { - lin.next(lin_bass); - nonlin.next(nonlin_bass); - } - } - - lin.end( buf ); - nonlin.end( tnd ); - - buf.remove_samples( count ); - tnd.remove_samples( count ); - } - - return count; -} - -void Nes_Buffer::SaveAudioBufferState() -{ - SaveAudioBufferStatePrivate(); - nonlin.SaveAudioBufferState(); - buf.SaveAudioBufferState(); - tnd.SaveAudioBufferState(); -} - -void Nes_Buffer::RestoreAudioBufferState() -{ - RestoreAudioBufferStatePrivate(); - nonlin.RestoreAudioBufferState(); - buf.RestoreAudioBufferState(); - tnd.RestoreAudioBufferState(); -} - -// Nes_Nonlinearizer - -Nes_Nonlinearizer::Nes_Nonlinearizer() -{ - apu = nullptr; - enabled = true; - - float const gain = 0x7fff * 1.3f; - // don't use entire range, so any overflow will stay within table - int const range = (int) ( (double)table_size * Nes_Apu::nonlinear_tnd_gain()); - for ( int i = 0; i < table_size; i++ ) - { - int const offset = table_size - range; - int j = i - offset; - float n = 202.0f / (range - 1) * j; - float d = 0; - // Prevent division by zero - if ( n ) - d = gain * 163.67f / (24329.0f / n + 100.0f); - int out = (int) d; - table [j & (table_size - 1)] = out; - } - extra_accum = 0; - extra_prev = 0; -} - -Nes_Apu* Nes_Nonlinearizer::enable( bool b, Blip_Buffer* buf ) -{ - apu->osc_output( 2, buf ); - apu->osc_output( 3, buf ); - apu->osc_output( 4, buf ); - enabled = b; - if ( b ) - apu->enable_nonlinear( 1.0 ); - else - apu->volume( 1.0 ); - return apu; -} - -#define ENTRY( s ) table [(s) >> (blip_sample_bits - table_bits - 1) & (table_size - 1)] - -long Nes_Nonlinearizer::make_nonlinear( Blip_Buffer& buf, long count ) -{ - long avail = buf.samples_avail(); - if ( count > avail ) - count = avail; - if ( count && enabled ) - { - - Blip_Buffer::buf_t_* p = buf.buffer_; - long accum = this->accum; - long prev = this->prev; - for ( unsigned n = count; n; --n ) - { - long entry = ENTRY( accum ); - accum += *p; - *p++ = (entry - prev) << (blip_sample_bits - 16); - prev = entry; - } - - this->prev = prev; - this->accum = accum; - } - - return count; -} - -void Nes_Nonlinearizer::clear() -{ - accum = 0; - prev = ENTRY( 86016000 ); // avoid thump due to APU's triangle dc bias - // TODO: still results in slight clicks and thumps -} - -void Nes_Nonlinearizer::SaveAudioBufferState() -{ - extra_accum = accum; - extra_prev = prev; -} - -void Nes_Nonlinearizer::RestoreAudioBufferState() -{ - accum = extra_accum; - prev = extra_prev; -} diff --git a/source/quickerNES/apu/Nes_Buffer.h b/source/quickerNES/apu/Nes_Buffer.h deleted file mode 100644 index d8a847b..0000000 --- a/source/quickerNES/apu/Nes_Buffer.h +++ /dev/null @@ -1,72 +0,0 @@ - -// NES non-linear audio buffer - -// Nes_Emu 0.7.0 - -#ifndef NES_BUFFER_H -#define NES_BUFFER_H - -#include "Multi_Buffer.h" -#include - -class Nes_Apu; - -class Nes_Nonlinearizer { -private: - enum { table_bits = 11 }; - enum { table_size = 1 << table_bits }; - int16_t table [table_size]; - Nes_Apu* apu; - long accum; - long prev; - - long extra_accum; - long extra_prev; -public: - Nes_Nonlinearizer(); - bool enabled; - void clear(); - void set_apu( Nes_Apu* a ) { apu = a; } - Nes_Apu* enable( bool, Blip_Buffer* tnd ); - long make_nonlinear( Blip_Buffer& buf, long count ); - void SaveAudioBufferState(); - void RestoreAudioBufferState(); -}; - -class Nes_Buffer : public Multi_Buffer { -public: - Nes_Buffer(); - ~Nes_Buffer(); - - // Setup APU for use with buffer, including setting its output to this buffer. - // If you're using Nes_Emu, this is automatically called for you. - void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); } - - // Enable/disable non-linear output - void enable_nonlinearity( bool = true ); - - // Blip_Buffer to output other sound chips to - Blip_Buffer* buffer() { return &buf; } - - // See Multi_Buffer.h - const char *set_sample_rate( long rate, int msec = blip_default_length ); - - void clock_rate( long ); - void bass_freq( int ); - void clear(); - channel_t channel( int ); - void end_frame( blip_time_t, bool unused = true ); - long samples_avail() const; - long read_samples( blip_sample_t*, long ); - -private: - Blip_Buffer buf; - Blip_Buffer tnd; - Nes_Nonlinearizer nonlin; - friend Multi_Buffer* set_apu( Nes_Buffer*, Nes_Apu* ); -public: - virtual void SaveAudioBufferState(); - virtual void RestoreAudioBufferState(); -}; - -#endif diff --git a/source/quickerNES/apu/Nes_Effects_Buffer.cpp b/source/quickerNES/apu/Nes_Effects_Buffer.cpp deleted file mode 100644 index 2b9627e..0000000 --- a/source/quickerNES/apu/Nes_Effects_Buffer.cpp +++ /dev/null @@ -1,88 +0,0 @@ - -// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ - -#include "apu/Nes_Effects_Buffer.h" -#include "apu/apu.h" - -/* Copyright (C) 2004-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -Nes_Effects_Buffer::Nes_Effects_Buffer() : - Effects_Buffer( true ) // nes never uses stereo channels -{ - config_t c; - c.effects_enabled = false; - config( c ); -} - -Nes_Effects_Buffer::~Nes_Effects_Buffer() { } - -Multi_Buffer* set_apu( Nes_Effects_Buffer* buf, Nes_Apu* apu ) -{ - buf->set_apu( apu ); - return buf; -} - -void Nes_Effects_Buffer::enable_nonlinearity( bool b ) -{ - if ( b ) - clear(); - Nes_Apu* apu = nonlin.enable( b, channel( 2 ).center ); - apu->osc_output( 0, channel( 0 ).center ); - apu->osc_output( 1, channel( 1 ).center ); -} - -void Nes_Effects_Buffer::config( const config_t& in ) -{ - config_t c = in; - if ( !c.effects_enabled ) - { - // effects must always be enabled to keep separate buffers, so - // set parameters to be equivalent to disabled - c.pan_1 = 0; - c.pan_2 = 0; - c.echo_level = 0; - c.reverb_level = 0; - c.effects_enabled = true; - } - Effects_Buffer::config( c ); -} - -const char * Nes_Effects_Buffer::set_sample_rate( long rate, int msec ) -{ - enable_nonlinearity( nonlin.enabled ); // reapply - return Effects_Buffer::set_sample_rate( rate, msec ); -} - -void Nes_Effects_Buffer::clear() -{ - nonlin.clear(); - Effects_Buffer::clear(); -} - -Nes_Effects_Buffer::channel_t Nes_Effects_Buffer::channel( int i ) -{ - return Effects_Buffer::channel( (2 <= i && i <= 4) ? 2 : i & 1 ); -} - -long Nes_Effects_Buffer::read_samples( blip_sample_t* out, long count ) -{ - count = 2 * nonlin.make_nonlinear( *channel( 2 ).center, count / 2 ); - return Effects_Buffer::read_samples( out, count ); -} - -void Nes_Effects_Buffer::SaveAudioBufferState() -{ -} - -void Nes_Effects_Buffer::RestoreAudioBufferState() -{ -} diff --git a/source/quickerNES/apu/Nes_Effects_Buffer.h b/source/quickerNES/apu/Nes_Effects_Buffer.h deleted file mode 100644 index 2f751c2..0000000 --- a/source/quickerNES/apu/Nes_Effects_Buffer.h +++ /dev/null @@ -1,40 +0,0 @@ - -// Effects_Buffer with non-linear sound - -// Nes_Emu 0.7.0 - -#ifndef NES_EFFECTS_BUFFER_H -#define NES_EFFECTS_BUFFER_H - -#include "Nes_Buffer.h" -#include "Effects_Buffer.h" - -// Effects_Buffer uses several buffers and outputs stereo sample pairs. -class Nes_Effects_Buffer : public Effects_Buffer { -public: - Nes_Effects_Buffer(); - ~Nes_Effects_Buffer(); - - // Setup APU for use with buffer, including setting its output to this buffer. - // If you're using Nes_Emu, this is automatically called for you. - void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); } - - // Enable/disable non-linear output - void enable_nonlinearity( bool = true ); - - // See Effects_Buffer.h for reference - const char *set_sample_rate( long rate, int msec = blip_default_length ); - void config( const config_t& ); - void clear(); - channel_t channel( int ); - long read_samples( blip_sample_t*, long ); - - void SaveAudioBufferState(); - void RestoreAudioBufferState(); - -private: - Nes_Nonlinearizer nonlin; - friend Multi_Buffer* set_apu( Nes_Effects_Buffer*, Nes_Apu* ); -}; - -#endif diff --git a/source/quickerNES/apu/Nes_Oscs.cpp b/source/quickerNES/apu/Nes_Oscs.cpp deleted file mode 100644 index 485fad6..0000000 --- a/source/quickerNES/apu/Nes_Oscs.cpp +++ /dev/null @@ -1,533 +0,0 @@ - -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ - -#include "apu.h" - -/* Copyright (C) 2003-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -// Nes_Osc - -void Nes_Osc::clock_length( int halt_mask ) -{ - if ( length_counter && !(regs [0] & halt_mask) ) - length_counter--; -} - -void Nes_Envelope::clock_envelope() -{ - int period = regs [0] & 15; - if ( reg_written [3] ) { - reg_written [3] = false; - env_delay = period; - envelope = 15; - } - else if ( --env_delay < 0 ) { - env_delay = period; - if ( envelope | (regs [0] & 0x20) ) - envelope = (envelope - 1) & 15; - } -} - -int Nes_Envelope::volume() const -{ - return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope; -} - -// Nes_Square - -void Nes_Square::clock_sweep( int negative_adjust ) -{ - int sweep = regs [1]; - - if ( --sweep_delay < 0 ) - { - reg_written [1] = true; - - int period = this->period(); - int shift = sweep & shift_mask; - if ( shift && (sweep & 0x80) && period >= 8 ) - { - int offset = period >> shift; - - if ( sweep & negate_flag ) - offset = negative_adjust - offset; - - if ( period + offset < 0x800 ) - { - period += offset; - // rewrite period - regs [2] = period & 0xff; - regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); - } - } - } - - if ( reg_written [1] ) { - reg_written [1] = false; - sweep_delay = (sweep >> 4) & 7; - } -} - -// TODO: clean up -inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time, - nes_time_t timer_period ) -{ - long remain = end_time - time; - if ( remain > 0 ) - { - int count = (remain + timer_period - 1) / timer_period; - phase = (phase + count) & (phase_range - 1); - time += (long) count * timer_period; - } - return time; -} - -void Nes_Square::run( nes_time_t time, nes_time_t end_time ) -{ - const int period = this->period(); - const int timer_period = (period + 1) * 2; - - if ( !output ) - { - delay = maintain_phase( time + delay, end_time, timer_period ) - end_time; - return; - } - - int offset = period >> (regs [1] & shift_mask); - if ( regs [1] & negate_flag ) - offset = 0; - - const int volume = this->volume(); - if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) - { - if ( last_amp ) { - synth.offset( time, -last_amp, output ); - last_amp = 0; - } - - time += delay; - time = maintain_phase( time, end_time, timer_period ); - } - else - { - // handle duty select - int duty_select = (regs [0] >> 6) & 3; - int duty = 1 << duty_select; // 1, 2, 4, 2 - int amp = 0; - if ( duty_select == 3 ) { - duty = 2; // negated 25% - amp = volume; - } - if ( phase < duty ) - amp ^= volume; - - int delta = update_amp( amp ); - if ( delta ) - synth.offset( time, delta, output ); - - time += delay; - if ( time < end_time ) - { - Blip_Buffer* const output = this->output; - const Synth& synth = this->synth; - int delta = amp * 2 - volume; - int phase = this->phase; - - do { - phase = (phase + 1) & (phase_range - 1); - if ( phase == 0 || phase == duty ) { - delta = -delta; - synth.offset_inline( time, delta, output ); - } - time += timer_period; - } - while ( time < end_time ); - - last_amp = (delta + volume) >> 1; - this->phase = phase; - } - } - - delay = time - end_time; -} - -// Nes_Triangle - -void Nes_Triangle::clock_linear_counter() -{ - if ( reg_written [3] ) - linear_counter = regs [0] & 0x7f; - else if ( linear_counter ) - linear_counter--; - - if ( !(regs [0] & 0x80) ) - reg_written [3] = false; -} - -inline int Nes_Triangle::calc_amp() const -{ - int amp = phase_range - phase; - if ( amp < 0 ) - amp = phase - (phase_range + 1); - return amp; -} - -// TODO: clean up -inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time, - nes_time_t timer_period ) -{ - long remain = end_time - time; - if ( remain > 0 ) - { - int count = (remain + timer_period - 1) / timer_period; - phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1); - phase++; - time += (long) count * timer_period; - } - return time; -} - -void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) -{ - const int timer_period = period() + 1; - if ( !output ) - { - time += delay; - delay = 0; - if ( length_counter && linear_counter && timer_period >= 3 ) - delay = maintain_phase( time, end_time, timer_period ) - end_time; - return; - } - - // to do: track phase when period < 3 - // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. - - int delta = update_amp( calc_amp() ); - if ( delta ) - synth.offset( time, delta, output ); - - time += delay; - if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) - { - time = end_time; - } - else if ( time < end_time ) - { - Blip_Buffer* const output = this->output; - - int phase = this->phase; - int volume = 1; - if ( phase > phase_range ) { - phase -= phase_range; - volume = -volume; - } - - do { - if ( --phase == 0 ) { - phase = phase_range; - volume = -volume; - } - else { - synth.offset_inline( time, volume, output ); - } - - time += timer_period; - } - while ( time < end_time ); - - if ( volume < 0 ) - phase += phase_range; - this->phase = phase; - last_amp = calc_amp(); - } - delay = time - end_time; -} - -// Nes_Dmc - -void Nes_Dmc::reset() -{ - address = 0; - dac = 0; - buf = 0; - bits_remain = 1; - bits = 0; - buf_full = false; - silence = true; - next_irq = Nes_Apu::no_irq; - irq_flag = false; - irq_enabled = false; - - Nes_Osc::reset(); - period = 0x1ac; -} - -void Nes_Dmc::recalc_irq() -{ - nes_time_t irq = Nes_Apu::no_irq; - if ( irq_enabled && length_counter ) - irq = apu->last_dmc_time + delay + - ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; - if ( irq != next_irq ) { - next_irq = irq; - apu->irq_changed(); - } -} - -int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const -{ - if ( last_read ) - *last_read = time; - - if ( length_counter == 0 ) - return 0; // not reading - - long first_read = next_read_time(); - long avail = time - first_read; - if ( avail <= 0 ) - return 0; - - int count = (avail - 1) / (period * 8) + 1; - if ( !(regs [0] & loop_flag) && count > length_counter ) - count = length_counter; - - if ( last_read ) - *last_read = first_read + (count - 1) * (period * 8) + 1; - - return count; -} - -static const short dmc_period_table [2] [16] = { - {0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC - 0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036}, - - {0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) - 0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032} // to do: verify PAL periods -}; - -inline void Nes_Dmc::reload_sample() -{ - address = 0x4000 + regs [2] * 0x40; - length_counter = regs [3] * 0x10 + 1; -} - -static const unsigned char dac_table [128] = -{ - 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, - 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, - 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, - 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, - 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, - 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, - 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, - 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, -}; - -void Nes_Dmc::write_register( int addr, int data ) -{ - if ( addr == 0 ) - { - period = dmc_period_table [pal_mode] [data & 15]; - irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled - irq_flag &= irq_enabled; - recalc_irq(); - } - else if ( addr == 1 ) - { - int old_dac = dac; - dac = data & 0x7F; - - // adjust last_amp so that "pop" amplitude will be properly non-linear - // with respect to change in dac - int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]); - if ( !nonlinear ) - last_amp = faked_nonlinear; - } -} - -void Nes_Dmc::start() -{ - reload_sample(); - fill_buffer(); - recalc_irq(); -} - -void Nes_Dmc::fill_buffer() -{ - if ( !buf_full && length_counter ) - { - buf = prg_reader( prg_reader_data, 0x8000u + address ); - address = (address + 1) & 0x7FFF; - buf_full = true; - if ( --length_counter == 0 ) - { - if ( regs [0] & loop_flag ) { - reload_sample(); - } - else { - apu->osc_enables &= ~0x10; - irq_flag = irq_enabled; - next_irq = Nes_Apu::no_irq; - apu->irq_changed(); - } - } - } -} - -void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) -{ - int delta = update_amp( dac ); - if ( !output ) - silence = true; - else if ( delta ) - synth.offset( time, delta, output ); - - time += delay; - if ( time < end_time ) - { - int bits_remain = this->bits_remain; - if ( silence && !buf_full ) - { - int count = (end_time - time + period - 1) / period; - bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; - time += count * period; - } - else - { - Blip_Buffer* const output = this->output; - const int period = this->period; - int bits = this->bits; - int dac = this->dac; - - do - { - if ( !silence ) - { - int step = (bits & 1) * 4 - 2; - bits >>= 1; - if ( unsigned (dac + step) <= 0x7F ) { - dac += step; - synth.offset_inline( time, step, output ); - } - } - - time += period; - - if ( --bits_remain == 0 ) - { - bits_remain = 8; - if ( !buf_full ) { - silence = true; - } - else { - silence = false; - bits = buf; - buf_full = false; - if ( !output ) - silence = true; - fill_buffer(); - } - } - } - while ( time < end_time ); - - this->dac = dac; - this->last_amp = dac; - this->bits = bits; - } - this->bits_remain = bits_remain; - } - delay = time - end_time; -} - -// Nes_Noise - -static const short noise_period_table [16] = { - 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, - 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 -}; - -void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) -{ - int period = noise_period_table [regs [2] & 15]; - #if NES_APU_NOISE_LOW_CPU - if ( period < 8 ) - { - period = 8; - } - #endif - - if ( !output ) - { - // TODO: clean up - time += delay; - delay = time + (end_time - time + period - 1) / period * period - end_time; - return; - } - - const int volume = this->volume(); - int amp = (noise & 1) ? volume : 0; - int delta = update_amp( amp ); - if ( delta ) - synth.offset( time, delta, output ); - - time += delay; - if ( time < end_time ) - { - const int mode_flag = 0x80; - - if ( !volume ) - { - // round to next multiple of period - time += (end_time - time + period - 1) / period * period; - - // approximate noise cycling while muted, by shuffling up noise register - // to do: precise muted noise cycling? - if ( !(regs [2] & mode_flag) ) { - int feedback = (noise << 13) ^ (noise << 14); - noise = (feedback & 0x4000) | (noise >> 1); - } - } - else - { - Blip_Buffer* const output = this->output; - - // using resampled time avoids conversion in synth.offset() - blip_resampled_time_t rperiod = output->resampled_duration( period ); - blip_resampled_time_t rtime = output->resampled_time( time ); - - int noise = this->noise; - int delta = amp * 2 - volume; - const int tap = (regs [2] & mode_flag ? 8 : 13); - - do { - int feedback = (noise << tap) ^ (noise << 14); - time += period; - - if ( (noise + 1) & 2 ) { - // bits 0 and 1 of noise differ - delta = -delta; - synth.offset_resampled( rtime, delta, output ); - } - - rtime += rperiod; - noise = (feedback & 0x4000) | (noise >> 1); - } - while ( time < end_time ); - - last_amp = (delta + volume) >> 1; - this->noise = noise; - } - } - - delay = time - end_time; -} diff --git a/source/quickerNES/apu/Nes_Oscs.h b/source/quickerNES/apu/Nes_Oscs.h deleted file mode 100644 index 650f64a..0000000 --- a/source/quickerNES/apu/Nes_Oscs.h +++ /dev/null @@ -1,151 +0,0 @@ - -// Private oscillators used by Nes_Apu - -// Nes_Snd_Emu 0.1.7 - -#ifndef NES_OSCS_H -#define NES_OSCS_H - -#include "Blip_Buffer.h" - -class Nes_Apu; - -typedef long nes_time_t; // CPU clock cycle count -typedef unsigned nes_addr_t; // 16-bit memory address - -struct Nes_Osc -{ - unsigned char regs [4]; - bool reg_written [4]; - Blip_Buffer* output; - int length_counter;// length counter (0 if unused by oscillator) - int delay; // delay until next (potential) transition - int last_amp; // last amplitude oscillator was outputting - - void clock_length( int halt_mask ); - int period() const { - return (regs [3] & 7) * 0x100 + (regs [2] & 0xff); - } - void reset() { - delay = 0; - last_amp = 0; - } - int update_amp( int amp ) { - int delta = amp - last_amp; - last_amp = amp; - return delta; - } -}; - -struct Nes_Envelope : Nes_Osc -{ - int envelope; - int env_delay; - - void clock_envelope(); - int volume() const; - void reset() { - envelope = 0; - env_delay = 0; - Nes_Osc::reset(); - } -}; - -// Nes_Square -struct Nes_Square : Nes_Envelope -{ - enum { negate_flag = 0x08 }; - enum { shift_mask = 0x07 }; - enum { phase_range = 8 }; - int phase; - int sweep_delay; - - typedef Blip_Synth Synth; - Synth const& synth; // shared between squares - - Nes_Square( Synth const* s ) : synth( *s ) { } - - void clock_sweep( int adjust ); - void run( nes_time_t, nes_time_t ); - void reset() { - sweep_delay = 0; - Nes_Envelope::reset(); - } - nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, - nes_time_t timer_period ); -}; - -// Nes_Triangle -struct Nes_Triangle : Nes_Osc -{ - enum { phase_range = 16 }; - int phase; - int linear_counter; - Blip_Synth synth; - - int calc_amp() const; - void run( nes_time_t, nes_time_t ); - void clock_linear_counter(); - void reset() { - linear_counter = 0; - phase = 1; - Nes_Osc::reset(); - } - nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, - nes_time_t timer_period ); -}; - -// Nes_Noise -struct Nes_Noise : Nes_Envelope -{ - int noise; - Blip_Synth synth; - - void run( nes_time_t, nes_time_t ); - void reset() { - noise = 1 << 14; - Nes_Envelope::reset(); - } -}; - -// Nes_Dmc -struct Nes_Dmc : Nes_Osc -{ - int address; // address of next byte to read - int period; - //int length_counter; // bytes remaining to play (already defined in Nes_Osc) - int buf; - int bits_remain; - int bits; - bool buf_full; - bool silence; - - enum { loop_flag = 0x40 }; - - int dac; - - nes_time_t next_irq; - bool irq_enabled; - bool irq_flag; - bool pal_mode; - bool nonlinear; - - int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function - void* prg_reader_data; - - Nes_Apu* apu; - - Blip_Synth synth; - - void start(); - void write_register( int, int ); - void run( nes_time_t, nes_time_t ); - void recalc_irq(); - void fill_buffer(); - void reload_sample(); - void reset(); - int count_reads( nes_time_t, nes_time_t* ) const; - nes_time_t next_read_time() const; -}; - -#endif diff --git a/source/quickerNES/apu/apu.cpp b/source/quickerNES/apu/apu.cpp index 119c037..a62af88 100644 --- a/source/quickerNES/apu/apu.cpp +++ b/source/quickerNES/apu/apu.cpp @@ -1,7 +1,6 @@ +// Snd_Emu 0.1.7. http://www.slack.net/~ant/ -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ - -#include "apu.h" +#include "apu.hpp" /* Copyright (C) 2003-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -14,353 +13,363 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + int const amp_range = 15; -Nes_Apu::Nes_Apu() : - square1( &square_synth ), - square2( &square_synth ) +Apu::Apu() : square1(&square_synth), + square2(&square_synth) { - dmc.apu = this; - dmc.prg_reader = 0; - irq_notifier_ = 0; - - oscs [0] = &square1; - oscs [1] = &square2; - oscs [2] = ▵ - oscs [3] = &noise; - oscs [4] = &dmc; - - output( 0 ); - volume( 1.0 ); - reset( false ); + dmc.apu = this; + dmc.prg_reader = 0; + irq_notifier_ = 0; + + oscs[0] = &square1; + oscs[1] = &square2; + oscs[2] = ▵ + oscs[3] = &noise; + oscs[4] = &dmc; + + output(0); + volume(1.0); + reset(false); } -Nes_Apu::~Nes_Apu() +Apu::~Apu() { } -void Nes_Apu::treble_eq( const blip_eq_t& eq ) +void Apu::treble_eq(const blip_eq_t &eq) { - square_synth.treble_eq( eq ); - triangle.synth.treble_eq( eq ); - noise.synth.treble_eq( eq ); - dmc.synth.treble_eq( eq ); + square_synth.treble_eq(eq); + triangle.synth.treble_eq(eq); + noise.synth.treble_eq(eq); + dmc.synth.treble_eq(eq); } -void Nes_Apu::enable_nonlinear( double v ) +void Apu::enable_nonlinear(double v) { - dmc.nonlinear = true; - square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v ); - - const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); - triangle.synth.volume( 3.0 * tnd ); - noise.synth.volume( 2.0 * tnd ); - dmc.synth.volume( tnd ); - - square1 .last_amp = 0; - square2 .last_amp = 0; - triangle.last_amp = 0; - noise .last_amp = 0; - dmc .last_amp = 0; + dmc.nonlinear = true; + square_synth.volume(1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v); + + const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); + triangle.synth.volume(3.0 * tnd); + noise.synth.volume(2.0 * tnd); + dmc.synth.volume(tnd); + + square1.last_amp = 0; + square2.last_amp = 0; + triangle.last_amp = 0; + noise.last_amp = 0; + dmc.last_amp = 0; } -void Nes_Apu::volume( double v ) +void Apu::volume(double v) { - dmc.nonlinear = false; - square_synth.volume( 0.1128 / amp_range * v ); - triangle.synth.volume( 0.12765 / amp_range * v ); - noise.synth.volume( 0.0741 / amp_range * v ); - dmc.synth.volume( 0.42545 / 127 * v ); + dmc.nonlinear = false; + square_synth.volume(0.1128 / amp_range * v); + triangle.synth.volume(0.12765 / amp_range * v); + noise.synth.volume(0.0741 / amp_range * v); + dmc.synth.volume(0.42545 / 127 * v); } -void Nes_Apu::output( Blip_Buffer* buffer ) +void Apu::output(Blip_Buffer *buffer) { - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buffer ); + for (int i = 0; i < osc_count; i++) + osc_output(i, buffer); } -void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) +void Apu::reset(bool pal_mode, int initial_dmc_dac) { - // to do: time pal frame periods exactly - frame_period = pal_mode ? 8314 : 7458; - dmc.pal_mode = pal_mode; - - square1.reset(); - square2.reset(); - triangle.reset(); - noise.reset(); - dmc.reset(); - - last_time = 0; - last_dmc_time = 0; - osc_enables = 0; - irq_flag = false; - earliest_irq_ = no_irq; - frame_delay = 1; - write_register( 0, 0x4017, 0x00 ); - write_register( 0, 0x4015, 0x00 ); - - for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ ) - write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); - - dmc.dac = initial_dmc_dac; - if ( !dmc.nonlinear ) - triangle.last_amp = 15; - //if ( !dmc.nonlinear ) // to do: remove? - // dmc.last_amp = initial_dmc_dac; // prevent output transition + // to do: time pal frame periods exactly + frame_period = pal_mode ? 8314 : 7458; + dmc.pal_mode = pal_mode; + + square1.reset(); + square2.reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); + + last_time = 0; + last_dmc_time = 0; + osc_enables = 0; + irq_flag = false; + earliest_irq_ = no_irq; + frame_delay = 1; + write_register(0, 0x4017, 0x00); + write_register(0, 0x4015, 0x00); + + for (nes_addr_t addr = start_addr; addr <= 0x4013; addr++) + write_register(0, addr, (addr & 3) ? 0x00 : 0x10); + + dmc.dac = initial_dmc_dac; + if (!dmc.nonlinear) + triangle.last_amp = 15; + // if ( !dmc.nonlinear ) // to do: remove? + // dmc.last_amp = initial_dmc_dac; // prevent output transition } -void Nes_Apu::irq_changed() +void Apu::irq_changed() { - nes_time_t new_irq = dmc.next_irq; - if ( dmc.irq_flag | irq_flag ) { - new_irq = 0; - } - else if ( new_irq > next_irq ) { - new_irq = next_irq; - } - - if ( new_irq != earliest_irq_ ) { - earliest_irq_ = new_irq; - if ( irq_notifier_ ) - irq_notifier_( irq_data ); - } + nes_time_t new_irq = dmc.next_irq; + if (dmc.irq_flag | irq_flag) + { + new_irq = 0; + } + else if (new_irq > next_irq) + { + new_irq = next_irq; + } + + if (new_irq != earliest_irq_) + { + earliest_irq_ = new_irq; + if (irq_notifier_) + irq_notifier_(irq_data); + } } // frames -void Nes_Apu::run_until( nes_time_t end_time ) +void Apu::run_until(nes_time_t end_time) { - if ( end_time > next_dmc_read_time() ) - { - nes_time_t start = last_dmc_time; - last_dmc_time = end_time; - dmc.run( start, end_time ); - } + if (end_time > next_dmc_read_time()) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run(start, end_time); + } } -void Nes_Apu::run_until_( nes_time_t end_time ) +void Apu::run_until_(nes_time_t end_time) { - if ( end_time == last_time ) - return; - - if ( last_dmc_time < end_time ) - { - nes_time_t start = last_dmc_time; - last_dmc_time = end_time; - dmc.run( start, end_time ); - } - - while ( true ) - { - // earlier of next frame time or end time - nes_time_t time = last_time + frame_delay; - if ( time > end_time ) - time = end_time; - frame_delay -= time - last_time; - - // run oscs to present - square1.run( last_time, time ); - square2.run( last_time, time ); - triangle.run( last_time, time ); - noise.run( last_time, time ); - last_time = time; - - if ( time == end_time ) - break; // no more frames to run - - // take frame-specific actions - frame_delay = frame_period; - switch ( frame++ ) - { - case 0: - if ( !(frame_mode & 0xc0) ) { - next_irq = time + frame_period * 4 + 1; - irq_flag = true; - } - // fall through - case 2: - // clock length and sweep on frames 0 and 2 - square1.clock_length( 0x20 ); - square2.clock_length( 0x20 ); - noise.clock_length( 0x20 ); - triangle.clock_length( 0x80 ); // different bit for halt flag on triangle - - square1.clock_sweep( -1 ); - square2.clock_sweep( 0 ); - break; - - case 1: - // frame 1 is slightly shorter - frame_delay -= 2; - break; - - case 3: - frame = 0; - - // frame 3 is almost twice as long in mode 1 - if ( frame_mode & 0x80 ) - frame_delay += frame_period - 6; - break; - } - - // clock envelopes and linear counter every frame - triangle.clock_linear_counter(); - square1.clock_envelope(); - square2.clock_envelope(); - noise.clock_envelope(); - } + if (end_time == last_time) + return; + + if (last_dmc_time < end_time) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run(start, end_time); + } + + while (true) + { + // earlier of next frame time or end time + nes_time_t time = last_time + frame_delay; + if (time > end_time) + time = end_time; + frame_delay -= time - last_time; + + // run oscs to present + square1.run(last_time, time); + square2.run(last_time, time); + triangle.run(last_time, time); + noise.run(last_time, time); + last_time = time; + + if (time == end_time) + break; // no more frames to run + + // take frame-specific actions + frame_delay = frame_period; + switch (frame++) + { + case 0: + if (!(frame_mode & 0xc0)) + { + next_irq = time + frame_period * 4 + 1; + irq_flag = true; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + square1.clock_length(0x20); + square2.clock_length(0x20); + noise.clock_length(0x20); + triangle.clock_length(0x80); // different bit for halt flag on triangle + + square1.clock_sweep(-1); + square2.clock_sweep(0); + break; + + case 1: + // frame 1 is slightly shorter + frame_delay -= 2; + break; + + case 3: + frame = 0; + + // frame 3 is almost twice as long in mode 1 + if (frame_mode & 0x80) + frame_delay += frame_period - 6; + break; + } + + // clock envelopes and linear counter every frame + triangle.clock_linear_counter(); + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } } -template -inline void zero_apu_osc( T* osc, nes_time_t time ) +template +inline void zero_apu_osc(T *osc, nes_time_t time) { - Blip_Buffer* output = osc->output; - int last_amp = osc->last_amp; - osc->last_amp = 0; - if ( output && last_amp ) - osc->synth.offset( time, -last_amp, output ); + Blip_Buffer *output = osc->output; + int last_amp = osc->last_amp; + osc->last_amp = 0; + if (output && last_amp) + osc->synth.offset(time, -last_amp, output); } -void Nes_Apu::end_frame( nes_time_t end_time ) +void Apu::end_frame(nes_time_t end_time) { - if ( end_time > last_time ) - run_until_( end_time ); - - if ( dmc.nonlinear ) - { - zero_apu_osc( &square1, last_time ); - zero_apu_osc( &square2, last_time ); - zero_apu_osc( &triangle, last_time ); - zero_apu_osc( &noise, last_time ); - zero_apu_osc( &dmc, last_time ); - } - - // make times relative to new frame - last_time -= end_time; - last_dmc_time -= end_time; - - if ( next_irq != no_irq ) { - next_irq -= end_time; - } - if ( dmc.next_irq != no_irq ) { - dmc.next_irq -= end_time; - } - if ( earliest_irq_ != no_irq ) { - earliest_irq_ -= end_time; - if ( earliest_irq_ < 0 ) - earliest_irq_ = 0; - } + if (end_time > last_time) + run_until_(end_time); + + if (dmc.nonlinear) + { + zero_apu_osc(&square1, last_time); + zero_apu_osc(&square2, last_time); + zero_apu_osc(&triangle, last_time); + zero_apu_osc(&noise, last_time); + zero_apu_osc(&dmc, last_time); + } + + // make times relative to new frame + last_time -= end_time; + last_dmc_time -= end_time; + + if (next_irq != no_irq) + { + next_irq -= end_time; + } + if (dmc.next_irq != no_irq) + { + dmc.next_irq -= end_time; + } + if (earliest_irq_ != no_irq) + { + earliest_irq_ -= end_time; + if (earliest_irq_ < 0) + earliest_irq_ = 0; + } } // registers -static const unsigned char length_table [0x20] = { - 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, - 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, - 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, - 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E -}; +static const unsigned char length_table[0x20] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E}; -void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) +void Apu::write_register(nes_time_t time, nes_addr_t addr, int data) { - // Ignore addresses outside range - if ( addr < start_addr || end_addr < addr ) - return; - - run_until_( time ); - - if ( addr < 0x4014 ) - { - // Write to channel - int osc_index = (addr - start_addr) >> 2; - Nes_Osc* osc = oscs [osc_index]; - - int reg = addr & 3; - osc->regs [reg] = data; - osc->reg_written [reg] = true; - - if ( osc_index == 4 ) - { - // handle DMC specially - dmc.write_register( reg, data ); - } - else if ( reg == 3 ) - { - // load length counter - if ( (osc_enables >> osc_index) & 1 ) - osc->length_counter = length_table [(data >> 3) & 0x1f]; - - // reset square phase - if ( osc_index < 2 ) - ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; - } - } - else if ( addr == 0x4015 ) - { - // Channel enables - for ( int i = osc_count; i--; ) - if ( !((data >> i) & 1) ) - oscs [i]->length_counter = 0; - - bool recalc_irq = dmc.irq_flag; - dmc.irq_flag = false; - - int old_enables = osc_enables; - osc_enables = data; - if ( !(data & 0x10) ) { - dmc.next_irq = no_irq; - recalc_irq = true; - } - else if ( !(old_enables & 0x10) ) { - dmc.start(); // dmc just enabled - } - - if ( recalc_irq ) - irq_changed(); - } - else if ( addr == 0x4017 ) - { - // Frame mode - frame_mode = data; - - bool irq_enabled = !(data & 0x40); - irq_flag &= irq_enabled; - next_irq = no_irq; - - // mode 1 - frame_delay = (frame_delay & 1); - frame = 0; - - if ( !(data & 0x80) ) - { - // mode 0 - frame = 1; - frame_delay += frame_period; - if ( irq_enabled ) - next_irq = time + frame_delay + frame_period * 3; - } - - irq_changed(); - } + // Ignore addresses outside range + if (addr < start_addr || end_addr < addr) + return; + + run_until_(time); + + if (addr < 0x4014) + { + // Write to channel + int osc_index = (addr - start_addr) >> 2; + Osc *osc = oscs[osc_index]; + + int reg = addr & 3; + osc->regs[reg] = data; + osc->reg_written[reg] = true; + + if (osc_index == 4) + { + // handle DMC specially + dmc.write_register(reg, data); + } + else if (reg == 3) + { + // load length counter + if ((osc_enables >> osc_index) & 1) + osc->length_counter = length_table[(data >> 3) & 0x1f]; + + // reset square phase + if (osc_index < 2) + ((Square *)osc)->phase = Square::phase_range - 1; + } + } + else if (addr == 0x4015) + { + // Channel enables + for (int i = osc_count; i--;) + if (!((data >> i) & 1)) + oscs[i]->length_counter = 0; + + bool recalc_irq = dmc.irq_flag; + dmc.irq_flag = false; + + int old_enables = osc_enables; + osc_enables = data; + if (!(data & 0x10)) + { + dmc.next_irq = no_irq; + recalc_irq = true; + } + else if (!(old_enables & 0x10)) + { + dmc.start(); // dmc just enabled + } + + if (recalc_irq) + irq_changed(); + } + else if (addr == 0x4017) + { + // Frame mode + frame_mode = data; + + bool irq_enabled = !(data & 0x40); + irq_flag &= irq_enabled; + next_irq = no_irq; + + // mode 1 + frame_delay = (frame_delay & 1); + frame = 0; + + if (!(data & 0x80)) + { + // mode 0 + frame = 1; + frame_delay += frame_period; + if (irq_enabled) + next_irq = time + frame_delay + frame_period * 3; + } + + irq_changed(); + } } -int Nes_Apu::read_status( nes_time_t time ) +int Apu::read_status(nes_time_t time) { - run_until_( time - 1 ); - - int result = (dmc.irq_flag << 7) | (irq_flag << 6); - - for ( int i = 0; i < osc_count; i++ ) - if ( oscs [i]->length_counter ) - result |= 1 << i; - - run_until_( time ); - - if ( irq_flag ) { - irq_flag = false; - irq_changed(); - } - - return result; + run_until_(time - 1); + + int result = (dmc.irq_flag << 7) | (irq_flag << 6); + + for (int i = 0; i < osc_count; i++) + if (oscs[i]->length_counter) + result |= 1 << i; + + run_until_(time); + + if (irq_flag) + { + irq_flag = false; + irq_changed(); + } + + return result; } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/apu.h b/source/quickerNES/apu/apu.h deleted file mode 100644 index 77bfcf4..0000000 --- a/source/quickerNES/apu/apu.h +++ /dev/null @@ -1,356 +0,0 @@ -#pragma once - -// NES 2A03 APU sound chip emulator - -// Nes_Snd_Emu 0.1.7 - -#include -#include -#include -#include "Nes_Oscs.h" - -class Nes_Apu { -public: - - typedef uint8_t env_t [3]; - /*struct env_t { - uint8_t delay; - uint8_t env; - uint8_t written; - };*/ - - struct apu_t { - uint8_t w40xx [0x14]; // $4000-$4013 - uint8_t w4015; // enables - uint8_t w4017; // mode - uint16_t frame_delay; - uint8_t frame_step; - uint8_t irq_flag; - }; - - struct square_t { - uint16_t delay; - env_t env; - uint8_t length_counter; - uint8_t phase; - uint8_t swp_delay; - uint8_t swp_reset; - uint8_t unused2 [1]; - }; - - struct triangle_t { - uint16_t delay; - uint8_t length_counter; - uint8_t phase; - uint8_t linear_counter; - uint8_t linear_mode; - }; - - struct noise_t { - uint16_t delay; - env_t env; - uint8_t length_counter; - uint16_t shift_reg; - }; - - struct dmc_t { - uint16_t delay; - uint16_t remain; - uint16_t addr; - uint8_t buf; - uint8_t bits_remain; - uint8_t bits; - uint8_t buf_full; - uint8_t silence; - uint8_t irq_flag; - }; - - struct apu_state_t - { - apu_t apu; - square_t square1; - square_t square2; - triangle_t triangle; - noise_t noise; - dmc_t dmc; - }; - static_assert( sizeof (apu_state_t) == 72 ); - - Nes_Apu(); - ~Nes_Apu(); - - // Set buffer to generate all sound into, or disable sound if NULL - void output( Blip_Buffer* ); - - // Set memory reader callback used by DMC oscillator to fetch samples. - // When callback is invoked, 'user_data' is passed unchanged as the - // first parameter. - void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = nullptr ); - - // All time values are the number of CPU clock cycles relative to the - // beginning of the current time frame. Before resetting the CPU clock - // count, call end_frame( last_cpu_time ). - - // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) - static const uint16_t start_addr = 0x4000; - static const uint16_t end_addr = 0x4017; - void write_register( nes_time_t, nes_addr_t, int data ); - - // Read from status register at 0x4015 - static const uint16_t status_addr = 0x4015; - int read_status( nes_time_t ); - - // Run all oscillators up to specified time, end current time frame, then - // start a new time frame at time 0. Time frames have no effect on emulation - // and each can be whatever length is convenient. - void end_frame( nes_time_t ); - -// Additional optional features (can be ignored without any problem) - - // Reset internal frame counter, registers, and all oscillators. - // Use PAL timing if pal_timing is true, otherwise use NTSC timing. - // Set the DMC oscillator's initial DAC value to initial_dmc_dac without - // any audible click. - void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); - - // Save/load exact emulation state - void save_state( apu_state_t* out ) const; - void load_state( apu_state_t const& ); - - // Set overall volume (default is 1.0) - void volume( double ); - - // Set treble equalization (see notes.txt) - void treble_eq( const blip_eq_t& ); - - // Set sound output of specific oscillator to buffer. If buffer is NULL, - // the specified oscillator is muted and emulation accuracy is reduced. - // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, - // 2) Triangle, 3) Noise, 4) DMC. - static const uint16_t osc_count = 5; - void osc_output( int index, Blip_Buffer* buffer ); - - // Set IRQ time callback that is invoked when the time of earliest IRQ - // may have changed, or NULL to disable. When callback is invoked, - // 'user_data' is passed unchanged as the first parameter. - void irq_notifier( void (*callback)( void* user_data ), void* user_data = nullptr ); - - // Get time that APU-generated IRQ will occur if no further register reads - // or writes occur. If IRQ is already pending, returns irq_waiting. If no - // IRQ will occur, returns no_irq. - static const uint64_t no_irq = LONG_MAX / 2 + 1; - static const uint16_t irq_waiting = 0; - nes_time_t earliest_irq( nes_time_t ) const; - - // Count number of DMC reads that would occur if 'run_until( t )' were executed. - // If last_read is not NULL, set *last_read to the earliest time that - // 'count_dmc_reads( time )' would result in the same result. - int count_dmc_reads( nes_time_t t, nes_time_t* last_read = nullptr ) const; - - // Time when next DMC memory read will occur - nes_time_t next_dmc_read_time() const; - - // Run DMC until specified time, so that any DMC memory reads can be - // accounted for (i.e. inserting CPU wait states). - void run_until( nes_time_t ); - -// End of public interface. -private: - friend class Nes_Nonlinearizer; - void enable_nonlinear( double volume ); - static double nonlinear_tnd_gain() { return 0.75; } -private: - friend struct Nes_Dmc; - - // noncopyable - Nes_Apu( const Nes_Apu& ); - Nes_Apu& operator = ( const Nes_Apu& ); - - Nes_Osc* oscs [osc_count]; - Nes_Square square1; - Nes_Square square2; - Nes_Noise noise; - Nes_Triangle triangle; - Nes_Dmc dmc; - - nes_time_t last_time; // has been run until this time in current frame - nes_time_t last_dmc_time; - nes_time_t earliest_irq_; - nes_time_t next_irq; - int frame_period; - int frame_delay; // cycles until frame counter runs next - int frame; // current frame (0-3) - int osc_enables; - int frame_mode; - bool irq_flag; - void (*irq_notifier_)( void* user_data ); - void* irq_data; - Nes_Square::Synth square_synth; // shared by squares - - void irq_changed(); - void state_restored(); - void run_until_( nes_time_t ); - - // TODO: remove - friend class Nes_Core; -}; - -inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) -{ - oscs [osc]->output = buf; -} - -inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const -{ - return earliest_irq_; -} - -inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) -{ - dmc.prg_reader_data = user_data; - dmc.prg_reader = func; -} - -inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) -{ - irq_notifier_ = func; - irq_data = user_data; -} - -inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const -{ - return dmc.count_reads( time, last_read ); -} - -inline nes_time_t Nes_Dmc::next_read_time() const -{ - if ( length_counter == 0 ) - return Nes_Apu::no_irq; // not reading - - return apu->last_dmc_time + delay + long (bits_remain - 1) * period; -} - -inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } - - -template -struct apu_reflection -{ - #define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu)) - - static void reflect_env( Nes_Apu::env_t* state, Nes_Envelope& osc ) - { - REFLECT( (*state) [0], osc.env_delay ); - REFLECT( (*state) [1], osc.envelope ); - REFLECT( (*state) [2], osc.reg_written [3] ); - } - - static void reflect_square( Nes_Apu::square_t& state, Nes_Square& osc ) - { - reflect_env( &state.env, osc ); - REFLECT( state.delay, osc.delay ); - REFLECT( state.length_counter, osc.length_counter ); - REFLECT( state.phase, osc.phase ); - REFLECT( state.swp_delay, osc.sweep_delay ); - REFLECT( state.swp_reset, osc.reg_written [1] ); - } - - static void reflect_triangle( Nes_Apu::triangle_t& state, Nes_Triangle& osc ) - { - REFLECT( state.delay, osc.delay ); - REFLECT( state.length_counter, osc.length_counter ); - REFLECT( state.linear_counter, osc.linear_counter ); - REFLECT( state.phase, osc.phase ); - REFLECT( state.linear_mode, osc.reg_written [3] ); - } - - static void reflect_noise( Nes_Apu::noise_t& state, Nes_Noise& osc ) - { - reflect_env( &state.env, osc ); - REFLECT( state.delay, osc.delay ); - REFLECT( state.length_counter, osc.length_counter ); - REFLECT( state.shift_reg, osc.noise ); - } - - static void reflect_dmc( Nes_Apu::dmc_t& state, Nes_Dmc& osc ) - { - REFLECT( state.delay, osc.delay ); - REFLECT( state.remain, osc.length_counter ); - REFLECT( state.buf, osc.buf ); - REFLECT( state.bits_remain, osc.bits_remain ); - REFLECT( state.bits, osc.bits ); - REFLECT( state.buf_full, osc.buf_full ); - REFLECT( state.silence, osc.silence ); - REFLECT( state.irq_flag, osc.irq_flag ); - if ( mode ) - state.addr = osc.address | 0x8000; - else - osc.address = state.addr & 0x7fff; - } -}; - - -inline void Nes_Apu::save_state( apu_state_t* state ) const -{ - for ( int i = 0; i < osc_count * 4; i++ ) - { - int index = i >> 2; - state->apu.w40xx [i] = oscs [index]->regs [i & 3]; - //if ( index < 4 ) - // state->length_counters [index] = oscs [index]->length_counter; - } - state->apu.w40xx [0x11] = dmc.dac; - - state->apu.w4015 = osc_enables; - state->apu.w4017 = frame_mode; - state->apu.frame_delay = frame_delay; - state->apu.frame_step = frame; - state->apu.irq_flag = irq_flag; - - typedef apu_reflection<1> refl; - Nes_Apu& apu = *(Nes_Apu*) this; // const_cast - refl::reflect_square ( state->square1, apu.square1 ); - refl::reflect_square ( state->square2, apu.square2 ); - refl::reflect_triangle( state->triangle, apu.triangle ); - refl::reflect_noise ( state->noise, apu.noise ); - refl::reflect_dmc ( state->dmc, apu.dmc ); -} - -inline void Nes_Apu::load_state( apu_state_t const& state ) -{ - reset(); - - write_register( 0, 0x4017, state.apu.w4017 ); - write_register( 0, 0x4015, state.apu.w4015 ); - osc_enables = state.apu.w4015; // DMC clears bit 4 - - for ( int i = 0; i < osc_count * 4; i++ ) - { - int n = state.apu.w40xx [i]; - int index = i >> 2; - oscs [index]->regs [i & 3] = n; - write_register( 0, 0x4000 + i, n ); - //if ( index < 4 ) - // oscs [index]->length_counter = state.length_counters [index]; - } - - frame_delay = state.apu.frame_delay; - frame = state.apu.frame_step; - irq_flag = state.apu.irq_flag; - - typedef apu_reflection<0> refl; - apu_state_t& st = (apu_state_t&) state; // const_cast - refl::reflect_square ( st.square1, square1 ); - refl::reflect_square ( st.square2, square2 ); - refl::reflect_triangle( st.triangle, triangle ); - refl::reflect_noise ( st.noise, noise ); - refl::reflect_dmc ( st.dmc, dmc ); - dmc.recalc_irq(); - - //force channels to have correct last_amp levels after load state - square1.run(last_time, last_time); - square2.run(last_time, last_time); - triangle.run(last_time, last_time); - noise.run(last_time, last_time); - dmc.run(last_time, last_time); -} \ No newline at end of file diff --git a/source/quickerNES/apu/apu.hpp b/source/quickerNES/apu/apu.hpp new file mode 100644 index 0000000..104ef1e --- /dev/null +++ b/source/quickerNES/apu/apu.hpp @@ -0,0 +1,363 @@ +#pragma once + +// NES 2A03 APU sound chip emulator +// Snd_Emu 0.1.7 + +#include +#include +#include "oscs.hpp" + +namespace quickerNES +{ + +class Apu +{ + public: + typedef uint8_t env_t[3]; + /*struct env_t { + uint8_t delay; + uint8_t env; + uint8_t written; + };*/ + + struct apu_t + { + uint8_t w40xx[0x14]; // $4000-$4013 + uint8_t w4015; // enables + uint8_t w4017; // mode + uint16_t frame_delay; + uint8_t frame_step; + uint8_t irq_flag; + }; + + struct square_t + { + uint16_t delay; + env_t env; + uint8_t length_counter; + uint8_t phase; + uint8_t swp_delay; + uint8_t swp_reset; + uint8_t unused2[1]; + }; + + struct triangle_t + { + uint16_t delay; + uint8_t length_counter; + uint8_t phase; + uint8_t linear_counter; + uint8_t linear_mode; + }; + + struct noise_t + { + uint16_t delay; + env_t env; + uint8_t length_counter; + uint16_t shift_reg; + }; + + struct dmc_t + { + uint16_t delay; + uint16_t remain; + uint16_t addr; + uint8_t buf; + uint8_t bits_remain; + uint8_t bits; + uint8_t buf_full; + uint8_t silence; + uint8_t irq_flag; + }; + + struct apu_state_t + { + apu_t apu; + square_t square1; + square_t square2; + triangle_t triangle; + noise_t noise; + dmc_t dmc; + }; + static_assert(sizeof(apu_state_t) == 72); + + Apu(); + ~Apu(); + + // Set buffer to generate all sound into, or disable sound if NULL + void output(Blip_Buffer *); + + // Set memory reader callback used by DMC oscillator to fetch samples. + // When callback is invoked, 'user_data' is passed unchanged as the + // first parameter. + void dmc_reader(int (*callback)(void *user_data, nes_addr_t), void *user_data = nullptr); + + // All time values are the number of CPU clock cycles relative to the + // beginning of the current time frame. Before resetting the CPU clock + // count, call end_frame( last_cpu_time ). + + // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) + static const uint16_t start_addr = 0x4000; + static const uint16_t end_addr = 0x4017; + void write_register(nes_time_t, nes_addr_t, int data); + + // Read from status register at 0x4015 + static const uint16_t status_addr = 0x4015; + int read_status(nes_time_t); + + // Run all oscillators up to specified time, end current time frame, then + // start a new time frame at time 0. Time frames have no effect on emulation + // and each can be whatever length is convenient. + void end_frame(nes_time_t); + + // Additional optional features (can be ignored without any problem) + + // Reset internal frame counter, registers, and all oscillators. + // Use PAL timing if pal_timing is true, otherwise use NTSC timing. + // Set the DMC oscillator's initial DAC value to initial_dmc_dac without + // any audible click. + void reset(bool pal_timing = false, int initial_dmc_dac = 0); + + // Save/load exact emulation state + void save_state(apu_state_t *out) const; + void load_state(apu_state_t const &); + + // Set overall volume (default is 1.0) + void volume(double); + + // Set treble equalization (see notes.txt) + void treble_eq(const blip_eq_t &); + + // Set sound output of specific oscillator to buffer. If buffer is NULL, + // the specified oscillator is muted and emulation accuracy is reduced. + // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, + // 2) Triangle, 3) Noise, 4) DMC. + static const uint16_t osc_count = 5; + void osc_output(int index, Blip_Buffer *buffer); + + // Set IRQ time callback that is invoked when the time of earliest IRQ + // may have changed, or NULL to disable. When callback is invoked, + // 'user_data' is passed unchanged as the first parameter. + void irq_notifier(void (*callback)(void *user_data), void *user_data = nullptr); + + // Get time that APU-generated IRQ will occur if no further register reads + // or writes occur. If IRQ is already pending, returns irq_waiting. If no + // IRQ will occur, returns no_irq. + static const uint64_t no_irq = LONG_MAX / 2 + 1; + static const uint16_t irq_waiting = 0; + nes_time_t earliest_irq(nes_time_t) const; + + // Count number of DMC reads that would occur if 'run_until( t )' were executed. + // If last_read is not NULL, set *last_read to the earliest time that + // 'count_dmc_reads( time )' would result in the same result. + int count_dmc_reads(nes_time_t t, nes_time_t *last_read = nullptr) const; + + // Time when next DMC memory read will occur + nes_time_t next_dmc_read_time() const; + + // Run DMC until specified time, so that any DMC memory reads can be + // accounted for (i.e. inserting CPU wait states). + void run_until(nes_time_t); + + // End of public interface. + private: + friend class Nonlinearizer; + void enable_nonlinear(double volume); + static double nonlinear_tnd_gain() { return 0.75; } + + private: + friend struct Dmc; + + // noncopyable + Apu(const Apu &); + Apu &operator=(const Apu &); + + Osc *oscs[osc_count]; + Square square1; + Square square2; + Noise noise; + Triangle triangle; + Dmc dmc; + + nes_time_t last_time; // has been run until this time in current frame + nes_time_t last_dmc_time; + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int osc_enables; + int frame_mode; + bool irq_flag; + void (*irq_notifier_)(void *user_data); + void *irq_data; + Square::Synth square_synth; // shared by squares + + void irq_changed(); + void state_restored(); + void run_until_(nes_time_t); + + // TODO: remove + friend class Core; +}; + +inline void Apu::osc_output(int osc, Blip_Buffer *buf) +{ + oscs[osc]->output = buf; +} + +inline nes_time_t Apu::earliest_irq(nes_time_t) const +{ + return earliest_irq_; +} + +inline void Apu::dmc_reader(int (*func)(void *, nes_addr_t), void *user_data) +{ + dmc.prg_reader_data = user_data; + dmc.prg_reader = func; +} + +inline void Apu::irq_notifier(void (*func)(void *user_data), void *user_data) +{ + irq_notifier_ = func; + irq_data = user_data; +} + +inline int Apu::count_dmc_reads(nes_time_t time, nes_time_t *last_read) const +{ + return dmc.count_reads(time, last_read); +} + +inline nes_time_t Dmc::next_read_time() const +{ + if (length_counter == 0) + return Apu::no_irq; // not reading + + return apu->last_dmc_time + delay + long(bits_remain - 1) * period; +} + +inline nes_time_t Apu::next_dmc_read_time() const { return dmc.next_read_time(); } + +template +struct apu_reflection +{ +#define REFLECT(apu, state) (mode ? void(apu = state) : void(state = apu)) + + static void reflect_env(Apu::env_t *state, Envelope &osc) + { + REFLECT((*state)[0], osc.env_delay); + REFLECT((*state)[1], osc.envelope); + REFLECT((*state)[2], osc.reg_written[3]); + } + + static void reflect_square(Apu::square_t &state, Square &osc) + { + reflect_env(&state.env, osc); + REFLECT(state.delay, osc.delay); + REFLECT(state.length_counter, osc.length_counter); + REFLECT(state.phase, osc.phase); + REFLECT(state.swp_delay, osc.sweep_delay); + REFLECT(state.swp_reset, osc.reg_written[1]); + } + + static void reflect_triangle(Apu::triangle_t &state, Triangle &osc) + { + REFLECT(state.delay, osc.delay); + REFLECT(state.length_counter, osc.length_counter); + REFLECT(state.linear_counter, osc.linear_counter); + REFLECT(state.phase, osc.phase); + REFLECT(state.linear_mode, osc.reg_written[3]); + } + + static void reflect_noise(Apu::noise_t &state, Noise &osc) + { + reflect_env(&state.env, osc); + REFLECT(state.delay, osc.delay); + REFLECT(state.length_counter, osc.length_counter); + REFLECT(state.shift_reg, osc.noise); + } + + static void reflect_dmc(Apu::dmc_t &state, Dmc &osc) + { + REFLECT(state.delay, osc.delay); + REFLECT(state.remain, osc.length_counter); + REFLECT(state.buf, osc.buf); + REFLECT(state.bits_remain, osc.bits_remain); + REFLECT(state.bits, osc.bits); + REFLECT(state.buf_full, osc.buf_full); + REFLECT(state.silence, osc.silence); + REFLECT(state.irq_flag, osc.irq_flag); + if (mode) + state.addr = osc.address | 0x8000; + else + osc.address = state.addr & 0x7fff; + } +}; + +inline void Apu::save_state(apu_state_t *state) const +{ + for (int i = 0; i < osc_count * 4; i++) + { + int index = i >> 2; + state->apu.w40xx[i] = oscs[index]->regs[i & 3]; + // if ( index < 4 ) + // state->length_counters [index] = oscs [index]->length_counter; + } + state->apu.w40xx[0x11] = dmc.dac; + + state->apu.w4015 = osc_enables; + state->apu.w4017 = frame_mode; + state->apu.frame_delay = frame_delay; + state->apu.frame_step = frame; + state->apu.irq_flag = irq_flag; + + typedef apu_reflection<1> refl; + Apu &apu = *(Apu *)this; // const_cast + refl::reflect_square(state->square1, apu.square1); + refl::reflect_square(state->square2, apu.square2); + refl::reflect_triangle(state->triangle, apu.triangle); + refl::reflect_noise(state->noise, apu.noise); + refl::reflect_dmc(state->dmc, apu.dmc); +} + +inline void Apu::load_state(apu_state_t const &state) +{ + reset(); + + write_register(0, 0x4017, state.apu.w4017); + write_register(0, 0x4015, state.apu.w4015); + osc_enables = state.apu.w4015; // DMC clears bit 4 + + for (int i = 0; i < osc_count * 4; i++) + { + int n = state.apu.w40xx[i]; + int index = i >> 2; + oscs[index]->regs[i & 3] = n; + write_register(0, 0x4000 + i, n); + // if ( index < 4 ) + // oscs [index]->length_counter = state.length_counters [index]; + } + + frame_delay = state.apu.frame_delay; + frame = state.apu.frame_step; + irq_flag = state.apu.irq_flag; + + typedef apu_reflection<0> refl; + apu_state_t &st = (apu_state_t &)state; // const_cast + refl::reflect_square(st.square1, square1); + refl::reflect_square(st.square2, square2); + refl::reflect_triangle(st.triangle, triangle); + refl::reflect_noise(st.noise, noise); + refl::reflect_dmc(st.dmc, dmc); + dmc.recalc_irq(); + + // force channels to have correct last_amp levels after load state + square1.run(last_time, last_time); + square2.run(last_time, last_time); + triangle.run(last_time, last_time); + noise.run(last_time, last_time); + dmc.run(last_time, last_time); +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/buffer.cpp b/source/quickerNES/apu/buffer.cpp new file mode 100644 index 0000000..9bd83a2 --- /dev/null +++ b/source/quickerNES/apu/buffer.cpp @@ -0,0 +1,236 @@ +// Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "apu/buffer.hpp" +#include "apu/apu.hpp" + +/* Library Copyright (C) 2003-2006 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +namespace quickerNES +{ + +// Buffer + +Buffer::Buffer() : Multi_Buffer(1) {} + +Buffer::~Buffer() {} + +Multi_Buffer *set_apu(Buffer *buf, Apu *apu) +{ + buf->set_apu(apu); + return buf; +} + +void Buffer::enable_nonlinearity(bool b) +{ + if (b) + clear(); + + Apu *apu = nonlin.enable(b, &tnd); + apu->osc_output(0, &buf); + apu->osc_output(1, &buf); +} + +const char *Buffer::set_sample_rate(long rate, int msec) +{ + enable_nonlinearity(nonlin.enabled); // reapply + buf.set_sample_rate(rate, msec); + tnd.set_sample_rate(rate, msec); + return Multi_Buffer::set_sample_rate(buf.sample_rate(), buf.length()); +} + +void Buffer::clock_rate(long rate) +{ + buf.clock_rate(rate); + tnd.clock_rate(rate); +} + +void Buffer::bass_freq(int freq) +{ + buf.bass_freq(freq); + tnd.bass_freq(freq); +} + +void Buffer::clear() +{ + nonlin.clear(); + buf.clear(); + tnd.clear(); +} + +Buffer::channel_t Buffer::channel(int i) +{ + channel_t c; + c.center = &buf; + if (2 <= i && i <= 4) + c.center = &tnd; // only use for triangle, noise, and dmc + c.left = c.center; + c.right = c.center; + return c; +} + +void Buffer::end_frame(blip_time_t length, bool) +{ + buf.end_frame(length); + tnd.end_frame(length); +} + +long Buffer::samples_avail() const +{ + return buf.samples_avail(); +} + +long Buffer::read_samples(blip_sample_t *out, long count) +{ + count = nonlin.make_nonlinear(tnd, count); + if (count) + { + Blip_Reader lin; + Blip_Reader nonlin; + + int lin_bass = lin.begin(buf); + int nonlin_bass = nonlin.begin(tnd); + + if (out != 0) + { + for (int n = count; n--;) + { + int s = lin.read() + nonlin.read(); + lin.next(lin_bass); + nonlin.next(nonlin_bass); + *out++ = s; + + if ((int16_t)s != s) + out[-1] = 0x7FFF - (s >> 24); + } + } + else + { + // only run accumulators, do not output audio + for (int n = count; n--;) + { + lin.next(lin_bass); + nonlin.next(nonlin_bass); + } + } + + lin.end(buf); + nonlin.end(tnd); + + buf.remove_samples(count); + tnd.remove_samples(count); + } + + return count; +} + +void Buffer::SaveAudioBufferState() +{ + SaveAudioBufferStatePrivate(); + nonlin.SaveAudioBufferState(); + buf.SaveAudioBufferState(); + tnd.SaveAudioBufferState(); +} + +void Buffer::RestoreAudioBufferState() +{ + RestoreAudioBufferStatePrivate(); + nonlin.RestoreAudioBufferState(); + buf.RestoreAudioBufferState(); + tnd.RestoreAudioBufferState(); +} + +// Nonlinearizer + +Nonlinearizer::Nonlinearizer() +{ + apu = nullptr; + enabled = true; + + float const gain = 0x7fff * 1.3f; + // don't use entire range, so any overflow will stay within table + int const range = (int)((double)table_size * Apu::nonlinear_tnd_gain()); + for (int i = 0; i < table_size; i++) + { + int const offset = table_size - range; + int j = i - offset; + float n = 202.0f / (range - 1) * j; + float d = 0; + // Prevent division by zero + if (n) + d = gain * 163.67f / (24329.0f / n + 100.0f); + int out = (int)d; + table[j & (table_size - 1)] = out; + } + extra_accum = 0; + extra_prev = 0; +} + +Apu *Nonlinearizer::enable(bool b, Blip_Buffer *buf) +{ + apu->osc_output(2, buf); + apu->osc_output(3, buf); + apu->osc_output(4, buf); + enabled = b; + if (b) + apu->enable_nonlinear(1.0); + else + apu->volume(1.0); + return apu; +} + +#define ENTRY(s) table[(s) >> (blip_sample_bits - table_bits - 1) & (table_size - 1)] + +long Nonlinearizer::make_nonlinear(Blip_Buffer &buf, long count) +{ + long avail = buf.samples_avail(); + if (count > avail) + count = avail; + if (count && enabled) + { + Blip_Buffer::buf_t_ *p = buf.buffer_; + long accum = this->accum; + long prev = this->prev; + for (unsigned n = count; n; --n) + { + long entry = ENTRY(accum); + accum += *p; + *p++ = (entry - prev) << (blip_sample_bits - 16); + prev = entry; + } + + this->prev = prev; + this->accum = accum; + } + + return count; +} + +void Nonlinearizer::clear() +{ + accum = 0; + prev = ENTRY(86016000); // avoid thump due to APU's triangle dc bias + // TODO: still results in slight clicks and thumps +} + +void Nonlinearizer::SaveAudioBufferState() +{ + extra_accum = accum; + extra_prev = prev; +} + +void Nonlinearizer::RestoreAudioBufferState() +{ + accum = extra_accum; + prev = extra_prev; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/buffer.hpp b/source/quickerNES/apu/buffer.hpp new file mode 100644 index 0000000..d368dd5 --- /dev/null +++ b/source/quickerNES/apu/buffer.hpp @@ -0,0 +1,82 @@ +#pragma once + +// NES non-linear audio buffer +// Emu 0.7.0 + +#include "Multi_Buffer.hpp" +#include + +namespace quickerNES +{ + +class Apu; + +class Nonlinearizer +{ + private: + enum + { + table_bits = 11 + }; + enum + { + table_size = 1 << table_bits + }; + int16_t table[table_size]; + Apu *apu; + long accum; + long prev; + + long extra_accum; + long extra_prev; + + public: + Nonlinearizer(); + bool enabled; + void clear(); + void set_apu(Apu *a) { apu = a; } + Apu *enable(bool, Blip_Buffer *tnd); + long make_nonlinear(Blip_Buffer &buf, long count); + void SaveAudioBufferState(); + void RestoreAudioBufferState(); +}; + +class Buffer : public Multi_Buffer +{ + public: + Buffer(); + ~Buffer(); + + // Setup APU for use with buffer, including setting its output to this buffer. + // If you're using Emu, this is automatically called for you. + void set_apu(Apu *apu) { nonlin.set_apu(apu); } + + // Enable/disable non-linear output + void enable_nonlinearity(bool = true); + + // Blip_Buffer to output other sound chips to + Blip_Buffer *buffer() { return &buf; } + + // See Multi_Buffer.h + const char *set_sample_rate(long rate, int msec = blip_default_length); + + void clock_rate(long); + void bass_freq(int); + void clear(); + channel_t channel(int); + void end_frame(blip_time_t, bool unused = true); + long samples_avail() const; + long read_samples(blip_sample_t *, long); + + private: + Blip_Buffer buf; + Blip_Buffer tnd; + Nonlinearizer nonlin; + friend Multi_Buffer *set_apu(Buffer *, Apu *); + + public: + virtual void SaveAudioBufferState(); + virtual void RestoreAudioBufferState(); +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/effectsBuffer.cpp b/source/quickerNES/apu/effectsBuffer.cpp new file mode 100644 index 0000000..b623de1 --- /dev/null +++ b/source/quickerNES/apu/effectsBuffer.cpp @@ -0,0 +1,518 @@ +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include +#include "effectsBuffer.hpp" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +namespace quickerNES +{ + +typedef long fixed_t; + +#define TO_FIXED(f) fixed_t((f) * (1L << 15) + 0.5) +#define FMUL(x, y) (((x) * (y)) >> 15) + +const unsigned echo_size = 4096; +const unsigned echo_mask = echo_size - 1; +static_assert((echo_size & echo_mask) == 0); // must be power of 2 + +const unsigned reverb_size = 8192 * 2; +const unsigned reverb_mask = reverb_size - 1; +static_assert((reverb_size & reverb_mask) == 0); // must be power of 2 + +Effects_Buffer::config_t::config_t() +{ + pan_1 = -0.15f; + pan_2 = 0.15f; + reverb_delay = 88.0f; + reverb_level = 0.12f; + echo_delay = 61.0f; + echo_level = 0.10f; + delay_variance = 18.0f; + effects_enabled = false; +} + +void Effects_Buffer::set_depth(double d) +{ + float f = (float)d; + config_t c; + c.pan_1 = -0.6f * f; + c.pan_2 = 0.6f * f; + c.reverb_delay = 880 * 0.1f; + c.echo_delay = 610 * 0.1f; + if (f > 0.5) + f = 0.5; // TODO: more linear reduction of extreme reverb/echo + c.reverb_level = 0.5f * f; + c.echo_level = 0.30f * f; + c.delay_variance = 180 * 0.1f; + c.effects_enabled = (d > 0.0f); + config(c); +} + +Effects_Buffer::Effects_Buffer(bool center_only) : Multi_Buffer(2) +{ + buf_count = center_only ? max_buf_count - 4 : max_buf_count; + + echo_buf = NULL; + echo_pos = 0; + + reverb_buf = NULL; + reverb_pos = 0; + + stereo_remain = 0; + effect_remain = 0; + effects_enabled = false; + set_depth(0); +} + +Effects_Buffer::~Effects_Buffer() +{ + delete[] echo_buf; + delete[] reverb_buf; +} + +const char *Effects_Buffer::set_sample_rate(long rate, int msec) +{ + if (!echo_buf) + { + echo_buf = new blip_sample_t[echo_size]; + } + + if (!reverb_buf) + { + reverb_buf = new blip_sample_t[reverb_size]; + } + + for (int i = 0; i < buf_count; i++) bufs[i].set_sample_rate(rate, msec); + + config(config_); + clear(); + + return Multi_Buffer::set_sample_rate(bufs[0].sample_rate(), bufs[0].length()); +} + +void Effects_Buffer::clock_rate(long rate) +{ + for (int i = 0; i < buf_count; i++) + bufs[i].clock_rate(rate); +} + +void Effects_Buffer::bass_freq(int freq) +{ + for (int i = 0; i < buf_count; i++) + bufs[i].bass_freq(freq); +} + +void Effects_Buffer::clear() +{ + stereo_remain = 0; + effect_remain = 0; + if (echo_buf) + memset(echo_buf, 0, echo_size * sizeof *echo_buf); + if (reverb_buf) + memset(reverb_buf, 0, reverb_size * sizeof *reverb_buf); + for (int i = 0; i < buf_count; i++) + bufs[i].clear(); +} + +inline int pin_range(int n, int max, int min = 0) +{ + if (n < min) + return min; + if (n > max) + return max; + return n; +} + +void Effects_Buffer::config(const config_t &cfg) +{ + channels_changed(); + + // clear echo and reverb buffers + if (!config_.effects_enabled && cfg.effects_enabled && echo_buf) + { + memset(echo_buf, 0, echo_size * sizeof(blip_sample_t)); + memset(reverb_buf, 0, reverb_size * sizeof(blip_sample_t)); + } + + config_ = cfg; + + if (config_.effects_enabled) + { + // convert to internal format + + chans.pan_1_levels[0] = TO_FIXED(1) - TO_FIXED(config_.pan_1); + chans.pan_1_levels[1] = TO_FIXED(2) - chans.pan_1_levels[0]; + + chans.pan_2_levels[0] = TO_FIXED(1) - TO_FIXED(config_.pan_2); + chans.pan_2_levels[1] = TO_FIXED(2) - chans.pan_2_levels[0]; + + chans.reverb_level = TO_FIXED(config_.reverb_level); + chans.echo_level = TO_FIXED(config_.echo_level); + + int delay_offset = int(1.0 / 2000 * config_.delay_variance * sample_rate()); + + int reverb_sample_delay = int(1.0 / 1000 * config_.reverb_delay * sample_rate()); + chans.reverb_delay_l = pin_range(reverb_size - + (reverb_sample_delay - delay_offset) * 2, + reverb_size - 2, + 0); + chans.reverb_delay_r = pin_range(reverb_size + 1 - + (reverb_sample_delay + delay_offset) * 2, + reverb_size - 1, + 1); + + int echo_sample_delay = int(1.0 / 1000 * config_.echo_delay * sample_rate()); + chans.echo_delay_l = pin_range(echo_size - 1 - (echo_sample_delay - delay_offset), + echo_size - 1); + chans.echo_delay_r = pin_range(echo_size - 1 - (echo_sample_delay + delay_offset), + echo_size - 1); + + // set up outputs + for (unsigned i = 0; i < chan_count; i++) + { + channel_t &o = channels[i]; + if (i < 2) + { + o.center = &bufs[i]; + o.left = &bufs[3]; + o.right = &bufs[4]; + } + else + { + o.center = &bufs[2]; + o.left = &bufs[5]; + o.right = &bufs[6]; + } + } + } + else + { + // set up outputs + for (unsigned i = 0; i < chan_count; i++) + { + channel_t &o = channels[i]; + o.center = &bufs[0]; + o.left = &bufs[1]; + o.right = &bufs[2]; + } + } + + if (buf_count < max_buf_count) + { + for (unsigned i = 0; i < chan_count; i++) + { + channel_t &o = channels[i]; + o.left = o.center; + o.right = o.center; + } + } +} + +void Effects_Buffer::end_frame(blip_time_t clock_count, bool stereo) +{ + for (int i = 0; i < buf_count; i++) + bufs[i].end_frame(clock_count); + + if (stereo && buf_count == max_buf_count) + stereo_remain = bufs[0].samples_avail() + bufs[0].output_latency(); + + if (effects_enabled || config_.effects_enabled) + effect_remain = bufs[0].samples_avail() + bufs[0].output_latency(); + + effects_enabled = config_.effects_enabled; +} + +long Effects_Buffer::samples_avail() const +{ + return bufs[0].samples_avail() * 2; +} + +long Effects_Buffer::read_samples(blip_sample_t *out, long total_samples) +{ + long remain = bufs[0].samples_avail(); + if (remain > (total_samples >> 1)) + remain = (total_samples >> 1); + total_samples = remain; + while (remain) + { + int active_bufs = buf_count; + long count = remain; + + // optimizing mixing to skip any channels which had nothing added + if (effect_remain) + { + if (count > effect_remain) + count = effect_remain; + + if (stereo_remain) + { + mix_enhanced(out, count); + } + else + { + mix_mono_enhanced(out, count); + active_bufs = 3; + } + } + else if (stereo_remain) + { + mix_stereo(out, count); + active_bufs = 3; + } + else + { + mix_mono(out, count); + active_bufs = 1; + } + + out += count * 2; + remain -= count; + + stereo_remain -= count; + if (stereo_remain < 0) + stereo_remain = 0; + + effect_remain -= count; + if (effect_remain < 0) + effect_remain = 0; + + for (int i = 0; i < buf_count; i++) + { + if (i < active_bufs) + bufs[i].remove_samples(count); + else + bufs[i].remove_silence(count); // keep time synchronized + } + } + + return total_samples * 2; +} + +void Effects_Buffer::mix_mono(blip_sample_t *out, long count) +{ + Blip_Reader c; + int shift = c.begin(bufs[0]); + + // unrolled loop + for (long n = count >> 1; n--;) + { + long cs0 = c.read(); + c.next(shift); + + long cs1 = c.read(); + c.next(shift); + + if ((int16_t)cs0 != cs0) + cs0 = 0x7FFF - (cs0 >> 24); + ((uint32_t *)out)[0] = ((uint16_t)cs0) | (cs0 << 16); + + if ((int16_t)cs1 != cs1) + cs1 = 0x7FFF - (cs1 >> 24); + ((uint32_t *)out)[1] = ((uint16_t)cs1) | (cs1 << 16); + out += 4; + } + + if (count & 1) + { + int s = c.read(); + c.next(shift); + out[0] = s; + out[1] = s; + if ((int16_t)s != s) + { + s = 0x7FFF - (s >> 24); + out[0] = s; + out[1] = s; + } + } + + c.end(bufs[0]); +} + +void Effects_Buffer::mix_stereo(blip_sample_t *out, long count) +{ + Blip_Reader l; + l.begin(bufs[1]); + Blip_Reader r; + r.begin(bufs[2]); + Blip_Reader c; + int shift = c.begin(bufs[0]); + + while (count--) + { + int cs = c.read(); + c.next(shift); + int left = cs + l.read(); + int right = cs + r.read(); + l.next(shift); + r.next(shift); + + if ((int16_t)left != left) + left = 0x7FFF - (left >> 24); + + out[0] = left; + out[1] = right; + + out += 2; + + if ((int16_t)right != right) + out[-1] = 0x7FFF - (right >> 24); + } + + c.end(bufs[0]); + r.end(bufs[2]); + l.end(bufs[1]); +} + +void Effects_Buffer::mix_mono_enhanced(blip_sample_t *out, long count) +{ + Blip_Reader sq1; + sq1.begin(bufs[0]); + Blip_Reader sq2; + sq2.begin(bufs[1]); + Blip_Reader center; + int shift = center.begin(bufs[2]); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while (count--) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next(shift); + sq2.next(shift); + + int new_reverb_l = FMUL(sum1_s, chans.pan_1_levels[0]) + + FMUL(sum2_s, chans.pan_2_levels[0]) + + reverb_buf[(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL(sum1_s, chans.pan_1_levels[1]) + + FMUL(sum2_s, chans.pan_2_levels[1]) + + reverb_buf[(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + fixed_t reverb_level = chans.reverb_level; + reverb_buf[reverb_pos] = FMUL(new_reverb_l, reverb_level); + reverb_buf[reverb_pos + 1] = FMUL(new_reverb_r, reverb_level); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next(shift); + + int left = new_reverb_l + sum3_s + FMUL(chans.echo_level, echo_buf[(echo_pos + chans.echo_delay_l) & echo_mask]); + int right = new_reverb_r + sum3_s + FMUL(chans.echo_level, echo_buf[(echo_pos + chans.echo_delay_r) & echo_mask]); + + echo_buf[echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ((int16_t)left != left) + left = 0x7FFF - (left >> 24); + + out[0] = left; + out[1] = right; + + out += 2; + + if ((int16_t)right != right) + out[-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end(bufs[0]); + sq2.end(bufs[1]); + center.end(bufs[2]); +} + +void Effects_Buffer::mix_enhanced(blip_sample_t *out, long count) +{ + Blip_Reader l1; + l1.begin(bufs[3]); + Blip_Reader r1; + r1.begin(bufs[4]); + Blip_Reader l2; + l2.begin(bufs[5]); + Blip_Reader r2; + r2.begin(bufs[6]); + Blip_Reader sq1; + sq1.begin(bufs[0]); + Blip_Reader sq2; + sq2.begin(bufs[1]); + Blip_Reader center; + int shift = center.begin(bufs[2]); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while (count--) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next(shift); + sq2.next(shift); + + int new_reverb_l = FMUL(sum1_s, chans.pan_1_levels[0]) + + FMUL(sum2_s, chans.pan_2_levels[0]) + l1.read() + + reverb_buf[(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL(sum1_s, chans.pan_1_levels[1]) + + FMUL(sum2_s, chans.pan_2_levels[1]) + r1.read() + + reverb_buf[(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + l1.next(shift); + r1.next(shift); + + fixed_t reverb_level = chans.reverb_level; + reverb_buf[reverb_pos] = FMUL(new_reverb_l, reverb_level); + reverb_buf[reverb_pos + 1] = FMUL(new_reverb_r, reverb_level); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next(shift); + + int left = new_reverb_l + sum3_s + l2.read() + FMUL(chans.echo_level, echo_buf[(echo_pos + chans.echo_delay_l) & echo_mask]); + int right = new_reverb_r + sum3_s + r2.read() + FMUL(chans.echo_level, echo_buf[(echo_pos + chans.echo_delay_r) & echo_mask]); + + l2.next(shift); + r2.next(shift); + + echo_buf[echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ((int16_t)left != left) + left = 0x7FFF - (left >> 24); + + out[0] = left; + out[1] = right; + + out += 2; + + if ((int16_t)right != right) + out[-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end(bufs[0]); + sq2.end(bufs[1]); + center.end(bufs[2]); + l1.end(bufs[3]); + r1.end(bufs[4]); + l2.end(bufs[5]); + r2.end(bufs[6]); +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/effectsBuffer.hpp b/source/quickerNES/apu/effectsBuffer.hpp new file mode 100644 index 0000000..b5ae33c --- /dev/null +++ b/source/quickerNES/apu/effectsBuffer.hpp @@ -0,0 +1,104 @@ +#pragma once + +// Multi-channel effects buffer with panning, echo and reverb +// Game_Music_Emu 0.3.0 + +#include "Multi_Buffer.hpp" +#include + +namespace quickerNES +{ + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Effects_Buffer : public Multi_Buffer +{ + public: + // If center_only is true, only center buffers are created and + // less memory is used. + Effects_Buffer(bool center_only = false); + + // Channel Effect Center Pan + // --------------------------------- + // 0,5 reverb pan_1 + // 1,6 reverb pan_2 + // 2,7 echo - + // 3 echo - + // 4 echo - + + // Channel configuration + struct config_t + { + double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right + double pan_2; + double echo_delay; // msec + double echo_level; // 0.0 to 1.0 + double reverb_delay; // msec + double delay_variance; // difference between left/right delays (msec) + double reverb_level; // 0.0 to 1.0 + bool effects_enabled; // if false, use optimized simple mixer + config_t(); + }; + + // Set configuration of buffer + virtual void config(const config_t &); + void set_depth(double); + + public: + ~Effects_Buffer(); + const char *set_sample_rate(long samples_per_sec, int msec = blip_default_length); + void clock_rate(long); + void bass_freq(int); + void clear(); + channel_t channel(int); + void end_frame(blip_time_t, bool was_stereo = true); + long read_samples(blip_sample_t *, long); + long samples_avail() const; + + private: + typedef long fixed_t; + + enum + { + max_buf_count = 7 + }; + Blip_Buffer bufs[max_buf_count]; + enum + { + chan_count = 5 + }; + channel_t channels[chan_count]; + config_t config_; + long stereo_remain; + long effect_remain; + int buf_count; + bool effects_enabled; + + blip_sample_t *reverb_buf; + blip_sample_t *echo_buf; + int reverb_pos; + int echo_pos; + + struct + { + fixed_t pan_1_levels[2]; + fixed_t pan_2_levels[2]; + int echo_delay_l; + int echo_delay_r; + fixed_t echo_level; + int reverb_delay_l; + int reverb_delay_r; + fixed_t reverb_level; + } chans; + + void mix_mono(blip_sample_t *, long); + void mix_stereo(blip_sample_t *, long); + void mix_enhanced(blip_sample_t *, long); + void mix_mono_enhanced(blip_sample_t *, long); +}; + +inline Effects_Buffer::channel_t Effects_Buffer::channel(int i) +{ + return channels[i % chan_count]; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/fme7/apu.cpp b/source/quickerNES/apu/fme7/apu.cpp index 29a7fd7..ac4faea 100644 --- a/source/quickerNES/apu/fme7/apu.cpp +++ b/source/quickerNES/apu/fme7/apu.cpp @@ -1,7 +1,7 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "apu/fme7/apu.h" +#include "apu/fme7/apu.hpp" #include /* Copyright (C) 2003-2006 Shay Green. This module is free software; you @@ -15,93 +15,94 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -void Nes_Fme7_Apu::reset() +namespace quickerNES { - last_time = 0; - - for ( int i = 0; i < osc_count; i++ ) - oscs [i].last_amp = 0; - - fme7_apu_state_t* state = this; - memset( state, 0, sizeof *state ); + +void Fme7_Apu::reset() +{ + last_time = 0; + + for (int i = 0; i < osc_count; i++) + oscs[i].last_amp = 0; + + fme7_apu_state_t *state = this; + memset(state, 0, sizeof *state); } -unsigned char Nes_Fme7_Apu::amp_table [16] = -{ - #define ENTRY( n ) (unsigned char) (n * +amp_range + 0.5) - ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), - ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), - ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), - ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) - #undef ENTRY +unsigned char Fme7_Apu::amp_table[16] = + { +#define ENTRY(n) (unsigned char)(n * +amp_range + 0.5) + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) +#undef ENTRY }; -void Nes_Fme7_Apu::run_until( blip_time_t end_time ) +void Fme7_Apu::run_until(blip_time_t end_time) { - for ( int index = 0; index < osc_count; index++ ) - { - int mode = regs [7] >> index; - int vol_mode = regs [010 + index]; - int volume = amp_table [vol_mode & 0x0f]; - - if ( !oscs [index].output ) - continue; - - if ( (mode & 001) | (vol_mode & 0x10) ) - volume = 0; // noise and envelope aren't supported - - // period - int const period_factor = 16; - unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor + - regs [index * 2] * period_factor; - if ( period < 50 ) // around 22 kHz - { - volume = 0; - if ( !period ) // on my AY-3-8910A, period doesn't have extra one added - period = period_factor; - } - - // current amplitude - int amp = volume; - if ( !phases [index] ) - amp = 0; - int delta = amp - oscs [index].last_amp; - if ( delta ) - { - oscs [index].last_amp = amp; - synth.offset( last_time, delta, oscs [index].output ); - } - - blip_time_t time = last_time + delays [index]; - if ( time < end_time ) - { - Blip_Buffer* const osc_output = oscs [index].output; - int delta = amp * 2 - volume; - - if ( volume ) - { - do - { - delta = -delta; - synth.offset_inline( time, delta, osc_output ); - time += period; - } - while ( time < end_time ); - - oscs [index].last_amp = (delta + volume) >> 1; - phases [index] = (delta > 0); - } - else - { - // maintain phase when silent - int count = (end_time - time + period - 1) / period; - phases [index] ^= count & 1; - time += (long) count * period; - } - } - - delays [index] = time - end_time; - } - - last_time = end_time; + for (int index = 0; index < osc_count; index++) + { + int mode = regs[7] >> index; + int vol_mode = regs[010 + index]; + int volume = amp_table[vol_mode & 0x0f]; + + if (!oscs[index].output) + continue; + + if ((mode & 001) | (vol_mode & 0x10)) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (regs[index * 2 + 1] & 0x0f) * 0x100 * period_factor + + regs[index * 2] * period_factor; + if (period < 50) // around 22 kHz + { + volume = 0; + if (!period) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if (!phases[index]) + amp = 0; + int delta = amp - oscs[index].last_amp; + if (delta) + { + oscs[index].last_amp = amp; + synth.offset(last_time, delta, oscs[index].output); + } + + blip_time_t time = last_time + delays[index]; + if (time < end_time) + { + Blip_Buffer *const osc_output = oscs[index].output; + int delta = amp * 2 - volume; + + if (volume) + { + do + { + delta = -delta; + synth.offset_inline(time, delta, osc_output); + time += period; + } while (time < end_time); + + oscs[index].last_amp = (delta + volume) >> 1; + phases[index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + phases[index] ^= count & 1; + time += (long)count * period; + } + } + + delays[index] = time - end_time; + } + + last_time = end_time; } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/fme7/apu.h b/source/quickerNES/apu/fme7/apu.h deleted file mode 100644 index 861d780..0000000 --- a/source/quickerNES/apu/fme7/apu.h +++ /dev/null @@ -1,131 +0,0 @@ -#pragma once - -// Sunsoft FME-7 sound emulator -// Nes_Emu 0.7.0 - -#include -#include "apu/Blip_Buffer.h" - -struct fme7_apu_state_t -{ - enum { reg_count = 14 }; - uint8_t regs [reg_count]; - uint8_t phases [3]; // 0 or 1 - uint8_t latch; - uint16_t delays [3]; // a, b, c -}; -static_assert( sizeof (fme7_apu_state_t) == 24 ); - -class Nes_Fme7_Apu : private fme7_apu_state_t { -public: - Nes_Fme7_Apu(); - - // See Nes_Apu.h for reference - void reset(); - void volume( double ); - void treble_eq( blip_eq_t const& ); - void output( Blip_Buffer* ); - enum { osc_count = 3 }; - void osc_output( int index, Blip_Buffer* ); - void end_frame( blip_time_t ); - void save_state( fme7_apu_state_t* ) const; - void load_state( fme7_apu_state_t const& ); - - // Mask and addresses of registers - enum { addr_mask = 0xe000 }; - enum { data_addr = 0xe000 }; - enum { latch_addr = 0xc000 }; - - // (addr & addr_mask) == latch_addr - void write_latch( int ); - - // (addr & addr_mask) == data_addr - void write_data( blip_time_t, int data ); - - // End of public interface -private: - // noncopyable - Nes_Fme7_Apu( const Nes_Fme7_Apu& ); - Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); - - static unsigned char amp_table [16]; - - struct { - Blip_Buffer* output; - int last_amp; - } oscs [osc_count]; - blip_time_t last_time; - - enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff - Blip_Synth synth; - - void run_until( blip_time_t ); -}; - -inline void Nes_Fme7_Apu::volume( double v ) -{ - synth.volume( 0.38 / +amp_range * v ); // to do: fine-tune -} - -inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq ) -{ - synth.treble_eq( eq ); -} - -inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf ) -{ - oscs [i].output = buf; -} - -inline void Nes_Fme7_Apu::output( Blip_Buffer* buf ) -{ - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buf ); -} - -inline Nes_Fme7_Apu::Nes_Fme7_Apu() -{ - output( 0 ); - volume( 1.0 ); - reset(); -} - -inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; } - -inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data ) -{ - if ( (unsigned) latch >= reg_count ) - { - #ifdef dprintf - dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); - #endif - return; - } - - run_until( time ); - regs [latch] = data; -} - -inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) -{ - if ( time > last_time ) - run_until( time ); - - last_time -= time; -} - -inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const -{ - *out = *this; -} - -inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) -{ - reset(); - fme7_apu_state_t* state = this; - *state = in; - - //Run sound channels for 0 cycles for clean audio after loading state - run_until(last_time); -} - diff --git a/source/quickerNES/apu/fme7/apu.hpp b/source/quickerNES/apu/fme7/apu.hpp new file mode 100644 index 0000000..503b1d9 --- /dev/null +++ b/source/quickerNES/apu/fme7/apu.hpp @@ -0,0 +1,155 @@ +#pragma once + +// Sunsoft FME-7 sound emulator +// Emu 0.7.0 + +#include +#include "apu/Blip_Buffer.hpp" + +namespace quickerNES +{ + +struct fme7_apu_state_t +{ + enum + { + reg_count = 14 + }; + uint8_t regs[reg_count]; + uint8_t phases[3]; // 0 or 1 + uint8_t latch; + uint16_t delays[3]; // a, b, c +}; +static_assert(sizeof(fme7_apu_state_t) == 24); + +class Fme7_Apu : private fme7_apu_state_t +{ + public: + Fme7_Apu(); + + // See Apu.h for reference + void reset(); + void volume(double); + void treble_eq(blip_eq_t const &); + void output(Blip_Buffer *); + enum + { + osc_count = 3 + }; + void osc_output(int index, Blip_Buffer *); + void end_frame(blip_time_t); + void save_state(fme7_apu_state_t *) const; + void load_state(fme7_apu_state_t const &); + + // Mask and addresses of registers + enum + { + addr_mask = 0xe000 + }; + enum + { + data_addr = 0xe000 + }; + enum + { + latch_addr = 0xc000 + }; + + // (addr & addr_mask) == latch_addr + void write_latch(int); + + // (addr & addr_mask) == data_addr + void write_data(blip_time_t, int data); + + // End of public interface + private: + // noncopyable + Fme7_Apu(const Fme7_Apu &); + Fme7_Apu &operator=(const Fme7_Apu &); + + static unsigned char amp_table[16]; + + struct + { + Blip_Buffer *output; + int last_amp; + } oscs[osc_count]; + blip_time_t last_time; + + enum + { + amp_range = 192 + }; // can be any value; this gives best error/quality tradeoff + Blip_Synth synth; + + void run_until(blip_time_t); +}; + +inline void Fme7_Apu::volume(double v) +{ + synth.volume(0.38 / +amp_range * v); // to do: fine-tune +} + +inline void Fme7_Apu::treble_eq(blip_eq_t const &eq) +{ + synth.treble_eq(eq); +} + +inline void Fme7_Apu::osc_output(int i, Blip_Buffer *buf) +{ + oscs[i].output = buf; +} + +inline void Fme7_Apu::output(Blip_Buffer *buf) +{ + for (int i = 0; i < osc_count; i++) + osc_output(i, buf); +} + +inline Fme7_Apu::Fme7_Apu() +{ + output(0); + volume(1.0); + reset(); +} + +inline void Fme7_Apu::write_latch(int data) { latch = data; } + +inline void Fme7_Apu::write_data(blip_time_t time, int data) +{ + if ((unsigned)latch >= reg_count) + { +#ifdef dprintf + dprintf("FME7 write to %02X (past end of sound registers)\n", (int)latch); +#endif + return; + } + + run_until(time); + regs[latch] = data; +} + +inline void Fme7_Apu::end_frame(blip_time_t time) +{ + if (time > last_time) + run_until(time); + + last_time -= time; +} + +inline void Fme7_Apu::save_state(fme7_apu_state_t *out) const +{ + *out = *this; +} + +inline void Fme7_Apu::load_state(fme7_apu_state_t const &in) +{ + reset(); + fme7_apu_state_t *state = this; + *state = in; + + // Run sound channels for 0 cycles for clean audio after loading state + run_until(last_time); +} + +} // namespace quickNES diff --git a/source/quickerNES/apu/namco/apu.cpp b/source/quickerNES/apu/namco/apu.cpp index ffec4d0..b6064a4 100644 --- a/source/quickerNES/apu/namco/apu.cpp +++ b/source/quickerNES/apu/namco/apu.cpp @@ -1,8 +1,8 @@ -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ +// Snd_Emu 0.1.7. http://www.slack.net/~ant/ -#include "apu/Blip_Buffer.h" -#include "apu/namco/apu.h" +#include "apu/namco/apu.hpp" +#include "apu/Blip_Buffer.hpp" /* Copyright (C) 2003-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -15,165 +15,169 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -Nes_Namco_Apu::Nes_Namco_Apu() +namespace quickerNES { - output( 0 ); - volume( 1.0 ); - reset(); + +Namco_Apu::Namco_Apu() +{ + output(0); + volume(1.0); + reset(); } -Nes_Namco_Apu::~Nes_Namco_Apu() +Namco_Apu::~Namco_Apu() { } -void Nes_Namco_Apu::reset() +void Namco_Apu::reset() { - last_time = 0; - addr_reg = 0; + last_time = 0; + addr_reg = 0; - int i; - for ( i = 0; i < reg_count; i++ ) - reg [i] = 0; + int i; + for (i = 0; i < reg_count; i++) + reg[i] = 0; - for ( i = 0; i < osc_count; i++ ) - { - Namco_Osc& osc = oscs [i]; - osc.delay = 0; - osc.last_amp = 0; - osc.wave_pos = 0; - } + for (i = 0; i < osc_count; i++) + { + Namco_Osc &osc = oscs[i]; + osc.delay = 0; + osc.last_amp = 0; + osc.wave_pos = 0; + } } -void Nes_Namco_Apu::output( Blip_Buffer* buf ) +void Namco_Apu::output(Blip_Buffer *buf) { - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buf ); + for (int i = 0; i < osc_count; i++) + osc_output(i, buf); } /* -void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) +void Namco_Apu::reflect_state( Tagged_Data& data ) { - reflect_int16( data, 'ADDR', &addr_reg ); + reflect_int16( data, 'ADDR', &addr_reg ); - static const char hex [17] = "0123456789ABCDEF"; - int i; - for ( i = 0; i < reg_count; i++ ) - reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); + static const char hex [17] = "0123456789ABCDEF"; + int i; + for ( i = 0; i < reg_count; i++ ) + reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); - for ( i = 0; i < osc_count; i++ ) - { - reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); - reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); - } + for ( i = 0; i < osc_count; i++ ) + { + reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); + reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); + } } */ -void Nes_Namco_Apu::end_frame( nes_time_t time ) +void Namco_Apu::end_frame(nes_time_t time) { - if ( time > last_time ) - run_until( time ); + if (time > last_time) + run_until(time); - last_time -= time; + last_time -= time; } -void Nes_Namco_Apu::run_until( nes_time_t nes_end_time ) +void Namco_Apu::run_until(nes_time_t nes_end_time) { - int active_oscs = (reg [0x7F] >> 4 & 7) + 1; - for ( int i = osc_count - active_oscs; i < osc_count; i++ ) - { - Namco_Osc& osc = oscs [i]; - Blip_Buffer* output = osc.output; - if ( !output ) - continue; + int active_oscs = (reg[0x7F] >> 4 & 7) + 1; + for (int i = osc_count - active_oscs; i < osc_count; i++) + { + Namco_Osc &osc = oscs[i]; + Blip_Buffer *output = osc.output; + if (!output) + continue; - blip_resampled_time_t time = - output->resampled_time( last_time ) + osc.delay; - blip_resampled_time_t end_time = output->resampled_time( nes_end_time ); - osc.delay = 0; - if ( time < end_time ) - { - const uint8_t* osc_reg = ® [i * 8 + 0x40]; - if ( !(osc_reg [4] & 0xE0) ) - continue; + blip_resampled_time_t time = + output->resampled_time(last_time) + osc.delay; + blip_resampled_time_t end_time = output->resampled_time(nes_end_time); + osc.delay = 0; + if (time < end_time) + { + const uint8_t *osc_reg = ®[i * 8 + 0x40]; + if (!(osc_reg[4] & 0xE0)) + continue; - int volume = osc_reg [7] & 15; - if ( !volume ) - continue; + int volume = osc_reg[7] & 15; + if (!volume) + continue; - long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; - if ( freq < 64 * active_oscs ) - continue; // prevent low frequencies from excessively delaying freq changes - blip_resampled_time_t period = - output->resampled_duration( 983040 ) / freq * active_oscs; + long freq = (osc_reg[4] & 3) * 0x10000 + osc_reg[2] * 0x100L + osc_reg[0]; + if (freq < 64 * active_oscs) + continue; // prevent low frequencies from excessively delaying freq changes + blip_resampled_time_t period = + output->resampled_duration(983040) / freq * active_oscs; - int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; - if ( !wave_size ) - continue; + int wave_size = 32 - (osc_reg[4] >> 2 & 7) * 4; + if (!wave_size) + continue; - int last_amp = osc.last_amp; - int wave_pos = osc.wave_pos; + int last_amp = osc.last_amp; + int wave_pos = osc.wave_pos; - do - { - // read wave sample - int addr = wave_pos + osc_reg [6]; - int sample = reg [addr >> 1] >> (addr << 2 & 4); - wave_pos++; - sample = (sample & 15) * volume; + do + { + // read wave sample + int addr = wave_pos + osc_reg[6]; + int sample = reg[addr >> 1] >> (addr << 2 & 4); + wave_pos++; + sample = (sample & 15) * volume; - // output impulse if amplitude changed - int delta = sample - last_amp; - if ( delta ) - { - last_amp = sample; - synth.offset_resampled( time, delta, output ); - } + // output impulse if amplitude changed + int delta = sample - last_amp; + if (delta) + { + last_amp = sample; + synth.offset_resampled(time, delta, output); + } - // next sample - time += period; - if ( wave_pos >= wave_size ) - wave_pos = 0; - } - while ( time < end_time ); + // next sample + time += period; + if (wave_pos >= wave_size) + wave_pos = 0; + } while (time < end_time); - osc.wave_pos = wave_pos; - osc.last_amp = last_amp; - } - osc.delay = time - end_time; - } + osc.wave_pos = wave_pos; + osc.last_amp = last_amp; + } + osc.delay = time - end_time; + } - last_time = nes_end_time; + last_time = nes_end_time; } -void Nes_Namco_Apu::save_state( namco_state_t* out ) const +void Namco_Apu::save_state(namco_state_t *out) const { - out->addr = addr_reg; - for ( int r = 0; r < reg_count; r++ ) - out->regs [ r ] = reg [ r ]; + out->addr = addr_reg; + for (int r = 0; r < reg_count; r++) + out->regs[r] = reg[r]; - for ( int i = 0; i < osc_count; i++ ) - { - Namco_Osc const& osc = oscs [ i ]; + for (int i = 0; i < osc_count; i++) + { + Namco_Osc const &osc = oscs[i]; - out->delays [ i ] = osc.delay; - out->positions [ i ] = osc.wave_pos; - } + out->delays[i] = osc.delay; + out->positions[i] = osc.wave_pos; + } } -void Nes_Namco_Apu::load_state( namco_state_t const& in ) +void Namco_Apu::load_state(namco_state_t const &in) { - reset(); - addr_reg = in.addr; - for ( int r = 0; r < reg_count; r++ ) - reg [ r ] = in.regs [ r ]; + reset(); + addr_reg = in.addr; + for (int r = 0; r < reg_count; r++) + reg[r] = in.regs[r]; - for ( int i = 0; i < osc_count; i++ ) - { - Namco_Osc& osc = oscs [ i ]; + for (int i = 0; i < osc_count; i++) + { + Namco_Osc &osc = oscs[i]; - osc.delay = in.delays [ i ]; - osc.wave_pos = in.positions [ i ]; - } + osc.delay = in.delays[i]; + osc.wave_pos = in.positions[i]; + } - run_until( last_time ); + run_until(last_time); } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/namco/apu.h b/source/quickerNES/apu/namco/apu.h deleted file mode 100644 index e53eb61..0000000 --- a/source/quickerNES/apu/namco/apu.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -// Namco 106 sound chip emulator -// Nes_Snd_Emu 0.1.7 - -#include -#include "apu/apu.h" - -struct namco_state_t -{ - uint8_t regs [0x80]; - uint8_t addr; - uint8_t unused; - uint8_t positions [8]; - uint32_t delays [8]; -}; -static_assert( sizeof (namco_state_t) == 172 ); - -class Nes_Namco_Apu { -public: - Nes_Namco_Apu(); - ~Nes_Namco_Apu(); - - // See Nes_Apu.h for reference. - void volume( double ); - void treble_eq( const blip_eq_t& ); - void output( Blip_Buffer* ); - enum { osc_count = 8 }; - void osc_output( int index, Blip_Buffer* ); - void reset(); - void end_frame( nes_time_t ); - - // Read/write data register is at 0x4800 - enum { data_reg_addr = 0x4800 }; - void write_data( nes_time_t, int ); - int read_data(); - - // Write-only address register is at 0xF800 - enum { addr_reg_addr = 0xF800 }; - void write_addr( int ); - - // to do: implement save/restore - void save_state( namco_state_t* out ) const; - void load_state( namco_state_t const& ); - -private: - // noncopyable - Nes_Namco_Apu( const Nes_Namco_Apu& ); - Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); - - struct Namco_Osc { - long delay; - Blip_Buffer* output; - short last_amp; - short wave_pos; - }; - - Namco_Osc oscs [osc_count]; - - nes_time_t last_time; - int addr_reg; - - enum { reg_count = 0x80 }; - uint8_t reg [reg_count]; - Blip_Synth synth; - - uint8_t& access(); - void run_until( nes_time_t ); -}; - -inline uint8_t& Nes_Namco_Apu::access() -{ - int addr = addr_reg & 0x7f; - if ( addr_reg & 0x80 ) - addr_reg = (addr + 1) | 0x80; - return reg [addr]; -} - -inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / +osc_count * v ); } - -inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } - -inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; } - -inline int Nes_Namco_Apu::read_data() { return access(); } - -inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf ) -{ - oscs [i].output = buf; -} - -inline void Nes_Namco_Apu::write_data( nes_time_t time, int data ) -{ - run_until( time ); - access() = data; -} - diff --git a/source/quickerNES/apu/namco/apu.hpp b/source/quickerNES/apu/namco/apu.hpp new file mode 100644 index 0000000..f08b7f9 --- /dev/null +++ b/source/quickerNES/apu/namco/apu.hpp @@ -0,0 +1,115 @@ +#pragma once + +// Namco 106 sound chip emulator +// Snd_Emu 0.1.7 + +#include +#include "apu/apu.hpp" + +namespace quickerNES +{ + +struct namco_state_t +{ + uint8_t regs[0x80]; + uint8_t addr; + uint8_t unused; + uint8_t positions[8]; + uint32_t delays[8]; +}; +static_assert(sizeof(namco_state_t) == 172); + +class Namco_Apu +{ + public: + Namco_Apu(); + ~Namco_Apu(); + + // See Apu.h for reference. + void volume(double); + void treble_eq(const blip_eq_t &); + void output(Blip_Buffer *); + enum + { + osc_count = 8 + }; + void osc_output(int index, Blip_Buffer *); + void reset(); + void end_frame(nes_time_t); + + // Read/write data register is at 0x4800 + enum + { + data_reg_addr = 0x4800 + }; + void write_data(nes_time_t, int); + int read_data(); + + // Write-only address register is at 0xF800 + enum + { + addr_reg_addr = 0xF800 + }; + void write_addr(int); + + // to do: implement save/restore + void save_state(namco_state_t *out) const; + void load_state(namco_state_t const &); + + private: + // noncopyable + Namco_Apu(const Namco_Apu &); + Namco_Apu &operator=(const Namco_Apu &); + + struct Namco_Osc + { + long delay; + Blip_Buffer *output; + short last_amp; + short wave_pos; + }; + + Namco_Osc oscs[osc_count]; + + nes_time_t last_time; + int addr_reg; + + enum + { + reg_count = 0x80 + }; + uint8_t reg[reg_count]; + Blip_Synth synth; + + uint8_t &access(); + void run_until(nes_time_t); +}; + +inline uint8_t &Namco_Apu::access() +{ + int addr = addr_reg & 0x7f; + if (addr_reg & 0x80) + addr_reg = (addr + 1) | 0x80; + return reg[addr]; +} + +inline void Namco_Apu::volume(double v) { synth.volume(0.10 / +osc_count * v); } + +inline void Namco_Apu::treble_eq(const blip_eq_t &eq) { synth.treble_eq(eq); } + +inline void Namco_Apu::write_addr(int v) { addr_reg = v; } + +inline int Namco_Apu::read_data() { return access(); } + +inline void Namco_Apu::osc_output(int i, Blip_Buffer *buf) +{ + oscs[i].output = buf; +} + +inline void Namco_Apu::write_data(nes_time_t time, int data) +{ + run_until(time); + access() = data; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/oscs.cpp b/source/quickerNES/apu/oscs.cpp new file mode 100644 index 0000000..f7f9033 --- /dev/null +++ b/source/quickerNES/apu/oscs.cpp @@ -0,0 +1,682 @@ + +// Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "apu.hpp" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +namespace quickerNES +{ + +// Osc + +void Osc::clock_length(int halt_mask) +{ + if (length_counter && !(regs[0] & halt_mask)) + length_counter--; +} + +void Envelope::clock_envelope() +{ + int period = regs[0] & 15; + if (reg_written[3]) + { + reg_written[3] = false; + env_delay = period; + envelope = 15; + } + else if (--env_delay < 0) + { + env_delay = period; + if (envelope | (regs[0] & 0x20)) + envelope = (envelope - 1) & 15; + } +} + +int Envelope::volume() const +{ + return length_counter == 0 ? 0 : (regs[0] & 0x10) ? (regs[0] & 15) + : envelope; +} + +// Square + +void Square::clock_sweep(int negative_adjust) +{ + int sweep = regs[1]; + + if (--sweep_delay < 0) + { + reg_written[1] = true; + + int period = this->period(); + int shift = sweep & shift_mask; + if (shift && (sweep & 0x80) && period >= 8) + { + int offset = period >> shift; + + if (sweep & negate_flag) + offset = negative_adjust - offset; + + if (period + offset < 0x800) + { + period += offset; + // rewrite period + regs[2] = period & 0xff; + regs[3] = (regs[3] & ~7) | ((period >> 8) & 7); + } + } + } + + if (reg_written[1]) + { + reg_written[1] = false; + sweep_delay = (sweep >> 4) & 7; + } +} + +// TODO: clean up +inline nes_time_t Square::maintain_phase(nes_time_t time, nes_time_t end_time, nes_time_t timer_period) +{ + long remain = end_time - time; + if (remain > 0) + { + int count = (remain + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (long)count * timer_period; + } + return time; +} + +void Square::run(nes_time_t time, nes_time_t end_time) +{ + const int period = this->period(); + const int timer_period = (period + 1) * 2; + + if (!output) + { + delay = maintain_phase(time + delay, end_time, timer_period) - end_time; + return; + } + + int offset = period >> (regs[1] & shift_mask); + if (regs[1] & negate_flag) + offset = 0; + + const int volume = this->volume(); + if (volume == 0 || period < 8 || (period + offset) >= 0x800) + { + if (last_amp) + { + synth.offset(time, -last_amp, output); + last_amp = 0; + } + + time += delay; + time = maintain_phase(time, end_time, timer_period); + } + else + { + // handle duty select + int duty_select = (regs[0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if (duty_select == 3) + { + duty = 2; // negated 25% + amp = volume; + } + if (phase < duty) + amp ^= volume; + + int delta = update_amp(amp); + if (delta) + synth.offset(time, delta, output); + + time += delay; + if (time < end_time) + { + Blip_Buffer *const output = this->output; + const Synth &synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (phase_range - 1); + if (phase == 0 || phase == duty) + { + delta = -delta; + synth.offset_inline(time, delta, output); + } + time += timer_period; + } while (time < end_time); + + last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + delay = time - end_time; +} + +// Triangle + +void Triangle::clock_linear_counter() +{ + if (reg_written[3]) + linear_counter = regs[0] & 0x7f; + else if (linear_counter) + linear_counter--; + + if (!(regs[0] & 0x80)) + reg_written[3] = false; +} + +inline int Triangle::calc_amp() const +{ + int amp = phase_range - phase; + if (amp < 0) + amp = phase - (phase_range + 1); + return amp; +} + +// TODO: clean up +inline nes_time_t Triangle::maintain_phase(nes_time_t time, nes_time_t end_time, nes_time_t timer_period) +{ + long remain = end_time - time; + if (remain > 0) + { + int count = (remain + timer_period - 1) / timer_period; + phase = ((unsigned)phase + 1 - count) & (phase_range * 2 - 1); + phase++; + time += (long)count * timer_period; + } + return time; +} + +void Triangle::run(nes_time_t time, nes_time_t end_time) +{ + const int timer_period = period() + 1; + if (!output) + { + time += delay; + delay = 0; + if (length_counter && linear_counter && timer_period >= 3) + delay = maintain_phase(time, end_time, timer_period) - end_time; + return; + } + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + int delta = update_amp(calc_amp()); + if (delta) + synth.offset(time, delta, output); + + time += delay; + if (length_counter == 0 || linear_counter == 0 || timer_period < 3) + { + time = end_time; + } + else if (time < end_time) + { + Blip_Buffer *const output = this->output; + + int phase = this->phase; + int volume = 1; + if (phase > phase_range) + { + phase -= phase_range; + volume = -volume; + } + + do { + if (--phase == 0) + { + phase = phase_range; + volume = -volume; + } + else + { + synth.offset_inline(time, volume, output); + } + + time += timer_period; + } while (time < end_time); + + if (volume < 0) + phase += phase_range; + this->phase = phase; + last_amp = calc_amp(); + } + delay = time - end_time; +} + +// Dmc + +void Dmc::reset() +{ + address = 0; + dac = 0; + buf = 0; + bits_remain = 1; + bits = 0; + buf_full = false; + silence = true; + next_irq = Apu::no_irq; + irq_flag = false; + irq_enabled = false; + + Osc::reset(); + period = 0x1ac; +} + +void Dmc::recalc_irq() +{ + nes_time_t irq = Apu::no_irq; + if (irq_enabled && length_counter) + irq = apu->last_dmc_time + delay + + ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t(period) + 1; + if (irq != next_irq) + { + next_irq = irq; + apu->irq_changed(); + } +} + +int Dmc::count_reads(nes_time_t time, nes_time_t *last_read) const +{ + if (last_read) + *last_read = time; + + if (length_counter == 0) + return 0; // not reading + + long first_read = next_read_time(); + long avail = time - first_read; + if (avail <= 0) + return 0; + + int count = (avail - 1) / (period * 8) + 1; + if (!(regs[0] & loop_flag) && count > length_counter) + count = length_counter; + + if (last_read) + *last_read = first_read + (count - 1) * (period * 8) + 1; + + return count; +} + +static const short dmc_period_table[2][16] = { + {0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC + 0x0be, + 0x0a0, + 0x08e, + 0x080, + 0x06a, + 0x054, + 0x048, + 0x036}, + + {0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) + 0x0b1, + 0x095, + 0x084, + 0x077, + 0x062, + 0x04e, + 0x043, + 0x032} // to do: verify PAL periods +}; + +inline void Dmc::reload_sample() +{ + address = 0x4000 + regs[2] * 0x40; + length_counter = regs[3] * 0x10 + 1; +} + +static const unsigned char dac_table[128] = + { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 15, + 16, + 17, + 18, + 19, + 20, + 20, + 21, + 22, + 23, + 24, + 24, + 25, + 26, + 27, + 27, + 28, + 29, + 30, + 31, + 31, + 32, + 33, + 33, + 34, + 35, + 36, + 36, + 37, + 38, + 38, + 39, + 40, + 41, + 41, + 42, + 43, + 43, + 44, + 45, + 45, + 46, + 47, + 47, + 48, + 48, + 49, + 50, + 50, + 51, + 52, + 52, + 53, + 53, + 54, + 55, + 55, + 56, + 56, + 57, + 58, + 58, + 59, + 59, + 60, + 60, + 61, + 61, + 62, + 63, + 63, + 64, + 64, + 65, + 65, + 66, + 66, + 67, + 67, + 68, + 68, + 69, + 70, + 70, + 71, + 71, + 72, + 72, + 73, + 73, + 74, + 74, + 75, + 75, + 75, + 76, + 76, + 77, + 77, + 78, + 78, + 79, + 79, + 80, + 80, + 81, + 81, + 82, + 82, + 82, + 83, +}; + +void Dmc::write_register(int addr, int data) +{ + if (addr == 0) + { + period = dmc_period_table[pal_mode][data & 15]; + irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled + irq_flag &= irq_enabled; + recalc_irq(); + } + else if (addr == 1) + { + int old_dac = dac; + dac = data & 0x7F; + + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int faked_nonlinear = dac - (dac_table[dac] - dac_table[old_dac]); + if (!nonlinear) + last_amp = faked_nonlinear; + } +} + +void Dmc::start() +{ + reload_sample(); + fill_buffer(); + recalc_irq(); +} + +void Dmc::fill_buffer() +{ + if (!buf_full && length_counter) + { + buf = prg_reader(prg_reader_data, 0x8000u + address); + address = (address + 1) & 0x7FFF; + buf_full = true; + if (--length_counter == 0) + { + if (regs[0] & loop_flag) + { + reload_sample(); + } + else + { + apu->osc_enables &= ~0x10; + irq_flag = irq_enabled; + next_irq = Apu::no_irq; + apu->irq_changed(); + } + } + } +} + +void Dmc::run(nes_time_t time, nes_time_t end_time) +{ + int delta = update_amp(dac); + if (!output) + silence = true; + else if (delta) + synth.offset(time, delta, output); + + time += delay; + if (time < end_time) + { + int bits_remain = this->bits_remain; + if (silence && !buf_full) + { + int count = (end_time - time + period - 1) / period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * period; + } + else + { + Blip_Buffer *const output = this->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if (!silence) + { + int step = (bits & 1) * 4 - 2; + bits >>= 1; + if (unsigned(dac + step) <= 0x7F) + { + dac += step; + synth.offset_inline(time, step, output); + } + } + + time += period; + + if (--bits_remain == 0) + { + bits_remain = 8; + if (!buf_full) + { + silence = true; + } + else + { + silence = false; + bits = buf; + buf_full = false; + if (!output) + silence = true; + fill_buffer(); + } + } + } while (time < end_time); + + this->dac = dac; + this->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + delay = time - end_time; +} + +// Noise + +static const short noise_period_table[16] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4}; + +void Noise::run(nes_time_t time, nes_time_t end_time) +{ + int period = noise_period_table[regs[2] & 15]; +#if NES_APU_NOISE_LOW_CPU + if (period < 8) + { + period = 8; + } +#endif + + if (!output) + { + // TODO: clean up + time += delay; + delay = time + (end_time - time + period - 1) / period * period - end_time; + return; + } + + const int volume = this->volume(); + int amp = (noise & 1) ? volume : 0; + int delta = update_amp(amp); + if (delta) + synth.offset(time, delta, output); + + time += delay; + if (time < end_time) + { + const int mode_flag = 0x80; + + if (!volume) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register + // to do: precise muted noise cycling? + if (!(regs[2] & mode_flag)) + { + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + else + { + Blip_Buffer *const output = this->output; + + // using resampled time avoids conversion in synth.offset() + blip_resampled_time_t rperiod = output->resampled_duration(period); + blip_resampled_time_t rtime = output->resampled_time(time); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (regs[2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ((noise + 1) & 2) + { + // bits 0 and 1 of noise differ + delta = -delta; + synth.offset_resampled(rtime, delta, output); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } while (time < end_time); + + last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + delay = time - end_time; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/oscs.hpp b/source/quickerNES/apu/oscs.hpp new file mode 100644 index 0000000..88e8b24 --- /dev/null +++ b/source/quickerNES/apu/oscs.hpp @@ -0,0 +1,172 @@ + +#pragma once + +// Private oscillators used by Apu +// Snd_Emu 0.1.7 + +#include "Blip_Buffer.hpp" + +namespace quickerNES +{ + +class Apu; + +typedef long nes_time_t; // CPU clock cycle count +typedef unsigned nes_addr_t; // 16-bit memory address + +struct Osc +{ + unsigned char regs[4]; + bool reg_written[4]; + Blip_Buffer *output; + int length_counter; // length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting + + void clock_length(int halt_mask); + int period() const + { + return (regs[3] & 7) * 0x100 + (regs[2] & 0xff); + } + void reset() + { + delay = 0; + last_amp = 0; + } + int update_amp(int amp) + { + int delta = amp - last_amp; + last_amp = amp; + return delta; + } +}; + +struct Envelope : Osc +{ + int envelope; + int env_delay; + + void clock_envelope(); + int volume() const; + void reset() + { + envelope = 0; + env_delay = 0; + Osc::reset(); + } +}; + +// Square +struct Square : Envelope +{ + enum + { + negate_flag = 0x08 + }; + enum + { + shift_mask = 0x07 + }; + enum + { + phase_range = 8 + }; + int phase; + int sweep_delay; + + typedef Blip_Synth Synth; + Synth const &synth; // shared between squares + + Square(Synth const *s) : synth(*s) {} + + void clock_sweep(int adjust); + void run(nes_time_t, nes_time_t); + void reset() + { + sweep_delay = 0; + Envelope::reset(); + } + nes_time_t maintain_phase(nes_time_t time, nes_time_t end_time, nes_time_t timer_period); +}; + +// Triangle +struct Triangle : Osc +{ + enum + { + phase_range = 16 + }; + int phase; + int linear_counter; + Blip_Synth synth; + + int calc_amp() const; + void run(nes_time_t, nes_time_t); + void clock_linear_counter(); + void reset() + { + linear_counter = 0; + phase = 1; + Osc::reset(); + } + nes_time_t maintain_phase(nes_time_t time, nes_time_t end_time, nes_time_t timer_period); +}; + +// Noise +struct Noise : Envelope +{ + int noise; + Blip_Synth synth; + + void run(nes_time_t, nes_time_t); + void reset() + { + noise = 1 << 14; + Envelope::reset(); + } +}; + +// Dmc +struct Dmc : Osc +{ + int address; // address of next byte to read + int period; + // int length_counter; // bytes remaining to play (already defined in Osc) + int buf; + int bits_remain; + int bits; + bool buf_full; + bool silence; + + enum + { + loop_flag = 0x40 + }; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*prg_reader)(void *, nes_addr_t); // needs to be initialized to prg read function + void *prg_reader_data; + + Apu *apu; + + Blip_Synth synth; + + void start(); + void write_register(int, int); + void run(nes_time_t, nes_time_t); + void recalc_irq(); + void fill_buffer(); + void reload_sample(); + void reset(); + int count_reads(nes_time_t, nes_time_t *) const; + nes_time_t next_read_time() const; +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc6/apu.cpp b/source/quickerNES/apu/vrc6/apu.cpp index 2b636f8..8a2473d 100644 --- a/source/quickerNES/apu/vrc6/apu.cpp +++ b/source/quickerNES/apu/vrc6/apu.cpp @@ -1,7 +1,7 @@ -// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ +// Snd_Emu 0.1.7. http://www.slack.net/~ant/ -#include "apu/vrc6/apu.h" +#include "apu/vrc6/apu.hpp" /* Copyright (C) 2003-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -14,199 +14,202 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -Nes_Vrc6_Apu::Nes_Vrc6_Apu() +namespace quickerNES { - output( 0 ); - volume( 1.0 ); - reset(); + +Vrc6_Apu::Vrc6_Apu() +{ + output(0); + volume(1.0); + reset(); } -Nes_Vrc6_Apu::~Nes_Vrc6_Apu() +Vrc6_Apu::~Vrc6_Apu() { } -void Nes_Vrc6_Apu::reset() +void Vrc6_Apu::reset() { - last_time = 0; - for ( int i = 0; i < osc_count; i++ ) - { - Vrc6_Osc& osc = oscs [i]; - for ( int j = 0; j < reg_count; j++ ) - osc.regs [j] = 0; - osc.delay = 0; - osc.last_amp = 0; - osc.phase = 1; - osc.amp = 0; - } + last_time = 0; + for (int i = 0; i < osc_count; i++) + { + Vrc6_Osc &osc = oscs[i]; + for (int j = 0; j < reg_count; j++) + osc.regs[j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } } -void Nes_Vrc6_Apu::output( Blip_Buffer* buf ) +void Vrc6_Apu::output(Blip_Buffer *buf) { - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buf ); + for (int i = 0; i < osc_count; i++) + osc_output(i, buf); } -void Nes_Vrc6_Apu::run_until( nes_time_t time ) +void Vrc6_Apu::run_until(nes_time_t time) { - run_square( oscs [0], time ); - run_square( oscs [1], time ); - run_saw( time ); - last_time = time; + run_square(oscs[0], time); + run_square(oscs[1], time); + run_saw(time); + last_time = time; } -void Nes_Vrc6_Apu::write_osc( nes_time_t time, int osc_index, int reg, int data ) +void Vrc6_Apu::write_osc(nes_time_t time, int osc_index, int reg, int data) { - run_until( time ); - oscs [osc_index].regs [reg] = data; + run_until(time); + oscs[osc_index].regs[reg] = data; } -void Nes_Vrc6_Apu::end_frame( nes_time_t time ) +void Vrc6_Apu::end_frame(nes_time_t time) { - if ( time > last_time ) - run_until( time ); - - last_time -= time; + if (time > last_time) + run_until(time); + + last_time -= time; } -void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const +void Vrc6_Apu::save_state(vrc6_apu_state_t *out) const { - out->saw_amp = oscs [2].amp; - for ( int i = 0; i < osc_count; i++ ) - { - Vrc6_Osc const& osc = oscs [i]; - for ( int r = 0; r < reg_count; r++ ) - out->regs [i] [r] = osc.regs [r]; - - out->delays [i] = osc.delay; - out->phases [i] = osc.phase; - } + out->saw_amp = oscs[2].amp; + for (int i = 0; i < osc_count; i++) + { + Vrc6_Osc const &osc = oscs[i]; + for (int r = 0; r < reg_count; r++) + out->regs[i][r] = osc.regs[r]; + + out->delays[i] = osc.delay; + out->phases[i] = osc.phase; + } } -void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in ) +void Vrc6_Apu::load_state(vrc6_apu_state_t const &in) { - reset(); - oscs [2].amp = in.saw_amp; - for ( int i = 0; i < osc_count; i++ ) - { - Vrc6_Osc& osc = oscs [i]; - for ( int r = 0; r < reg_count; r++ ) - osc.regs [r] = in.regs [i] [r]; - - osc.delay = in.delays [i]; - osc.phase = in.phases [i]; - } - if ( !oscs [2].phase ) - oscs [2].phase = 1; + reset(); + oscs[2].amp = in.saw_amp; + for (int i = 0; i < osc_count; i++) + { + Vrc6_Osc &osc = oscs[i]; + for (int r = 0; r < reg_count; r++) + osc.regs[r] = in.regs[i][r]; - //Run sound channels for 0 cycles for clean audio after loading state - this->run_until(this->last_time); + osc.delay = in.delays[i]; + osc.phase = in.phases[i]; + } + if (!oscs[2].phase) + oscs[2].phase = 1; + + // Run sound channels for 0 cycles for clean audio after loading state + this->run_until(this->last_time); } -void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +void Vrc6_Apu::run_square(Vrc6_Osc &osc, nes_time_t end_time) { - Blip_Buffer* output = osc.output; - if ( !output ) - return; - - int volume = osc.regs [0] & 15; - if ( !(osc.regs [2] & 0x80) ) - volume = 0; - - int gate = osc.regs [0] & 0x80; - int duty = ((osc.regs [0] >> 4) & 7) + 1; - int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; - nes_time_t time = last_time; - if ( delta ) - { - osc.last_amp += delta; - square_synth.offset( time, delta, output ); - } - - time += osc.delay; - osc.delay = 0; - int period = osc.period(); - if ( volume && !gate && period > 4 ) - { - if ( time < end_time ) - { - int phase = osc.phase; - - do - { - phase++; - if ( phase == 16 ) - { - phase = 0; - osc.last_amp = volume; - square_synth.offset( time, volume, output ); - } - if ( phase == duty ) - { - osc.last_amp = 0; - square_synth.offset( time, -volume, output ); - } - time += period; - } - while ( time < end_time ); - - osc.phase = phase; - } - osc.delay = time - end_time; - } + Blip_Buffer *output = osc.output; + if (!output) + return; + + int volume = osc.regs[0] & 15; + if (!(osc.regs[2] & 0x80)) + volume = 0; + + int gate = osc.regs[0] & 0x80; + int duty = ((osc.regs[0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + nes_time_t time = last_time; + if (delta) + { + osc.last_amp += delta; + square_synth.offset(time, delta, output); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if (volume && !gate && period > 4) + { + if (time < end_time) + { + int phase = osc.phase; + + do + { + phase++; + if (phase == 16) + { + phase = 0; + osc.last_amp = volume; + square_synth.offset(time, volume, output); + } + if (phase == duty) + { + osc.last_amp = 0; + square_synth.offset(time, -volume, output); + } + time += period; + } while (time < end_time); + + osc.phase = phase; + } + osc.delay = time - end_time; + } } -void Nes_Vrc6_Apu::run_saw( nes_time_t end_time ) +void Vrc6_Apu::run_saw(nes_time_t end_time) { - Vrc6_Osc& osc = oscs [2]; - Blip_Buffer* output = osc.output; - if ( !output ) - return; - - int amp = osc.amp; - int amp_step = osc.regs [0] & 0x3F; - nes_time_t time = last_time; - int last_amp = osc.last_amp; - if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) - { - osc.delay = 0; - int delta = (amp >> 3) - last_amp; - last_amp = amp >> 3; - saw_synth.offset( time, delta, output ); - } - else - { - time += osc.delay; - if ( time < end_time ) - { - int period = osc.period() * 2; - int phase = osc.phase; - - do - { - if ( --phase == 0 ) - { - phase = 7; - amp = 0; - } - - int delta = (amp >> 3) - last_amp; - if ( delta ) - { - last_amp = amp >> 3; - saw_synth.offset( time, delta, output ); - } - - time += period; - amp = (amp + amp_step) & 0xFF; - } - while ( time < end_time ); - - osc.phase = phase; - osc.amp = amp; - } - - osc.delay = time - end_time; - } - - osc.last_amp = last_amp; + Vrc6_Osc &osc = oscs[2]; + Blip_Buffer *output = osc.output; + if (!output) + return; + + int amp = osc.amp; + int amp_step = osc.regs[0] & 0x3F; + nes_time_t time = last_time; + int last_amp = osc.last_amp; + if (!(osc.regs[2] & 0x80) || !(amp_step | amp)) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset(time, delta, output); + } + else + { + time += osc.delay; + if (time < end_time) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do + { + if (--phase == 0) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if (delta) + { + last_amp = amp >> 3; + saw_synth.offset(time, delta, output); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } while (time < end_time); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc6/apu.h b/source/quickerNES/apu/vrc6/apu.h deleted file mode 100644 index 9883cb2..0000000 --- a/source/quickerNES/apu/vrc6/apu.h +++ /dev/null @@ -1,94 +0,0 @@ - -#pragma once - -// Konami VRC6 sound chip emulator -// Nes_Snd_Emu 0.1.7 - -#include -#include "apu/apu.h" -#include "apu/Blip_Buffer.h" - -struct vrc6_apu_state_t; - -class Nes_Vrc6_Apu { -public: - Nes_Vrc6_Apu(); - ~Nes_Vrc6_Apu(); - - // See Nes_Apu.h for reference - void reset(); - void volume( double ); - void treble_eq( blip_eq_t const& ); - void output( Blip_Buffer* ); - enum { osc_count = 3 }; - void osc_output( int index, Blip_Buffer* ); - void end_frame( nes_time_t ); - void save_state( vrc6_apu_state_t* ) const; - void load_state( vrc6_apu_state_t const& ); - - // Oscillator 0 write-only registers are at $9000-$9002 - // Oscillator 1 write-only registers are at $A000-$A002 - // Oscillator 2 write-only registers are at $B000-$B002 - enum { reg_count = 3 }; - enum { base_addr = 0x9000 }; - enum { addr_step = 0x1000 }; - void write_osc( nes_time_t, int osc, int reg, int data ); - -private: - // noncopyable - Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); - Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& ); - - struct Vrc6_Osc - { - uint8_t regs [3]; - Blip_Buffer* output; - int delay; - int last_amp; - int phase; - int amp; // only used by saw - - int period() const - { - return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; - } - }; - - Vrc6_Osc oscs [osc_count]; - nes_time_t last_time; - - Blip_Synth saw_synth; - Blip_Synth square_synth; - - void run_until( nes_time_t ); - void run_square( Vrc6_Osc& osc, nes_time_t ); - void run_saw( nes_time_t ); -}; - -struct vrc6_apu_state_t -{ - uint8_t regs [3] [3]; - uint8_t saw_amp; - uint16_t delays [3]; - uint8_t phases [3]; - uint8_t unused; -}; -static_assert( sizeof (vrc6_apu_state_t) == 20 ); - -inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) -{ - oscs [i].output = buf; -} - -inline void Nes_Vrc6_Apu::volume( double v ) -{ - double const factor = 0.0967 * 2; - saw_synth.volume( factor / 31 * v ); - square_synth.volume( factor * 0.5 / 15 * v ); -} - -inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) -{ - saw_synth.treble_eq( eq ); - square_synth.treble_eq( eq ); -} diff --git a/source/quickerNES/apu/vrc6/apu.hpp b/source/quickerNES/apu/vrc6/apu.hpp new file mode 100644 index 0000000..6e3dcec --- /dev/null +++ b/source/quickerNES/apu/vrc6/apu.hpp @@ -0,0 +1,112 @@ + +#pragma once + +// Konami VRC6 sound chip emulator +// Snd_Emu 0.1.7 + +#include +#include "apu/Blip_Buffer.hpp" +#include "apu/apu.hpp" + +namespace quickerNES +{ + +struct vrc6_apu_state_t; + +class Vrc6_Apu +{ + public: + Vrc6_Apu(); + ~Vrc6_Apu(); + + // See Apu.h for reference + void reset(); + void volume(double); + void treble_eq(blip_eq_t const &); + void output(Blip_Buffer *); + enum + { + osc_count = 3 + }; + void osc_output(int index, Blip_Buffer *); + void end_frame(nes_time_t); + void save_state(vrc6_apu_state_t *) const; + void load_state(vrc6_apu_state_t const &); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum + { + reg_count = 3 + }; + enum + { + base_addr = 0x9000 + }; + enum + { + addr_step = 0x1000 + }; + void write_osc(nes_time_t, int osc, int reg, int data); + + private: + // noncopyable + Vrc6_Apu(const Vrc6_Apu &); + Vrc6_Apu &operator=(const Vrc6_Apu &); + + struct Vrc6_Osc + { + uint8_t regs[3]; + Blip_Buffer *output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const + { + return (regs[2] & 0x0f) * 0x100L + regs[1] + 1; + } + }; + + Vrc6_Osc oscs[osc_count]; + nes_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until(nes_time_t); + void run_square(Vrc6_Osc &osc, nes_time_t); + void run_saw(nes_time_t); +}; + +struct vrc6_apu_state_t +{ + uint8_t regs[3][3]; + uint8_t saw_amp; + uint16_t delays[3]; + uint8_t phases[3]; + uint8_t unused; +}; +static_assert(sizeof(vrc6_apu_state_t) == 20); + +inline void Vrc6_Apu::osc_output(int i, Blip_Buffer *buf) +{ + oscs[i].output = buf; +} + +inline void Vrc6_Apu::volume(double v) +{ + double const factor = 0.0967 * 2; + saw_synth.volume(factor / 31 * v); + square_synth.volume(factor * 0.5 / 15 * v); +} + +inline void Vrc6_Apu::treble_eq(blip_eq_t const &eq) +{ + saw_synth.treble_eq(eq); + square_synth.treble_eq(eq); +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc7/apu.cpp b/source/quickerNES/apu/vrc7/apu.cpp index a7c1617..da64901 100644 --- a/source/quickerNES/apu/vrc7/apu.cpp +++ b/source/quickerNES/apu/vrc7/apu.cpp @@ -1,204 +1,211 @@ -#include "apu/vrc7/apu.h" -#include "apu/vrc7/emu2413.h" #include +#include "apu/vrc7/apu.hpp" +#include "apu/vrc7/emu2413.hpp" -#define BYTESWAP(xxxx) {uint32_t _temp = (uint32_t)(xxxx);\ -((uint8_t*)&(xxxx))[0] = (uint8_t)((_temp) >> 24);\ -((uint8_t*)&(xxxx))[1] = (uint8_t)((_temp) >> 16);\ -((uint8_t*)&(xxxx))[2] = (uint8_t)((_temp) >> 8);\ -((uint8_t*)&(xxxx))[3] = (uint8_t)((_temp) >> 0);\ -} +namespace quickerNES +{ + +#define BYTESWAP(xxxx) \ + { \ + uint32_t _temp = (uint32_t)(xxxx); \ + ((uint8_t *)&(xxxx))[0] = (uint8_t)((_temp) >> 24); \ + ((uint8_t *)&(xxxx))[1] = (uint8_t)((_temp) >> 16); \ + ((uint8_t *)&(xxxx))[2] = (uint8_t)((_temp) >> 8); \ + ((uint8_t *)&(xxxx))[3] = (uint8_t)((_temp) >> 0); \ + } static bool IsLittleEndian() { - int i = 42; - if (((char*)&i)[0] == 42) - { - return true; - } - return false; + int i = 42; + if (((char *)&i)[0] == 42) + { + return true; + } + return false; } -Nes_Vrc7::Nes_Vrc7() +Vrc7::Vrc7() { - opll = OPLL_new( 3579545 ); - output( NULL ); - volume( 1.0 ); - reset(); + opll = OPLL_new(3579545); + output(NULL); + volume(1.0); + reset(); } -Nes_Vrc7::~Nes_Vrc7() +Vrc7::~Vrc7() { - OPLL_delete( ( OPLL * ) opll ); + OPLL_delete((OPLL *)opll); } -void Nes_Vrc7::reset() +void Vrc7::reset() { - last_time = 0; - count = 0; + last_time = 0; + count = 0; - for ( int i = 0; i < osc_count; ++i ) - { - Vrc7_Osc& osc = oscs [i]; - for ( int j = 0; j < 3; ++j ) - osc.regs [j] = 0; - osc.last_amp = 0; - } + for (int i = 0; i < osc_count; ++i) + { + Vrc7_Osc &osc = oscs[i]; + for (int j = 0; j < 3; ++j) + osc.regs[j] = 0; + osc.last_amp = 0; + } - OPLL_reset( ( OPLL * ) opll ); + OPLL_reset((OPLL *)opll); } -void Nes_Vrc7::volume( double v ) +void Vrc7::volume(double v) { - synth.volume( v * 1. / 3. ); + synth.volume(v * 1. / 3.); } -void Nes_Vrc7::treble_eq( blip_eq_t const& eq ) +void Vrc7::treble_eq(blip_eq_t const &eq) { - synth.treble_eq( eq ); + synth.treble_eq(eq); } -void Nes_Vrc7::output( Blip_Buffer* buf ) +void Vrc7::output(Blip_Buffer *buf) { - for ( int i = 0; i < osc_count; i++ ) - osc_output( i, buf ); + for (int i = 0; i < osc_count; i++) + osc_output(i, buf); } -void Nes_Vrc7::run_until( nes_time_t end_time ) +void Vrc7::run_until(nes_time_t end_time) { - nes_time_t time = last_time; + nes_time_t time = last_time; - while ( time < end_time ) - { - if ( ++count == 36 ) - { - count = 0; - bool run = false; - for ( unsigned i = 0; i < osc_count; ++i ) - { - Vrc7_Osc & osc = oscs [i]; - if ( osc.output ) - { - if ( ! run ) - { - run = true; - OPLL_run( ( OPLL * ) opll ); - } - int amp = OPLL_calcCh( ( OPLL * ) opll, i ); - int delta = amp - osc.last_amp; - if ( delta ) - { - osc.last_amp = amp; - synth.offset( time, delta, osc.output ); - } - } - } - } - ++time; - } + while (time < end_time) + { + if (++count == 36) + { + count = 0; + bool run = false; + for (unsigned i = 0; i < osc_count; ++i) + { + Vrc7_Osc &osc = oscs[i]; + if (osc.output) + { + if (!run) + { + run = true; + OPLL_run((OPLL *)opll); + } + int amp = OPLL_calcCh((OPLL *)opll, i); + int delta = amp - osc.last_amp; + if (delta) + { + osc.last_amp = amp; + synth.offset(time, delta, osc.output); + } + } + } + } + ++time; + } - last_time = end_time; + last_time = end_time; } -void Nes_Vrc7::write_reg( int data ) +void Vrc7::write_reg(int data) { - OPLL_writeIO( ( OPLL * ) opll, 0, data ); + OPLL_writeIO((OPLL *)opll, 0, data); } -void Nes_Vrc7::write_data( nes_time_t time, int data ) +void Vrc7::write_data(nes_time_t time, int data) { - if ( ( unsigned ) ( ( ( OPLL * ) opll )->adr - 0x10 ) < 0x36 ) - { - int type = ( ( OPLL * ) opll )->adr >> 4; - int chan = ( ( OPLL * ) opll )->adr & 15; - - if ( chan < 6 ) oscs [chan].regs [type-1] = data; - } + if ((unsigned)(((OPLL *)opll)->adr - 0x10) < 0x36) + { + int type = ((OPLL *)opll)->adr >> 4; + int chan = ((OPLL *)opll)->adr & 15; - run_until( time ); - OPLL_writeIO( ( OPLL * ) opll, 1, data ); + if (chan < 6) oscs[chan].regs[type - 1] = data; + } + + run_until(time); + OPLL_writeIO((OPLL *)opll, 1, data); } -void Nes_Vrc7::end_frame( nes_time_t time ) +void Vrc7::end_frame(nes_time_t time) { - if ( time > last_time ) - run_until( time ); - last_time -= time; + if (time > last_time) + run_until(time); + last_time -= time; } -void Nes_Vrc7::save_snapshot( vrc7_snapshot_t* out ) +void Vrc7::save_snapshot(vrc7_snapshot_t *out) { - out->latch = ( ( OPLL * ) opll )->adr; - memcpy( out->inst, ( ( OPLL * ) opll )->CustInst, 8 ); - for ( int i = 0; i < osc_count; ++i ) - { - for ( int j = 0; j < 3; ++j ) - { - out->regs [i] [j] = oscs [i].regs [j]; - } - } - out->count = count; - out->internal_opl_state_size = sizeof(OPLL_STATE); - if (!IsLittleEndian()) - { - BYTESWAP(out->internal_opl_state_size); - } - OPLL_serialize((OPLL*)opll, &(out->internal_opl_state)); - OPLL_state_byteswap(&(out->internal_opl_state)); + out->latch = ((OPLL *)opll)->adr; + memcpy(out->inst, ((OPLL *)opll)->CustInst, 8); + for (int i = 0; i < osc_count; ++i) + { + for (int j = 0; j < 3; ++j) + { + out->regs[i][j] = oscs[i].regs[j]; + } + } + out->count = count; + out->internal_opl_state_size = sizeof(OPLL_STATE); + if (!IsLittleEndian()) + { + BYTESWAP(out->internal_opl_state_size); + } + OPLL_serialize((OPLL *)opll, &(out->internal_opl_state)); + OPLL_state_byteswap(&(out->internal_opl_state)); } -void Nes_Vrc7::load_snapshot( vrc7_snapshot_t & in, int dataSize ) +void Vrc7::load_snapshot(vrc7_snapshot_t &in, int dataSize) { - reset(); - write_reg( in.latch ); - int i; - for ( i = 0; i < osc_count; ++i ) - { - for ( int j = 0; j < 3; ++j ) - { - oscs [i].regs [j] = in.regs [i] [j]; - } - } - count = in.count; + reset(); + write_reg(in.latch); + int i; + for (i = 0; i < osc_count; ++i) + { + for (int j = 0; j < 3; ++j) + { + oscs[i].regs[j] = in.regs[i][j]; + } + } + count = in.count; - for ( i = 0; i < 8; ++i ) - { - OPLL_writeReg( ( OPLL * ) opll, i, in.inst [i] ); - } + for (i = 0; i < 8; ++i) + { + OPLL_writeReg((OPLL *)opll, i, in.inst[i]); + } - for ( i = 0; i < 3; ++i ) - { - for ( int j = 0; j < 6; ++j ) - { - OPLL_writeReg( ( OPLL * ) opll, 0x10 + i * 0x10 + j, oscs [j].regs [i] ); - } - } - if (!IsLittleEndian()) - { - BYTESWAP(in.internal_opl_state_size); - } - if (in.internal_opl_state_size == sizeof(OPLL_STATE)) - { - OPLL_state_byteswap(&(in.internal_opl_state)); - OPLL_deserialize((OPLL*)opll, &(in.internal_opl_state)); - } - update_last_amp(); + for (i = 0; i < 3; ++i) + { + for (int j = 0; j < 6; ++j) + { + OPLL_writeReg((OPLL *)opll, 0x10 + i * 0x10 + j, oscs[j].regs[i]); + } + } + if (!IsLittleEndian()) + { + BYTESWAP(in.internal_opl_state_size); + } + if (in.internal_opl_state_size == sizeof(OPLL_STATE)) + { + OPLL_state_byteswap(&(in.internal_opl_state)); + OPLL_deserialize((OPLL *)opll, &(in.internal_opl_state)); + } + update_last_amp(); } -void Nes_Vrc7::update_last_amp() +void Vrc7::update_last_amp() { - for (unsigned i = 0; i < osc_count; ++i) - { - Vrc7_Osc & osc = oscs[i]; - if (osc.output) - { - int amp = OPLL_calcCh((OPLL *)opll, i); - int delta = amp - osc.last_amp; - if (delta) - { - osc.last_amp = amp; - synth.offset(last_time, delta, osc.output); - } - } - } + for (unsigned i = 0; i < osc_count; ++i) + { + Vrc7_Osc &osc = oscs[i]; + if (osc.output) + { + int amp = OPLL_calcCh((OPLL *)opll, i); + int delta = amp - osc.last_amp; + if (delta) + { + osc.last_amp = amp; + synth.offset(last_time, delta, osc.output); + } + } + } } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc7/apu.h b/source/quickerNES/apu/vrc7/apu.h deleted file mode 100644 index 4a33476..0000000 --- a/source/quickerNES/apu/vrc7/apu.h +++ /dev/null @@ -1,71 +0,0 @@ - -#pragma once - -// Konami VRC7 sound chip emulator -// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. - -#include -#include "apu/vrc7/emu2413_state.h" -#include "apu/Blip_Buffer.h" - -struct vrc7_snapshot_t; -typedef long nes_time_t; - -class Nes_Vrc7 { -public: - Nes_Vrc7(); - ~Nes_Vrc7(); - - // See Nes_Apu.h for reference - void reset(); - void volume( double ); - void treble_eq( blip_eq_t const& ); - void output( Blip_Buffer* ); - enum { osc_count = 6 }; - void osc_output( int index, Blip_Buffer* ); - void end_frame( nes_time_t ); - void save_snapshot(vrc7_snapshot_t*); - void load_snapshot(vrc7_snapshot_t &, int dataSize); - void update_last_amp(); - - void write_reg( int reg ); - void write_data( nes_time_t, int data ); - -private: - // noncopyable - Nes_Vrc7( const Nes_Vrc7& ); - Nes_Vrc7& operator = ( const Nes_Vrc7& ); - - struct Vrc7_Osc - { - uint8_t regs [3]; - Blip_Buffer* output; - int last_amp; - }; - - void * opll; - nes_time_t last_time; - - Blip_Synth synth; // DB2LIN_AMP_BITS == 11, * 2 - int count; - Vrc7_Osc oscs [osc_count]; - - void run_until( nes_time_t ); -}; - -struct vrc7_snapshot_t -{ - uint8_t latch; - uint8_t inst [8]; - uint8_t regs [6] [3]; - uint8_t count; - int internal_opl_state_size; - OPLL_STATE internal_opl_state; -}; -static_assert( sizeof (vrc7_snapshot_t) == 28 + 440 + 4 ); - -inline void Nes_Vrc7::osc_output( int i, Blip_Buffer* buf ) -{ - oscs [i].output = buf; -} - diff --git a/source/quickerNES/apu/vrc7/apu.hpp b/source/quickerNES/apu/vrc7/apu.hpp new file mode 100644 index 0000000..c48949f --- /dev/null +++ b/source/quickerNES/apu/vrc7/apu.hpp @@ -0,0 +1,79 @@ + +#pragma once + +// Konami VRC7 sound chip emulator +// Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#include +#include "apu/Blip_Buffer.hpp" +#include "apu/vrc7/emu2413_state.hpp" + +namespace quickerNES +{ + +struct vrc7_snapshot_t; +typedef long nes_time_t; + +class Vrc7 +{ + public: + Vrc7(); + ~Vrc7(); + + // See Apu.h for reference + void reset(); + void volume(double); + void treble_eq(blip_eq_t const &); + void output(Blip_Buffer *); + enum + { + osc_count = 6 + }; + void osc_output(int index, Blip_Buffer *); + void end_frame(nes_time_t); + void save_snapshot(vrc7_snapshot_t *); + void load_snapshot(vrc7_snapshot_t &, int dataSize); + void update_last_amp(); + + void write_reg(int reg); + void write_data(nes_time_t, int data); + + private: + // noncopyable + Vrc7(const Vrc7 &); + Vrc7 &operator=(const Vrc7 &); + + struct Vrc7_Osc + { + uint8_t regs[3]; + Blip_Buffer *output; + int last_amp; + }; + + void *opll; + nes_time_t last_time; + + Blip_Synth synth; // DB2LIN_AMP_BITS == 11, * 2 + int count; + Vrc7_Osc oscs[osc_count]; + + void run_until(nes_time_t); +}; + +struct vrc7_snapshot_t +{ + uint8_t latch; + uint8_t inst[8]; + uint8_t regs[6][3]; + uint8_t count; + int internal_opl_state_size; + OPLL_STATE internal_opl_state; +}; +static_assert(sizeof(vrc7_snapshot_t) == 28 + 440 + 4); + +inline void Vrc7::osc_output(int i, Blip_Buffer *buf) +{ + oscs[i].output = buf; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc7/emu2413.cpp b/source/quickerNES/apu/vrc7/emu2413.cpp index 5faeb17..37aa13e 100644 --- a/source/quickerNES/apu/vrc7/emu2413.cpp +++ b/source/quickerNES/apu/vrc7/emu2413.cpp @@ -19,7 +19,7 @@ if the origin of this software is not misrepresented. : Version 0.32 -- LPF implemented. 2001 01-18 : Version 0.33 -- Fixed the drum problem, refine the mix-down method. -- Fixed the LFO bug. - 2001 01-24 : Version 0.35 -- Fixed the drum problem, + 2001 01-24 : Version 0.35 -- Fixed the drum problem, support undocumented EG behavior. 2001 02-02 : Version 0.38 -- Improved the performance. Fixed the hi-hat and cymbal model. @@ -45,7 +45,7 @@ if the origin of this software is not misrepresented. 2004 01-24 : Modified by xodnizel to remove code not needed for the VRC7, among other things. - References: + References: fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development). fmopl.c(fixed) -- (C) 2002 Jarek Burczynski. s_opl.c -- 2001 written by Mamiya (NEZplug development). @@ -56,37 +56,39 @@ if the origin of this software is not misrepresented. YM2143 data sheet **************************************************************************************/ +#include "emu2413.hpp" +#include #include #include -#include -#include "emu2413.h" + +namespace quickerNES +{ static const unsigned char default_inst[15][8] = { - /* 2019-03-19 VRC7 instrument patchset dumped by Nuke.YKT */ - /* https://wiki.nesdev.com/w/index.php/VRC7_audio */ - { 0x03, 0x21, 0x05, 0x06, 0xE8, 0x81, 0x42, 0x27 }, - { 0x13, 0x41, 0x14, 0x0D, 0xD8, 0xF6, 0x23, 0x12 }, - { 0x11, 0x11, 0x08, 0x08, 0xFA, 0xB2, 0x20, 0x12 }, - { 0x31, 0x61, 0x0C, 0x07, 0xA8, 0x64, 0x61, 0x27 }, - { 0x32, 0x21, 0x1E, 0x06, 0xE1, 0x76, 0x01, 0x28 }, - { 0x02, 0x01, 0x06, 0x00, 0xA3, 0xE2, 0xF4, 0xF4 }, - { 0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x11, 0x07 }, - { 0x23, 0x21, 0x22, 0x17, 0xA2, 0x72, 0x01, 0x17 }, - { 0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01 }, - { 0xB5, 0x01, 0x0F, 0x0F, 0xA8, 0xA5, 0x51, 0x02 }, - { 0x17, 0xC1, 0x24, 0x07, 0xF8, 0xF8, 0x22, 0x12 }, - { 0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16 }, - { 0x01, 0x02, 0xD3, 0x05, 0xC9, 0x95, 0x03, 0x02 }, - { 0x61, 0x63, 0x0C, 0x00, 0x94, 0xC0, 0x33, 0xF6 }, - { 0x21, 0x72, 0x0D, 0x00, 0xC1, 0xD5, 0x56, 0x06 } -}; + /* 2019-03-19 VRC7 instrument patchset dumped by Nuke.YKT */ + /* https://wiki.nesdev.com/w/index.php/VRC7_audio */ + {0x03, 0x21, 0x05, 0x06, 0xE8, 0x81, 0x42, 0x27}, + {0x13, 0x41, 0x14, 0x0D, 0xD8, 0xF6, 0x23, 0x12}, + {0x11, 0x11, 0x08, 0x08, 0xFA, 0xB2, 0x20, 0x12}, + {0x31, 0x61, 0x0C, 0x07, 0xA8, 0x64, 0x61, 0x27}, + {0x32, 0x21, 0x1E, 0x06, 0xE1, 0x76, 0x01, 0x28}, + {0x02, 0x01, 0x06, 0x00, 0xA3, 0xE2, 0xF4, 0xF4}, + {0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x11, 0x07}, + {0x23, 0x21, 0x22, 0x17, 0xA2, 0x72, 0x01, 0x17}, + {0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01}, + {0xB5, 0x01, 0x0F, 0x0F, 0xA8, 0xA5, 0x51, 0x02}, + {0x17, 0xC1, 0x24, 0x07, 0xF8, 0xF8, 0x22, 0x12}, + {0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16}, + {0x01, 0x02, 0xD3, 0x05, 0xC9, 0x95, 0x03, 0x02}, + {0x61, 0x63, 0x0C, 0x00, 0x94, 0xC0, 0x33, 0xF6}, + {0x21, 0x72, 0x0D, 0x00, 0xC1, 0xD5, 0x56, 0x06}}; -#define EG2DB(d) ((d)*(e_int32)(EG_STEP/DB_STEP)) -#define TL2EG(d) ((d)*(e_int32)(TL_STEP/EG_STEP)) -#define SL2EG(d) ((d)*(e_int32)(SL_STEP/EG_STEP)) +#define EG2DB(d) ((d) * (e_int32)(EG_STEP / DB_STEP)) +#define TL2EG(d) ((d) * (e_int32)(TL_STEP / EG_STEP)) +#define SL2EG(d) ((d) * (e_int32)(SL_STEP / EG_STEP)) -#define DB_POS(x) (e_uint32)((x)/DB_STEP) -#define DB_NEG(x) (e_uint32)(DB_MUTE+DB_MUTE+(x)/DB_STEP) +#define DB_POS(x) (e_uint32)((x) / DB_STEP) +#define DB_NEG(x) (e_uint32)(DB_MUTE + DB_MUTE + (x) / DB_STEP) /* Bits for liner value */ #define DB2LIN_AMP_BITS 11 @@ -94,11 +96,11 @@ static const unsigned char default_inst[15][8] = { /* Bits for envelope phase incremental counter */ #define EG_DP_BITS 22 -#define EG_DP_WIDTH (1<>(b)) +#define HIGHBITS(c, b) ((c) >> (b)) /* Leave the lower b bit(s). */ -#define LOWBITS(c,b) ((c)&((1<<(b))-1)) +#define LOWBITS(c, b) ((c) & ((1 << (b)) - 1)) /* Expand x which is s bits to d bits. */ -#define EXPAND_BITS(x,s,d) ((x)<<((d)-(s))) +#define EXPAND_BITS(x, s, d) ((x) << ((d) - (s))) /* Expand x which is s bits to d bits and fill expanded bits '1' */ -#define EXPAND_BITS_X(x,s,d) (((x)<<((d)-(s)))|((1<<((d)-(s)))-1)) +#define EXPAND_BITS_X(x, s, d) (((x) << ((d) - (s))) | ((1 << ((d) - (s))) - 1)) -#define MOD(o,x) (&(o)->slot[(x)<<1]) -#define CAR(o,x) (&(o)->slot[((x)<<1)|1]) +#define MOD(o, x) (&(o)->slot[(x) << 1]) +#define CAR(o, x) (&(o)->slot[((x) << 1) | 1]) -#define BIT(s,b) (((s)>>(b))&1) +#define BIT(s, b) (((s) >> (b)) & 1) /* Definition of envelope mode */ enum -{ SETTLE, ATTACK, DECAY, SUSHOLD, SUSTINE, RELEASE, FINISH }; +{ + SETTLE, + ATTACK, + DECAY, + SUSHOLD, + SUSTINE, + RELEASE, + FINISH +}; /*************************************************** - + Create tables - + ****************************************************/ -INLINE static e_int32 Min (e_int32 i, e_int32 j) +INLINE static e_int32 Min(e_int32 i, e_int32 j) { if (i < j) return i; @@ -144,46 +154,44 @@ INLINE static e_int32 Min (e_int32 i, e_int32 j) } /* Table for AR to LogCurve. */ -static void makeAdjustTable (OPLL * opll) +static void makeAdjustTable(OPLL *opll) { e_int32 i; opll->AR_ADJUST_TABLE[0] = (1 << EG_BITS); for (i = 1; i < 128; i++) - opll->AR_ADJUST_TABLE[i] = (e_uint16) ((double) (1 << EG_BITS) - 1 - (1 << EG_BITS) * log ((double)i) / log (128.)); + opll->AR_ADJUST_TABLE[i] = (e_uint16)((double)(1 << EG_BITS) - 1 - (1 << EG_BITS) * log((double)i) / log(128.)); } - /* Table for dB(0 -- (1<DB2LIN_TABLE[i] = (e_int16) ((double) ((1 << DB2LIN_AMP_BITS) - 1) * powf(10, -(double) i * DB_STEP / 20)); + opll->DB2LIN_TABLE[i] = (e_int16)((double)((1 << DB2LIN_AMP_BITS) - 1) * powf(10, -(double)i * DB_STEP / 20)); if (i >= DB_MUTE) opll->DB2LIN_TABLE[i] = 0; - opll->DB2LIN_TABLE[i + DB_MUTE + DB_MUTE] = (e_int16) (-opll->DB2LIN_TABLE[i]); + opll->DB2LIN_TABLE[i + DB_MUTE + DB_MUTE] = (e_int16)(-opll->DB2LIN_TABLE[i]); } } /* Liner(+0.0 - +1.0) to dB((1<fullsintable[i] = (e_uint32) lin2db (sin (2.0 * PI * i / PG_WIDTH) ); + opll->fullsintable[i] = (e_uint32)lin2db(sin(2.0 * PI * i / PG_WIDTH)); } for (i = 0; i < PG_WIDTH / 4; i++) @@ -193,7 +201,7 @@ static void makeSinTable (OPLL * opll) for (i = 0; i < PG_WIDTH / 2; i++) { - opll->fullsintable[PG_WIDTH / 2 + i] = (e_uint32) (DB_MUTE + DB_MUTE + opll->fullsintable[i]); + opll->fullsintable[PG_WIDTH / 2 + i] = (e_uint32)(DB_MUTE + DB_MUTE + opll->fullsintable[i]); } for (i = 0; i < PG_WIDTH / 2; i++) @@ -203,29 +211,29 @@ static void makeSinTable (OPLL * opll) } /* Table for Pitch Modulator */ -static void makePmTable (OPLL * opll) +static void makePmTable(OPLL *opll) { e_int32 i; for (i = 0; i < PM_PG_WIDTH; i++) - opll->pmtable[i] = (e_int32) ((double) PM_AMP * powf(2, (double) PM_DEPTH * sin (2.0 * PI * i / PM_PG_WIDTH) / 1200)); + opll->pmtable[i] = (e_int32)((double)PM_AMP * powf(2, (double)PM_DEPTH * sin(2.0 * PI * i / PM_PG_WIDTH) / 1200)); } /* Table for Amp Modulator */ -static void makeAmTable (OPLL * opll) +static void makeAmTable(OPLL *opll) { e_int32 i; for (i = 0; i < AM_PG_WIDTH; i++) - opll->amtable[i] = (e_int32) ((double) AM_DEPTH / 2 / DB_STEP * (1.0 + sin (2.0 * PI * i / PM_PG_WIDTH))); + opll->amtable[i] = (e_int32)((double)AM_DEPTH / 2 / DB_STEP * (1.0 + sin(2.0 * PI * i / PM_PG_WIDTH))); } /* Phase increment counter table */ -static void makeDphaseTable (OPLL * opll) +static void makeDphaseTable(OPLL *opll) { e_uint32 fnum, block, ML; e_uint32 mltable[16] = - { 1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2 }; + {1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2}; for (fnum = 0; fnum < 512; fnum++) for (block = 0; block < 8; block++) @@ -233,14 +241,12 @@ static void makeDphaseTable (OPLL * opll) opll->dphaseTable[fnum][block][ML] = (((fnum * mltable[ML]) << block) >> (20 - DP_BITS)); } -static void makeTllTable (OPLL *opll) +static void makeTllTable(OPLL *opll) { #define dB2(x) ((x)*2) static const double kltable[16] = { - dB2 (0.000), dB2 (9.000), dB2 (12.000), dB2 (13.875), dB2 (15.000), dB2 (16.125), dB2 (16.875), dB2 (17.625), - dB2 (18.000), dB2 (18.750), dB2 (19.125), dB2 (19.500), dB2 (19.875), dB2 (20.250), dB2 (20.625), dB2 (21.000) - }; + dB2(0.000), dB2(9.000), dB2(12.000), dB2(13.875), dB2(15.000), dB2(16.125), dB2(16.875), dB2(17.625), dB2(18.000), dB2(18.750), dB2(19.125), dB2(19.500), dB2(19.875), dB2(20.250), dB2(20.625), dB2(21.000)}; e_int32 tmp; e_int32 fnum, block, TL, KL; @@ -252,15 +258,15 @@ static void makeTllTable (OPLL *opll) { if (KL == 0) { - opll->tllTable[fnum][block][TL][KL] = TL2EG (TL); + opll->tllTable[fnum][block][TL][KL] = TL2EG(TL); } else { - tmp = (e_int32) (kltable[fnum] - dB2 (3.000) * (7 - block)); + tmp = (e_int32)(kltable[fnum] - dB2(3.000) * (7 - block)); if (tmp <= 0) - opll->tllTable[fnum][block][TL][KL] = TL2EG (TL); + opll->tllTable[fnum][block][TL][KL] = TL2EG(TL); else - opll->tllTable[fnum][block][TL][KL] = (e_uint32) ((tmp >> (3 - KL)) / EG_STEP) + TL2EG (TL); + opll->tllTable[fnum][block][TL][KL] = (e_uint32)((tmp >> (3 - KL)) / EG_STEP) + TL2EG(TL); } } } @@ -282,8 +288,7 @@ static const double attacktime[16][4] = { {0.84, 0.70, 0.60, 0.54}, {0.50, 0.42, 0.34, 0.30}, {0.28, 0.22, 0.18, 0.14}, - {0.00, 0.00, 0.00, 0.00} -}; + {0.00, 0.00, 0.00, 0.00}}; static const double decaytime[16][4] = { {0, 0, 0, 0}, @@ -301,12 +306,11 @@ static const double decaytime[16][4] = { {10.22, 8.21, 6.84, 5.87}, {5.11, 4.10, 3.42, 2.94}, {2.55, 2.05, 1.71, 1.47}, - {1.27, 1.27, 1.27, 1.27} -}; + {1.27, 1.27, 1.27, 1.27}}; #endif /* Rate Table for Attack */ -static void makeDphaseARTable(OPLL * opll) +static void makeDphaseARTable(OPLL *opll) { e_int32 AR, Rks, RM, RL; #ifdef USE_SPEC_ENV_SPEED @@ -320,8 +324,7 @@ static void makeDphaseARTable(OPLL * opll) else if (RM == 15) attacktable[RM][RL] = EG_DP_WIDTH; else - attacktable[RM][RL] = (e_uint32) ((double) (1 << EG_DP_BITS) / (attacktime[RM][RL] * 3579545 / 72000)); - + attacktable[RM][RL] = (e_uint32)((double)(1 << EG_DP_BITS) / (attacktime[RM][RL] * 3579545 / 72000)); } #endif @@ -338,7 +341,7 @@ static void makeDphaseARTable(OPLL * opll) opll->dphaseARTable[AR][Rks] = 0; break; case 15: - opll->dphaseARTable[AR][Rks] = 0;/*EG_DP_WIDTH;*/ + opll->dphaseARTable[AR][Rks] = 0; /*EG_DP_WIDTH;*/ break; default: #ifdef USE_SPEC_ENV_SPEED @@ -352,7 +355,7 @@ static void makeDphaseARTable(OPLL * opll) } /* Rate Table for Decay and Release */ -static void makeDphaseDRTable (OPLL * opll) +static void makeDphaseDRTable(OPLL *opll) { e_int32 DR, Rks, RM, RL; @@ -364,7 +367,7 @@ static void makeDphaseDRTable (OPLL * opll) if (RM == 0) decaytable[RM][RL] = 0; else - decaytable[RM][RL] = (e_uint32) ((double) (1 << EG_DP_BITS) / (decaytime[RM][RL] * 3579545 / 72000)); + decaytable[RM][RL] = (e_uint32)((double)(1 << EG_DP_BITS) / (decaytime[RM][RL] * 3579545 / 72000)); #endif for (DR = 0; DR < 16; DR++) @@ -390,9 +393,8 @@ static void makeDphaseDRTable (OPLL * opll) } } -static void makeRksTable (OPLL *opll) +static void makeRksTable(OPLL *opll) { - e_int32 fnum8, block, KR; for (fnum8 = 0; fnum8 < 2; fnum8++) @@ -412,9 +414,8 @@ static void makeRksTable (OPLL *opll) ************************************************************/ -INLINE static e_uint32 calc_eg_dphase (OPLL *opll, OPLL_SLOT * slot) +INLINE static e_uint32 calc_eg_dphase(OPLL *opll, OPLL_SLOT *slot) { - switch (slot->eg_mode) { case ATTACK: @@ -451,24 +452,21 @@ INLINE static e_uint32 calc_eg_dphase (OPLL *opll, OPLL_SLOT * slot) *************************************************************/ -#define UPDATE_PG(S) (S)->dphase = opll->dphaseTable[(S)->fnum][(S)->block][(S)->patch.ML] -#define UPDATE_TLL(S)\ -(((S)->type==0)?\ -((S)->tll = opll->tllTable[((S)->fnum)>>5][(S)->block][(S)->patch.TL][(S)->patch.KL]):\ -((S)->tll = opll->tllTable[((S)->fnum)>>5][(S)->block][(S)->volume][(S)->patch.KL])) -#define UPDATE_RKS(S) (S)->rks = opll->rksTable[((S)->fnum)>>8][(S)->block][(S)->patch.KR] -#define UPDATE_WF(S) (S)->sintbl = opll->waveform[(S)->patch.WF] -#define UPDATE_EG(S) (S)->eg_dphase = calc_eg_dphase(opll,S) -#define UPDATE_ALL(S)\ - UPDATE_PG(S);\ - UPDATE_TLL(S);\ - UPDATE_RKS(S);\ - UPDATE_WF(S); \ - UPDATE_EG(S) /* EG should be updated last. */ - +#define UPDATE_PG(S) (S)->dphase = opll->dphaseTable[(S)->fnum][(S)->block][(S)->patch.ML] +#define UPDATE_TLL(S) \ + (((S)->type == 0) ? ((S)->tll = opll->tllTable[((S)->fnum) >> 5][(S)->block][(S)->patch.TL][(S)->patch.KL]) : ((S)->tll = opll->tllTable[((S)->fnum) >> 5][(S)->block][(S)->volume][(S)->patch.KL])) +#define UPDATE_RKS(S) (S)->rks = opll->rksTable[((S)->fnum) >> 8][(S)->block][(S)->patch.KR] +#define UPDATE_WF(S) (S)->sintbl = opll->waveform[(S)->patch.WF] +#define UPDATE_EG(S) (S)->eg_dphase = calc_eg_dphase(opll, S) +#define UPDATE_ALL(S) \ + UPDATE_PG(S); \ + UPDATE_TLL(S); \ + UPDATE_RKS(S); \ + UPDATE_WF(S); \ + UPDATE_EG(S) /* EG should be updated last. */ /* Slot key on */ -INLINE static void slotOn (OPLL_SLOT * slot) +INLINE static void slotOn(OPLL_SLOT *slot) { slot->eg_mode = ATTACK; slot->eg_phase = 0; @@ -476,60 +474,60 @@ INLINE static void slotOn (OPLL_SLOT * slot) } /* Slot key off */ -INLINE static void slotOff (OPLL *opll, OPLL_SLOT * slot) +INLINE static void slotOff(OPLL *opll, OPLL_SLOT *slot) { if (slot->eg_mode == ATTACK) - slot->eg_phase = EXPAND_BITS (opll->AR_ADJUST_TABLE[HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS)], EG_BITS, EG_DP_BITS); + slot->eg_phase = EXPAND_BITS(opll->AR_ADJUST_TABLE[HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS)], EG_BITS, EG_DP_BITS); slot->eg_mode = RELEASE; } /* Channel key on */ -INLINE static void keyOn (OPLL * opll, e_int32 i) +INLINE static void keyOn(OPLL *opll, e_int32 i) { if (!opll->slot_on_flag[i * 2]) - slotOn (MOD(opll,i)); + slotOn(MOD(opll, i)); if (!opll->slot_on_flag[i * 2 + 1]) - slotOn (CAR(opll,i)); + slotOn(CAR(opll, i)); opll->key_status[i] = 1; } /* Channel key off */ -INLINE static void keyOff (OPLL * opll, e_int32 i) +INLINE static void keyOff(OPLL *opll, e_int32 i) { if (opll->slot_on_flag[i * 2 + 1]) - slotOff (opll, CAR(opll,i)); + slotOff(opll, CAR(opll, i)); opll->key_status[i] = 0; } /* Set sustine parameter */ -INLINE static void setSustine (OPLL * opll, e_int32 c, e_int32 sustine) +INLINE static void setSustine(OPLL *opll, e_int32 c, e_int32 sustine) { - CAR(opll,c)->sustine = sustine; - if (MOD(opll,c)->type) - MOD(opll,c)->sustine = sustine; + CAR(opll, c)->sustine = sustine; + if (MOD(opll, c)->type) + MOD(opll, c)->sustine = sustine; } /* Volume : 6bit ( Volume register << 2 ) */ -INLINE static void setVolume (OPLL * opll, e_int32 c, e_int32 volume) +INLINE static void setVolume(OPLL *opll, e_int32 c, e_int32 volume) { - CAR(opll,c)->volume = volume; + CAR(opll, c)->volume = volume; } /* Set F-Number ( fnum : 9bit ) */ -INLINE static void setFnumber (OPLL * opll, e_int32 c, e_int32 fnum) +INLINE static void setFnumber(OPLL *opll, e_int32 c, e_int32 fnum) { - CAR(opll,c)->fnum = fnum; - MOD(opll,c)->fnum = fnum; + CAR(opll, c)->fnum = fnum; + MOD(opll, c)->fnum = fnum; } /* Set Block data (block : 3bit ) */ -INLINE static void setBlock (OPLL * opll, e_int32 c, e_int32 block) +INLINE static void setBlock(OPLL *opll, e_int32 c, e_int32 block) { - CAR(opll,c)->block = block; - MOD(opll,c)->block = block; + CAR(opll, c)->block = block; + MOD(opll, c)->block = block; } -INLINE static void update_key_status (OPLL * opll) +INLINE static void update_key_status(OPLL *opll) { int ch; @@ -543,7 +541,7 @@ INLINE static void update_key_status (OPLL * opll) ***********************************************************/ -static void OPLL_SLOT_reset (OPLL *opll, OPLL_SLOT * slot, int type) +static void OPLL_SLOT_reset(OPLL *opll, OPLL_SLOT *slot, int type) { slot->type = type; slot->sintbl = opll->waveform[0]; @@ -565,56 +563,55 @@ static void OPLL_SLOT_reset (OPLL *opll, OPLL_SLOT * slot, int type) slot->egout = 0; } -static void internal_refresh (OPLL *opll) +static void internal_refresh(OPLL *opll) { - makeDphaseTable (opll); - makeDphaseARTable (opll); - makeDphaseDRTable (opll); - opll->pm_dphase = (e_uint32) (PM_SPEED * PM_DP_WIDTH / (opll->clk / 72)); - opll->am_dphase = (e_uint32) (AM_SPEED * AM_DP_WIDTH / (opll->clk / 72)); + makeDphaseTable(opll); + makeDphaseARTable(opll); + makeDphaseDRTable(opll); + opll->pm_dphase = (e_uint32)(PM_SPEED * PM_DP_WIDTH / (opll->clk / 72)); + opll->am_dphase = (e_uint32)(AM_SPEED * AM_DP_WIDTH / (opll->clk / 72)); } -static void maketables (OPLL *opll, e_uint32 c) +static void maketables(OPLL *opll, e_uint32 c) { - opll->clk = c; - makePmTable (opll); - makeAmTable (opll); - makeDB2LinTable (opll); - makeAdjustTable (opll); - makeTllTable (opll); - makeRksTable (opll); - makeSinTable (opll); - //makeDefaultPatch (); - internal_refresh (opll); + opll->clk = c; + makePmTable(opll); + makeAmTable(opll); + makeDB2LinTable(opll); + makeAdjustTable(opll); + makeTllTable(opll); + makeRksTable(opll); + makeSinTable(opll); + // makeDefaultPatch (); + internal_refresh(opll); } -OPLL *OPLL_new (e_uint32 clk) +OPLL *OPLL_new(e_uint32 clk) { OPLL *opll; - opll = (OPLL *) calloc (sizeof (OPLL), 1); + opll = (OPLL *)calloc(sizeof(OPLL), 1); if (opll == NULL) return NULL; - maketables(opll,clk); + maketables(opll, clk); - opll->waveform[0]=opll->fullsintable; - opll->waveform[1]=opll->halfsintable; + opll->waveform[0] = opll->fullsintable; + opll->waveform[1] = opll->halfsintable; opll->mask = 0; - OPLL_reset (opll); + OPLL_reset(opll); return opll; } - -void OPLL_delete (OPLL * opll) +void OPLL_delete(OPLL *opll) { - free (opll); + free(opll); } /* Reset whole of OPLL except patch datas. */ -void OPLL_reset (OPLL * opll) +void OPLL_reset(OPLL *opll) { e_int32 i; @@ -630,20 +627,20 @@ void OPLL_reset (OPLL * opll) opll->mask = 0; for (i = 0; i < 12; i++) - OPLL_SLOT_reset(opll, &opll->slot[i], i%2); + OPLL_SLOT_reset(opll, &opll->slot[i], i % 2); for (i = 0; i < 6; i++) { opll->key_status[i] = 0; - //setPatch (opll, i, 0); + // setPatch (opll, i, 0); } for (i = 0; i < 0x40; i++) - OPLL_writeReg (opll, i, 0); + OPLL_writeReg(opll, i, 0); } /* Force Refresh (When external program changes some parameters). */ -void OPLL_forceRefresh (OPLL * opll) +void OPLL_forceRefresh(OPLL *opll) { e_int32 i; @@ -652,11 +649,11 @@ void OPLL_forceRefresh (OPLL * opll) for (i = 0; i < 12; i++) { - UPDATE_PG (&opll->slot[i]); - UPDATE_RKS (&opll->slot[i]); - UPDATE_TLL (&opll->slot[i]); - UPDATE_WF (&opll->slot[i]); - UPDATE_EG (&opll->slot[i]); + UPDATE_PG(&opll->slot[i]); + UPDATE_RKS(&opll->slot[i]); + UPDATE_TLL(&opll->slot[i]); + UPDATE_WF(&opll->slot[i]); + UPDATE_EG(&opll->slot[i]); } } @@ -666,44 +663,42 @@ void OPLL_forceRefresh (OPLL * opll) *********************************************************/ /* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 2PI). */ -#if ( SLOT_AMP_BITS - PG_BITS ) > 0 -#define wave2_2pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS )) +#if (SLOT_AMP_BITS - PG_BITS) > 0 + #define wave2_2pi(e) ((e) >> (SLOT_AMP_BITS - PG_BITS)) #else -#define wave2_2pi(e) ( (e) << ( PG_BITS - SLOT_AMP_BITS )) + #define wave2_2pi(e) ((e) << (PG_BITS - SLOT_AMP_BITS)) #endif /* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 4PI). */ -#if ( SLOT_AMP_BITS - PG_BITS - 1 ) == 0 -#define wave2_4pi(e) (e) -#elif ( SLOT_AMP_BITS - PG_BITS - 1 ) > 0 -#define wave2_4pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS - 1 )) +#if (SLOT_AMP_BITS - PG_BITS - 1) == 0 + #define wave2_4pi(e) (e) +#elif (SLOT_AMP_BITS - PG_BITS - 1) > 0 + #define wave2_4pi(e) ((e) >> (SLOT_AMP_BITS - PG_BITS - 1)) #else -#define wave2_4pi(e) ( (e) << ( 1 + PG_BITS - SLOT_AMP_BITS )) + #define wave2_4pi(e) ((e) << (1 + PG_BITS - SLOT_AMP_BITS)) #endif /* Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). */ -#if ( SLOT_AMP_BITS - PG_BITS - 2 ) == 0 -#define wave2_8pi(e) (e) -#elif ( SLOT_AMP_BITS - PG_BITS - 2 ) > 0 -#define wave2_8pi(e) ( (e) >> ( SLOT_AMP_BITS - PG_BITS - 2 )) +#if (SLOT_AMP_BITS - PG_BITS - 2) == 0 + #define wave2_8pi(e) (e) +#elif (SLOT_AMP_BITS - PG_BITS - 2) > 0 + #define wave2_8pi(e) ((e) >> (SLOT_AMP_BITS - PG_BITS - 2)) #else -#define wave2_8pi(e) ( (e) << ( 2 + PG_BITS - SLOT_AMP_BITS )) + #define wave2_8pi(e) ((e) << (2 + PG_BITS - SLOT_AMP_BITS)) #endif - - /* Update AM, PM unit */ -static void update_ampm (OPLL * opll) +static void update_ampm(OPLL *opll) { opll->pm_phase = (opll->pm_phase + opll->pm_dphase) & (PM_DP_WIDTH - 1); opll->am_phase = (opll->am_phase + opll->am_dphase) & (AM_DP_WIDTH - 1); - opll->lfo_am = opll->amtable[HIGHBITS (opll->am_phase, AM_DP_BITS - AM_PG_BITS)]; - opll->lfo_pm = opll->pmtable[HIGHBITS (opll->pm_phase, PM_DP_BITS - PM_PG_BITS)]; + opll->lfo_am = opll->amtable[HIGHBITS(opll->am_phase, AM_DP_BITS - AM_PG_BITS)]; + opll->lfo_pm = opll->pmtable[HIGHBITS(opll->pm_phase, PM_DP_BITS - PM_PG_BITS)]; } /* PG */ INLINE static void -calc_phase (OPLL_SLOT * slot, e_int32 lfo) +calc_phase(OPLL_SLOT *slot, e_int32 lfo) { if (slot->patch.PM) slot->phase += (slot->dphase * lfo) >> PM_AMP_BITS; @@ -712,38 +707,35 @@ calc_phase (OPLL_SLOT * slot, e_int32 lfo) slot->phase &= (DP_WIDTH - 1); - slot->pgout = HIGHBITS (slot->phase, DP_BASE_BITS); + slot->pgout = HIGHBITS(slot->phase, DP_BASE_BITS); } /* EG */ -static void calc_envelope(OPLL *opll, OPLL_SLOT * slot, e_int32 lfo) +static void calc_envelope(OPLL *opll, OPLL_SLOT *slot, e_int32 lfo) { -#define S2E(x) (SL2EG((e_int32)(x/SL_STEP))<<(EG_DP_BITS-EG_BITS)) +#define S2E(x) (SL2EG((e_int32)(x / SL_STEP)) << (EG_DP_BITS - EG_BITS)) static const e_uint32 SL[16] = { - S2E (0.0), S2E (3.0), S2E (6.0), S2E (9.0), S2E (12.0), S2E (15.0), S2E (18.0), S2E (21.0), - S2E (24.0), S2E (27.0), S2E (30.0), S2E (33.0), S2E (36.0), S2E (39.0), S2E (42.0), S2E (48.0) - }; + S2E(0.0), S2E(3.0), S2E(6.0), S2E(9.0), S2E(12.0), S2E(15.0), S2E(18.0), S2E(21.0), S2E(24.0), S2E(27.0), S2E(30.0), S2E(33.0), S2E(36.0), S2E(39.0), S2E(42.0), S2E(48.0)}; e_uint32 egout; switch (slot->eg_mode) { - case ATTACK: - egout = opll->AR_ADJUST_TABLE[HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS)]; + egout = opll->AR_ADJUST_TABLE[HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS)]; slot->eg_phase += slot->eg_dphase; - if((EG_DP_WIDTH & slot->eg_phase)||(slot->patch.AR==15)) + if ((EG_DP_WIDTH & slot->eg_phase) || (slot->patch.AR == 15)) { egout = 0; slot->eg_phase = 0; slot->eg_mode = DECAY; - UPDATE_EG (slot); + UPDATE_EG(slot); } break; case DECAY: - egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); slot->eg_phase += slot->eg_dphase; if (slot->eg_phase >= SL[slot->patch.SL]) { @@ -751,29 +743,29 @@ static void calc_envelope(OPLL *opll, OPLL_SLOT * slot, e_int32 lfo) { slot->eg_phase = SL[slot->patch.SL]; slot->eg_mode = SUSHOLD; - UPDATE_EG (slot); + UPDATE_EG(slot); } else { slot->eg_phase = SL[slot->patch.SL]; slot->eg_mode = SUSTINE; - UPDATE_EG (slot); + UPDATE_EG(slot); } } break; case SUSHOLD: - egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); if (slot->patch.EG == 0) { slot->eg_mode = SUSTINE; - UPDATE_EG (slot); + UPDATE_EG(slot); } break; case SUSTINE: case RELEASE: - egout = HIGHBITS (slot->eg_phase, EG_DP_BITS - EG_BITS); + egout = HIGHBITS(slot->eg_phase, EG_DP_BITS - EG_BITS); slot->eg_phase += slot->eg_dphase; if (egout >= (1 << EG_BITS)) { @@ -792,18 +784,18 @@ static void calc_envelope(OPLL *opll, OPLL_SLOT * slot, e_int32 lfo) } if (slot->patch.AM) - egout = EG2DB (egout + slot->tll) + lfo; + egout = EG2DB(egout + slot->tll) + lfo; else - egout = EG2DB (egout + slot->tll); + egout = EG2DB(egout + slot->tll); if (egout >= DB_MUTE) egout = DB_MUTE - 1; - + slot->egout = egout; } /* CARRIOR */ -INLINE static e_int32 calc_slot_car(OPLL *opll, OPLL_SLOT * slot, e_int32 fm) +INLINE static e_int32 calc_slot_car(OPLL *opll, OPLL_SLOT *slot, e_int32 fm) { slot->output[1] = slot->output[0]; @@ -813,14 +805,14 @@ INLINE static e_int32 calc_slot_car(OPLL *opll, OPLL_SLOT * slot, e_int32 fm) } else { - slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[(slot->pgout+wave2_8pi(fm))&(PG_WIDTH-1)] + slot->egout]; + slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[(slot->pgout + wave2_8pi(fm)) & (PG_WIDTH - 1)] + slot->egout]; } return (slot->output[1] + slot->output[0]) >> 1; } /* MODULATOR */ -INLINE static e_int32 calc_slot_mod(OPLL *opll, OPLL_SLOT * slot) +INLINE static e_int32 calc_slot_mod(OPLL *opll, OPLL_SLOT *slot) { e_int32 fm; @@ -832,8 +824,8 @@ INLINE static e_int32 calc_slot_mod(OPLL *opll, OPLL_SLOT * slot) } else if (slot->patch.FB != 0) { - fm = wave2_4pi (slot->feedback) >> (7 - slot->patch.FB); - slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[(slot->pgout + fm)&(PG_WIDTH-1)] + slot->egout]; + fm = wave2_4pi(slot->feedback) >> (7 - slot->patch.FB); + slot->output[0] = opll->DB2LIN_TABLE[slot->sintbl[(slot->pgout + fm) & (PG_WIDTH - 1)] + slot->egout]; } else { @@ -843,68 +835,67 @@ INLINE static e_int32 calc_slot_mod(OPLL *opll, OPLL_SLOT * slot) slot->feedback = (slot->output[1] + slot->output[0]) >> 1; return slot->feedback; - } -static INLINE e_int16 calc (OPLL * opll) +static INLINE e_int16 calc(OPLL *opll) { e_int32 inst = 0, out = 0; e_int32 i; - update_ampm (opll); + update_ampm(opll); for (i = 0; i < 12; i++) { - calc_phase(&opll->slot[i],opll->lfo_pm); - calc_envelope(opll, &opll->slot[i],opll->lfo_am); + calc_phase(&opll->slot[i], opll->lfo_pm); + calc_envelope(opll, &opll->slot[i], opll->lfo_am); } for (i = 0; i < 6; i++) - if (!(opll->mask & OPLL_MASK_CH (i)) && (CAR(opll,i)->eg_mode != FINISH)) - inst += calc_slot_car (opll, CAR(opll,i), calc_slot_mod(opll,MOD(opll,i))); + if (!(opll->mask & OPLL_MASK_CH(i)) && (CAR(opll, i)->eg_mode != FINISH)) + inst += calc_slot_car(opll, CAR(opll, i), calc_slot_mod(opll, MOD(opll, i))); out = inst; - return (e_int16) out; + return (e_int16)out; } -static INLINE void run (OPLL * opll) +static INLINE void run(OPLL *opll) { e_int32 i; - update_ampm (opll); + update_ampm(opll); for (i = 0; i < 12; i++) { - calc_phase(&opll->slot[i],opll->lfo_pm); - calc_envelope(opll, &opll->slot[i],opll->lfo_am); + calc_phase(&opll->slot[i], opll->lfo_pm); + calc_envelope(opll, &opll->slot[i], opll->lfo_am); } } -static INLINE e_int16 calc_ch (OPLL * opll, int ch) +static INLINE e_int16 calc_ch(OPLL *opll, int ch) { - if ( /*!(opll->mask & OPLL_MASK_CH (i)) &&*/ (CAR(opll,ch)->eg_mode != FINISH)) - return calc_slot_car (opll, CAR(opll,ch), calc_slot_mod(opll,MOD(opll,ch))); + if (/*!(opll->mask & OPLL_MASK_CH (i)) &&*/ (CAR(opll, ch)->eg_mode != FINISH)) + return calc_slot_car(opll, CAR(opll, ch), calc_slot_mod(opll, MOD(opll, ch))); else return 0; } -e_int16 OPLL_calc (OPLL * opll) +e_int16 OPLL_calc(OPLL *opll) { - return calc (opll); + return calc(opll); } -void OPLL_run (OPLL * opll) +void OPLL_run(OPLL *opll) { - run (opll); + run(opll); } -e_uint32 OPLL_calcCh (OPLL * opll, e_uint32 ch) +e_uint32 OPLL_calcCh(OPLL *opll, e_uint32 ch) { - return calc_ch (opll, ch); + return calc_ch(opll, ch); } e_uint32 -OPLL_setMask (OPLL * opll, e_uint32 mask) +OPLL_setMask(OPLL *opll, e_uint32 mask) { e_uint32 ret; @@ -919,7 +910,7 @@ OPLL_setMask (OPLL * opll, e_uint32 mask) } e_uint32 -OPLL_toggleMask (OPLL * opll, e_uint32 mask) +OPLL_toggleMask(OPLL *opll, e_uint32 mask) { e_uint32 ret; @@ -939,60 +930,58 @@ OPLL_toggleMask (OPLL * opll, e_uint32 mask) *****************************************************/ -static void setInstrument(OPLL * opll, e_uint i, e_uint inst) +static void setInstrument(OPLL *opll, e_uint i, e_uint inst) { - const e_uint8 *src; - OPLL_PATCH *modp, *carp; + const e_uint8 *src; + OPLL_PATCH *modp, *carp; - opll->patch_number[i]=inst; + opll->patch_number[i] = inst; - if(inst) - src=default_inst[inst-1]; - else - src=opll->CustInst; + if (inst) + src = default_inst[inst - 1]; + else + src = opll->CustInst; - modp=&MOD(opll,i)->patch; - carp=&CAR(opll,i)->patch; + modp = &MOD(opll, i)->patch; + carp = &CAR(opll, i)->patch; - modp->AM=(src[0]>>7)&1; - modp->PM=(src[0]>>6)&1; - modp->EG=(src[0]>>5)&1; - modp->KR=(src[0]>>4)&1; - modp->ML=(src[0]&0xF); + modp->AM = (src[0] >> 7) & 1; + modp->PM = (src[0] >> 6) & 1; + modp->EG = (src[0] >> 5) & 1; + modp->KR = (src[0] >> 4) & 1; + modp->ML = (src[0] & 0xF); - carp->AM=(src[1]>>7)&1; - carp->PM=(src[1]>>6)&1; - carp->EG=(src[1]>>5)&1; - carp->KR=(src[1]>>4)&1; - carp->ML=(src[1]&0xF); + carp->AM = (src[1] >> 7) & 1; + carp->PM = (src[1] >> 6) & 1; + carp->EG = (src[1] >> 5) & 1; + carp->KR = (src[1] >> 4) & 1; + carp->ML = (src[1] & 0xF); - modp->KL=(src[2]>>6)&3; - modp->TL=(src[2]&0x3F); + modp->KL = (src[2] >> 6) & 3; + modp->TL = (src[2] & 0x3F); - carp->KL = (src[3] >> 6) & 3; - carp->WF = (src[3] >> 4) & 1; + carp->KL = (src[3] >> 6) & 3; + carp->WF = (src[3] >> 4) & 1; - modp->WF = (src[3] >> 3) & 1; + modp->WF = (src[3] >> 3) & 1; - modp->FB = (src[3]) & 7; + modp->FB = (src[3]) & 7; - modp->AR = (src[4]>>4)&0xF; - modp->DR = (src[4]&0xF); + modp->AR = (src[4] >> 4) & 0xF; + modp->DR = (src[4] & 0xF); - carp->AR = (src[5]>>4)&0xF; - carp->DR = (src[5]&0xF); + carp->AR = (src[5] >> 4) & 0xF; + carp->DR = (src[5] & 0xF); - modp->SL = (src[6]>>4)&0xF; - modp->RR = (src[6]&0xF); - - carp->SL = (src[7]>>4)&0xF; - carp->RR = (src[7]&0xF); + modp->SL = (src[6] >> 4) & 0xF; + modp->RR = (src[6] & 0xF); + + carp->SL = (src[7] >> 4) & 0xF; + carp->RR = (src[7] & 0xF); } - -void OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) +void OPLL_writeReg(OPLL *opll, e_uint32 reg, e_uint32 data) { - e_int32 i, v, ch; data = data & 0xff; @@ -1000,103 +989,103 @@ void OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) switch (reg) { - case 0x00: - opll->CustInst[0]=data; + case 0x00: + opll->CustInst[0] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_PG (MOD(opll,i)); - UPDATE_RKS (MOD(opll,i)); - UPDATE_EG (MOD(opll,i)); + setInstrument(opll, i, 0); + UPDATE_PG(MOD(opll, i)); + UPDATE_RKS(MOD(opll, i)); + UPDATE_EG(MOD(opll, i)); } } break; case 0x01: - opll->CustInst[1]=data; + opll->CustInst[1] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_PG (CAR(opll,i)); - UPDATE_RKS (CAR(opll,i)); - UPDATE_EG (CAR(opll,i)); + setInstrument(opll, i, 0); + UPDATE_PG(CAR(opll, i)); + UPDATE_RKS(CAR(opll, i)); + UPDATE_EG(CAR(opll, i)); } } break; case 0x02: - opll->CustInst[2]=data; + opll->CustInst[2] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_TLL(MOD(opll,i)); + setInstrument(opll, i, 0); + UPDATE_TLL(MOD(opll, i)); } } break; case 0x03: - opll->CustInst[3]=data; + opll->CustInst[3] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_WF(MOD(opll,i)); - UPDATE_WF(CAR(opll,i)); + setInstrument(opll, i, 0); + UPDATE_WF(MOD(opll, i)); + UPDATE_WF(CAR(opll, i)); } } break; case 0x04: - opll->CustInst[4]=data; + opll->CustInst[4] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_EG (MOD(opll,i)); + setInstrument(opll, i, 0); + UPDATE_EG(MOD(opll, i)); } } break; case 0x05: - opll->CustInst[5]=data; + opll->CustInst[5] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_EG(CAR(opll,i)); + setInstrument(opll, i, 0); + UPDATE_EG(CAR(opll, i)); } } break; case 0x06: - opll->CustInst[6]=data; + opll->CustInst[6] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_EG (MOD(opll,i)); + setInstrument(opll, i, 0); + UPDATE_EG(MOD(opll, i)); } } break; case 0x07: - opll->CustInst[7]=data; + opll->CustInst[7] = data; for (i = 0; i < 6; i++) { if (opll->patch_number[i] == 0) { - setInstrument(opll, i, 0); - UPDATE_EG (CAR(opll,i)); + setInstrument(opll, i, 0); + UPDATE_EG(CAR(opll, i)); } } break; @@ -1108,10 +1097,10 @@ void OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) case 0x14: case 0x15: ch = reg - 0x10; - opll->LowFreq[ch]=data; - setFnumber (opll, ch, data + ((opll->HiFreq[ch] & 1) << 8)); - UPDATE_ALL (MOD(opll,ch)); - UPDATE_ALL (CAR(opll,ch)); + opll->LowFreq[ch] = data; + setFnumber(opll, ch, data + ((opll->HiFreq[ch] & 1) << 8)); + UPDATE_ALL(MOD(opll, ch)); + UPDATE_ALL(CAR(opll, ch)); break; case 0x20: @@ -1121,18 +1110,18 @@ void OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) case 0x24: case 0x25: ch = reg - 0x20; - opll->HiFreq[ch]=data; + opll->HiFreq[ch] = data; - setFnumber (opll, ch, ((data & 1) << 8) + opll->LowFreq[ch]); - setBlock (opll, ch, (data >> 1) & 7); - setSustine (opll, ch, (data >> 5) & 1); + setFnumber(opll, ch, ((data & 1) << 8) + opll->LowFreq[ch]); + setBlock(opll, ch, (data >> 1) & 7); + setSustine(opll, ch, (data >> 5) & 1); if (data & 0x10) - keyOn (opll, ch); + keyOn(opll, ch); else - keyOff (opll, ch); - UPDATE_ALL (MOD(opll,ch)); - UPDATE_ALL (CAR(opll,ch)); - update_key_status (opll); + keyOff(opll, ch); + UPDATE_ALL(MOD(opll, ch)); + UPDATE_ALL(CAR(opll, ch)); + update_key_status(opll); break; case 0x30: @@ -1141,25 +1130,26 @@ void OPLL_writeReg (OPLL * opll, e_uint32 reg, e_uint32 data) case 0x33: case 0x34: case 0x35: - opll->InstVol[reg-0x30]=data; + opll->InstVol[reg - 0x30] = data; i = (data >> 4) & 15; v = data & 15; - setInstrument(opll, reg-0x30, i); - setVolume (opll, reg - 0x30, v << 2); - UPDATE_ALL (MOD(opll,reg - 0x30)); - UPDATE_ALL (CAR(opll,reg - 0x30)); + setInstrument(opll, reg - 0x30, i); + setVolume(opll, reg - 0x30, v << 2); + UPDATE_ALL(MOD(opll, reg - 0x30)); + UPDATE_ALL(CAR(opll, reg - 0x30)); break; default: break; - } } -void OPLL_writeIO (OPLL * opll, e_uint32 adr, e_uint32 val) +void OPLL_writeIO(OPLL *opll, e_uint32 adr, e_uint32 val) { if (adr & 1) - OPLL_writeReg (opll, opll->adr, val); + OPLL_writeReg(opll, opll->adr, val); else opll->adr = val; } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc7/emu2413.h b/source/quickerNES/apu/vrc7/emu2413.h deleted file mode 100644 index 657266a..0000000 --- a/source/quickerNES/apu/vrc7/emu2413.h +++ /dev/null @@ -1,216 +0,0 @@ -#pragma once - -typedef signed int e_int; -typedef unsigned int e_uint; -typedef signed char e_int8; -typedef unsigned char e_uint8; -typedef signed short e_int16; -typedef unsigned short e_uint16; -typedef signed int e_int32; -typedef unsigned int e_uint32; - -#define INLINE inline - -/* Size of Sintable ( 8 -- 18 can be used. 9 recommended.)*/ -#define PG_BITS 9 -#define PG_WIDTH (1< +namespace quickerNES +{ + #ifdef __cplusplus extern "C" { #endif -int OPLL_serialize_size() -{ - return sizeof(OPLL_STATE); -} + int OPLL_serialize_size() + { + return sizeof(OPLL_STATE); + } -void OPLL_serialize(const OPLL * opll, OPLL_STATE* state) -{ - int i; + void OPLL_serialize(const OPLL *opll, OPLL_STATE *state) + { + int i; - state->pm_phase = opll->pm_phase; - state->am_phase = opll->am_phase; + state->pm_phase = opll->pm_phase; + state->am_phase = opll->am_phase; - for (i = 0; i < 12; i++) - { - OPLL_SLOT_STATE *slotState = &(state->slot[i]); - const OPLL_SLOT *slot= &(opll->slot[i]); - slotState->feedback = slot->feedback; - slotState->output[0] = slot->output[0]; - slotState->output[1] = slot->output[1]; - slotState->phase = slot->phase; - slotState->pgout = slot->pgout; - slotState->eg_mode = slot->eg_mode; - slotState->eg_phase = slot->eg_phase; - slotState->eg_dphase = slot->eg_dphase; - slotState->egout = slot->egout; - } -} + for (i = 0; i < 12; i++) + { + OPLL_SLOT_STATE *slotState = &(state->slot[i]); + const OPLL_SLOT *slot = &(opll->slot[i]); + slotState->feedback = slot->feedback; + slotState->output[0] = slot->output[0]; + slotState->output[1] = slot->output[1]; + slotState->phase = slot->phase; + slotState->pgout = slot->pgout; + slotState->eg_mode = slot->eg_mode; + slotState->eg_phase = slot->eg_phase; + slotState->eg_dphase = slot->eg_dphase; + slotState->egout = slot->egout; + } + } -#define BYTESWAP(xxxx) {uint32_t _temp = (uint32_t)(xxxx);\ -((uint8_t*)&(xxxx))[0] = (uint8_t)((_temp) >> 24);\ -((uint8_t*)&(xxxx))[1] = (uint8_t)((_temp) >> 16);\ -((uint8_t*)&(xxxx))[2] = (uint8_t)((_temp) >> 8);\ -((uint8_t*)&(xxxx))[3] = (uint8_t)((_temp) >> 0);\ -} +#define BYTESWAP(xxxx) \ + { \ + uint32_t _temp = (uint32_t)(xxxx); \ + ((uint8_t *)&(xxxx))[0] = (uint8_t)((_temp) >> 24); \ + ((uint8_t *)&(xxxx))[1] = (uint8_t)((_temp) >> 16); \ + ((uint8_t *)&(xxxx))[2] = (uint8_t)((_temp) >> 8); \ + ((uint8_t *)&(xxxx))[3] = (uint8_t)((_temp) >> 0); \ + } +#define SET(xxxx, yyyy) \ + { \ + if ((xxxx) != (yyyy)) \ + { \ + (xxxx) = (yyyy); \ + } -#define SET(xxxx,yyyy) { if ((xxxx) != (yyyy)) {\ -(xxxx) = (yyyy);\ -} + void OPLL_deserialize(OPLL *opll, const OPLL_STATE *state) + { + int i; -void OPLL_deserialize(OPLL * opll, const OPLL_STATE* state) -{ - int i; + opll->pm_phase = state->pm_phase; + opll->am_phase = state->am_phase; - opll->pm_phase = state->pm_phase; - opll->am_phase = state->am_phase; + for (i = 0; i < 12; i++) + { + const OPLL_SLOT_STATE *slotState = &(state->slot[i]); + OPLL_SLOT *slot = &(opll->slot[i]); + slot->feedback = slotState->feedback; + slot->output[0] = slotState->output[0]; + slot->output[1] = slotState->output[1]; + slot->phase = slotState->phase; + slot->pgout = slotState->pgout; + slot->eg_mode = slotState->eg_mode; + slot->eg_phase = slotState->eg_phase; + slot->eg_dphase = slotState->eg_dphase; + slot->egout = slotState->egout; + } + } - for (i = 0; i < 12; i++) - { - const OPLL_SLOT_STATE *slotState = &(state->slot[i]); - OPLL_SLOT *slot = &(opll->slot[i]); - slot->feedback = slotState->feedback; - slot->output[0] = slotState->output[0]; - slot->output[1] = slotState->output[1]; - slot->phase = slotState->phase; - slot->pgout = slotState->pgout; - slot->eg_mode = slotState->eg_mode; - slot->eg_phase = slotState->eg_phase; - slot->eg_dphase = slotState->eg_dphase; - slot->egout = slotState->egout; - } -} + static bool IsLittleEndian() + { + int i = 42; + if (((char *)&i)[0] == 42) + { + return true; + } + return false; + } -static bool IsLittleEndian() -{ - int i = 42; - if (((char*)&i)[0] == 42) - { - return true; - } - return false; -} + void OPLL_state_byteswap(OPLL_STATE *state) + { + int i; + if (IsLittleEndian()) return; -void OPLL_state_byteswap(OPLL_STATE *state) -{ - int i; - if (IsLittleEndian()) return; + BYTESWAP(state->pm_phase); + BYTESWAP(state->am_phase); - BYTESWAP(state->pm_phase); - BYTESWAP(state->am_phase); - - for (i = 0; i < 12; i++) - { - OPLL_SLOT_STATE *slotState = &(state->slot[i]); - BYTESWAP(slotState->feedback); - BYTESWAP(slotState->output[0]); - BYTESWAP(slotState->output[1]); - BYTESWAP(slotState->phase); - BYTESWAP(slotState->pgout); - BYTESWAP(slotState->eg_mode); - BYTESWAP(slotState->eg_phase); - BYTESWAP(slotState->eg_dphase); - BYTESWAP(slotState->egout); - } -} + for (i = 0; i < 12; i++) + { + OPLL_SLOT_STATE *slotState = &(state->slot[i]); + BYTESWAP(slotState->feedback); + BYTESWAP(slotState->output[0]); + BYTESWAP(slotState->output[1]); + BYTESWAP(slotState->phase); + BYTESWAP(slotState->pgout); + BYTESWAP(slotState->eg_mode); + BYTESWAP(slotState->eg_phase); + BYTESWAP(slotState->eg_dphase); + BYTESWAP(slotState->egout); + } + } #ifdef __cplusplus } #endif + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/apu/vrc7/emu2413_state.h b/source/quickerNES/apu/vrc7/emu2413_state.h deleted file mode 100644 index 6fd4338..0000000 --- a/source/quickerNES/apu/vrc7/emu2413_state.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "emu2413.h" - -typedef struct { - e_int32 feedback; - e_int32 output[2]; - e_uint32 phase; - e_uint32 pgout; - e_int32 eg_mode; - e_uint32 eg_phase; - e_uint32 eg_dphase; - e_uint32 egout; -} OPLL_SLOT_STATE; - -typedef struct { - e_uint32 pm_phase; - e_int32 am_phase; - OPLL_SLOT_STATE slot[6 * 2]; -} OPLL_STATE; - -#ifdef __cplusplus -extern "C" -{ -#endif - -int OPLL_serialize_size(); -void OPLL_serialize(const OPLL * opll, OPLL_STATE* state); -void OPLL_deserialize(OPLL * opll, const OPLL_STATE* state); -void OPLL_state_byteswap(OPLL_STATE *state); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/source/quickerNES/apu/vrc7/emu2413_state.hpp b/source/quickerNES/apu/vrc7/emu2413_state.hpp new file mode 100644 index 0000000..2a97a60 --- /dev/null +++ b/source/quickerNES/apu/vrc7/emu2413_state.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "emu2413.hpp" + +namespace quickerNES +{ + +typedef struct +{ + e_int32 feedback; + e_int32 output[2]; + e_uint32 phase; + e_uint32 pgout; + e_int32 eg_mode; + e_uint32 eg_phase; + e_uint32 eg_dphase; + e_uint32 egout; +} OPLL_SLOT_STATE; + +typedef struct +{ + e_uint32 pm_phase; + e_int32 am_phase; + OPLL_SLOT_STATE slot[6 * 2]; +} OPLL_STATE; + +#ifdef __cplusplus +extern "C" +{ +#endif + + int OPLL_serialize_size(); + void OPLL_serialize(const OPLL *opll, OPLL_STATE *state); + void OPLL_deserialize(OPLL *opll, const OPLL_STATE *state); + void OPLL_state_byteswap(OPLL_STATE *state); + +#ifdef __cplusplus +} +#endif + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/cart.hpp b/source/quickerNES/cart.hpp new file mode 100644 index 0000000..f67fba2 --- /dev/null +++ b/source/quickerNES/cart.hpp @@ -0,0 +1,117 @@ +#pragma once + +// NES cartridge data (PRG, CHR, mapper) + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// Emu 0.7.0. http://www.slack.net/~ant/ + +#include +#include +#include + +namespace quickerNES +{ + +class Cart +{ + public: + Cart() = default; + + struct ines_header_t + { + uint8_t signature[4]; + uint8_t prg_count; // number of 16K PRG banks + uint8_t chr_count; // number of 8K CHR banks + uint8_t flags; // MMMM FTBV Mapper low, Four-screen, Trainer, Battery, V mirror + uint8_t flags2; // MMMM --XX Mapper high 4 bits + uint8_t zero[8]; // if zero [7] is non-zero, treat flags2 as zero + }; + static_assert(sizeof(ines_header_t) == 16); + + // Load iNES file + void load_ines(const uint8_t *buffer) + { + ines_header_t h; + + size_t bufferPos = 0; + { + size_t copySize = sizeof(ines_header_t); + memcpy(&h, &buffer[bufferPos], copySize); + bufferPos += copySize; + } + if (h.zero[7]) h.flags2 = 0; + set_mapper(h.flags, h.flags2); + + // skip trainer + if (h.flags & 0x04) bufferPos += 512; + + // Allocating memory for prg and chr + prg_size_ = h.prg_count * 16 * 1024L; + chr_size_ = h.chr_count * 8 * 1024L; + + auto p = malloc(prg_size_ + chr_size_); + prg_ = (uint8_t *)p; + chr_ = &prg_[prg_size_]; + + { + size_t copySize = prg_size(); + memcpy(prg(), &buffer[bufferPos], copySize); + bufferPos += copySize; + } + { + size_t copySize = chr_size(); + memcpy(chr(), &buffer[bufferPos], copySize); + bufferPos += copySize; + } + } + + inline bool has_battery_ram() const { return mapper & 0x02; } + + // Set mapper and information bytes. LSB and MSB are the standard iNES header + // bytes at offsets 6 and 7. + inline void set_mapper(int mapper_lsb, int mapper_msb) + { + mapper = mapper_msb * 0x100 + mapper_lsb; + } + + inline int mapper_code() const { return ((mapper >> 8) & 0xf0) | ((mapper >> 4) & 0x0f); } + + // Size of PRG data + long prg_size() const { return prg_size_; } + + // Size of CHR data + long chr_size() const { return chr_size_; } + + unsigned mapper_data() const { return mapper; } + + // Initial mirroring setup + int mirroring() const { return mapper & 0x09; } + + // Pointer to beginning of PRG data + inline uint8_t *prg() { return prg_; } + inline uint8_t const *prg() const { return prg_; } + + // Pointer to beginning of CHR data + inline uint8_t *chr() { return chr_; } + inline uint8_t const *chr() const { return chr_; } + + // End of public interface + private: + uint8_t *prg_; + uint8_t *chr_; + long prg_size_; + long chr_size_; + unsigned mapper; +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/core.hpp b/source/quickerNES/core.hpp new file mode 100644 index 0000000..f0f3b62 --- /dev/null +++ b/source/quickerNES/core.hpp @@ -0,0 +1,1050 @@ +#pragma once + +// Internal NES emulator + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// Emu 0.7.0 + +#include "cpu.hpp" +#include "apu/apu.hpp" +#include "mappers/mapper.hpp" +#include "ppu/ppu.hpp" +#include +#include + +namespace quickerNES +{ + +class Cart; + +#undef NES_EMU_CPU_HOOK +#ifndef NES_EMU_CPU_HOOK + #define NES_EMU_CPU_HOOK(cpu, end_time) cpu::run(end_time) +#endif + +bool const wait_states_enabled = true; +bool const single_instruction_mode = false; // for debugging irq/nmi timing issues +const int unmapped_fill = Cpu::page_wrap_opcode; +unsigned const low_ram_size = 0x800; +unsigned const low_ram_end = 0x2000; +unsigned const sram_end = 0x8000; +const int irq_inhibit_mask = 0x04; + +struct nes_state_t +{ + uint16_t timestamp; // CPU clocks * 15 (for NTSC) + uint8_t pal; + uint8_t unused[1]; + uint32_t frame_count; // number of frames emulated since power-up +}; + +struct joypad_state_t +{ + uint32_t joypad_latches[2]; // joypad 1 & 2 shift registers + uint8_t w4016; // strobe + uint8_t unused[3]; +}; +static_assert(sizeof(joypad_state_t) == 12); + +struct cpu_state_t +{ + uint16_t pc; + uint8_t s; + uint8_t p; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t unused[1]; +}; +static_assert(sizeof(cpu_state_t) == 8); + +class Core : private Cpu +{ + typedef Cpu cpu; + + public: + Core() : ppu(this) + { + cart = NULL; + impl = NULL; + mapper = NULL; + memset(&nes, 0, sizeof nes); + memset(&joypad, 0, sizeof joypad); + } + + ~Core() + { + close(); + delete impl; + } + + const char *init() + { + if (!impl) + { + impl = new impl_t; + impl->apu.dmc_reader(read_dmc, this); + impl->apu.irq_notifier(apu_irq_changed, this); + } + + return 0; + } + + void open(Cart const *new_cart) + { + close(); + init(); + + // Getting cartdrige mapper code + auto mapperCode = new_cart->mapper_code(); + + // Getting mapper corresponding to that code + mapper = Mapper::getMapperFromCode(mapperCode); + + // If no mapper was found, return null (error) now + if (mapper == nullptr) + { + fprintf(stderr, "Could not find mapper for code: %u\n", mapperCode); + exit(-1); + } + + // Assigning backwards pointers to cartdrige and emulator now + mapper->cart_ = new_cart; + mapper->emu_ = this; + + ppu.open_chr(new_cart->chr(), new_cart->chr_size()); + + cart = new_cart; + memset(impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page); + reset(true, true); + } + + size_t getLiteStateSize() const + { + size_t size = 0; + + size += sizeof(nes_state_t); + size += sizeof(registers_t); + size += sizeof(ppu_state_t); + size += sizeof(Apu::apu_state_t); + size += sizeof(joypad_state_t); + size += mapper->state_size; + size += low_ram_size; + size += Ppu::spr_ram_size; + size_t nametable_size = 0x800; + if (ppu.nt_banks[3] >= &ppu.impl->nt_ram[0xC00]) nametable_size = 0x1000; + size += nametable_size; + if (ppu.chr_is_writable) size += ppu.chr_size; + if (sram_present) size += impl->sram_size; + + return size; + } + + size_t getStateSize() const + { + size_t size = 0; + + size += sizeof(char[4]); // NESS Block + size += sizeof(uint32_t); // Block Size + + size += sizeof(char[4]); // TIME Block + size += sizeof(uint32_t); // Block Size + size += sizeof(nes_state_t); + + size += sizeof(char[4]); // CPUR Block + size += sizeof(uint32_t); // Block Size + size += sizeof(registers_t); + + size += sizeof(char[4]); // PPUR Block + size += sizeof(uint32_t); // Block Size + size += sizeof(ppu_state_t); + + size += sizeof(char[4]); // APUR Block + size += sizeof(uint32_t); // Block Size + size += sizeof(Apu::apu_state_t); + + size += sizeof(char[4]); // CTRL Block + size += sizeof(uint32_t); // Block Size + size += sizeof(joypad_state_t); + + size += sizeof(char[4]); // MAPR Block + size += sizeof(uint32_t); // Block Size + size += mapper->state_size; + + size += sizeof(char[4]); // LRAM Block + size += sizeof(uint32_t); // Block Size + size += low_ram_size; + + size += sizeof(char[4]); // SPRT Block + size += sizeof(uint32_t); // Block Size + size += Ppu::spr_ram_size; + + size += sizeof(char[4]); // NTAB Block + size += sizeof(uint32_t); // Block Size + size_t nametable_size = 0x800; + if (ppu.nt_banks[3] >= &ppu.impl->nt_ram[0xC00]) nametable_size = 0x1000; + size += nametable_size; + + if (ppu.chr_is_writable) + { + size += sizeof(char[4]); // CHRR Block + size += sizeof(uint32_t); // Block Size + size += ppu.chr_size; + } + + if (sram_present) + { + size += sizeof(char[4]); // SRAM Block + size += sizeof(uint32_t); // Block Size + size += impl->sram_size; + } + + size += sizeof(char[4]); // gend Block + size += sizeof(uint32_t); // Block Size + + return size; + } + + size_t serializeState(uint8_t *buffer) const + { + size_t pos = 0; + std::string headerCode; + const uint32_t headerSize = sizeof(char) * 4; + uint32_t blockSize = 0; + void *dataSource; + + headerCode = "NESS"; // NESS Block + blockSize = 0xFFFFFFFF; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + + headerCode = "TIME"; // TIME Block + nes_state_t state = nes; + state.timestamp *= 5; + blockSize = sizeof(nes_state_t); + dataSource = (void *)&state; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "CPUR"; // CPUR Block + cpu_state_t s; + memset(&s, 0, sizeof s); + s.pc = r.pc; + s.s = r.sp; + s.a = r.a; + s.x = r.x; + s.y = r.y; + s.p = r.status; + blockSize = sizeof(cpu_state_t); + dataSource = (void *)&s; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "PPUR"; // PPUR Block + blockSize = sizeof(ppu_state_t); + dataSource = (void *)&ppu; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "APUR"; // APUR Block + Apu::apu_state_t apuState; + impl->apu.save_state(&apuState); + blockSize = sizeof(Apu::apu_state_t); + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], &apuState, blockSize); + pos += blockSize; + + headerCode = "CTRL"; // CTRL Block + blockSize = sizeof(joypad_state_t); + dataSource = (void *)&joypad; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "MAPR"; // MAPR Block + blockSize = mapper->state_size; + dataSource = (void *)mapper->state; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "LRAM"; // LRAM Block + blockSize = low_ram_size; + dataSource = (void *)low_mem; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "SPRT"; // SPRT Block + blockSize = Ppu::spr_ram_size; + dataSource = (void *)ppu.spr_ram; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + headerCode = "NTAB"; // NTAB Block + size_t nametable_size = 0x800; + if (ppu.nt_banks[3] >= &ppu.impl->nt_ram[0xC00]) nametable_size = 0x1000; + blockSize = nametable_size; + dataSource = (void *)ppu.impl->nt_ram; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + + if (ppu.chr_is_writable) + { + headerCode = "CHRR"; // CHRR Block + blockSize = ppu.chr_size; + dataSource = (void *)ppu.impl->chr_ram; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + } + + if (sram_present) + { + headerCode = "SRAM"; // SRAM Block + blockSize = impl->sram_size; + dataSource = (void *)impl->sram; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + memcpy(&buffer[pos], dataSource, blockSize); + pos += blockSize; + } + + headerCode = "gend"; // gend Block + blockSize = 0; + memcpy(&buffer[pos], headerCode.data(), headerSize); + pos += headerSize; + memcpy(&buffer[pos], &blockSize, headerSize); + pos += headerSize; + + return pos; // Bytes written + } + + size_t deserializeState(const uint8_t *buffer) + { + disable_rendering(); + error_count = 0; + ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over + + size_t pos = 0; + const uint32_t headerSize = sizeof(char) * 4; + uint32_t blockSize = 0; + + // NESS Block + pos += headerSize; + pos += headerSize; + + // TIME Block + nes_state_t nesState; + pos += headerSize; + pos += headerSize; + blockSize = sizeof(nes_state_t); + memcpy(&nesState, &buffer[pos], blockSize); + pos += blockSize; + nes = nesState; + nes.timestamp /= 5; + + // CPUR Block + cpu_state_t s; + blockSize = sizeof(cpu_state_t); + pos += headerSize; + pos += headerSize; + memcpy((void *)&s, &buffer[pos], blockSize); + pos += blockSize; + r.pc = s.pc; + r.sp = s.s; + r.a = s.a; + r.x = s.x; + r.y = s.y; + r.status = s.p; + + // PPUR Block + blockSize = sizeof(ppu_state_t); + pos += headerSize; + pos += headerSize; + memcpy((void *)&ppu, &buffer[pos], blockSize); + pos += blockSize; + + // APUR Block + Apu::apu_state_t apuState; + blockSize = sizeof(Apu::apu_state_t); + pos += headerSize; + pos += headerSize; + memcpy(&apuState, &buffer[pos], blockSize); + pos += blockSize; + impl->apu.load_state(apuState); + impl->apu.end_frame(-(int)nes.timestamp / ppu_overclock); + + // CTRL Block + blockSize = sizeof(joypad_state_t); + pos += headerSize; + pos += headerSize; + memcpy((void *)&joypad, &buffer[pos], blockSize); + pos += blockSize; + + // MAPR Block + mapper->default_reset_state(); + blockSize = mapper->state_size; + pos += headerSize; + pos += headerSize; + memcpy((void *)mapper->state, &buffer[pos], blockSize); + pos += blockSize; + mapper->apply_mapping(); + + // LRAM Block + blockSize = low_ram_size; + pos += headerSize; + pos += headerSize; + memcpy((void *)low_mem, &buffer[pos], blockSize); + pos += blockSize; + + // SPRT Block + blockSize = Ppu::spr_ram_size; + pos += headerSize; + pos += headerSize; + memcpy((void *)ppu.spr_ram, &buffer[pos], blockSize); + pos += blockSize; + + // NTAB Block + size_t nametable_size = 0x800; + if (ppu.nt_banks[3] >= &ppu.impl->nt_ram[0xC00]) nametable_size = 0x1000; + blockSize = nametable_size; + pos += headerSize; + pos += headerSize; + memcpy((void *)ppu.impl->nt_ram, &buffer[pos], blockSize); + pos += blockSize; + + if (ppu.chr_is_writable) + { + // CHRR Block + blockSize = ppu.chr_size; + pos += headerSize; + pos += headerSize; + memcpy((void *)ppu.impl->chr_ram, &buffer[pos], blockSize); + pos += blockSize; + } + + if (sram_present) + { + // SRAM Block + blockSize = impl->sram_size; + pos += headerSize; + pos += headerSize; + memcpy((void *)impl->sram, &buffer[pos], blockSize); + pos += blockSize; + enable_sram(true); + } + + // headerCode = "gend"; // gend Block + pos += headerSize; + pos += headerSize; + + return pos; // Bytes read + } + + void reset(bool full_reset, bool erase_battery_ram) + { + if (full_reset) + { + cpu::reset(impl->unmapped_page); + cpu_time_offset = -1; + clock_ = 0; + + // Low RAM + memset(cpu::low_mem, 0xFF, low_ram_size); + cpu::low_mem[8] = 0xf7; + cpu::low_mem[9] = 0xef; + cpu::low_mem[10] = 0xdf; + cpu::low_mem[15] = 0xbf; + + // SRAM + lrom_readable = 0; + sram_present = true; + enable_sram(false); + if (!cart->has_battery_ram() || erase_battery_ram) + memset(impl->sram, 0xFF, impl->sram_size); + + joypad.joypad_latches[0] = 0; + joypad.joypad_latches[1] = 0; + + nes.frame_count = 0; + } + + // to do: emulate partial reset + + ppu.reset(full_reset); + impl->apu.reset(); + + mapper->reset(); + + cpu::r.pc = read_vector(0xFFFC); + cpu::r.sp = 0xfd; + cpu::r.a = 0; + cpu::r.x = 0; + cpu::r.y = 0; + cpu::r.status = irq_inhibit_mask; + nes.timestamp = 0; + error_count = 0; + } + + nes_time_t emulate_frame(int joypad1, int joypad2) + { + current_joypad[0] = (joypad1 |= ~0xFF); + current_joypad[1] = (joypad2 |= ~0xFF); + + cpu_time_offset = ppu.begin_frame(nes.timestamp) - 1; + ppu_2002_time = 0; + clock_ = cpu_time_offset; + + // TODO: clean this fucking mess up + auto t0 = emulate_frame_(); + impl->apu.run_until_(t0); + clock_ = cpu_time_offset; + auto t1 = cpu_time(); + impl->apu.run_until_(t1); + + nes_time_t ppu_frame_length = ppu.frame_length(); + nes_time_t length = cpu_time(); + nes.timestamp = ppu.end_frame(length); + mapper->end_frame(length); + + impl->apu.end_frame(ppu_frame_length); + + disable_rendering(); + nes.frame_count++; + + return ppu_frame_length; + } + + void close() + { + cart = NULL; + delete mapper; + mapper = NULL; + + ppu.close_chr(); + + disable_rendering(); + } + + void irq_changed() + { + cpu_set_irq_time(earliest_irq(cpu_time())); + } + + void event_changed() + { + cpu_set_end_time(earliest_event(cpu_time())); + } + + public: + private: + friend class Emu; + + struct impl_t + { + enum + { + sram_size = 0x2000 + }; + uint8_t sram[sram_size]; + Apu apu; + + // extra byte allows CPU to always read operand of instruction, which + // might go past end of data + uint8_t unmapped_page[Cpu::page_size + 1]; + }; + impl_t *impl; // keep large arrays separate + unsigned long error_count; + bool sram_present; + + public: + unsigned long current_joypad[2]; + Cart const *cart; + Mapper *mapper; + nes_state_t nes; + Ppu ppu; + + private: + // noncopyable + Core(const Core &); + Core &operator=(const Core &); + + // Timing + nes_time_t ppu_2002_time; + void disable_rendering() { clock_ = 0; } + + inline nes_time_t earliest_irq(nes_time_t present) + { + return std::min(impl->apu.earliest_irq(present), mapper->next_irq(present)); + } + + inline nes_time_t ppu_frame_length(nes_time_t present) + { + nes_time_t t = ppu.frame_length(); + if (t > present) + return t; + + ppu.render_bg_until(clock()); // to do: why this call to clock() rather than using present? + return ppu.frame_length(); + } + + inline nes_time_t earliest_event(nes_time_t present) + { + // PPU frame + nes_time_t t = ppu_frame_length(present); + + // DMC + if (wait_states_enabled) + t = std::min(t, impl->apu.next_dmc_read_time() + 1); + + // NMI + t = std::min(t, ppu.nmi_time()); + + if (single_instruction_mode) + t = std::min(t, present + 1); + + return t; + } + + // APU and Joypad + joypad_state_t joypad; + + int read_io(nes_addr_t addr) + { + if ((addr & 0xFFFE) == 0x4016) + { + // to do: to aid with recording, doesn't emulate transparent latch, + // so a game that held strobe at 1 and read $4016 or $4017 would not get + // the current A status as occurs on a NES + unsigned long result = joypad.joypad_latches[addr & 1]; + if (!(joypad.w4016 & 1)) + joypad.joypad_latches[addr & 1] = (result >> 1) | 0x80000000; + return result & 1; + } + + if (addr == Apu::status_addr) + return impl->apu.read_status(clock()); + + return addr >> 8; // simulate open bus + } + + void write_io(nes_addr_t addr, int data) + { + // sprite dma + if (addr == 0x4014) + { + ppu.dma_sprites(clock(), cpu::get_code(data * 0x100)); + cpu_adjust_time(513); + return; + } + + // joypad strobe + if (addr == 0x4016) + { + // if strobe goes low, latch data + if (joypad.w4016 & 1 & ~data) + { + joypad.joypad_latches[0] = current_joypad[0]; + joypad.joypad_latches[1] = current_joypad[1]; + } + joypad.w4016 = data; + return; + } + + // apu + if (unsigned(addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr) + { + impl->apu.write_register(clock(), addr, data); + if (wait_states_enabled) + { + if (addr == 0x4010 || (addr == 0x4015 && (data & 0x10))) + { + impl->apu.run_until(clock() + 1); + event_changed(); + } + } + return; + } + } + + static inline int read_dmc(void *data, nes_addr_t addr) + { + Core *emu = (Core *)data; + int result = *emu->cpu::get_code(addr); + if (wait_states_enabled) + emu->cpu_adjust_time(4); + return result; + } + + static inline void apu_irq_changed(void *emu) + { + ((Core *)emu)->irq_changed(); + } + + // CPU + unsigned sram_readable; + unsigned sram_writable; + unsigned lrom_readable; + nes_time_t clock_; + nes_time_t cpu_time_offset; + + nes_time_t emulate_frame_() + { + Cpu::result_t last_result = cpu::result_cycles; + int extra_instructions = 0; + while (true) + { + // Add DMC wait-states to CPU time + if (wait_states_enabled) + { + impl->apu.run_until(cpu_time()); + clock_ = cpu_time_offset; + } + + nes_time_t present = cpu_time(); + if (present >= ppu_frame_length(present)) + { + if (ppu.nmi_time() <= present) + { + // NMI will occur next, so delayed CLI and SEI don't need to be handled. + // If NMI will occur normally ($2000.7 and $2002.7 set), let it occur + // next frame, otherwise vector it now. + + if (!(ppu.w2000 & 0x80 & ppu.r2002)) + { + /* vectored NMI at end of frame */ + vector_interrupt(0xFFFA); + present += 7; + } + return present; + } + + if (extra_instructions > 2) + { + return present; + } + + if (last_result != cpu::result_cli && last_result != cpu::result_sei && + (ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002))) + return present; + + /* Executing extra instructions for frame */ + extra_instructions++; // execute one more instruction + } + + // NMI + if (present >= ppu.nmi_time()) + { + ppu.acknowledge_nmi(); + vector_interrupt(0xFFFA); + last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now + } + + // IRQ + nes_time_t irq_time = earliest_irq(present); + cpu_set_irq_time(irq_time); + if (present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) || + last_result == cpu::result_sei)) + { + if (last_result != cpu::result_cli) + { + /* IRQ vectored */ + mapper->run_until(present); + vector_interrupt(0xFFFE); + } + else + { + // CLI delays IRQ + cpu_set_irq_time(present + 1); + } + } + + // CPU + nes_time_t end_time = earliest_event(present); + if (extra_instructions) + end_time = present + 1; + unsigned long cpu_error_count = cpu::error_count(); + last_result = NES_EMU_CPU_HOOK(cpu, end_time - cpu_time_offset - 1); + cpu_adjust_time(cpu::time()); + clock_ = cpu_time_offset; + error_count += cpu::error_count() - cpu_error_count; + } + } + + nes_addr_t read_vector(nes_addr_t addr) + { + uint8_t const *p = cpu::get_code(addr); + return p[1] * 0x100 + p[0]; + } + + void vector_interrupt(nes_addr_t vector) + { + cpu::push_byte(cpu::r.pc >> 8); + cpu::push_byte(cpu::r.pc & 0xFF); + cpu::push_byte(cpu::r.status | 0x20); // reserved bit is set + + cpu_adjust_time(7); + cpu::r.status |= irq_inhibit_mask; + cpu::r.pc = read_vector(vector); + } + + static void log_unmapped(nes_addr_t addr, int data = -1); + void cpu_set_irq_time(nes_time_t t) { cpu::set_irq_time_(t - 1 - cpu_time_offset); } + void cpu_set_end_time(nes_time_t t) { cpu::set_end_time_(t - 1 - cpu_time_offset); } + nes_time_t cpu_time() const { return clock_ + 1; } + + inline void cpu_adjust_time(int n) + { + ppu_2002_time -= n; + cpu_time_offset += n; + cpu::reduce_limit(n); + } + + public: + private: + friend class Ppu; + void set_ppu_2002_time(nes_time_t t) { ppu_2002_time = t - 1 - cpu_time_offset; } + + public: + private: + friend class Mapper; + + void enable_prg_6000() + { + sram_writable = 0; + sram_readable = 0; + lrom_readable = 0x8000; + } + + void enable_sram(bool b, bool read_only = false) + { + sram_writable = 0; + if (b) + { + if (!sram_present) + { + sram_present = true; + memset(impl->sram, 0xFF, impl->sram_size); + } + sram_readable = sram_end; + if (!read_only) + sram_writable = sram_end; + cpu::map_code(0x6000, impl->sram_size, impl->sram); + } + else + { + sram_readable = 0; + for (int i = 0; i < impl->sram_size; i += cpu::page_size) + cpu::map_code(0x6000 + i, cpu::page_size, impl->unmapped_page); + } + } + + nes_time_t clock() const { return clock_; } + + void add_mapper_intercept(nes_addr_t addr, unsigned size, bool read, bool write) + { + int end = (addr + size + (page_size - 1)) >> page_bits; + for (int page = addr >> page_bits; page < end; page++) + { + data_reader_mapped[page] |= read; + data_writer_mapped[page] |= write; + } + } + + public: + private: + friend class Cpu; + int cpu_read_ppu(nes_addr_t, nes_time_t); + int cpu_read(nes_addr_t, nes_time_t); + void cpu_write(nes_addr_t, int data, nes_time_t); + void cpu_write_2007(int data); + + private: + unsigned char data_reader_mapped[page_count + 1]; // extra entry for overflow + unsigned char data_writer_mapped[page_count + 1]; +}; + +inline int Core::cpu_read(nes_addr_t addr, nes_time_t time) +{ + { + int result = cpu::low_mem[addr & 0x7FF]; + if (!(addr & 0xE000)) + return result; + } + + { + int result = *cpu::get_code(addr); + if (addr > 0x7FFF) + return result; + } + + time += cpu_time_offset; + if (addr < 0x4000) + return ppu.read(addr, time); + + clock_ = time; + if (data_reader_mapped[addr >> page_bits]) + { + int result = mapper->read(time, addr); + if (result >= 0) + return result; + } + + if (addr < 0x6000) + return read_io(addr); + + if (addr < sram_readable) + return impl->sram[addr & (impl_t::sram_size - 1)]; + + if (addr < lrom_readable) + return *cpu::get_code(addr); + + return addr >> 8; // simulate open bus +} + +inline int Core::cpu_read_ppu(nes_addr_t addr, nes_time_t time) +{ + // LOG_FREQ( "cpu_read_ppu", 16, addr >> 12 ); + + // Read of status register (0x2002) is heavily optimized since many games + // poll it hundreds of times per frame. + nes_time_t next = ppu_2002_time; + int result = ppu.r2002; + if (addr == 0x2002) + { + ppu.second_write = false; + if (time >= next) + result = ppu.read_2002(time + cpu_time_offset); + } + else + { + result = cpu::low_mem[addr & 0x7FF]; + if (addr >= 0x2000) + result = cpu_read(addr, time); + } + + return result; +} + +inline void Core::cpu_write_2007(int data) +{ + // ppu.write_2007() is inlined + if (ppu.write_2007(data) & Ppu::vaddr_clock_mask) + mapper->a12_clocked(); +} + +inline void Core::cpu_write(nes_addr_t addr, int data, nes_time_t time) +{ + // LOG_FREQ( "cpu_write", 16, addr >> 12 ); + + if (!(addr & 0xE000)) + { + cpu::low_mem[addr & 0x7FF] = data; + return; + } + + time += cpu_time_offset; + if (addr < 0x4000) + { + if ((addr & 7) == 7) + cpu_write_2007(data); + else + ppu.write(time, addr, data); + return; + } + + clock_ = time; + if (data_writer_mapped[addr >> page_bits] && mapper->write_intercepted(time, addr, data)) + return; + + if (addr < 0x6000) + { + write_io(addr, data); + return; + } + + if (addr < sram_writable) + { + impl->sram[addr & (impl_t::sram_size - 1)] = data; + return; + } + + if (addr > 0x7FFF) + { + mapper->write(clock_, addr, data); + return; + } +} + +#define NES_CPU_READ_PPU(cpu, addr, time) \ + static_cast(*cpu).cpu_read_ppu(addr, time) + +#define NES_CPU_READ(cpu, addr, time) \ + static_cast(*cpu).cpu_read(addr, time) + +#define NES_CPU_WRITEX(cpu, addr, data, time) \ + { \ + static_cast(*cpu).cpu_write(addr, data, time); \ + } + +#define NES_CPU_WRITE(cpu, addr, data, time) \ + { \ + if (addr < 0x800) \ + cpu->low_mem[addr] = data; \ + else if (addr == 0x2007) \ + static_cast(*cpu).cpu_write_2007(data); \ + else \ + static_cast(*cpu).cpu_write(addr, data, time); \ + } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/Nes_Cpu.cpp b/source/quickerNES/cpu.cpp similarity index 98% rename from source/quickerNES/Nes_Cpu.cpp rename to source/quickerNES/cpu.cpp index 7a8e9ce..1327002 100644 --- a/source/quickerNES/Nes_Cpu.cpp +++ b/source/quickerNES/cpu.cpp @@ -1,10 +1,10 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/nes-emu/ +// Emu 0.7.0. http://www.slack.net/~ant/nes-emu/ #include #include #include -#include -#include +#include "cpu.hpp" +#include "core.hpp" /** * Optimizations by Sergio Martin (eien86) 2023-2024 @@ -22,6 +22,9 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + #define st_n 0x80 #define st_v 0x40 #define st_r 0x20 @@ -127,7 +130,7 @@ imm##op: \ } -void Nes_Cpu::reset( void const* unmapped_page ) +void Cpu::reset( void const* unmapped_page ) { r.status = 0; r.sp = 0; @@ -187,12 +190,12 @@ void Nes_Cpu::reset( void const* unmapped_page ) nz |= ~in & st_z; \ } while ( 0 ) -inline int32_t Nes_Cpu::read( nes_addr_t addr ) +inline int32_t Cpu::read( nes_addr_t addr ) { return READ( addr ); } -inline void Nes_Cpu::write( nes_addr_t addr, int value ) +inline void Cpu::write( nes_addr_t addr, int value ) { WRITE( addr, value ); } @@ -201,7 +204,7 @@ inline void Nes_Cpu::write( nes_addr_t addr, int value ) extern uint8_t clock_table [256]; __attribute__((optimize("align-functions=" _PAGE_SIZE))) - Nes_Cpu::result_t Nes_Cpu::run ( nes_time_t end ) + Cpu::result_t Cpu::run ( nes_time_t end ) { set_end_time_( end ); clock_count = 0; @@ -1163,4 +1166,6 @@ uint8_t clock_table [256] = { 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F -}; \ No newline at end of file +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/cpu.hpp b/source/quickerNES/cpu.hpp new file mode 100644 index 0000000..137a7fc --- /dev/null +++ b/source/quickerNES/cpu.hpp @@ -0,0 +1,143 @@ +#pragma once + +// NES 6502 CPU emulator +// Emu 0.7.0 + +#include + +namespace quickerNES +{ + +typedef long nes_time_t; // clock cycle count +typedef unsigned nes_addr_t; // 16-bit address + +class Cpu +{ + public: + // NES 6502 registers. *Not* kept updated during a call to run(). + struct registers_t + { + uint16_t pc; // Should be more than 16 bits to allow overflow detection -- but I (eien86) removed it to maximize performance. + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + }; + + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. + enum + { + page_bits = 11 + }; + enum + { + page_count = 0x10000 >> page_bits + }; + enum + { + page_size = 1L << page_bits + }; + + // Clear registers, unmap memory, and map code pages to unmapped_page. + void reset(void const *unmapped_page = 0); + + inline void map_code(nes_addr_t start, unsigned size, const void *data) + { + unsigned first_page = start / page_size; + const uint8_t *newPtr = (uint8_t *)data - start; + for (unsigned i = size / page_size; i--;) code_map[first_page + i] = newPtr; + } + + // Access memory as the emulated CPU does. + int read(nes_addr_t); + void write(nes_addr_t, int data); + + // Push a byte on the stack + inline void push_byte(int data) + { + int sp = r.sp; + r.sp = (sp - 1) & 0xFF; + low_mem[0x100 + sp] = data; + } + + // Reasons that run() returns + enum result_t + { + result_cycles, // Requested number of cycles (or more) were executed + result_sei, // I flag just set and IRQ time would generate IRQ now + result_cli, // I flag just cleared but IRQ should occur *after* next instr + result_badop // unimplemented/illegal instruction + }; + + result_t run(nes_time_t end_time); + + nes_time_t time() const { return clock_count; } + + inline void reduce_limit(int offset) + { + clock_limit -= offset; + end_time_ -= offset; + irq_time_ -= offset; + } + + inline void set_end_time_(nes_time_t t) + { + end_time_ = t; + update_clock_limit(); + } + + inline void set_irq_time_(nes_time_t t) + { + irq_time_ = t; + update_clock_limit(); + } + + unsigned long error_count() const { return error_count_; } + + // If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped. + enum + { + page_wrap_opcode = 0xF2 + }; + + // One of the many opcodes that are undefined and stop CPU emulation. + enum + { + bad_opcode = 0xD2 + }; + + uint8_t const *code_map[page_count + 1]; + nes_time_t clock_limit; + nes_time_t clock_count; + nes_time_t irq_time_; + nes_time_t end_time_; + unsigned long error_count_; + + enum + { + irq_inhibit = 0x04 + }; + + inline void update_clock_limit() + { + nes_time_t t = end_time_; + if (t > irq_time_ && !(r.status & irq_inhibit)) + t = irq_time_; + clock_limit = t; + } + + registers_t r; + bool isCorrectExecution = true; + + // low_mem is a full page size so it can be mapped with code_map + uint8_t low_mem[page_size > 0x800 ? page_size : 0x800]; + + inline uint8_t *get_code(nes_addr_t addr) + { + return (uint8_t *)code_map[addr >> page_bits] + addr; + } +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/Nes_Emu.cpp b/source/quickerNES/emu.cpp similarity index 86% rename from source/quickerNES/Nes_Emu.cpp rename to source/quickerNES/emu.cpp index c2188e2..27cc026 100644 --- a/source/quickerNES/Nes_Emu.cpp +++ b/source/quickerNES/emu.cpp @@ -1,8 +1,8 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ #include -#include "mappers/mapper.h" -#include "Nes_Emu.h" +#include "mappers/mapper.hpp" +#include "emu.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -15,19 +15,22 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + int const sound_fade_size = 384; -Nes_Emu::equalizer_t const Nes_Emu::nes_eq = { -1.0, 80 }; -Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 }; -Nes_Emu::equalizer_t const Nes_Emu::tv_eq = { -12.0, 180 }; -Nes_Emu::equalizer_t const Nes_Emu::flat_eq = { 0.0, 1 }; -Nes_Emu::equalizer_t const Nes_Emu::crisp_eq = { 5.0, 1 }; -Nes_Emu::equalizer_t const Nes_Emu::tinny_eq = { -47.0, 2000 }; +Emu::equalizer_t const Emu::nes_eq = { -1.0, 80 }; +Emu::equalizer_t const Emu::famicom_eq = { -15.0, 80 }; +Emu::equalizer_t const Emu::tv_eq = { -12.0, 180 }; +Emu::equalizer_t const Emu::flat_eq = { 0.0, 1 }; +Emu::equalizer_t const Emu::crisp_eq = { 5.0, 1 }; +Emu::equalizer_t const Emu::tinny_eq = { -47.0, 2000 }; -Nes_Emu::Nes_Emu() +Emu::Emu() { frame_ = &single_frame; - buffer_height_ = Nes_Ppu::buffer_height + 2; + buffer_height_ = Ppu::buffer_height + 2; default_sound_buf = NULL; sound_buf = &silent_buffer; sound_buf_changed_count = 0; @@ -46,17 +49,17 @@ Nes_Emu::Nes_Emu() extra_sound_buf_changed_count = 0; } -Nes_Emu::~Nes_Emu() +Emu::~Emu() { delete default_sound_buf; } -const char * Nes_Emu::init_() +const char * Emu::init_() { return emu.init(); } -inline const char * Nes_Emu::auto_init() +inline const char * Emu::auto_init() { if ( !init_called ) { @@ -67,40 +70,40 @@ inline const char * Nes_Emu::auto_init() } -inline void Nes_Emu::clear_sound_buf() +inline void Emu::clear_sound_buf() { fade_sound_out = false; fade_sound_in = true; sound_buf->clear(); } -void Nes_Emu::set_cart( Nes_Cart const* new_cart ) +void Emu::set_cart( Cart const* new_cart ) { auto_init(); emu.open( new_cart ); - channel_count_ = Nes_Apu::osc_count + emu.mapper->channel_count(); + channel_count_ = Apu::osc_count + emu.mapper->channel_count(); sound_buf->set_channel_count( channel_count() ); set_equalizer( equalizer_ ); enable_sound( true ); reset(); } -void Nes_Emu::reset( bool full_reset, bool erase_battery_ram ) +void Emu::reset( bool full_reset, bool erase_battery_ram ) { clear_sound_buf(); set_timestamp( 0 ); emu.reset( full_reset, erase_battery_ram ); } -void Nes_Emu::set_palette_range( int begin, int end ) +void Emu::set_palette_range( int begin, int end ) { // round up to alignment emu.ppu.palette_begin = (begin + palette_alignment - 1) & ~(palette_alignment - 1); host_palette_size = end - emu.ppu.palette_begin; } -const char * Nes_Emu::emulate_skip_frame( int joypad1, int joypad2 ) +const char * Emu::emulate_skip_frame( int joypad1, int joypad2 ) { char *old_host_pixels = host_pixels; host_pixels = NULL; @@ -109,7 +112,7 @@ const char * Nes_Emu::emulate_skip_frame( int joypad1, int joypad2 ) return 0; } -const char * Nes_Emu::emulate_frame( int joypad1, int joypad2 ) +const char * Emu::emulate_frame( int joypad1, int joypad2 ) { emu.ppu.host_pixels = NULL; @@ -163,41 +166,41 @@ const char * Nes_Emu::emulate_frame( int joypad1, int joypad2 ) // Extras -void Nes_Emu::load_ines( const uint8_t* buffer ) +void Emu::load_ines( const uint8_t* buffer ) { private_cart.load_ines( buffer ); set_cart( &private_cart ); } -void Nes_Emu::write_chr( void const* p, long count, long offset ) +void Emu::write_chr( void const* p, long count, long offset ) { long end = offset + count; memcpy( (uint8_t*) chr_mem() + offset, p, count ); emu.ppu.rebuild_chr( offset, end ); } -const char * Nes_Emu::set_sample_rate( long rate, class Nes_Buffer* buf ) +const char * Emu::set_sample_rate( long rate, class Buffer* buf ) { - extern Multi_Buffer* set_apu( class Nes_Buffer*, Nes_Apu* ); + extern Multi_Buffer* set_apu( class Buffer*, Apu* ); auto_init(); return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) ); } -const char * Nes_Emu::set_sample_rate( long rate, class Nes_Effects_Buffer* buf ) +const char * Emu::set_sample_rate( long rate, class Nes_Effects_Buffer* buf ) { - extern Multi_Buffer* set_apu( class Nes_Effects_Buffer*, Nes_Apu* ); + extern Multi_Buffer* set_apu( class Nes_Effects_Buffer*, Apu* ); auto_init(); return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) ); } // Sound -void Nes_Emu::set_frame_rate( double rate ) +void Emu::set_frame_rate( double rate ) { sound_buf->clock_rate( (long) (1789773 / 60.0 * rate) ); } -const char * Nes_Emu::set_sample_rate( long rate, Multi_Buffer* new_buf ) +const char * Emu::set_sample_rate( long rate, Multi_Buffer* new_buf ) { auto_init(); emu.impl->apu.volume( 1.0 ); // cancel any previous non-linearity @@ -213,13 +216,13 @@ const char * Nes_Emu::set_sample_rate( long rate, Multi_Buffer* new_buf ) return 0; } -const char * Nes_Emu::set_sample_rate( long rate ) +const char * Emu::set_sample_rate( long rate ) { if ( !default_sound_buf ) default_sound_buf = new Mono_Buffer; return set_sample_rate( rate, default_sound_buf ); } -void Nes_Emu::set_equalizer( equalizer_t const& eq ) +void Emu::set_equalizer( equalizer_t const& eq ) { equalizer_ = eq; if ( cart() ) @@ -231,14 +234,14 @@ void Nes_Emu::set_equalizer( equalizer_t const& eq ) } } -void Nes_Emu::enable_sound( bool enabled ) +void Emu::enable_sound( bool enabled ) { if ( enabled ) { for ( int i = channel_count(); i-- > 0; ) { Blip_Buffer* buf = sound_buf->channel( i ).center; - int mapper_index = i - Nes_Apu::osc_count; + int mapper_index = i - Apu::osc_count; if ( mapper_index < 0 ) emu.impl->apu.osc_output( i, buf ); else @@ -248,12 +251,12 @@ void Nes_Emu::enable_sound( bool enabled ) else { emu.impl->apu.output( NULL ); - for ( int i = channel_count() - Nes_Apu::osc_count; i-- > 0; ) + for ( int i = channel_count() - Apu::osc_count; i-- > 0; ) emu.mapper->set_channel_buf( i, NULL ); } } -void Nes_Emu::fade_samples( blip_sample_t* p, int size, int step ) +void Emu::fade_samples( blip_sample_t* p, int size, int step ) { if ( size >= sound_fade_size ) { @@ -273,7 +276,7 @@ void Nes_Emu::fade_samples( blip_sample_t* p, int size, int step ) } } -long Nes_Emu::read_samples( short* out, long out_size ) +long Emu::read_samples( short* out, long out_size ) { long count = sound_buf->read_samples( out, out_size ); if ( fade_sound_in ) @@ -293,7 +296,7 @@ long Nes_Emu::read_samples( short* out, long out_size ) return count; } -Nes_Emu::rgb_t const Nes_Emu::nes_colors [color_table_size] = +Emu::rgb_t const Emu::nes_colors [color_table_size] = { // generated with nes_ntsc default settings {102,102,102},{ 0, 42,136},{ 20, 18,168},{ 59, 0,164}, @@ -427,17 +430,19 @@ Nes_Emu::rgb_t const Nes_Emu::nes_colors [color_table_size] = {136,190,197},{184,184,184},{ 0, 0, 0},{ 0, 0, 0} }; -void Nes_Emu::SaveAudioBufferState() +void Emu::SaveAudioBufferState() { extra_fade_sound_in = fade_sound_in; extra_fade_sound_out = fade_sound_out; extra_sound_buf_changed_count = sound_buf_changed_count; sound_buf->SaveAudioBufferState(); } -void Nes_Emu::RestoreAudioBufferState() +void Emu::RestoreAudioBufferState() { fade_sound_in = extra_fade_sound_in; fade_sound_out = extra_fade_sound_out; sound_buf_changed_count = extra_sound_buf_changed_count; sound_buf->RestoreAudioBufferState(); } + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/emu.hpp b/source/quickerNES/emu.hpp new file mode 100644 index 0000000..7fb74d4 --- /dev/null +++ b/source/quickerNES/emu.hpp @@ -0,0 +1,288 @@ +#pragma once + +// NES video game console emulator with snapshot support + +// Emu 0.7.0 + +#include "cart.hpp" +#include "core.hpp" +#include "apu/Multi_Buffer.hpp" + +namespace quickerNES +{ + +class State; + +class Emu +{ + public: + Emu(); + virtual ~Emu(); + + // Basic setup + + // Load iNES file into emulator and clear recording + void load_ines(const uint8_t *buffer); + + // Set sample rate for sound generation + const char *set_sample_rate(long); + + // Size and depth of graphics buffer required for rendering. Note that this + // is larger than the actual image, with a temporary area around the edge + // that gets filled with junk. + static const uint16_t buffer_width = Ppu::buffer_width; + uint16_t buffer_height() const { return buffer_height_; } + static const uint8_t bits_per_pixel = 8; + + // Set graphics buffer to render pixels to. Pixels points to top-left pixel and + // row_bytes is the number of bytes to get to the next line (positive or negative). + void set_pixels(void *pixels, long row_bytes); + + // Size of image generated in graphics buffer + static const uint16_t image_width = 256; + static const uint16_t image_height = 240; + + const uint8_t *getHostPixels() const { return emu.ppu.host_pixels; } + + size_t getLiteStateSize() const { return emu.getLiteStateSize(); } + size_t getStateSize() const { return emu.getStateSize(); } + + // Basic emulation + + // Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image + // and sound are available for output using the accessors below. + virtual const char *emulate_frame(int joypad1, int joypad2 = 0); + + // Emulate one video frame using joypad1 and joypad2 as input, but skips drawing. + // Afterwards, audio is available for output using the accessors below. + virtual const char *emulate_skip_frame(int joypad1, int joypad2 = 0); + + // Maximum size of palette that can be generated + static const uint16_t max_palette_size = 256; + + // Result of current frame + struct frame_t + { + static const uint8_t left = 8; + + int joypad_read_count; // number of times joypads were strobed (read) + int burst_phase; // NTSC burst phase for frame (0, 1, or 2) + + int sample_count; // number of samples (always a multiple of chan_count) + int chan_count; // 1: mono, 2: stereo + + int top; // top-left position of image in graphics buffer + unsigned char *pixels; // pointer to top-left pixel of image + long pitch; // number of bytes to get to next row of image + + int palette_begin; // first host palette entry, as set by set_palette_range() + int palette_size; // number of entries used for current frame + short palette[max_palette_size]; // [palette_begin to palette_begin+palette_size-1] + }; + frame_t const &frame() const { return *frame_; } + + // Read samples for the current frame. Returns number of samples read into buffer. + // Currently all samples must be read in one call. + virtual long read_samples(short *out, long max_samples); + + // Additional features + + // Use already-loaded cartridge. Retains pointer, so it must be kept around until + // closed. A cartridge can be shared among multiple emulators. After opening, + // cartridge's CHR data shouldn't be modified since a copy is cached internally. + void set_cart(Cart const *); + + // Pointer to current cartridge, or NULL if none is loaded + Cart const *cart() const { return emu.cart; } + + // Emulate powering NES off and then back on. If full_reset is false, emulates + // pressing the reset button only, which doesn't affect memory, otherwise + // emulates powering system off then on. + virtual void reset(bool full_reset = true, bool erase_battery_ram = false); + + // Number of undefined CPU instructions encountered. Cleared after reset() and + // load_state(). A non-zero value indicates that cartridge is probably + // incompatible. + unsigned long error_count() const { return emu.error_count; } + + // Sound + + // Set sample rate and use a custom sound buffer instead of the default + // mono buffer, i.e. Buffer, Effects_Buffer, etc.. + const char *set_sample_rate(long rate, Multi_Buffer *); + + // Adjust effective frame rate by changing how many samples are generated each frame. + // Allows fine tuning of frame rate to improve synchronization. + void set_frame_rate(double rate); + + // Number of sound channels for current cartridge + int channel_count() const { return channel_count_; } + + // Frequency equalizer parameters + struct equalizer_t + { + double treble; // 5.0 = extra-crisp, -200.0 = muffled + long bass; // 0 = deep, 20000 = tinny + }; + + // Current frequency equalization + equalizer_t const &equalizer() const { return equalizer_; } + + // Change frequency equalization + void set_equalizer(equalizer_t const &); + + // Equalizer presets + static equalizer_t const nes_eq; // NES + static equalizer_t const famicom_eq; // Famicom + static equalizer_t const tv_eq; // TV speaker + static equalizer_t const flat_eq; // Flat EQ + static equalizer_t const crisp_eq; // Crisp EQ (Treble boost) + static equalizer_t const tinny_eq; // Tinny EQ (Like a handheld speaker) + + // File save/load + + // Save emulator state + size_t serializeState(uint8_t *buffer) const { return emu.serializeState(buffer); } + size_t deserializeState(const uint8_t *buffer) { return emu.deserializeState(buffer); } + + // True if current cartridge claims it uses battery-backed memory + bool has_battery_ram() const { return cart()->has_battery_ram(); } + + // Graphics + + // Number of frames generated per second + enum + { + frame_rate = 60 + }; + + // Size of fixed NES color table (including the 8 color emphasis modes) + enum + { + color_table_size = 8 * 64 + }; + + // NES color lookup table based on standard NTSC TV decoder. Use nes_ntsc.h to + // generate a palette with custom parameters. + struct rgb_t + { + unsigned char red, green, blue; + }; + static rgb_t const nes_colors[color_table_size]; + + // Hide/show/enhance sprites. Sprite mode does not affect emulation accuracy. + enum sprite_mode_t + { + sprites_hidden = 0, + sprites_visible = 8, // limit of 8 sprites per scanline as on NES (default) + sprites_enhanced = 64 // unlimited sprites per scanline (no flickering) + }; + void set_sprite_mode(sprite_mode_t n) { emu.ppu.sprite_limit = n; } + + // Set range of host palette entries to use in graphics buffer; default uses + // all of them. Begin will be rounded up to next multiple of palette_alignment. + // Use frame().palette_begin to find the adjusted beginning entry used. + enum + { + palette_alignment = 64 + }; + void set_palette_range(int begin, int end = 256); + + // Access to emulated memory, for viewer/cheater/debugger + + // CHR + uint8_t const *chr_mem(); + long chr_size() const; + void write_chr(void const *, long count, long offset); + + // Nametable + uint8_t *nametable_mem() { return emu.ppu.impl->nt_ram; } + long nametable_size() const { return 0x1000; } + + // Built-in 2K memory + enum + { + low_mem_size = 0x800 + }; + uint8_t *low_mem() { return emu.low_mem; } + + // Optional 8K memory + enum + { + high_mem_size = 0x2000 + }; + uint8_t *high_mem() { return emu.impl->sram; } + + // Sprite memory + uint8_t *spr_mem() { return emu.ppu.getSpriteRAM(); } + uint16_t spr_mem_size() { return emu.ppu.getSpriteRAMSize(); } + + // End of public interface + public: + const char *set_sample_rate(long rate, class Buffer *); + const char *set_sample_rate(long rate, class Nes_Effects_Buffer *); + void irq_changed() { emu.irq_changed(); } + + private: + frame_t *frame_; + int buffer_height_; + bool fade_sound_in; + bool fade_sound_out; + virtual const char *init_(); + + virtual void loading_state(State const &) {} + long timestamp() const { return emu.nes.frame_count; } + void set_timestamp(long t) { emu.nes.frame_count = t; } + + private: + // noncopyable + Emu(const Emu &); + Emu &operator=(const Emu &); + + // sound + Multi_Buffer *default_sound_buf; + Multi_Buffer *sound_buf; + unsigned sound_buf_changed_count; + Silent_Buffer silent_buffer; + equalizer_t equalizer_; + int channel_count_; + bool sound_enabled; + void enable_sound(bool); + void clear_sound_buf(); + void fade_samples(blip_sample_t *, int size, int step); + + char *host_pixels; + int host_palette_size; + frame_t single_frame; + Cart private_cart; + Core emu; // large; keep at end + + bool init_called; + const char *auto_init(); + + bool extra_fade_sound_in; + bool extra_fade_sound_out; + unsigned extra_sound_buf_changed_count; + + public: + void SaveAudioBufferState(); + void RestoreAudioBufferState(); +}; + +inline void Emu::set_pixels(void *p, long n) +{ + host_pixels = (char *)p + n; + emu.ppu.host_row_bytes = n; +} + +inline uint8_t const *Emu::chr_mem() +{ + return cart()->chr_size() ? (uint8_t *)cart()->chr() : emu.ppu.impl->chr_ram; +} + +inline long Emu::chr_size() const +{ + return cart()->chr_size() ? cart()->chr_size() : emu.ppu.chr_addr_size; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper.cpp b/source/quickerNES/mappers/mapper.cpp index a486055..0f721a3 100644 --- a/source/quickerNES/mappers/mapper.cpp +++ b/source/quickerNES/mappers/mapper.cpp @@ -1,9 +1,9 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ +#include "mappers/mapper.hpp" +#include "core.hpp" #include -#include "mappers/mapper.h" -#include "Nes_Core.h" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -76,215 +76,219 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "mappers/mapper244.hpp" #include "mappers/mapper246.hpp" -Nes_Mapper::Nes_Mapper() +namespace quickerNES { - emu_ = NULL; - static char c; - state = &c; // TODO: state must not be null? - state_size = 0; + +Mapper::Mapper() +{ + emu_ = NULL; + static char c; + state = &c; // TODO: state must not be null? + state_size = 0; } -Nes_Mapper::~Nes_Mapper() +Mapper::~Mapper() { } // Sets mirroring, maps first 8K CHR in, first and last 16K of PRG, // intercepts writes to upper half of memory, and clears registered state. -void Nes_Mapper::default_reset_state() +void Mapper::default_reset_state() { - int mirroring = cart_->mirroring(); - if ( mirroring & 8 ) - mirror_full(); - else if ( mirroring & 1 ) - mirror_vert(); - else - mirror_horiz(); - - set_chr_bank( 0, bank_8k, 0 ); - - set_prg_bank( 0x8000, bank_16k, 0 ); - set_prg_bank( 0xC000, bank_16k, last_bank ); - - intercept_writes( 0x8000, 0x8000 ); - - memset( state, 0, state_size ); + int mirroring = cart_->mirroring(); + if (mirroring & 8) + mirror_full(); + else if (mirroring & 1) + mirror_vert(); + else + mirror_horiz(); + + set_chr_bank(0, bank_8k, 0); + + set_prg_bank(0x8000, bank_16k, 0); + set_prg_bank(0xC000, bank_16k, last_bank); + + intercept_writes(0x8000, 0x8000); + + memset(state, 0, state_size); } -void Nes_Mapper::reset() +void Mapper::reset() { - default_reset_state(); - reset_state(); - apply_mapping(); + default_reset_state(); + reset_state(); + apply_mapping(); } -void mapper_state_t::write( const void* p, unsigned long s ) +void mapper_state_t::write(const void *p, unsigned long s) { - size = s; - memcpy( data, p, s ); + size = s; + memcpy(data, p, s); } -int mapper_state_t::read( void* p, unsigned long s ) const +int mapper_state_t::read(void *p, unsigned long s) const { - if ( (long) s > size ) - s = size; - memcpy( p, data, s ); - return s; + if ((long)s > size) + s = size; + memcpy(p, data, s); + return s; } -void Nes_Mapper::save_state( mapper_state_t& out ) +void Mapper::save_state(mapper_state_t &out) { - out.write( state, state_size ); + out.write(state, state_size); } -void Nes_Mapper::load_state( mapper_state_t const& in ) +void Mapper::load_state(mapper_state_t const &in) { - default_reset_state(); - read_state( in ); - apply_mapping(); + default_reset_state(); + read_state(in); + apply_mapping(); } -void Nes_Mapper::read_state( mapper_state_t const& in ) +void Mapper::read_state(mapper_state_t const &in) { - memset( state, 0, state_size ); - in.read( state, state_size ); - apply_mapping(); + memset(state, 0, state_size); + in.read(state, state_size); + apply_mapping(); } // Timing -void Nes_Mapper::irq_changed() { emu_->irq_changed(); } - -nes_time_t Nes_Mapper::next_irq( nes_time_t ) { return no_irq; } +void Mapper::irq_changed() { emu_->irq_changed(); } -void Nes_Mapper::a12_clocked() { } +nes_time_t Mapper::next_irq(nes_time_t) { return no_irq; } -void Nes_Mapper::run_until( nes_time_t ) { } +void Mapper::a12_clocked() {} -void Nes_Mapper::end_frame( nes_time_t ) { } +void Mapper::run_until(nes_time_t) {} -bool Nes_Mapper::ppu_enabled() const { return emu().ppu.w2001 & 0x08; } +void Mapper::end_frame(nes_time_t) {} + +bool Mapper::ppu_enabled() const { return emu().ppu.w2001 & 0x08; } // Sound -int Nes_Mapper::channel_count() const { return 0; } +int Mapper::channel_count() const { return 0; } -void Nes_Mapper::set_channel_buf( int, Blip_Buffer* ) { } +void Mapper::set_channel_buf(int, Blip_Buffer *) {} -void Nes_Mapper::set_treble( blip_eq_t const& ) { } +void Mapper::set_treble(blip_eq_t const &) {} // Memory mapping -void Nes_Mapper::set_prg_bank( nes_addr_t addr, bank_size_t bs, int bank ) +void Mapper::set_prg_bank(nes_addr_t addr, bank_size_t bs, int bank) { - int bank_size = 1 << bs; - - int bank_count = cart_->prg_size() >> bs; - if ( bank < 0 ) - bank += bank_count; - - if ( bank >= bank_count ) - bank %= bank_count; - - emu().map_code( addr, bank_size, cart_->prg() + (bank << bs) ); - - if ( unsigned (addr - 0x6000) < 0x2000 ) - emu().enable_prg_6000(); + int bank_size = 1 << bs; + + int bank_count = cart_->prg_size() >> bs; + if (bank < 0) + bank += bank_count; + + if (bank >= bank_count) + bank %= bank_count; + + emu().map_code(addr, bank_size, cart_->prg() + (bank << bs)); + + if (unsigned(addr - 0x6000) < 0x2000) + emu().enable_prg_6000(); } -void Nes_Mapper::set_chr_bank( nes_addr_t addr, bank_size_t bs, int bank ) +void Mapper::set_chr_bank(nes_addr_t addr, bank_size_t bs, int bank) { - emu().ppu.render_until( emu().clock() ); - emu().ppu.set_chr_bank( addr, 1 << bs, bank << bs ); + emu().ppu.render_until(emu().clock()); + emu().ppu.set_chr_bank(addr, 1 << bs, bank << bs); } -void Nes_Mapper::set_chr_bank_ex( nes_addr_t addr, bank_size_t bs, int bank ) +void Mapper::set_chr_bank_ex(nes_addr_t addr, bank_size_t bs, int bank) { - emu().ppu.render_until( emu().clock() ); - emu().ppu.set_chr_bank_ex( addr, 1 << bs, bank << bs ); + emu().ppu.render_until(emu().clock()); + emu().ppu.set_chr_bank_ex(addr, 1 << bs, bank << bs); } -void Nes_Mapper::mirror_manual( int page0, int page1, int page2, int page3 ) +void Mapper::mirror_manual(int page0, int page1, int page2, int page3) { - emu().ppu.render_bg_until( emu().clock() ); - emu().ppu.set_nt_banks( page0, page1, page2, page3 ); + emu().ppu.render_bg_until(emu().clock()); + emu().ppu.set_nt_banks(page0, page1, page2, page3); } - -void Nes_Mapper::intercept_reads( nes_addr_t addr, unsigned size ) +void Mapper::intercept_reads(nes_addr_t addr, unsigned size) { - emu().add_mapper_intercept( addr, size, true, false ); + emu().add_mapper_intercept(addr, size, true, false); } -void Nes_Mapper::intercept_writes( nes_addr_t addr, unsigned size ) +void Mapper::intercept_writes(nes_addr_t addr, unsigned size) { - emu().add_mapper_intercept( addr, size, false, true ); + emu().add_mapper_intercept(addr, size, false, true); } -void Nes_Mapper::enable_sram( bool enabled, bool read_only ) +void Mapper::enable_sram(bool enabled, bool read_only) { - emu_->enable_sram( enabled, read_only ); + emu_->enable_sram(enabled, read_only); } -Nes_Mapper* Nes_Mapper::getMapperFromCode(const int mapperCode) +Mapper *Mapper::getMapperFromCode(const int mapperCode) { - Nes_Mapper* mapper = nullptr; + Mapper *mapper = nullptr; - // Now checking if the detected mapper code is supported - if (mapperCode == 0) mapper = new Mapper000(); - if (mapperCode == 1) mapper = new Mapper001(); - if (mapperCode == 2) mapper = new Mapper002(); - if (mapperCode == 3) mapper = new Mapper003(); - if (mapperCode == 4) mapper = new Mapper004(); - if (mapperCode == 5) mapper = new Mapper005(); - if (mapperCode == 7) mapper = new Mapper007(); - if (mapperCode == 9) mapper = new Mapper009(); - if (mapperCode == 10) mapper = new Mapper010(); - if (mapperCode == 11) mapper = new Mapper011(); - if (mapperCode == 15) mapper = new Mapper015(); - if (mapperCode == 19) mapper = new Mapper019(); - if (mapperCode == 21) mapper = new Mapper021(); - if (mapperCode == 22) mapper = new Mapper022(); - if (mapperCode == 23) mapper = new Mapper023(); - if (mapperCode == 24) mapper = new Mapper024(); - if (mapperCode == 25) mapper = new Mapper025(); - if (mapperCode == 26) mapper = new Mapper026(); - if (mapperCode == 30) mapper = new Mapper030(); - if (mapperCode == 32) mapper = new Mapper032(); - if (mapperCode == 33) mapper = new Mapper033(); - if (mapperCode == 34) mapper = new Mapper034(); - if (mapperCode == 60) mapper = new Mapper060(); - if (mapperCode == 66) mapper = new Mapper066(); - if (mapperCode == 69) mapper = new Mapper069(); - if (mapperCode == 70) mapper = new Mapper070(); - if (mapperCode == 71) mapper = new Mapper071(); - if (mapperCode == 73) mapper = new Mapper073(); - if (mapperCode == 75) mapper = new Mapper075(); - if (mapperCode == 78) mapper = new Mapper078(); - if (mapperCode == 79) mapper = new Mapper079(); - if (mapperCode == 85) mapper = new Mapper085(); - if (mapperCode == 86) mapper = new Mapper086(); - if (mapperCode == 87) mapper = new Mapper087(); - if (mapperCode == 88) mapper = new Mapper088(); - if (mapperCode == 89) mapper = new Mapper089(); - if (mapperCode == 93) mapper = new Mapper093(); - if (mapperCode == 94) mapper = new Mapper094(); - if (mapperCode == 97) mapper = new Mapper097(); - if (mapperCode == 113) mapper = new Mapper113(); - if (mapperCode == 140) mapper = new Mapper140(); - if (mapperCode == 152) mapper = new Mapper152(); - if (mapperCode == 154) mapper = new Mapper154(); - if (mapperCode == 156) mapper = new Mapper156(); - if (mapperCode == 180) mapper = new Mapper180(); - if (mapperCode == 184) mapper = new Mapper184(); - if (mapperCode == 190) mapper = new Mapper190(); - if (mapperCode == 193) mapper = new Mapper193(); - if (mapperCode == 206) mapper = new Mapper206(); - if (mapperCode == 207) mapper = new Mapper207(); - if (mapperCode == 232) mapper = new Mapper232(); - if (mapperCode == 240) mapper = new Mapper240(); - if (mapperCode == 241) mapper = new Mapper241(); - if (mapperCode == 244) mapper = new Mapper244(); - if (mapperCode == 246) mapper = new Mapper246(); + // Now checking if the detected mapper code is supported + if (mapperCode == 0) mapper = new Mapper000(); + if (mapperCode == 1) mapper = new Mapper001(); + if (mapperCode == 2) mapper = new Mapper002(); + if (mapperCode == 3) mapper = new Mapper003(); + if (mapperCode == 4) mapper = new Mapper004(); + if (mapperCode == 5) mapper = new Mapper005(); + if (mapperCode == 7) mapper = new Mapper007(); + if (mapperCode == 9) mapper = new Mapper009(); + if (mapperCode == 10) mapper = new Mapper010(); + if (mapperCode == 11) mapper = new Mapper011(); + if (mapperCode == 15) mapper = new Mapper015(); + if (mapperCode == 19) mapper = new Mapper019(); + if (mapperCode == 21) mapper = new Mapper021(); + if (mapperCode == 22) mapper = new Mapper022(); + if (mapperCode == 23) mapper = new Mapper023(); + if (mapperCode == 24) mapper = new Mapper024(); + if (mapperCode == 25) mapper = new Mapper025(); + if (mapperCode == 26) mapper = new Mapper026(); + if (mapperCode == 30) mapper = new Mapper030(); + if (mapperCode == 32) mapper = new Mapper032(); + if (mapperCode == 33) mapper = new Mapper033(); + if (mapperCode == 34) mapper = new Mapper034(); + if (mapperCode == 60) mapper = new Mapper060(); + if (mapperCode == 66) mapper = new Mapper066(); + if (mapperCode == 69) mapper = new Mapper069(); + if (mapperCode == 70) mapper = new Mapper070(); + if (mapperCode == 71) mapper = new Mapper071(); + if (mapperCode == 73) mapper = new Mapper073(); + if (mapperCode == 75) mapper = new Mapper075(); + if (mapperCode == 78) mapper = new Mapper078(); + if (mapperCode == 79) mapper = new Mapper079(); + if (mapperCode == 85) mapper = new Mapper085(); + if (mapperCode == 86) mapper = new Mapper086(); + if (mapperCode == 87) mapper = new Mapper087(); + if (mapperCode == 88) mapper = new Mapper088(); + if (mapperCode == 89) mapper = new Mapper089(); + if (mapperCode == 93) mapper = new Mapper093(); + if (mapperCode == 94) mapper = new Mapper094(); + if (mapperCode == 97) mapper = new Mapper097(); + if (mapperCode == 113) mapper = new Mapper113(); + if (mapperCode == 140) mapper = new Mapper140(); + if (mapperCode == 152) mapper = new Mapper152(); + if (mapperCode == 154) mapper = new Mapper154(); + if (mapperCode == 156) mapper = new Mapper156(); + if (mapperCode == 180) mapper = new Mapper180(); + if (mapperCode == 184) mapper = new Mapper184(); + if (mapperCode == 190) mapper = new Mapper190(); + if (mapperCode == 193) mapper = new Mapper193(); + if (mapperCode == 206) mapper = new Mapper206(); + if (mapperCode == 207) mapper = new Mapper207(); + if (mapperCode == 232) mapper = new Mapper232(); + if (mapperCode == 240) mapper = new Mapper240(); + if (mapperCode == 241) mapper = new Mapper241(); + if (mapperCode == 244) mapper = new Mapper244(); + if (mapperCode == 246) mapper = new Mapper246(); - return mapper; -} \ No newline at end of file + return mapper; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper.h b/source/quickerNES/mappers/mapper.h deleted file mode 100644 index db2b78f..0000000 --- a/source/quickerNES/mappers/mapper.h +++ /dev/null @@ -1,203 +0,0 @@ -#pragma once - -// NES mapper interface - -// Nes_Emu 0.7.0 - -#include -#include "Nes_Cart.h" -#include "Nes_Cpu.h" - -class Blip_Buffer; -class blip_eq_t; -class Nes_Core; - -// Increase this (and let me know) if your mapper requires more state. This only -// sets the size of the in-memory buffer; it doesn't affect the file format at all. -static unsigned const max_mapper_state_size = 512; //was 256, needed more for VRC7 audio state -struct mapper_state_t -{ - int size; - union { - double align; - uint8_t data [max_mapper_state_size]; - }; - - void write( const void* p, unsigned long s ); - int read( void* p, unsigned long s ) const; -}; - -class Nes_Mapper { -public: - virtual ~Nes_Mapper(); - - // Reset mapper to power-up state. - virtual void reset(); - - // Save snapshot of mapper state. Default saves registered state. - virtual void save_state( mapper_state_t& ); - - // Resets mapper, loads state, then applies it - virtual void load_state( mapper_state_t const& ); - -// I/O - - // Read from memory - virtual int read( nes_time_t, nes_addr_t ); - - // Write to memory - virtual void write( nes_time_t, nes_addr_t, int data ) = 0; - - // Write to memory below 0x8000 (returns false if mapper didn't handle write) - virtual bool write_intercepted( nes_time_t, nes_addr_t, int data ); - -// Timing - - // Time returned when current mapper state won't ever cause an IRQ - enum { no_irq = LONG_MAX / 2 }; - - // Time next IRQ will occur at - virtual nes_time_t next_irq( nes_time_t present ); - - // Run mapper until given time - virtual void run_until( nes_time_t ); - - // End video frame of given length - virtual void end_frame( nes_time_t length ); - -// Sound - - // Number of sound channels - virtual int channel_count() const; - - // Set sound buffer for channel to output to, or NULL to silence channel. - virtual void set_channel_buf( int index, Blip_Buffer* ); - - // Set treble equalization - virtual void set_treble( blip_eq_t const& ); - -// Misc - - // Called when bit 12 of PPU's VRAM address changes from 0 to 1 due to - // $2006 and $2007 accesses (but not due to PPU scanline rendering). - virtual void a12_clocked(); - - void* state; - unsigned state_size; - -protected: - // Services provided for derived mapper classes - Nes_Mapper(); - - // Register state data to automatically save and load. Be sure the binary - // layout is suitable for use in a file, including any byte-order issues. - // Automatically cleared to zero by default reset(). - void register_state( void*, unsigned ); - - // Enable 8K of RAM at 0x6000-0x7FFF, optionally read-only. - void enable_sram( bool enabled = true, bool read_only = false ); - - // Cause CPU writes within given address range to call mapper's write() function. - // Might map a larger address range, which the mapper can ignore and pass to - // Nes_Mapper::write(). The range 0x8000-0xffff is always intercepted by the mapper. - void intercept_writes( nes_addr_t addr, unsigned size ); - - // Cause CPU reads within given address range to call mapper's read() function. - // Might map a larger address range, which the mapper can ignore and pass to - // Nes_Mapper::read(). CPU opcode/operand reads and low-memory reads always - // go directly to memory and cannot be intercepted. - void intercept_reads( nes_addr_t addr, unsigned size ); - - // Bank sizes for mapping - enum bank_size_t { // 1 << bank_Xk = X * 1024 - bank_1k = 10, - bank_2k = 11, - bank_4k = 12, - bank_8k = 13, - bank_16k = 14, - bank_32k = 15 - }; - - // Index of last PRG/CHR bank. Last_bank selects last bank, last_bank - 1 - // selects next-to-last bank, etc. - enum { last_bank = -1 }; - - // Map 'size' bytes from 'PRG + bank * size' to CPU address space starting at 'addr' - void set_prg_bank( nes_addr_t addr, bank_size_t size, int bank ); - - // Map 'size' bytes from 'CHR + bank * size' to PPU address space starting at 'addr' - void set_chr_bank( nes_addr_t addr, bank_size_t size, int bank ); - void set_chr_bank_ex( nes_addr_t addr, bank_size_t size, int bank ); - - // Set PPU mirroring. All mappings implemented using mirror_manual(). - void mirror_manual( int page0, int page1, int page2, int page3 ); - void mirror_single( int page ); - void mirror_horiz( int page = 0 ); - void mirror_vert( int page = 0 ); - void mirror_full(); - - // True if PPU rendering is enabled. Some mappers watch PPU memory accesses to determine - // when scanlines occur, and can only do this when rendering is enabled. - bool ppu_enabled() const; - - // Cartridge being emulated - Nes_Cart const& cart() const { return *cart_; } - - // Must be called when next_irq()'s return value is earlier than previous, - // current CPU run can be stopped earlier. Best to call whenever time may - // have changed (no performance impact if called even when time didn't change). - void irq_changed(); - - // Handle data written to mapper that doesn't handle bus conflict arising due to - // PRG also reading data. Returns data that mapper should act as if were - // written. Currently always returns 'data' and just checks that data written is - // the same as byte in PRG at same address and writes debug message if it doesn't. - int handle_bus_conflict( nes_addr_t addr, int data ); - - // Reference to emulator that uses this mapper. - Nes_Core& emu() const { return *emu_; } - -protected: - // Services derived classes provide - - // Read state from snapshot. Default reads data into registered state, then calls - // apply_mapping(). - virtual void read_state( mapper_state_t const& ); - - - - // Called by default reset() before apply_mapping() is called. - virtual void reset_state() { } - - // End of general interface - - public: - - Nes_Cart const* cart_; - Nes_Core* emu_; - - // Apply current mapping state to hardware. Called after reading mapper state - // from a snapshot. - virtual void apply_mapping() = 0; - - void default_reset_state(); - - static Nes_Mapper* getMapperFromCode(const int mapperCode); -}; - - -inline int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data ) { return data; } -inline void Nes_Mapper::mirror_horiz( int p ) { mirror_manual( p, p, p ^ 1, p ^ 1 ); } -inline void Nes_Mapper::mirror_vert( int p ) { mirror_manual( p, p ^ 1, p, p ^ 1 ); } -inline void Nes_Mapper::mirror_single( int p ) { mirror_manual( p, p, p, p ); } -inline void Nes_Mapper::mirror_full() { mirror_manual( 0, 1, 2, 3 ); } - -inline void Nes_Mapper::register_state( void* p, unsigned s ) -{ - state = p; - state_size = s; -} - -inline bool Nes_Mapper::write_intercepted( nes_time_t, nes_addr_t, int ) { return false; } - -inline int Nes_Mapper::read( nes_time_t, nes_addr_t ) { return -1; } // signal to caller diff --git a/source/quickerNES/mappers/mapper.hpp b/source/quickerNES/mappers/mapper.hpp new file mode 100644 index 0000000..9c6e10a --- /dev/null +++ b/source/quickerNES/mappers/mapper.hpp @@ -0,0 +1,212 @@ +#pragma once + +// NES mapper interface +// Emu 0.7.0 + +#include +#include "cart.hpp" +#include "cpu.hpp" + +namespace quickerNES +{ + +class Blip_Buffer; +class blip_eq_t; +class Core; + +// Increase this (and let me know) if your mapper requires more state. This only +// sets the size of the in-memory buffer; it doesn't affect the file format at all. +static unsigned const max_mapper_state_size = 512; // was 256, needed more for VRC7 audio state +struct mapper_state_t +{ + int size; + union + { + double align; + uint8_t data[max_mapper_state_size]; + }; + + void write(const void *p, unsigned long s); + int read(void *p, unsigned long s) const; +}; + +class Mapper +{ + public: + virtual ~Mapper(); + + // Reset mapper to power-up state. + virtual void reset(); + + // Save snapshot of mapper state. Default saves registered state. + virtual void save_state(mapper_state_t &); + + // Resets mapper, loads state, then applies it + virtual void load_state(mapper_state_t const &); + + // I/O + + // Read from memory + virtual int read(nes_time_t, nes_addr_t); + + // Write to memory + virtual void write(nes_time_t, nes_addr_t, int data) = 0; + + // Write to memory below 0x8000 (returns false if mapper didn't handle write) + virtual bool write_intercepted(nes_time_t, nes_addr_t, int data); + + // Timing + + // Time returned when current mapper state won't ever cause an IRQ + enum + { + no_irq = LONG_MAX / 2 + }; + + // Time next IRQ will occur at + virtual nes_time_t next_irq(nes_time_t present); + + // Run mapper until given time + virtual void run_until(nes_time_t); + + // End video frame of given length + virtual void end_frame(nes_time_t length); + + // Sound + + // Number of sound channels + virtual int channel_count() const; + + // Set sound buffer for channel to output to, or NULL to silence channel. + virtual void set_channel_buf(int index, Blip_Buffer *); + + // Set treble equalization + virtual void set_treble(blip_eq_t const &); + + // Misc + + // Called when bit 12 of PPU's VRAM address changes from 0 to 1 due to + // $2006 and $2007 accesses (but not due to PPU scanline rendering). + virtual void a12_clocked(); + + void *state; + unsigned state_size; + + protected: + // Services provided for derived mapper classes + Mapper(); + + // Register state data to automatically save and load. Be sure the binary + // layout is suitable for use in a file, including any byte-order issues. + // Automatically cleared to zero by default reset(). + void register_state(void *, unsigned); + + // Enable 8K of RAM at 0x6000-0x7FFF, optionally read-only. + void enable_sram(bool enabled = true, bool read_only = false); + + // Cause CPU writes within given address range to call mapper's write() function. + // Might map a larger address range, which the mapper can ignore and pass to + // Mapper::write(). The range 0x8000-0xffff is always intercepted by the mapper. + void intercept_writes(nes_addr_t addr, unsigned size); + + // Cause CPU reads within given address range to call mapper's read() function. + // Might map a larger address range, which the mapper can ignore and pass to + // Mapper::read(). CPU opcode/operand reads and low-memory reads always + // go directly to memory and cannot be intercepted. + void intercept_reads(nes_addr_t addr, unsigned size); + + // Bank sizes for mapping + enum bank_size_t + { // 1 << bank_Xk = X * 1024 + bank_1k = 10, + bank_2k = 11, + bank_4k = 12, + bank_8k = 13, + bank_16k = 14, + bank_32k = 15 + }; + + // Index of last PRG/CHR bank. Last_bank selects last bank, last_bank - 1 + // selects next-to-last bank, etc. + enum + { + last_bank = -1 + }; + + // Map 'size' bytes from 'PRG + bank * size' to CPU address space starting at 'addr' + void set_prg_bank(nes_addr_t addr, bank_size_t size, int bank); + + // Map 'size' bytes from 'CHR + bank * size' to PPU address space starting at 'addr' + void set_chr_bank(nes_addr_t addr, bank_size_t size, int bank); + void set_chr_bank_ex(nes_addr_t addr, bank_size_t size, int bank); + + // Set PPU mirroring. All mappings implemented using mirror_manual(). + void mirror_manual(int page0, int page1, int page2, int page3); + void mirror_single(int page); + void mirror_horiz(int page = 0); + void mirror_vert(int page = 0); + void mirror_full(); + + // True if PPU rendering is enabled. Some mappers watch PPU memory accesses to determine + // when scanlines occur, and can only do this when rendering is enabled. + bool ppu_enabled() const; + + // Cartridge being emulated + Cart const &cart() const { return *cart_; } + + // Must be called when next_irq()'s return value is earlier than previous, + // current CPU run can be stopped earlier. Best to call whenever time may + // have changed (no performance impact if called even when time didn't change). + void irq_changed(); + + // Handle data written to mapper that doesn't handle bus conflict arising due to + // PRG also reading data. Returns data that mapper should act as if were + // written. Currently always returns 'data' and just checks that data written is + // the same as byte in PRG at same address and writes debug message if it doesn't. + int handle_bus_conflict(nes_addr_t addr, int data); + + // Reference to emulator that uses this mapper. + Core &emu() const { return *emu_; } + + protected: + // Services derived classes provide + + // Read state from snapshot. Default reads data into registered state, then calls + // apply_mapping(). + virtual void read_state(mapper_state_t const &); + + // Called by default reset() before apply_mapping() is called. + virtual void reset_state() {} + + // End of general interface + + public: + Cart const *cart_; + Core *emu_; + + // Apply current mapping state to hardware. Called after reading mapper state + // from a snapshot. + virtual void apply_mapping() = 0; + + void default_reset_state(); + + static Mapper *getMapperFromCode(const int mapperCode); +}; + +inline int Mapper::handle_bus_conflict(nes_addr_t addr, int data) { return data; } +inline void Mapper::mirror_horiz(int p) { mirror_manual(p, p, p ^ 1, p ^ 1); } +inline void Mapper::mirror_vert(int p) { mirror_manual(p, p ^ 1, p, p ^ 1); } +inline void Mapper::mirror_single(int p) { mirror_manual(p, p, p, p); } +inline void Mapper::mirror_full() { mirror_manual(0, 1, 2, 3); } + +inline void Mapper::register_state(void *p, unsigned s) +{ + state = p; + state_size = s; +} + +inline bool Mapper::write_intercepted(nes_time_t, nes_addr_t, int) { return false; } + +inline int Mapper::read(nes_time_t, nes_addr_t) { return -1; } // signal to caller + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper000.hpp b/source/quickerNES/mappers/mapper000.hpp index 3ec01e6..192803b 100644 --- a/source/quickerNES/mappers/mapper000.hpp +++ b/source/quickerNES/mappers/mapper000.hpp @@ -2,9 +2,9 @@ // Common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -19,14 +19,20 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // NROM -class Mapper000 : public Nes_Mapper { -public: - Mapper000() { } - - virtual void apply_mapping() { } - - virtual void write( nes_time_t, nes_addr_t, int ) - { - // empty - } +namespace quickerNES +{ + +class Mapper000 : public Mapper +{ + public: + Mapper000() {} + + virtual void apply_mapping() {} + + virtual void write(nes_time_t, nes_addr_t, int) + { + // empty + } }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper001.hpp b/source/quickerNES/mappers/mapper001.hpp index 4a97cf5..c22f8fa 100644 --- a/source/quickerNES/mappers/mapper001.hpp +++ b/source/quickerNES/mappers/mapper001.hpp @@ -1,8 +1,8 @@ #pragma once -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" #include /* Copyright (C) 2004-2006 Shay Green. This module is free software; you @@ -16,111 +16,114 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + // MMC1 struct mmc1_state_t { - uint8_t regs [4]; // current registers (5 bits each) - uint8_t bit; // number of bits in buffer (0 to 4) - uint8_t buf; // currently buffered bits (new bits added to bottom) + uint8_t regs[4]; // current registers (5 bits each) + uint8_t bit; // number of bits in buffer (0 to 4) + uint8_t buf; // currently buffered bits (new bits added to bottom) }; -static_assert( sizeof (mmc1_state_t) == 6 ); +static_assert(sizeof(mmc1_state_t) == 6); -class Mapper001 : public Nes_Mapper, mmc1_state_t { -public: - Mapper001() - { - mmc1_state_t* state = this; - register_state( state, sizeof *state ); - } - - virtual void reset_state() - { - regs [0] = 0x0f; - regs [1] = 0x00; - regs [2] = 0x01; - regs [3] = 0x00; - } - - virtual void apply_mapping() - { - enable_sram(); // early MMC1 always had SRAM enabled - register_changed( 0 ); - } - - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( !(data & 0x80) ) - { - buf |= (data & 1) << bit; - bit++; - - if ( bit >= 5 ) - { - int reg = addr >> 13 & 3; - regs [reg] = buf & 0x1f; - - bit = 0; - buf = 0; - - register_changed( reg ); - } - } - else - { - bit = 0; - buf = 0; - regs [0] |= 0x0c; - register_changed( 0 ); - } - } +class Mapper001 : public Mapper, mmc1_state_t +{ + public: + Mapper001() + { + mmc1_state_t *state = this; + register_state(state, sizeof *state); + } - - void register_changed( int reg ) - { - // Mirroring - if ( reg == 0 ) - { - int mode = regs [0] & 3; - if ( mode < 2 ) - mirror_single( mode & 1 ); - else if ( mode == 2 ) - mirror_vert(); - else - mirror_horiz(); - } - - // CHR - if ( reg < 3 && cart().chr_size() > 0 ) - { - if ( regs [0] & 0x10 ) - { - set_chr_bank( 0x0000, bank_4k, regs [1] ); - set_chr_bank( 0x1000, bank_4k, regs [2] ); - } - else - { - set_chr_bank( 0, bank_8k, regs [1] >> 1 ); - } - } - - // PRG - int bank = (regs [1] & 0x10) | (regs [3] & 0x0f); - if ( !(regs [0] & 0x08) ) - { - set_prg_bank( 0x8000, bank_32k, bank >> 1 ); - } - else if ( regs [0] & 0x04 ) - { - set_prg_bank( 0x8000, bank_16k, bank ); - set_prg_bank( 0xC000, bank_16k, bank | 0x0f ); - } - else - { - set_prg_bank( 0x8000, bank_16k, bank & ~0x0f ); - set_prg_bank( 0xC000, bank_16k, bank ); - } - } + virtual void reset_state() + { + regs[0] = 0x0f; + regs[1] = 0x00; + regs[2] = 0x01; + regs[3] = 0x00; + } + + virtual void apply_mapping() + { + enable_sram(); // early MMC1 always had SRAM enabled + register_changed(0); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (!(data & 0x80)) + { + buf |= (data & 1) << bit; + bit++; + + if (bit >= 5) + { + int reg = addr >> 13 & 3; + regs[reg] = buf & 0x1f; + + bit = 0; + buf = 0; + + register_changed(reg); + } + } + else + { + bit = 0; + buf = 0; + regs[0] |= 0x0c; + register_changed(0); + } + } + + void register_changed(int reg) + { + // Mirroring + if (reg == 0) + { + int mode = regs[0] & 3; + if (mode < 2) + mirror_single(mode & 1); + else if (mode == 2) + mirror_vert(); + else + mirror_horiz(); + } + + // CHR + if (reg < 3 && cart().chr_size() > 0) + { + if (regs[0] & 0x10) + { + set_chr_bank(0x0000, bank_4k, regs[1]); + set_chr_bank(0x1000, bank_4k, regs[2]); + } + else + { + set_chr_bank(0, bank_8k, regs[1] >> 1); + } + } + + // PRG + int bank = (regs[1] & 0x10) | (regs[3] & 0x0f); + if (!(regs[0] & 0x08)) + { + set_prg_bank(0x8000, bank_32k, bank >> 1); + } + else if (regs[0] & 0x04) + { + set_prg_bank(0x8000, bank_16k, bank); + set_prg_bank(0xC000, bank_16k, bank | 0x0f); + } + else + { + set_prg_bank(0x8000, bank_16k, bank & ~0x0f); + set_prg_bank(0xC000, bank_16k, bank); + } + } }; - +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper002.hpp b/source/quickerNES/mappers/mapper002.hpp index 4377f9e..5118c0c 100644 --- a/source/quickerNES/mappers/mapper002.hpp +++ b/source/quickerNES/mappers/mapper002.hpp @@ -2,9 +2,9 @@ // Common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -17,26 +17,32 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + // UNROM -class Mapper002 : public Nes_Mapper { - uint8_t bank; -public: - Mapper002() - { - register_state( &bank, 1 ); - } - - virtual void apply_mapping() - { - enable_sram(); // at least one UNROM game needs sram (Bomberman 2) - set_prg_bank( 0x8000, bank_16k, bank ); - } - - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - bank = handle_bus_conflict( addr, data ); - set_prg_bank( 0x8000, bank_16k, data ); - } +class Mapper002 : public Mapper +{ + uint8_t bank; + + public: + Mapper002() + { + register_state(&bank, 1); + } + + virtual void apply_mapping() + { + enable_sram(); // at least one UNROM game needs sram (Bomberman 2) + set_prg_bank(0x8000, bank_16k, bank); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + bank = handle_bus_conflict(addr, data); + set_prg_bank(0x8000, bank_16k, data); + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper003.hpp b/source/quickerNES/mappers/mapper003.hpp index d6b99cb..5f935d1 100644 --- a/source/quickerNES/mappers/mapper003.hpp +++ b/source/quickerNES/mappers/mapper003.hpp @@ -2,9 +2,9 @@ // Common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -17,25 +17,31 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + // CNROM -class Mapper003 : public Nes_Mapper { - uint8_t bank; -public: - Mapper003() - { - register_state( &bank, 1 ); - } - - virtual void apply_mapping() - { - set_chr_bank( 0, bank_8k, bank & 7 ); - } - - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - bank = handle_bus_conflict( addr, data ); - set_chr_bank( 0, bank_8k, bank & 7 ); - } +class Mapper003 : public Mapper +{ + uint8_t bank; + + public: + Mapper003() + { + register_state(&bank, 1); + } + + virtual void apply_mapping() + { + set_chr_bank(0, bank_8k, bank & 7); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + bank = handle_bus_conflict(addr, data); + set_chr_bank(0, bank_8k, bank & 7); + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper004.hpp b/source/quickerNES/mappers/mapper004.hpp index 3637dc4..3147a47 100644 --- a/source/quickerNES/mappers/mapper004.hpp +++ b/source/quickerNES/mappers/mapper004.hpp @@ -1,11 +1,11 @@ #pragma once -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" +#include "core.hpp" #include -#include "Nes_Core.h" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -18,235 +18,241 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + // 264 or less breaks Gargoyle's Quest II // 267 or less breaks Magician int const irq_fine_tune = 268; -nes_time_t const first_scanline = 20 * Nes_Ppu::scanline_len + irq_fine_tune; -nes_time_t const last_scanline = first_scanline + 240 * Nes_Ppu::scanline_len; +nes_time_t const first_scanline = 20 * Ppu::scanline_len + irq_fine_tune; +nes_time_t const last_scanline = first_scanline + 240 * Ppu::scanline_len; // MMC3 struct mmc3_state_t { - uint8_t banks [8]; // last writes to $8001 indexed by (mode & 7) - uint8_t mode; // $8000 - uint8_t mirror; // $a000 - uint8_t sram_mode; // $a001 - uint8_t irq_ctr; // internal counter - uint8_t irq_latch; // $c000 - uint8_t irq_enabled;// last write was to 0) $e000, 1) $e001 - uint8_t irq_flag; + uint8_t banks[8]; // last writes to $8001 indexed by (mode & 7) + uint8_t mode; // $8000 + uint8_t mirror; // $a000 + uint8_t sram_mode; // $a001 + uint8_t irq_ctr; // internal counter + uint8_t irq_latch; // $c000 + uint8_t irq_enabled; // last write was to 0) $e000, 1) $e001 + uint8_t irq_flag; }; -static_assert( sizeof (mmc3_state_t) == 15 ); +static_assert(sizeof(mmc3_state_t) == 15); -class Mapper004 : public Nes_Mapper, mmc3_state_t { -public: - Mapper004() - { - mmc3_state_t* state = this; - register_state( state, sizeof *state ); - } - - virtual void reset_state() - { - memcpy( banks, "\0\2\4\5\6\7\0\1", sizeof banks ); - - counter_just_clocked = 0; - next_time = 0; - mirror = 1; +class Mapper004 : public Mapper, mmc3_state_t +{ + public: + Mapper004() + { + mmc3_state_t *state = this; + register_state(state, sizeof *state); + } - /* Cart specified vertical mirroring */ - if ( cart().mirroring() & 1 ) - mirror = 0; - } - - void start_frame() { next_time = first_scanline; } - - virtual void apply_mapping() - { - write( 0, 0xA000, mirror ); - write( 0, 0xA001, sram_mode ); - update_chr_banks(); - update_prg_banks(); - start_frame(); - } - - void clock_counter() - { - if ( counter_just_clocked ) - counter_just_clocked--; - - if ( !irq_ctr-- ) - { - irq_ctr = irq_latch; - //if ( !irq_latch ) - //dprintf( "MMC3 IRQ counter reloaded with 0\n" ); - } - - //dprintf( "%6d MMC3 IRQ clocked\n", time / ppu_overclock ); - if ( irq_ctr == 0 ) - { - //if ( irq_enabled && !irq_flag ) - //dprintf( "%6d MMC3 IRQ triggered: %f\n", time / ppu_overclock, time / scanline_len.0 - 20 ); - irq_flag = irq_enabled; - } - } - - - virtual void a12_clocked() - { - clock_counter(); - if ( irq_enabled ) - irq_changed(); - } - - virtual void end_frame( nes_time_t end_time ) - { - run_until( end_time ); - start_frame(); - } - - virtual nes_time_t next_irq( nes_time_t present ) - { - run_until( present ); - - if ( !irq_enabled ) - return no_irq; - - if ( irq_flag ) - return 0; - - if ( !ppu_enabled() ) - return no_irq; - - int remain = irq_ctr - 1; - if ( remain < 0 ) - remain = irq_latch; - - long time = remain * 341L + next_time; - if ( time > last_scanline ) - return no_irq; - - return time / ppu_overclock + 1; - } + virtual void reset_state() + { + memcpy(banks, "\0\2\4\5\6\7\0\1", sizeof banks); - void run_until( nes_time_t end_time ) - { - bool bg_enabled = ppu_enabled(); - - if (next_time < 0) next_time = 0; + counter_just_clocked = 0; + next_time = 0; + mirror = 1; - end_time *= ppu_overclock; - while ( next_time < end_time && next_time <= last_scanline ) - { - if ( bg_enabled ) - clock_counter(); - next_time += Nes_Ppu::scanline_len; - } - } + /* Cart specified vertical mirroring */ + if (cart().mirroring() & 1) + mirror = 0; + } - void update_chr_banks() - { - int chr_xor = (mode >> 7 & 1) * 0x1000; - set_chr_bank( 0x0000 ^ chr_xor, bank_2k, banks [0] >> 1 ); - set_chr_bank( 0x0800 ^ chr_xor, bank_2k, banks [1] >> 1 ); - set_chr_bank( 0x1000 ^ chr_xor, bank_1k, banks [2] ); - set_chr_bank( 0x1400 ^ chr_xor, bank_1k, banks [3] ); - set_chr_bank( 0x1800 ^ chr_xor, bank_1k, banks [4] ); - set_chr_bank( 0x1c00 ^ chr_xor, bank_1k, banks [5] ); - } + void start_frame() { next_time = first_scanline; } - void update_prg_banks() - { - set_prg_bank( 0xA000, bank_8k, banks [7] ); - nes_addr_t addr = 0x8000 + 0x4000 * (mode >> 6 & 1); - set_prg_bank( addr, bank_8k, banks [6] ); - set_prg_bank( addr ^ 0x4000, bank_8k, last_bank - 1 ); - } + virtual void apply_mapping() + { + write(0, 0xA000, mirror); + write(0, 0xA001, sram_mode); + update_chr_banks(); + update_prg_banks(); + start_frame(); + } - void write_irq( nes_addr_t addr, int data ) - { - switch ( addr & 0xE001 ) - { - case 0xC000: - irq_latch = data; - break; - - case 0xC001: - /* MMC3 IRQ counter pathological behavior triggered if - * counter_just_clocked is 1 */ - counter_just_clocked = 2; - irq_ctr = 0; - break; - - case 0xE000: - irq_flag = false; - irq_enabled = false; - break; - - case 0xE001: - irq_enabled = true; - break; - } - if ( irq_enabled ) - irq_changed(); - } + void clock_counter() + { + if (counter_just_clocked) + counter_just_clocked--; - void write( nes_time_t time, nes_addr_t addr, int data ) - { - switch ( addr & 0xE001 ) - { - case 0x8000: { - int changed = mode ^ data; - mode = data; - // avoid unnecessary bank updates - if ( changed & 0x80 ) - update_chr_banks(); - if ( changed & 0x40 ) - update_prg_banks(); - break; - } - - case 0x8001: { - int bank = mode & 7; - banks [bank] = data; - if ( bank < 6 ) - update_chr_banks(); - else - update_prg_banks(); - break; - } - - case 0xA000: - mirror = data; - if ( !(cart().mirroring() & 0x08) ) - { - if ( mirror & 1 ) - mirror_horiz(); - else - mirror_vert(); - } - break; - - case 0xA001: - sram_mode = data; - //dprintf( "%02X->%04X\n", data, addr ); - - // Startropics 1 & 2 use MMC6 and always enable low 512 bytes of SRAM - if ( (data & 0x3F) == 0x30 ) - enable_sram( true ); - else - enable_sram( data & 0x80, data & 0x40 ); - break; - - default: - run_until( time ); - write_irq( addr, data ); - break; - } - } + if (!irq_ctr--) + { + irq_ctr = irq_latch; + // if ( !irq_latch ) + // dprintf( "MMC3 IRQ counter reloaded with 0\n" ); + } - nes_time_t next_time; - int counter_just_clocked; // used only for debugging + // dprintf( "%6d MMC3 IRQ clocked\n", time / ppu_overclock ); + if (irq_ctr == 0) + { + // if ( irq_enabled && !irq_flag ) + // dprintf( "%6d MMC3 IRQ triggered: %f\n", time / ppu_overclock, time / scanline_len.0 - 20 ); + irq_flag = irq_enabled; + } + } + + virtual void a12_clocked() + { + clock_counter(); + if (irq_enabled) + irq_changed(); + } + + virtual void end_frame(nes_time_t end_time) + { + run_until(end_time); + start_frame(); + } + + virtual nes_time_t next_irq(nes_time_t present) + { + run_until(present); + + if (!irq_enabled) + return no_irq; + + if (irq_flag) + return 0; + + if (!ppu_enabled()) + return no_irq; + + int remain = irq_ctr - 1; + if (remain < 0) + remain = irq_latch; + + long time = remain * 341L + next_time; + if (time > last_scanline) + return no_irq; + + return time / ppu_overclock + 1; + } + + void run_until(nes_time_t end_time) + { + bool bg_enabled = ppu_enabled(); + + if (next_time < 0) next_time = 0; + + end_time *= ppu_overclock; + while (next_time < end_time && next_time <= last_scanline) + { + if (bg_enabled) + clock_counter(); + next_time += Ppu::scanline_len; + } + } + + void update_chr_banks() + { + int chr_xor = (mode >> 7 & 1) * 0x1000; + set_chr_bank(0x0000 ^ chr_xor, bank_2k, banks[0] >> 1); + set_chr_bank(0x0800 ^ chr_xor, bank_2k, banks[1] >> 1); + set_chr_bank(0x1000 ^ chr_xor, bank_1k, banks[2]); + set_chr_bank(0x1400 ^ chr_xor, bank_1k, banks[3]); + set_chr_bank(0x1800 ^ chr_xor, bank_1k, banks[4]); + set_chr_bank(0x1c00 ^ chr_xor, bank_1k, banks[5]); + } + + void update_prg_banks() + { + set_prg_bank(0xA000, bank_8k, banks[7]); + nes_addr_t addr = 0x8000 + 0x4000 * (mode >> 6 & 1); + set_prg_bank(addr, bank_8k, banks[6]); + set_prg_bank(addr ^ 0x4000, bank_8k, last_bank - 1); + } + + void write_irq(nes_addr_t addr, int data) + { + switch (addr & 0xE001) + { + case 0xC000: + irq_latch = data; + break; + + case 0xC001: + /* MMC3 IRQ counter pathological behavior triggered if + * counter_just_clocked is 1 */ + counter_just_clocked = 2; + irq_ctr = 0; + break; + + case 0xE000: + irq_flag = false; + irq_enabled = false; + break; + + case 0xE001: + irq_enabled = true; + break; + } + if (irq_enabled) + irq_changed(); + } + + void write(nes_time_t time, nes_addr_t addr, int data) + { + switch (addr & 0xE001) + { + case 0x8000: + { + int changed = mode ^ data; + mode = data; + // avoid unnecessary bank updates + if (changed & 0x80) + update_chr_banks(); + if (changed & 0x40) + update_prg_banks(); + break; + } + + case 0x8001: + { + int bank = mode & 7; + banks[bank] = data; + if (bank < 6) + update_chr_banks(); + else + update_prg_banks(); + break; + } + + case 0xA000: + mirror = data; + if (!(cart().mirroring() & 0x08)) + { + if (mirror & 1) + mirror_horiz(); + else + mirror_vert(); + } + break; + + case 0xA001: + sram_mode = data; + // dprintf( "%02X->%04X\n", data, addr ); + + // Startropics 1 & 2 use MMC6 and always enable low 512 bytes of SRAM + if ((data & 0x3F) == 0x30) + enable_sram(true); + else + enable_sram(data & 0x80, data & 0x40); + break; + + default: + run_until(time); + write_irq(addr, data); + break; + } + } + + nes_time_t next_time; + int counter_just_clocked; // used only for debugging }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper005.hpp b/source/quickerNES/mappers/mapper005.hpp index 89cb804..a10d49d 100644 --- a/source/quickerNES/mappers/mapper005.hpp +++ b/source/quickerNES/mappers/mapper005.hpp @@ -2,11 +2,10 @@ // NES MMC5 mapper, currently only tailored for Castlevania 3 (U) -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" - -#include "Nes_Core.h" +#include "core.hpp" +#include "mappers/mapper.hpp" #include /* Copyright (C) 2004-2006 Shay Green. This module is free software; you @@ -20,129 +19,134 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + struct mmc5_state_t { - enum { reg_count = 0x30 }; - uint8_t regs [0x30]; - uint8_t irq_enabled; + enum + { + reg_count = 0x30 + }; + uint8_t regs[0x30]; + uint8_t irq_enabled; }; -static_assert( sizeof (mmc5_state_t) == 0x31 ); +static_assert(sizeof(mmc5_state_t) == 0x31); // MMC5 -class Mapper005 : public Nes_Mapper, mmc5_state_t { -public: - Mapper005() - { - mmc5_state_t* state = this; - register_state( state, sizeof *state ); - } - - virtual void reset_state() - { - irq_time = no_irq; - regs [0x00] = 2; - regs [0x01] = 3; - regs [0x14] = 0x7f; - regs [0x15] = 0x7f; - regs [0x16] = 0x7f; - regs [0x17] = 0x7f; - } - - virtual void read_state( mapper_state_t const& in ) - { - Nes_Mapper::read_state( in ); - irq_time = no_irq; - } - - enum { regs_addr = 0x5100 }; - - virtual nes_time_t next_irq( nes_time_t ) - { - if ( irq_enabled & 0x80 ) - return irq_time; - - return no_irq; - } - - virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) - { - int reg = addr - regs_addr; - if ( (unsigned) reg < reg_count ) - { - regs [reg] = data; - switch ( reg ) - { - case 0x05: - mirror_manual( data & 3, data >> 2 & 3, - data >> 4 & 3, data >> 6 & 3 ); - break; - - case 0x15: - set_prg_bank( 0x8000, bank_16k, data >> 1 & 0x3f ); - break; - - case 0x16: - set_prg_bank( 0xC000, bank_8k, data & 0x7f ); - break; - - case 0x17: - set_prg_bank( 0xE000, bank_8k, data & 0x7f ); - break; - - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x28: - case 0x29: - case 0x2a: - case 0x2b: - set_chr_bank( ((reg >> 1 & 4) + (reg & 3)) * 0x400, bank_1k, data ); - break; - } - } - else if ( addr == 0x5203 ) - { - irq_time = no_irq; - if ( data && data < 240 ) - { - irq_time = (341 * 21 + 128 + (data * 341)) / 3; - if ( irq_time < time ) - irq_time = no_irq; - } - irq_changed(); - } - else if ( addr == 0x5204 ) - { - irq_enabled = data; - irq_changed(); - } - else - { - return false; - } - - return true; - } - - void apply_mapping() - { - static unsigned char list [] = { - 0x05, 0x15, 0x16, 0x17, - 0x20, 0x21, 0x22, 0x23, - 0x28, 0x29, 0x2a, 0x2b - }; - - for ( int i = 0; i < (int) sizeof list; i++ ) - write_intercepted( 0, regs_addr + list [i], regs [list [i]] ); - intercept_writes( 0x5100, 0x200 ); - } +class Mapper005 : public Mapper, mmc5_state_t +{ + public: + Mapper005() + { + mmc5_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void write( nes_time_t, nes_addr_t, int ) { } + virtual void reset_state() + { + irq_time = no_irq; + regs[0x00] = 2; + regs[0x01] = 3; + regs[0x14] = 0x7f; + regs[0x15] = 0x7f; + regs[0x16] = 0x7f; + regs[0x17] = 0x7f; + } - nes_time_t irq_time; + virtual void read_state(mapper_state_t const &in) + { + Mapper::read_state(in); + irq_time = no_irq; + } + + enum + { + regs_addr = 0x5100 + }; + + virtual nes_time_t next_irq(nes_time_t) + { + if (irq_enabled & 0x80) + return irq_time; + + return no_irq; + } + + virtual bool write_intercepted(nes_time_t time, nes_addr_t addr, int data) + { + int reg = addr - regs_addr; + if ((unsigned)reg < reg_count) + { + regs[reg] = data; + switch (reg) + { + case 0x05: + mirror_manual(data & 3, data >> 2 & 3, data >> 4 & 3, data >> 6 & 3); + break; + + case 0x15: + set_prg_bank(0x8000, bank_16k, data >> 1 & 0x3f); + break; + + case 0x16: + set_prg_bank(0xC000, bank_8k, data & 0x7f); + break; + + case 0x17: + set_prg_bank(0xE000, bank_8k, data & 0x7f); + break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + set_chr_bank(((reg >> 1 & 4) + (reg & 3)) * 0x400, bank_1k, data); + break; + } + } + else if (addr == 0x5203) + { + irq_time = no_irq; + if (data && data < 240) + { + irq_time = (341 * 21 + 128 + (data * 341)) / 3; + if (irq_time < time) + irq_time = no_irq; + } + irq_changed(); + } + else if (addr == 0x5204) + { + irq_enabled = data; + irq_changed(); + } + else + { + return false; + } + + return true; + } + + void apply_mapping() + { + static unsigned char list[] = { + 0x05, 0x15, 0x16, 0x17, 0x20, 0x21, 0x22, 0x23, 0x28, 0x29, 0x2a, 0x2b}; + + for (int i = 0; i < (int)sizeof list; i++) + write_intercepted(0, regs_addr + list[i], regs[list[i]]); + intercept_writes(0x5100, 0x200); + } + + virtual void write(nes_time_t, nes_addr_t, int) {} + + nes_time_t irq_time; }; - - +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper007.hpp b/source/quickerNES/mappers/mapper007.hpp index 4b22eae..3e5a41d 100644 --- a/source/quickerNES/mappers/mapper007.hpp +++ b/source/quickerNES/mappers/mapper007.hpp @@ -2,9 +2,9 @@ // Common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -17,34 +17,39 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + // AOROM -class Mapper007 : public Nes_Mapper { - uint8_t bank; -public: - Mapper007() - { - register_state( &bank, 1 ); - } - - virtual void apply_mapping() - { - int b = bank; - bank = ~b; // force update - write( 0, 0, b ); - } - - virtual void write( nes_time_t, nes_addr_t, int data ) - { - int changed = bank ^ data; - bank = data; - - if ( changed & 0x10 ) - mirror_single( bank >> 4 & 1 ); - - if ( changed & 0x0f ) - set_prg_bank( 0x8000, bank_32k, bank & 7 ); - } -}; - +class Mapper007 : public Mapper +{ + uint8_t bank; + public: + Mapper007() + { + register_state(&bank, 1); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; // force update + write(0, 0, b); + } + + virtual void write(nes_time_t, nes_addr_t, int data) + { + int changed = bank ^ data; + bank = data; + + if (changed & 0x10) + mirror_single(bank >> 4 & 1); + + if (changed & 0x0f) + set_prg_bank(0x8000, bank_32k, bank & 7); + } +}; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper009.hpp b/source/quickerNES/mappers/mapper009.hpp index 4fe2d14..94fdcd7 100644 --- a/source/quickerNES/mappers/mapper009.hpp +++ b/source/quickerNES/mappers/mapper009.hpp @@ -1,59 +1,81 @@ #pragma once +#include "mappers/mapper.hpp" #include -#include "mappers/mapper.h" -// MMC2 - -class Mapper009: public Nes_Mapper +namespace quickerNES { - uint8_t regs[6]; // A,B,C,D,E,F - void mirror(uint8_t val) - { - if (val & 1) - mirror_horiz(); - else - mirror_vert(); - } +// MMC2 -public: - Mapper009() - { - register_state(regs, sizeof(regs)); - } +class Mapper009 : public Mapper +{ + uint8_t regs[6]; // A,B,C,D,E,F - virtual void reset_state() - { - std::memset(regs, 0, sizeof(regs)); - } + void mirror(uint8_t val) + { + if (val & 1) + mirror_horiz(); + else + mirror_vert(); + } - virtual void apply_mapping() - { - mirror(regs[5]); - set_prg_bank(0x8000, bank_8k, regs[0]); - set_prg_bank(0xa000, bank_8k, 13); - set_prg_bank(0xc000, bank_8k, 14); - set_prg_bank(0xe000, bank_8k, 15); + public: + Mapper009() + { + register_state(regs, sizeof(regs)); + } - set_chr_bank(0x0000, bank_4k, regs[1]); - set_chr_bank(0x1000, bank_4k, regs[3]); + virtual void reset_state() + { + std::memset(regs, 0, sizeof(regs)); + } - set_chr_bank_ex(0x0000, bank_4k, regs[2]); - set_chr_bank_ex(0x1000, bank_4k, regs[4]); - } + virtual void apply_mapping() + { + mirror(regs[5]); + set_prg_bank(0x8000, bank_8k, regs[0]); + set_prg_bank(0xa000, bank_8k, 13); + set_prg_bank(0xc000, bank_8k, 14); + set_prg_bank(0xe000, bank_8k, 15); - virtual void write(nes_time_t, nes_addr_t addr, int data) - { - switch (addr >> 12) - { - case 0xa: regs[0] = data; set_prg_bank(0x8000, bank_8k, data); break; - case 0xb: regs[1] = data; set_chr_bank(0x0000, bank_4k, data); break; - case 0xc: regs[2] = data; set_chr_bank_ex(0x0000, bank_4k, data); break; - case 0xd: regs[3] = data; set_chr_bank(0x1000, bank_4k, data); break; - case 0xe: regs[4] = data; set_chr_bank_ex(0x1000, bank_4k, data); break; - case 0xf: regs[5] = data; mirror(data); break; - } - } + set_chr_bank(0x0000, bank_4k, regs[1]); + set_chr_bank(0x1000, bank_4k, regs[3]); + + set_chr_bank_ex(0x0000, bank_4k, regs[2]); + set_chr_bank_ex(0x1000, bank_4k, regs[4]); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr >> 12) + { + case 0xa: + regs[0] = data; + set_prg_bank(0x8000, bank_8k, data); + break; + case 0xb: + regs[1] = data; + set_chr_bank(0x0000, bank_4k, data); + break; + case 0xc: + regs[2] = data; + set_chr_bank_ex(0x0000, bank_4k, data); + break; + case 0xd: + regs[3] = data; + set_chr_bank(0x1000, bank_4k, data); + break; + case 0xe: + regs[4] = data; + set_chr_bank_ex(0x1000, bank_4k, data); + break; + case 0xf: + regs[5] = data; + mirror(data); + break; + } + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper010.hpp b/source/quickerNES/mappers/mapper010.hpp index 6e2679b..98d4559 100644 --- a/source/quickerNES/mappers/mapper010.hpp +++ b/source/quickerNES/mappers/mapper010.hpp @@ -1,56 +1,79 @@ #pragma once +#include "mappers/mapper.hpp" #include -#include "mappers/mapper.h" -// MMC4 - -class Mapper010: public Nes_Mapper +namespace quickerNES { - uint8_t regs[6]; // A,B,C,D,E,F - void mirror(uint8_t val) - { - if (val & 1) - mirror_horiz(); - else - mirror_vert(); - } +// MMC4 -public: - Mapper010() - { - register_state(regs, sizeof(regs)); - } +class Mapper010 : public Mapper +{ + uint8_t regs[6]; // A,B,C,D,E,F - virtual void reset_state() - { - std::memset(regs, 0, sizeof(regs)); - } + void mirror(uint8_t val) + { + if (val & 1) + mirror_horiz(); + else + mirror_vert(); + } - virtual void apply_mapping() - { - enable_sram(); + public: + Mapper010() + { + register_state(regs, sizeof(regs)); + } - mirror(regs[5]); - set_prg_bank(0x8000, bank_16k, regs[0]); + virtual void reset_state() + { + std::memset(regs, 0, sizeof(regs)); + } - set_chr_bank(0x0000, bank_4k, regs[1]); - set_chr_bank(0x1000, bank_4k, regs[3]); + virtual void apply_mapping() + { + enable_sram(); - set_chr_bank_ex(0x0000, bank_4k, regs[2]); - set_chr_bank_ex(0x1000, bank_4k, regs[4]); - } + mirror(regs[5]); + set_prg_bank(0x8000, bank_16k, regs[0]); - virtual void write(nes_time_t, nes_addr_t addr, int data) - { - switch (addr >> 12) - { - case 0xa: regs[0] = data; set_prg_bank(0x8000, bank_16k, data); break; - case 0xb: regs[1] = data; set_chr_bank(0x0000, bank_4k, data); break; - case 0xc: regs[2] = data; set_chr_bank_ex(0x0000, bank_4k, data); break; - case 0xd: regs[3] = data; set_chr_bank(0x1000, bank_4k, data); break; - case 0xe: regs[4] = data; set_chr_bank_ex(0x1000, bank_4k, data); break; - case 0xf: regs[5] = data; mirror(data); break; - } - } + set_chr_bank(0x0000, bank_4k, regs[1]); + set_chr_bank(0x1000, bank_4k, regs[3]); + + set_chr_bank_ex(0x0000, bank_4k, regs[2]); + set_chr_bank_ex(0x1000, bank_4k, regs[4]); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr >> 12) + { + case 0xa: + regs[0] = data; + set_prg_bank(0x8000, bank_16k, data); + break; + case 0xb: + regs[1] = data; + set_chr_bank(0x0000, bank_4k, data); + break; + case 0xc: + regs[2] = data; + set_chr_bank_ex(0x0000, bank_4k, data); + break; + case 0xd: + regs[3] = data; + set_chr_bank(0x1000, bank_4k, data); + break; + case 0xe: + regs[4] = data; + set_chr_bank_ex(0x1000, bank_4k, data); + break; + case 0xf: + regs[5] = data; + mirror(data); + break; + } + } }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper011.hpp b/source/quickerNES/mappers/mapper011.hpp index e4a1455..6f51044 100644 --- a/source/quickerNES/mappers/mapper011.hpp +++ b/source/quickerNES/mappers/mapper011.hpp @@ -2,9 +2,9 @@ // Optional less-common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -17,32 +17,39 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + // Color Dreams -class Mapper011 : public Nes_Mapper { - uint8_t bank; -public: - Mapper011() - { - register_state( &bank, 1 ); - } - - virtual void apply_mapping() - { - int b = bank; - bank = ~b; - write( 0, 0, b ); - } - - virtual void write( nes_time_t, nes_addr_t, int data ) - { - int changed = bank ^ data; - bank = data; - - if ( changed & 0x0f ) - set_prg_bank( 0x8000, bank_32k, bank & 0x0f ); - - if ( changed & 0xf0 ) - set_chr_bank( 0, bank_8k, bank >> 4 ); - } +class Mapper011 : public Mapper +{ + uint8_t bank; + + public: + Mapper011() + { + register_state(&bank, 1); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write(0, 0, b); + } + + virtual void write(nes_time_t, nes_addr_t, int data) + { + int changed = bank ^ data; + bank = data; + + if (changed & 0x0f) + set_prg_bank(0x8000, bank_32k, bank & 0x0f); + + if (changed & 0xf0) + set_chr_bank(0, bank_8k, bank >> 4); + } }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper015.hpp b/source/quickerNES/mappers/mapper015.hpp index 9e92d8b..6dea4eb 100644 --- a/source/quickerNES/mappers/mapper015.hpp +++ b/source/quickerNES/mappers/mapper015.hpp @@ -17,76 +17,81 @@ * 100-in-1 Contra Function 16 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct Mapper015_state_t { - uint8_t prg_bank [ 4 ]; - uint8_t mirroring; + uint8_t prg_bank[4]; + uint8_t mirroring; }; -static_assert( sizeof (Mapper015_state_t) == 5 ); +static_assert(sizeof(Mapper015_state_t) == 5); // K-1029, K-1030P -class Mapper015 : public Nes_Mapper, Mapper015_state_t { -public: - Mapper015() - { - i = 0; - Mapper015_state_t* state = this; - register_state( state, sizeof *state ); - } +class Mapper015 : public Mapper, Mapper015_state_t +{ + public: + Mapper015() + { + i = 0; + Mapper015_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { - write( 0, 0x8000, 0 ); - } + virtual void reset_state() + { + write(0, 0x8000, 0); + } - virtual void apply_mapping() - { - enable_sram(); - set_chr_bank ( 0, bank_8k, 0 ); - for ( i = 0; i < sizeof prg_bank; i++ ) - set_prg_bank ( 0x8000 + ( i * 0x2000 ), bank_8k, prg_bank [i] ); - switch ( mirroring ) - { - case 0: mirror_vert(); break; - case 1: mirror_horiz(); break; - } - } + virtual void apply_mapping() + { + enable_sram(); + set_chr_bank(0, bank_8k, 0); + for (i = 0; i < sizeof prg_bank; i++) + set_prg_bank(0x8000 + (i * 0x2000), bank_8k, prg_bank[i]); + switch (mirroring) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + } + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - uint8_t bank = ( data & 0x3F ) << 1; - uint8_t sbank = ( data >> 7 ) & 1; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + uint8_t bank = (data & 0x3F) << 1; + uint8_t sbank = (data >> 7) & 1; - mirroring = ( data >> 6 ) & 1; - switch ( addr & 3 ) - { - case 0: - for ( i = 0; i < sizeof prg_bank; i++ ) - prg_bank [ i ] = bank + i; - apply_mapping(); - break; - case 2: - for ( i = 0; i < sizeof prg_bank; i++ ) - prg_bank [ i ] = bank | sbank; - apply_mapping(); - break; - case 1: - case 3: - for ( i = 0; i < sizeof prg_bank; i++ ) - { - if ( i >= 2 && !( addr & 2 ) ) - bank = 0x7E; - prg_bank [ i ] = bank + ( i & 1 ); - } - apply_mapping(); - break; - } - } - - unsigned long int i; + mirroring = (data >> 6) & 1; + switch (addr & 3) + { + case 0: + for (i = 0; i < sizeof prg_bank; i++) + prg_bank[i] = bank + i; + apply_mapping(); + break; + case 2: + for (i = 0; i < sizeof prg_bank; i++) + prg_bank[i] = bank | sbank; + apply_mapping(); + break; + case 1: + case 3: + for (i = 0; i < sizeof prg_bank; i++) + { + if (i >= 2 && !(addr & 2)) + bank = 0x7E; + prg_bank[i] = bank + (i & 1); + } + apply_mapping(); + break; + } + } + + unsigned long int i; }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper019.hpp b/source/quickerNES/mappers/mapper019.hpp index 8b08097..e973b3f 100644 --- a/source/quickerNES/mappers/mapper019.hpp +++ b/source/quickerNES/mappers/mapper019.hpp @@ -2,10 +2,10 @@ // Namco 106 mapper -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" -#include "apu/namco/apu.h" +#include "apu/namco/apu.hpp" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -20,179 +20,183 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // to do: CHR mapping and nametable handling needs work +namespace quickerNES +{ + struct namco106_state_t { - uint8_t regs [16]; - uint16_t irq_ctr; - uint8_t irq_pending; - uint8_t unused1 [1]; - namco_state_t sound_state; + uint8_t regs[16]; + uint16_t irq_ctr; + uint8_t irq_pending; + uint8_t unused1[1]; + namco_state_t sound_state; }; -static_assert( sizeof (namco106_state_t) == 20 + sizeof (namco_state_t) ); +static_assert(sizeof(namco106_state_t) == 20 + sizeof(namco_state_t)); // Namco106 -class Mapper019 : public Nes_Mapper, namco106_state_t { -public: - Mapper019() - { - namco106_state_t* state = this; - register_state( state, sizeof *state ); - } +class Mapper019 : public Mapper, namco106_state_t +{ + public: + Mapper019() + { + namco106_state_t *state = this; + register_state(state, sizeof *state); + } - virtual int channel_count() const { return sound.osc_count; } + virtual int channel_count() const { return sound.osc_count; } - virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + virtual void set_channel_buf(int i, Blip_Buffer *b) { sound.osc_output(i, b); } - virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + virtual void set_treble(blip_eq_t const &eq) { sound.treble_eq(eq); } - void reset_state() - { - regs [12] = 0; - regs [13] = 1; - regs [14] = last_bank - 1; - sound.reset(); - } + void reset_state() + { + regs[12] = 0; + regs[13] = 1; + regs[14] = last_bank - 1; + sound.reset(); + } - virtual void apply_mapping() - { - last_time = 0; - enable_sram(); - intercept_writes( 0x4800, 1 ); - intercept_reads ( 0x4800, 1 ); + virtual void apply_mapping() + { + last_time = 0; + enable_sram(); + intercept_writes(0x4800, 1); + intercept_reads(0x4800, 1); - intercept_writes( 0x5000, 0x1000 ); - intercept_reads ( 0x5000, 0x1000 ); + intercept_writes(0x5000, 0x1000); + intercept_reads(0x5000, 0x1000); - for ( int i = 0; i < (int) sizeof regs; i++ ) - write( 0, 0x8000 + i * 0x800, regs [i] ); - } + for (int i = 0; i < (int)sizeof regs; i++) + write(0, 0x8000 + i * 0x800, regs[i]); + } - virtual nes_time_t next_irq( nes_time_t time ) - { - if ( irq_pending ) - return time; + virtual nes_time_t next_irq(nes_time_t time) + { + if (irq_pending) + return time; - if ( !(irq_ctr & 0x8000) ) - return no_irq; + if (!(irq_ctr & 0x8000)) + return no_irq; - return 0x10000 - irq_ctr + last_time; - } + return 0x10000 - irq_ctr + last_time; + } - virtual void run_until( nes_time_t end_time ) - { - long count = irq_ctr + (end_time - last_time); - if ( irq_ctr & 0x8000 ) - { - if ( count > 0xffff ) - { - count = 0xffff; - irq_pending = true; - } - } - else if ( count > 0x7fff ) - { - count = 0x7fff; - } + virtual void run_until(nes_time_t end_time) + { + long count = irq_ctr + (end_time - last_time); + if (irq_ctr & 0x8000) + { + if (count > 0xffff) + { + count = 0xffff; + irq_pending = true; + } + } + else if (count > 0x7fff) + { + count = 0x7fff; + } - irq_ctr = count; - last_time = end_time; - } + irq_ctr = count; + last_time = end_time; + } - virtual void end_frame( nes_time_t end_time ) - { - if ( end_time > last_time ) - run_until( end_time ); - last_time -= end_time; - sound.end_frame( end_time ); - } + virtual void end_frame(nes_time_t end_time) + { + if (end_time > last_time) + run_until(end_time); + last_time -= end_time; + sound.end_frame(end_time); + } - virtual int read( nes_time_t time, nes_addr_t addr ) - { - if ( addr == 0x4800 ) - return sound.read_data(); + virtual int read(nes_time_t time, nes_addr_t addr) + { + if (addr == 0x4800) + return sound.read_data(); - if ( addr == 0x5000 ) - { - irq_pending = false; - return irq_ctr & 0xff; - } + if (addr == 0x5000) + { + irq_pending = false; + return irq_ctr & 0xff; + } - if ( addr == 0x5800 ) - { - irq_pending = false; - return irq_ctr >> 8; - } + if (addr == 0x5800) + { + irq_pending = false; + return irq_ctr >> 8; + } - return Nes_Mapper::read( time, addr ); - } + return Mapper::read(time, addr); + } - virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) - { - if ( addr == 0x4800 ) - { - sound.write_data( time, data ); - } - else if ( addr == 0x5000 ) - { - irq_ctr = (irq_ctr & 0xff00) | data; - irq_pending = false; - irq_changed(); - } - else if ( addr == 0x5800 ) - { - irq_ctr = (data << 8) | (irq_ctr & 0xff); - irq_pending = false; - irq_changed(); - } - else - { - return false; - } + virtual bool write_intercepted(nes_time_t time, nes_addr_t addr, int data) + { + if (addr == 0x4800) + { + sound.write_data(time, data); + } + else if (addr == 0x5000) + { + irq_ctr = (irq_ctr & 0xff00) | data; + irq_pending = false; + irq_changed(); + } + else if (addr == 0x5800) + { + irq_ctr = (data << 8) | (irq_ctr & 0xff); + irq_pending = false; + irq_changed(); + } + else + { + return false; + } - return true; - } + return true; + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - int reg = addr >> 11 & 0x0F; - regs [reg] = data; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + int reg = addr >> 11 & 0x0F; + regs[reg] = data; - int prg_bank = reg - 0x0c; - if ( (unsigned) prg_bank < 3 ) - { - if ( prg_bank == 0 && (data & 0x40) ) - mirror_vert(); - set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data & 0x3F ); - } - else if ( reg < 8 ) - { - set_chr_bank( reg * 0x400, bank_1k, data ); - } - else if ( reg < 0x0c ) - { - mirror_manual( regs [8] & 1, regs [9] & 1, regs [10] & 1, regs [11] & 1 ); - } - else - { - sound.write_addr( data ); - } - } + int prg_bank = reg - 0x0c; + if ((unsigned)prg_bank < 3) + { + if (prg_bank == 0 && (data & 0x40)) + mirror_vert(); + set_prg_bank(0x8000 | (prg_bank << bank_8k), bank_8k, data & 0x3F); + } + else if (reg < 8) + { + set_chr_bank(reg * 0x400, bank_1k, data); + } + else if (reg < 0x0c) + { + mirror_manual(regs[8] & 1, regs[9] & 1, regs[10] & 1, regs[11] & 1); + } + else + { + sound.write_addr(data); + } + } - void save_state( mapper_state_t& out ) - { - sound.save_state( &sound_state ); - Nes_Mapper::save_state( out ); - } + void save_state(mapper_state_t &out) + { + sound.save_state(&sound_state); + Mapper::save_state(out); + } - void read_state( mapper_state_t const& in ) - { - Nes_Mapper::read_state( in ); - sound.load_state( sound_state ); - } + void read_state(mapper_state_t const &in) + { + Mapper::read_state(in); + sound.load_state(sound_state); + } - Nes_Namco_Apu sound; - nes_time_t last_time; + Namco_Apu sound; + nes_time_t last_time; }; - +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper021.hpp b/source/quickerNES/mappers/mapper021.hpp index 13ed4a0..be1af01 100644 --- a/source/quickerNES/mappers/mapper021.hpp +++ b/source/quickerNES/mappers/mapper021.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp * IRQ portion is from existing VRC6/VRC7 by Shay Green * This mapper was ported by retrowertz for Libretro port of QuickNES. @@ -26,225 +26,234 @@ * VRC-2/VRC-4 Konami */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct vrc2_state_t { - uint8_t prg_banks [ 2 ]; - uint8_t chr_banks [ 8 ]; - uint8_t mirroring; - uint8_t prg_swap; - uint8_t irq_latch; - uint8_t irq_control; + uint8_t prg_banks[2]; + uint8_t chr_banks[8]; + uint8_t mirroring; + uint8_t prg_swap; + uint8_t irq_latch; + uint8_t irq_control; - // internal state - uint16_t next_time; - uint8_t irq_pending; + // internal state + uint16_t next_time; + uint8_t irq_pending; }; -static_assert( sizeof ( vrc2_state_t ) == 18 ); +static_assert(sizeof(vrc2_state_t) == 18); template -class Mapper_VRC2_4 : public Nes_Mapper, vrc2_state_t { -public: - Mapper_VRC2_4() - { - if (type_a && type_b) // mapper 21 - { - is22 = 0; - reg1mask = 0x42; - reg2mask = 0x84; - } - else if (!type_a && type_b) // mapper 22 - { - is22 = 1; - reg1mask = 2; - reg2mask = 1; - } - else if (!type_a && !type_b) // mapper 23 - { - is22 = 0; - reg1mask = 0x15; - reg2mask = 0x2a; - } - else if (type_a && !type_b) // mapper 25 - { - is22 = 0; - reg1mask = 0xa; - reg2mask = 0x5; - } - vrc2_state_t * state = this; - register_state( state, sizeof * state ); - } - - void reset_state() - { - } - - void apply_mapping() - { - if ( !is22 ) enable_sram(); - update_prg(); - update_chr(); - set_mirroring(); - } - - void reset_timer( nes_time_t present ) - { - next_time = present + unsigned ( ( 0x100 - irq_latch ) * timer_period ) / 4; - } - - virtual void run_until( nes_time_t end_time ) - { - if ( irq_control & 2 ) - { - while ( next_time < end_time ) - { - // printf( "%d timer expired\n", next_time ); - irq_pending = true; - reset_timer( next_time ); - } - } - } - - virtual void end_frame( nes_time_t end_time ) - { - run_until( end_time ); - - // to do: next_time might go negative if IRQ is disabled - next_time -= end_time; - } - - virtual nes_time_t next_irq( nes_time_t present ) - { - if ( irq_pending ) - return present; - - if ( irq_control & 2 ) - return next_time + 1; - - return no_irq; - } - - void write_irq( nes_time_t time, nes_addr_t addr, int data ); - - void write( nes_time_t time, nes_addr_t addr, int data ) - { - addr = ( addr & 0xF000 ) | !!( addr & reg2mask ) << 1 | !!( addr & reg1mask ); - - if( addr >= 0xB000 && addr <= 0xE003) - { - unsigned banknumber = ( ( addr >> 1 ) & 1 ) | ( ( addr - 0xB000 ) >> 11 ); - unsigned offset = ( addr & 1 ) << 2; - - chr_banks [ banknumber ] &= ( 0xF0 ) >> offset; - chr_banks [ banknumber ] |= ( data & 0xF ) << offset; - chr_banks [ banknumber ] |= ( offset ? ( ( data & 0x10 ) << 4 ) : 0 ); - update_chr(); - } - else - { - switch ( addr & 0xF003 ) - { - case 0x8000: - case 0x8001: - case 0x8002: - case 0x8003: - prg_banks [ 0 ] = data & 0x1F; - update_prg(); - break; - case 0xA000: - case 0xA001: - case 0xA002: - case 0xA003: - prg_banks [ 1 ] = data & 0x1F; - update_prg(); - break; - case 0x9000: - case 0x9001: - mirroring = data; - set_mirroring(); - break; - case 0x9002: - case 0x9003: - prg_swap = data; - update_prg(); - break; - case 0xF000: - case 0xF001: - case 0xF002: - case 0xF003: - write_irq( time, addr, data ); - break; - } - } - } - - unsigned is22, reg1mask, reg2mask; - enum { timer_period = 113 * 4 + 3 }; - -private: - void set_mirroring() - { - switch ( mirroring & 3 ) - { - case 0: mirror_vert(); break; - case 1: mirror_horiz(); break; - case 2: - case 3: mirror_single( mirroring & 1 ); break; - } - } - - void update_prg() - { - if ( prg_swap & 2 ) - { - set_prg_bank( 0x8000, bank_8k, ( 0xFE ) ); - set_prg_bank( 0xC000, bank_8k, prg_banks [ 0 ] ); - } - else - { - set_prg_bank( 0x8000, bank_8k, prg_banks [ 0 ] ); - set_prg_bank( 0xC000, bank_8k, ( 0xFE ) ); - } - - set_prg_bank( 0xA000, bank_8k, prg_banks [ 1 ] ); - set_prg_bank( 0xE000, bank_8k, ( 0xFF ) ); - } - - void update_chr() - { - for ( int i = 0; i < (int) sizeof chr_banks; i++ ) - set_chr_bank( i * 0x400, bank_1k, chr_banks [ i ] >> is22 ); - } -}; - -template -void Mapper_VRC2_4::write_irq( nes_time_t time, - nes_addr_t addr, int data ) +class Mapper_VRC2_4 : public Mapper, vrc2_state_t { - // IRQ - run_until( time ); - //printf("%6d VRC2_4 [%d] A:%04x V:%02x\n", time, addr & 3, addr, data); - switch ( addr & 3 ) - { - case 0: - irq_latch = ( irq_latch & 0xF0 ) | ( data & 0xF ); - break; - case 1: - irq_latch = ( irq_latch & 0x0F ) | ( ( data & 0xF ) << 4 ); - break; - case 2: - irq_pending = false; - irq_control = data & 3; - if ( data & 2 ) reset_timer( time ); - break; - case 3: - irq_pending = false; - irq_control = ( irq_control & ~2 ) | ( ( irq_control << 1 ) & 2 ); - break; - } - irq_changed(); + public: + Mapper_VRC2_4() + { + if (type_a && type_b) // mapper 21 + { + is22 = 0; + reg1mask = 0x42; + reg2mask = 0x84; + } + else if (!type_a && type_b) // mapper 22 + { + is22 = 1; + reg1mask = 2; + reg2mask = 1; + } + else if (!type_a && !type_b) // mapper 23 + { + is22 = 0; + reg1mask = 0x15; + reg2mask = 0x2a; + } + else if (type_a && !type_b) // mapper 25 + { + is22 = 0; + reg1mask = 0xa; + reg2mask = 0x5; + } + vrc2_state_t *state = this; + register_state(state, sizeof *state); + } + + void reset_state() + { + } + + void apply_mapping() + { + if (!is22) enable_sram(); + update_prg(); + update_chr(); + set_mirroring(); + } + + void reset_timer(nes_time_t present) + { + next_time = present + unsigned((0x100 - irq_latch) * timer_period) / 4; + } + + virtual void run_until(nes_time_t end_time) + { + if (irq_control & 2) + { + while (next_time < end_time) + { + // printf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer(next_time); + } + } + } + + virtual void end_frame(nes_time_t end_time) + { + run_until(end_time); + + // to do: next_time might go negative if IRQ is disabled + next_time -= end_time; + } + + virtual nes_time_t next_irq(nes_time_t present) + { + if (irq_pending) + return present; + + if (irq_control & 2) + return next_time + 1; + + return no_irq; + } + + void write_irq(nes_time_t time, nes_addr_t addr, int data); + + void write(nes_time_t time, nes_addr_t addr, int data) + { + addr = (addr & 0xF000) | !!(addr & reg2mask) << 1 | !!(addr & reg1mask); + + if (addr >= 0xB000 && addr <= 0xE003) + { + unsigned banknumber = ((addr >> 1) & 1) | ((addr - 0xB000) >> 11); + unsigned offset = (addr & 1) << 2; + + chr_banks[banknumber] &= (0xF0) >> offset; + chr_banks[banknumber] |= (data & 0xF) << offset; + chr_banks[banknumber] |= (offset ? ((data & 0x10) << 4) : 0); + update_chr(); + } + else + { + switch (addr & 0xF003) + { + case 0x8000: + case 0x8001: + case 0x8002: + case 0x8003: + prg_banks[0] = data & 0x1F; + update_prg(); + break; + case 0xA000: + case 0xA001: + case 0xA002: + case 0xA003: + prg_banks[1] = data & 0x1F; + update_prg(); + break; + case 0x9000: + case 0x9001: + mirroring = data; + set_mirroring(); + break; + case 0x9002: + case 0x9003: + prg_swap = data; + update_prg(); + break; + case 0xF000: + case 0xF001: + case 0xF002: + case 0xF003: + write_irq(time, addr, data); + break; + } + } + } + + unsigned is22, reg1mask, reg2mask; + enum + { + timer_period = 113 * 4 + 3 + }; + + private: + void set_mirroring() + { + switch (mirroring & 3) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + case 2: + case 3: mirror_single(mirroring & 1); break; + } + } + + void update_prg() + { + if (prg_swap & 2) + { + set_prg_bank(0x8000, bank_8k, (0xFE)); + set_prg_bank(0xC000, bank_8k, prg_banks[0]); + } + else + { + set_prg_bank(0x8000, bank_8k, prg_banks[0]); + set_prg_bank(0xC000, bank_8k, (0xFE)); + } + + set_prg_bank(0xA000, bank_8k, prg_banks[1]); + set_prg_bank(0xE000, bank_8k, (0xFF)); + } + + void update_chr() + { + for (int i = 0; i < (int)sizeof chr_banks; i++) + set_chr_bank(i * 0x400, bank_1k, chr_banks[i] >> is22); + } +}; + +template +void Mapper_VRC2_4::write_irq(nes_time_t time, + nes_addr_t addr, + int data) +{ + // IRQ + run_until(time); + // printf("%6d VRC2_4 [%d] A:%04x V:%02x\n", time, addr & 3, addr, data); + switch (addr & 3) + { + case 0: + irq_latch = (irq_latch & 0xF0) | (data & 0xF); + break; + case 1: + irq_latch = (irq_latch & 0x0F) | ((data & 0xF) << 4); + break; + case 2: + irq_pending = false; + irq_control = data & 3; + if (data & 2) reset_timer(time); + break; + case 3: + irq_pending = false; + irq_control = (irq_control & ~2) | ((irq_control << 1) & 2); + break; + } + irq_changed(); } +typedef Mapper_VRC2_4 Mapper021; -typedef Mapper_VRC2_4 Mapper021; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper022.hpp b/source/quickerNES/mappers/mapper022.hpp index 0c81c1e..6f30328 100644 --- a/source/quickerNES/mappers/mapper022.hpp +++ b/source/quickerNES/mappers/mapper022.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp * IRQ portion is from existing VRC6/VRC7 by Shay Green * This mapper was ported by retrowertz for Libretro port of QuickNES. @@ -26,7 +26,12 @@ * VRC-2/VRC-4 Konami */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" #include "mappers/mapper021.hpp" -typedef Mapper_VRC2_4 Mapper022; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_VRC2_4 Mapper022; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper023.hpp b/source/quickerNES/mappers/mapper023.hpp index d20bf5f..d788fcd 100644 --- a/source/quickerNES/mappers/mapper023.hpp +++ b/source/quickerNES/mappers/mapper023.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp * IRQ portion is from existing VRC6/VRC7 by Shay Green * This mapper was ported by retrowertz for Libretro port of QuickNES. @@ -26,6 +26,11 @@ * VRC-2/VRC-4 Konami */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -typedef Mapper_VRC2_4 Mapper023; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_VRC2_4 Mapper023; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper024.hpp b/source/quickerNES/mappers/mapper024.hpp index 4a03cd8..a69c492 100644 --- a/source/quickerNES/mappers/mapper024.hpp +++ b/source/quickerNES/mappers/mapper024.hpp @@ -1,11 +1,11 @@ -#pragma once +#pragma once // Konami VRC6 mapper -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ +#include "apu/vrc6/apu.hpp" +#include "mappers/mapper.hpp" #include -#include "mappers/mapper.h" -#include "apu/vrc6/apu.h" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -18,211 +18,219 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + struct vrc6_state_t { - // written registers - uint8_t prg_16k_bank; - // could move sound regs int and out of vrc6_apu_state_t for state saving, - // allowing them to be stored here - uint8_t old_sound_regs [3] [3]; // to do: eliminate this duplicate - uint8_t mirroring; - uint8_t prg_8k_bank; - uint8_t chr_banks [8]; - uint8_t irq_reload; - uint8_t irq_mode; - - // internal state - uint16_t next_time; - uint8_t irq_pending; - uint8_t unused; - - vrc6_apu_state_t sound_state; + // written registers + uint8_t prg_16k_bank; + // could move sound regs int and out of vrc6_apu_state_t for state saving, + // allowing them to be stored here + uint8_t old_sound_regs[3][3]; // to do: eliminate this duplicate + uint8_t mirroring; + uint8_t prg_8k_bank; + uint8_t chr_banks[8]; + uint8_t irq_reload; + uint8_t irq_mode; + + // internal state + uint16_t next_time; + uint8_t irq_pending; + uint8_t unused; + + vrc6_apu_state_t sound_state; }; -static_assert( sizeof (vrc6_state_t) == 26 + sizeof (vrc6_apu_state_t) ); +static_assert(sizeof(vrc6_state_t) == 26 + sizeof(vrc6_apu_state_t)); template -class Mapper_Vrc6 : public Nes_Mapper, vrc6_state_t { -public: - Mapper_Vrc6( ) - { - swap_mask = swapMask; - vrc6_state_t* state = this; - register_state( state, sizeof *state ); - } - - virtual int channel_count() const { return sound.osc_count; } - - virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } - - virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } - - virtual void reset_state() - { - prg_8k_bank = last_bank - 1; - sound.reset(); - } - - virtual void save_state( mapper_state_t& out ) - { - sound.save_state( &sound_state ); - Nes_Mapper::save_state( out ); - } - - virtual void apply_mapping() - { - enable_sram(); - set_prg_bank( 0x8000, bank_16k, prg_16k_bank ); - set_prg_bank( 0xC000, bank_8k, prg_8k_bank ); - - for ( int i = 0; i < (int) sizeof chr_banks; i++ ) - set_chr_bank( i * 0x400, bank_1k, chr_banks [i] ); - - write_bank( 0xb003, mirroring ); - } - - void reset_timer( nes_time_t present ) - { - next_time = present + unsigned ((0x100 - irq_reload) * timer_period) / 4; - } - - virtual void run_until( nes_time_t end_time ) - { - if ( irq_mode & 2 ) - { - while ( next_time < end_time ) - { - //dprintf( "%d timer expired\n", next_time ); - irq_pending = true; - reset_timer( next_time ); - } - } - } - - virtual void end_frame( nes_time_t end_time ) - { - run_until( end_time ); - - // to do: next_time might go negative if IRQ is disabled - next_time -= end_time; - - sound.end_frame( end_time ); - } - - virtual nes_time_t next_irq( nes_time_t present ) - { - if ( irq_pending ) - return present; - - if ( irq_mode & 2 ) - return next_time + 1; - - return no_irq; - } - - virtual void write( nes_time_t time, nes_addr_t addr, int data ) - { - int osc = unsigned (addr - sound.base_addr) / sound.addr_step; - - if ( (addr + 1) & 2 ) // optionally swap 1 and 2 - addr ^= swap_mask; - - int reg = addr & 3; - if ( (unsigned) osc < sound.osc_count && reg < sound.reg_count ) - sound.write_osc( time, osc, reg, data ); - else if ( addr < 0xf000 ) - write_bank( addr, data ); - else - write_irq( time, addr, data ); - } - int swap_mask; - Nes_Vrc6_Apu sound; - enum { timer_period = 113 * 4 + 3 }; - - -void read_state( mapper_state_t const& in ) +class Mapper_Vrc6 : public Mapper, vrc6_state_t { - Nes_Mapper::read_state( in ); - - // to do: eliminate when format is updated - // old-style registers - static char zero [sizeof old_sound_regs] = { 0 }; - if ( 0 != memcmp( old_sound_regs, zero, sizeof zero ) ) - { - /* Using old VRC6 sound register format */ - memcpy( sound_state.regs, old_sound_regs, sizeof sound_state.regs ); - memset( old_sound_regs, 0, sizeof old_sound_regs ); - } - - sound.load_state( sound_state ); -} + public: + Mapper_Vrc6() + { + swap_mask = swapMask; + vrc6_state_t *state = this; + register_state(state, sizeof *state); + } -void write_irq( nes_time_t time, nes_addr_t addr, int data ) -{ - // IRQ - run_until( time ); - //dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); - switch ( addr & 3 ) - { - case 0: - irq_reload = data; - break; - - case 1: - irq_pending = false; - irq_mode = data; - if ( data & 2 ) - reset_timer( time ); - break; - - case 2: - irq_pending = false; - irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); - break; - } - irq_changed(); -} + virtual int channel_count() const { return sound.osc_count; } -void write_bank( nes_addr_t addr, int data ) -{ - switch ( addr & 0xf003 ) - { - case 0x8000: - prg_16k_bank = data; - set_prg_bank( 0x8000, bank_16k, data ); - break; - - case 0xb003: { - mirroring = data; - - //dprintf( "Change mirroring %d\n", data ); -// emu()->enable_sram( data & 0x80 ); // to do: needed? - int page = data >> 5 & 1; - if ( data & 8 ) - mirror_single( ((data >> 2) ^ page) & 1 ); - else if ( data & 4 ) - mirror_horiz( page ); - else - mirror_vert( page ); - break; - } - - case 0xc000: - prg_8k_bank = data; - set_prg_bank( 0xC000, bank_8k, data ); - break; - - default: - int bank = (addr >> 11 & 4) | (addr & 3); - if ( addr >= 0xd000 ) - { - //dprintf( "change chr bank %d\n", bank ); - chr_banks [bank] = data; - set_chr_bank( bank * 0x400, bank_1k, data ); - } - break; - } + virtual void set_channel_buf(int i, Blip_Buffer *b) { sound.osc_output(i, b); } + + virtual void set_treble(blip_eq_t const &eq) { sound.treble_eq(eq); } + + virtual void reset_state() + { + prg_8k_bank = last_bank - 1; + sound.reset(); + } + + virtual void save_state(mapper_state_t &out) + { + sound.save_state(&sound_state); + Mapper::save_state(out); + } + + virtual void apply_mapping() + { + enable_sram(); + set_prg_bank(0x8000, bank_16k, prg_16k_bank); + set_prg_bank(0xC000, bank_8k, prg_8k_bank); + + for (int i = 0; i < (int)sizeof chr_banks; i++) + set_chr_bank(i * 0x400, bank_1k, chr_banks[i]); + + write_bank(0xb003, mirroring); + } + + void reset_timer(nes_time_t present) + { + next_time = present + unsigned((0x100 - irq_reload) * timer_period) / 4; + } + + virtual void run_until(nes_time_t end_time) + { + if (irq_mode & 2) + { + while (next_time < end_time) + { + // dprintf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer(next_time); + } + } + } + + virtual void end_frame(nes_time_t end_time) + { + run_until(end_time); + + // to do: next_time might go negative if IRQ is disabled + next_time -= end_time; + + sound.end_frame(end_time); + } + + virtual nes_time_t next_irq(nes_time_t present) + { + if (irq_pending) + return present; + + if (irq_mode & 2) + return next_time + 1; + + return no_irq; + } + + virtual void write(nes_time_t time, nes_addr_t addr, int data) + { + int osc = unsigned(addr - sound.base_addr) / sound.addr_step; + + if ((addr + 1) & 2) // optionally swap 1 and 2 + addr ^= swap_mask; + + int reg = addr & 3; + if ((unsigned)osc < sound.osc_count && reg < sound.reg_count) + sound.write_osc(time, osc, reg, data); + else if (addr < 0xf000) + write_bank(addr, data); + else + write_irq(time, addr, data); + } + int swap_mask; + Vrc6_Apu sound; + enum + { + timer_period = 113 * 4 + 3 + }; + + void read_state(mapper_state_t const &in) + { + Mapper::read_state(in); + + // to do: eliminate when format is updated + // old-style registers + static char zero[sizeof old_sound_regs] = {0}; + if (0 != memcmp(old_sound_regs, zero, sizeof zero)) + { + /* Using old VRC6 sound register format */ + memcpy(sound_state.regs, old_sound_regs, sizeof sound_state.regs); + memset(old_sound_regs, 0, sizeof old_sound_regs); + } + + sound.load_state(sound_state); + } + + void write_irq(nes_time_t time, nes_addr_t addr, int data) + { + // IRQ + run_until(time); + // dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); + switch (addr & 3) + { + case 0: + irq_reload = data; + break; + + case 1: + irq_pending = false; + irq_mode = data; + if (data & 2) + reset_timer(time); + break; + + case 2: + irq_pending = false; + irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); + break; + } + irq_changed(); + } + + void write_bank(nes_addr_t addr, int data) + { + switch (addr & 0xf003) + { + case 0x8000: + prg_16k_bank = data; + set_prg_bank(0x8000, bank_16k, data); + break; + + case 0xb003: + { + mirroring = data; + + // dprintf( "Change mirroring %d\n", data ); + // emu()->enable_sram( data & 0x80 ); // to do: needed? + int page = data >> 5 & 1; + if (data & 8) + mirror_single(((data >> 2) ^ page) & 1); + else if (data & 4) + mirror_horiz(page); + else + mirror_vert(page); + break; + } + + case 0xc000: + prg_8k_bank = data; + set_prg_bank(0xC000, bank_8k, data); + break; + + default: + int bank = (addr >> 11 & 4) | (addr & 3); + if (addr >= 0xd000) + { + // dprintf( "change chr bank %d\n", bank ); + chr_banks[bank] = data; + set_chr_bank(bank * 0x400, bank_1k, data); + } + break; + } } }; +typedef Mapper_Vrc6<0> Mapper024; -typedef Mapper_Vrc6<0> Mapper024; \ No newline at end of file +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper025.hpp b/source/quickerNES/mappers/mapper025.hpp index 4efa13f..79ae1d8 100644 --- a/source/quickerNES/mappers/mapper025.hpp +++ b/source/quickerNES/mappers/mapper025.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * * General code is from FCEUX https://sourceforge.net/p/fceultra/code/HEAD/tree/fceu/trunk/src/boards/vrc2and4.cpp * IRQ portion is from existing VRC6/VRC7 by Shay Green * This mapper was ported by retrowertz for Libretro port of QuickNES. @@ -26,6 +26,11 @@ * VRC-2/VRC-4 Konami */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -typedef Mapper_VRC2_4 Mapper025; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_VRC2_4 Mapper025; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper026.hpp b/source/quickerNES/mappers/mapper026.hpp index d2b091a..ba293da 100644 --- a/source/quickerNES/mappers/mapper026.hpp +++ b/source/quickerNES/mappers/mapper026.hpp @@ -2,8 +2,13 @@ // Konami VRC6 mapper -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -typedef Mapper_Vrc6<3> Mapper026; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_Vrc6<3> Mapper026; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper030.hpp b/source/quickerNES/mappers/mapper030.hpp index 06f9b20..930b83f 100644 --- a/source/quickerNES/mappers/mapper030.hpp +++ b/source/quickerNES/mappers/mapper030.hpp @@ -21,31 +21,36 @@ * 3/24/18 * * Unrom-512 - * - * NOTE: + * + * NOTE: * No flash and one screen mirroring support. * Tested only on Troll Burner and Mystic Origins demo. */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -// Unrom512 +// Unrom512 -class Mapper030 : public Nes_Mapper { -public: - Mapper030() { } +namespace quickerNES +{ - void reset_state() { } +class Mapper030 : public Mapper +{ + public: + Mapper030() {} - void apply_mapping() { } + void reset_state() {} - void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( ( addr & 0xF000 ) >= 0x8000 ) - { - set_prg_bank(0x8000, bank_16k, data & 0x1F); - set_chr_bank(0x0000, bank_8k, (data >> 5) & 0x3); - } - } + void apply_mapping() {} + + void write(nes_time_t, nes_addr_t addr, int data) + { + if ((addr & 0xF000) >= 0x8000) + { + set_prg_bank(0x8000, bank_16k, data & 0x1F); + set_chr_bank(0x0000, bank_8k, (data >> 5) & 0x3); + } + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper032.hpp b/source/quickerNES/mappers/mapper032.hpp index 6ac122e..7d5aefd 100644 --- a/source/quickerNES/mappers/mapper032.hpp +++ b/source/quickerNES/mappers/mapper032.hpp @@ -23,96 +23,107 @@ * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct mapper32_state_t { - uint8_t chr_bank [ 8 ]; - uint8_t prg_bank [ 2 ]; - uint8_t prg_mode; - uint8_t mirr; + uint8_t chr_bank[8]; + uint8_t prg_bank[2]; + uint8_t prg_mode; + uint8_t mirr; }; -static_assert( sizeof ( mapper32_state_t ) == 12 ); +static_assert(sizeof(mapper32_state_t) == 12); // Irem_G101 -class Mapper032 : public Nes_Mapper, mapper32_state_t { -public: - Mapper032() - { - mapper32_state_t * state = this; - register_state( state, sizeof * state ); - } +class Mapper032 : public Mapper, mapper32_state_t +{ + public: + Mapper032() + { + mapper32_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { - prg_bank [ 0 ] = ~1; - prg_bank [ 1 ] = ~0; - enable_sram(); - } + virtual void reset_state() + { + prg_bank[0] = ~1; + prg_bank[1] = ~0; + enable_sram(); + } - virtual void apply_mapping() - { - if ( prg_mode == 0 ) - { - set_prg_bank ( 0x8000, bank_8k, prg_bank [ 0 ] ); - set_prg_bank ( 0xA000, bank_8k, prg_bank [ 1 ] ); - set_prg_bank ( 0xC000, bank_8k, ~1 ); - set_prg_bank ( 0xE000, bank_8k, ~0 ); - } - else - { - set_prg_bank ( 0xC000, bank_8k, prg_bank [ 0 ] ); - set_prg_bank ( 0xA000, bank_8k, prg_bank [ 1 ] ); - set_prg_bank ( 0x8000, bank_8k, ~1 ); - set_prg_bank ( 0xE000, bank_8k, ~0 ); - } + virtual void apply_mapping() + { + if (prg_mode == 0) + { + set_prg_bank(0x8000, bank_8k, prg_bank[0]); + set_prg_bank(0xA000, bank_8k, prg_bank[1]); + set_prg_bank(0xC000, bank_8k, ~1); + set_prg_bank(0xE000, bank_8k, ~0); + } + else + { + set_prg_bank(0xC000, bank_8k, prg_bank[0]); + set_prg_bank(0xA000, bank_8k, prg_bank[1]); + set_prg_bank(0x8000, bank_8k, ~1); + set_prg_bank(0xE000, bank_8k, ~0); + } - for ( unsigned long int i = 0; i < sizeof chr_bank; i++) - set_chr_bank( ( i << 10 ), bank_1k, chr_bank [ i ] ); + for (unsigned long int i = 0; i < sizeof chr_bank; i++) + set_chr_bank((i << 10), bank_1k, chr_bank[i]); - switch ( mirr ) - { - case 0: mirror_vert(); break; - case 1: mirror_horiz(); break; - } - } + switch (mirr) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + } + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - switch ( addr & 0xF000 ) - { - case 0x8000: - prg_bank [ 0 ] = data; - switch ( prg_mode ) - { - case 0: set_prg_bank ( 0x8000, bank_8k, data ); break; - case 1: set_prg_bank ( 0xC000, bank_8k, data ); break; - } - break; - case 0x9000: - mirr = data & 1; - prg_mode = ( data >> 1 ) & 1; - switch ( data & 1 ) - { - case 0: mirror_vert(); break; - case 1: mirror_horiz(); break; - } - break; - case 0xA000: - prg_bank [ 1 ] = data; - set_prg_bank ( 0xA000, bank_8k, data ); - break; - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr & 0xF000) + { + case 0x8000: + prg_bank[0] = data; + switch (prg_mode) + { + case 0: set_prg_bank(0x8000, bank_8k, data); break; + case 1: set_prg_bank(0xC000, bank_8k, data); break; + } + break; + case 0x9000: + mirr = data & 1; + prg_mode = (data >> 1) & 1; + switch (data & 1) + { + case 0: mirror_vert(); break; + case 1: mirror_horiz(); break; + } + break; + case 0xA000: + prg_bank[1] = data; + set_prg_bank(0xA000, bank_8k, data); + break; + } - switch ( addr & 0xF007 ) - { - case 0xB000: case 0xB001: case 0xB002: case 0xB003: - case 0xB004: case 0xB005: case 0xB006: case 0xB007: - chr_bank [ addr & 0x07 ] = data; - set_chr_bank( ( addr & 0x07 ) << 10, bank_1k, data ); - break; - } - } + switch (addr & 0xF007) + { + case 0xB000: + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + chr_bank[addr & 0x07] = data; + set_chr_bank((addr & 0x07) << 10, bank_1k, data); + break; + } + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper033.hpp b/source/quickerNES/mappers/mapper033.hpp index 894f781..d7392fd 100644 --- a/source/quickerNES/mappers/mapper033.hpp +++ b/source/quickerNES/mappers/mapper033.hpp @@ -20,73 +20,87 @@ * This mapper was added by retrowertz for Libretro port of QuickNES. * * Mapper 33 - Taito TC0190 - * + * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct tc0190_state_t { - uint8_t preg [ 2 ]; - uint8_t creg [ 6 ]; - uint8_t mirr; + uint8_t preg[2]; + uint8_t creg[6]; + uint8_t mirr; }; -static_assert( sizeof ( tc0190_state_t ) == 9 ); +static_assert(sizeof(tc0190_state_t) == 9); // TaitoTC0190 -class Mapper033 : public Nes_Mapper, tc0190_state_t { -public: - Mapper033() - { - tc0190_state_t *state = this; - register_state( state, sizeof *state ); - } +class Mapper033 : public Mapper, tc0190_state_t +{ + public: + Mapper033() + { + tc0190_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { } + virtual void reset_state() + { + } - virtual void apply_mapping() - { - for ( int i = 0; i < 2; i++ ) - { - set_prg_bank ( 0x8000 + ( i << 13 ), bank_8k, preg [ i ] ); - set_chr_bank ( 0x0000 + ( i << 11 ), bank_2k, creg [ i ] ); - } + virtual void apply_mapping() + { + for (int i = 0; i < 2; i++) + { + set_prg_bank(0x8000 + (i << 13), bank_8k, preg[i]); + set_chr_bank(0x0000 + (i << 11), bank_2k, creg[i]); + } - for ( int i = 0; i < 4; i++ ) - set_chr_bank ( 0x1000 + ( i << 10 ), bank_1k, creg [ 2 + i ] ); + for (int i = 0; i < 4; i++) + set_chr_bank(0x1000 + (i << 10), bank_1k, creg[2 + i]); - if ( mirr ) mirror_horiz(); - else mirror_vert(); - } + if (mirr) + mirror_horiz(); + else + mirror_vert(); + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - switch ( addr & 0xA003 ) - { - case 0x8000: - preg [ 0 ] = data & 0x3F; - mirr = data >> 6; - set_prg_bank ( 0x8000, bank_8k, preg [ 0 ] ); - if ( mirr ) mirror_horiz(); - else mirror_vert(); - break; - case 0x8001: - preg [ 1 ] = data & 0x3F; - set_prg_bank ( 0xA000, bank_8k, preg [ 1 ] ); - break; - case 0x8002: case 0x8003: - addr &= 0x01; - creg [ addr ] = data; - set_chr_bank ( addr << 11, bank_2k, creg [ addr ] ); - break; - case 0xA000: case 0xA001: - case 0xA002: case 0xA003: - addr &= 0x03; - creg [ 2 + addr ] = data; - set_chr_bank ( 0x1000 | ( addr << 10 ), bank_1k, creg [ 2 + addr ] ); - break; - } - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr & 0xA003) + { + case 0x8000: + preg[0] = data & 0x3F; + mirr = data >> 6; + set_prg_bank(0x8000, bank_8k, preg[0]); + if (mirr) + mirror_horiz(); + else + mirror_vert(); + break; + case 0x8001: + preg[1] = data & 0x3F; + set_prg_bank(0xA000, bank_8k, preg[1]); + break; + case 0x8002: + case 0x8003: + addr &= 0x01; + creg[addr] = data; + set_chr_bank(addr << 11, bank_2k, creg[addr]); + break; + case 0xA000: + case 0xA001: + case 0xA002: + case 0xA003: + addr &= 0x03; + creg[2 + addr] = data; + set_chr_bank(0x1000 | (addr << 10), bank_1k, creg[2 + addr]); + break; + } + } }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper034.hpp b/source/quickerNES/mappers/mapper034.hpp index c874eb6..3f8d683 100644 --- a/source/quickerNES/mappers/mapper034.hpp +++ b/source/quickerNES/mappers/mapper034.hpp @@ -2,9 +2,9 @@ // Optional less-common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -19,23 +19,29 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Nina-1 (Deadly Towers only) -class Mapper034 : public Nes_Mapper { - uint8_t bank; -public: - Mapper034() - { - register_state( &bank, 1 ); - } - - virtual void apply_mapping() - { - write( 0, 0, bank ); - } - - virtual void write( nes_time_t, nes_addr_t, int data ) - { - bank = data; - set_prg_bank( 0x8000, bank_32k, bank ); - } +namespace quickerNES +{ + +class Mapper034 : public Mapper +{ + uint8_t bank; + + public: + Mapper034() + { + register_state(&bank, 1); + } + + virtual void apply_mapping() + { + write(0, 0, bank); + } + + virtual void write(nes_time_t, nes_addr_t, int data) + { + bank = data; + set_prg_bank(0x8000, bank_32k, bank); + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper060.hpp b/source/quickerNES/mappers/mapper060.hpp index 84b6643..20718b2 100644 --- a/source/quickerNES/mappers/mapper060.hpp +++ b/source/quickerNES/mappers/mapper060.hpp @@ -17,34 +17,40 @@ * 4-in-1 Multicart ( Reset-based ) */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // NROM-128 4-in-1 multicart -class Mapper060 : public Nes_Mapper { -public: - Mapper060() - { - last_game = 2; - register_state( &game_sel, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - { - game_sel = last_game; - game_sel++; - game_sel &= 3; - } +class Mapper060 : public Mapper +{ + public: + Mapper060() + { + last_game = 2; + register_state(&game_sel, 1); + } - virtual void apply_mapping() - { - set_prg_bank ( 0x8000, bank_16k, game_sel ); - set_prg_bank ( 0xC000, bank_16k, game_sel ); - set_chr_bank ( 0, bank_8k, game_sel ); - last_game = game_sel; - } + virtual void reset_state() + { + game_sel = last_game; + game_sel++; + game_sel &= 3; + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) { } + virtual void apply_mapping() + { + set_prg_bank(0x8000, bank_16k, game_sel); + set_prg_bank(0xC000, bank_16k, game_sel); + set_chr_bank(0, bank_8k, game_sel); + last_game = game_sel; + } - uint8_t game_sel, last_game; + virtual void write(nes_time_t, nes_addr_t addr, int data) {} + + uint8_t game_sel, last_game; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper066.hpp b/source/quickerNES/mappers/mapper066.hpp index b99a842..ec11693 100644 --- a/source/quickerNES/mappers/mapper066.hpp +++ b/source/quickerNES/mappers/mapper066.hpp @@ -2,9 +2,9 @@ // Optional less-common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -19,31 +19,37 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // GNROM -class Mapper066 : public Nes_Mapper { - uint8_t bank; -public: - Mapper066() - { - register_state( &bank, 1 ); - } - - virtual void apply_mapping() - { - int b = bank; - bank = ~b; - write( 0, 0, b ); - } - - virtual void write( nes_time_t, nes_addr_t, int data ) - { - int changed = bank ^ data; - bank = data; - - if ( changed & 0x30 ) - set_prg_bank( 0x8000, bank_32k, bank >> 4 & 3 ); - - if ( changed & 0x03 ) - set_chr_bank( 0, bank_8k, bank & 3 ); - } +namespace quickerNES +{ + +class Mapper066 : public Mapper +{ + uint8_t bank; + + public: + Mapper066() + { + register_state(&bank, 1); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write(0, 0, b); + } + + virtual void write(nes_time_t, nes_addr_t, int data) + { + int changed = bank ^ data; + bank = data; + + if (changed & 0x30) + set_prg_bank(0x8000, bank_32k, bank >> 4 & 3); + + if (changed & 0x03) + set_chr_bank(0, bank_8k, bank & 3); + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper069.hpp b/source/quickerNES/mappers/mapper069.hpp index de003a2..9015f37 100644 --- a/source/quickerNES/mappers/mapper069.hpp +++ b/source/quickerNES/mappers/mapper069.hpp @@ -2,10 +2,10 @@ // Sunsoft FME-7 mapper -// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ +// Emu 0.7.0. http://www.slack.net/~ant/libs/ -#include "mappers/mapper.h" -#include "apu/fme7/apu.h" +#include "apu/fme7/apu.hpp" +#include "mappers/mapper.hpp" /* Copyright (C) 2005 Chris Moeller */ /* Copyright (C) 2005-2006 Shay Green. This module is free software; you @@ -19,172 +19,175 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + struct fme7_state_t { - // first 16 bytes in register order - uint8_t regs [13]; - uint8_t irq_mode; - uint16_t irq_count; - - uint8_t command; - uint8_t irq_pending; - fme7_apu_state_t sound_state; // only used when saving/restoring state + // first 16 bytes in register order + uint8_t regs[13]; + uint8_t irq_mode; + uint16_t irq_count; + + uint8_t command; + uint8_t irq_pending; + fme7_apu_state_t sound_state; // only used when saving/restoring state }; -static_assert( sizeof (fme7_state_t) == 18 + sizeof (fme7_apu_state_t) ); +static_assert(sizeof(fme7_state_t) == 18 + sizeof(fme7_apu_state_t)); // Fme7 -class Mapper069 : public Nes_Mapper, fme7_state_t { -public: - Mapper069() - { - fme7_state_t* state = this; - register_state( state, sizeof *state ); - } - - virtual int channel_count() const { return sound.osc_count; } +class Mapper069 : public Mapper, fme7_state_t +{ + public: + Mapper069() + { + fme7_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + virtual int channel_count() const { return sound.osc_count; } - virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } - - virtual void reset_state() - { - regs [8] = 0x40; // wram disabled - irq_count = 0xFFFF; - sound.reset(); - } - - virtual void save_state( mapper_state_t& out ) - { - sound.save_state( &sound_state ); - Nes_Mapper::save_state( out ); - } - - virtual void read_state( mapper_state_t const& in ) - { - Nes_Mapper::read_state( in ); - sound.load_state( sound_state ); - } - - virtual void apply_mapping() - { - last_time = 0; - for ( int i = 0; i < (int) sizeof regs; i++ ) - write_register( i, regs [i] ); - } + virtual void set_channel_buf(int i, Blip_Buffer *b) { sound.osc_output(i, b); } - virtual void run_until( nes_time_t end_time ) - { - int new_count = irq_count - (end_time - last_time); - last_time = end_time; - - if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 ) - irq_pending = true; - - if ( irq_mode & 0x01 ) - irq_count = new_count & 0xFFFF; - } - - virtual nes_time_t next_irq( nes_time_t ) - { - if ( irq_pending ) - return 0; - - if ( (irq_mode & 0x81) == 0x81 ) - return last_time + irq_count + 1; - - return no_irq; - } - - virtual void end_frame( nes_time_t end_time ) - { - if ( end_time > last_time ) - run_until( end_time ); - - last_time -= end_time; - - sound.end_frame( end_time ); - } - - virtual void write( nes_time_t time, nes_addr_t addr, int data ) - { - switch ( addr & 0xE000 ) - { - case 0x8000: - command = data & 0x0F; - break; - - case 0xA000: - if ( command < 0x0D ) - write_register( command, data ); - else - write_irq( time, command, data ); - break; - - case 0xC000: - sound.write_latch( data ); - break; - - case 0xE000: - sound.write_data( time, data ); - break; - } - } + virtual void set_treble(blip_eq_t const &eq) { sound.treble_eq(eq); } + virtual void reset_state() + { + regs[8] = 0x40; // wram disabled + irq_count = 0xFFFF; + sound.reset(); + } - void write_irq( nes_time_t time, int index, int data ) - { - run_until( time ); - switch ( index ) - { - case 0x0D: - irq_mode = data; - irq_pending = false; - irq_changed(); - break; + virtual void save_state(mapper_state_t &out) + { + sound.save_state(&sound_state); + Mapper::save_state(out); + } - case 0x0E: - irq_count = (irq_count & 0xFF00) | data; - break; + virtual void read_state(mapper_state_t const &in) + { + Mapper::read_state(in); + sound.load_state(sound_state); + } - case 0x0F: - irq_count = data << 8 | (irq_count & 0xFF); - break; - } - } + virtual void apply_mapping() + { + last_time = 0; + for (int i = 0; i < (int)sizeof regs; i++) + write_register(i, regs[i]); + } - void write_register( int index, int data ) - { - regs [index] = data; - int prg_bank = index - 0x09; - if ( (unsigned) prg_bank < 3 ) // most common - { - set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data ); - } - else if ( index == 0x08 ) - { - enable_sram( (data & 0xC0) == 0xC0 ); - if ( !(data & 0xC0) ) - set_prg_bank( 0x6000, bank_8k, data & 0x3F ); - } - else if ( index < 0x08 ) - { - set_chr_bank( index * 0x400, bank_1k, data ); - } - else - { - if ( data & 2 ) - mirror_single( data & 1 ); - else if ( data & 1 ) - mirror_horiz(); - else - mirror_vert(); - } - } + virtual void run_until(nes_time_t end_time) + { + int new_count = irq_count - (end_time - last_time); + last_time = end_time; - nes_time_t last_time; - Nes_Fme7_Apu sound; + if (new_count <= 0 && (irq_mode & 0x81) == 0x81) + irq_pending = true; + + if (irq_mode & 0x01) + irq_count = new_count & 0xFFFF; + } + + virtual nes_time_t next_irq(nes_time_t) + { + if (irq_pending) + return 0; + + if ((irq_mode & 0x81) == 0x81) + return last_time + irq_count + 1; + + return no_irq; + } + + virtual void end_frame(nes_time_t end_time) + { + if (end_time > last_time) + run_until(end_time); + + last_time -= end_time; + + sound.end_frame(end_time); + } + + virtual void write(nes_time_t time, nes_addr_t addr, int data) + { + switch (addr & 0xE000) + { + case 0x8000: + command = data & 0x0F; + break; + + case 0xA000: + if (command < 0x0D) + write_register(command, data); + else + write_irq(time, command, data); + break; + + case 0xC000: + sound.write_latch(data); + break; + + case 0xE000: + sound.write_data(time, data); + break; + } + } + + void write_irq(nes_time_t time, int index, int data) + { + run_until(time); + switch (index) + { + case 0x0D: + irq_mode = data; + irq_pending = false; + irq_changed(); + break; + + case 0x0E: + irq_count = (irq_count & 0xFF00) | data; + break; + + case 0x0F: + irq_count = data << 8 | (irq_count & 0xFF); + break; + } + } + + void write_register(int index, int data) + { + regs[index] = data; + int prg_bank = index - 0x09; + if ((unsigned)prg_bank < 3) // most common + { + set_prg_bank(0x8000 | (prg_bank << bank_8k), bank_8k, data); + } + else if (index == 0x08) + { + enable_sram((data & 0xC0) == 0xC0); + if (!(data & 0xC0)) + set_prg_bank(0x6000, bank_8k, data & 0x3F); + } + else if (index < 0x08) + { + set_chr_bank(index * 0x400, bank_1k, data); + } + else + { + if (data & 2) + mirror_single(data & 1); + else if (data & 1) + mirror_horiz(); + else + mirror_vert(); + } + } + + nes_time_t last_time; + Fme7_Apu sound; }; - +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper070.hpp b/source/quickerNES/mappers/mapper070.hpp index eadc6d0..005d4ec 100644 --- a/source/quickerNES/mappers/mapper070.hpp +++ b/source/quickerNES/mappers/mapper070.hpp @@ -23,59 +23,65 @@ * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // Mapper_74x161x162x32 -template < int mapperId > -class Mapper_74x161x162x32 : public Nes_Mapper { -public: - Mapper_74x161x162x32() - { - register_state( &bank, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - { - if ( mapperId == 86 ) - bank = ~0; - } +template +class Mapper_74x161x162x32 : public Mapper +{ + public: + Mapper_74x161x162x32() + { + register_state(&bank, 1); + } - virtual void apply_mapping() - { - if ( mapperId == 152 ) write( 0, 0, bank ); - if ( mapperId == 70 ) write( 0, 0, bank ); - if ( mapperId == 86 ) - { - intercept_writes( 0x6000, 1 ); - write_intercepted( 0, 0x6000, bank ); - } - } + virtual void reset_state() + { + if (mapperId == 86) + bank = ~0; + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr != 0x6000 ) return false; - if ( mapperId == 152 ) return false; - if ( mapperId == 70 ) return false; + virtual void apply_mapping() + { + if (mapperId == 152) write(0, 0, bank); + if (mapperId == 70) write(0, 0, bank); + if (mapperId == 86) + { + intercept_writes(0x6000, 1); + write_intercepted(0, 0x6000, bank); + } + } - bank = data; - set_prg_bank( 0x8000, bank_32k, ( bank >> 4 ) & 0x03 ); - set_chr_bank( 0x0000, bank_8k, ( ( bank >> 4 ) & 0x04 ) | ( bank & 0x03 ) ); + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr != 0x6000) return false; + if (mapperId == 152) return false; + if (mapperId == 70) return false; - return true; - } + bank = data; + set_prg_bank(0x8000, bank_32k, (bank >> 4) & 0x03); + set_chr_bank(0x0000, bank_8k, ((bank >> 4) & 0x04) | (bank & 0x03)); - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( mapperId == 86) return; + return true; + } - bank = handle_bus_conflict (addr, data ); - set_prg_bank( 0x8000, bank_16k, ( bank >> 4 ) & 0x07 ); - set_chr_bank( 0x0000, bank_8k, bank & 0x0F ); - mirror_single( ( bank >> 7) & 0x01 ); - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (mapperId == 86) return; - uint8_t bank; + bank = handle_bus_conflict(addr, data); + set_prg_bank(0x8000, bank_16k, (bank >> 4) & 0x07); + set_chr_bank(0x0000, bank_8k, bank & 0x0F); + mirror_single((bank >> 7) & 0x01); + } + + uint8_t bank; }; -typedef Mapper_74x161x162x32<70> Mapper070; \ No newline at end of file +typedef Mapper_74x161x162x32<70> Mapper070; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper071.hpp b/source/quickerNES/mappers/mapper071.hpp index c90a199..0e89ffb 100644 --- a/source/quickerNES/mappers/mapper071.hpp +++ b/source/quickerNES/mappers/mapper071.hpp @@ -2,9 +2,9 @@ // Optional less-common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -17,38 +17,41 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -// Camerica +// Camerica -class Mapper071 : public Nes_Mapper { - uint8_t regs [3]; -public: - Mapper071() - { - register_state( regs, sizeof regs ); - } - - virtual void apply_mapping() - { - write( 0, 0xc000, regs [0] ); - if ( regs [1] & 0x80 ) - write( 0, 0x9000, regs [1] ); - } - - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr >= 0xc000 ) - { - regs [0] = data; - set_prg_bank( 0x8000, bank_16k, data ); - } - else if ( (addr & 0xf000) == 0x9000 ) - { - regs [1] = 0x80 | data; - mirror_single( (data >> 4) & 1 ); - } - } +namespace quickerNES +{ + +class Mapper071 : public Mapper +{ + uint8_t regs[3]; + + public: + Mapper071() + { + register_state(regs, sizeof regs); + } + + virtual void apply_mapping() + { + write(0, 0xc000, regs[0]); + if (regs[1] & 0x80) + write(0, 0x9000, regs[1]); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (addr >= 0xc000) + { + regs[0] = data; + set_prg_bank(0x8000, bank_16k, data); + } + else if ((addr & 0xf000) == 0x9000) + { + regs[1] = 0x80 | data; + mirror_single((data >> 4) & 1); + } + } }; - - - +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper073.hpp b/source/quickerNES/mappers/mapper073.hpp index e0c013a..7591f86 100644 --- a/source/quickerNES/mappers/mapper073.hpp +++ b/source/quickerNES/mappers/mapper073.hpp @@ -23,115 +23,115 @@ * VRC-3 Konami, Salamander */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct vrc3_state_t { - bool irq_enable; - bool irq_awk; - uint16_t irq_latch; - uint16_t irq_counter; + bool irq_enable; + bool irq_awk; + uint16_t irq_latch; + uint16_t irq_counter; - uint8_t irq_pending; - uint16_t next_time; + uint8_t irq_pending; + uint16_t next_time; }; // VRC3 -class Mapper073 : public Nes_Mapper, vrc3_state_t { -public: - Mapper073() - { - vrc3_state_t * state = this; - register_state( state, sizeof * state ); - } +class Mapper073 : public Mapper, vrc3_state_t +{ + public: + Mapper073() + { + vrc3_state_t *state = this; + register_state(state, sizeof *state); + } - void reset_state() - { - } + void reset_state() + { + } - void apply_mapping() - { - enable_sram(); - mirror_vert(); - } + void apply_mapping() + { + enable_sram(); + mirror_vert(); + } - virtual void run_until( nes_time_t end_time ) - { - if ( irq_enable ) - { - long counter = irq_counter + ( end_time - next_time ); + virtual void run_until(nes_time_t end_time) + { + if (irq_enable) + { + long counter = irq_counter + (end_time - next_time); - if ( counter > 0xFFFF ) - { - irq_pending = true; - irq_enable = irq_awk; - irq_counter = irq_latch; - } - else - irq_counter = counter; - } + if (counter > 0xFFFF) + { + irq_pending = true; + irq_enable = irq_awk; + irq_counter = irq_latch; + } + else + irq_counter = counter; + } - next_time = end_time; - } + next_time = end_time; + } - virtual void end_frame( nes_time_t end_time ) - { - if ( end_time > next_time ) - run_until( end_time ); + virtual void end_frame(nes_time_t end_time) + { + if (end_time > next_time) + run_until(end_time); - next_time -= end_time; - } + next_time -= end_time; + } - virtual nes_time_t next_irq( nes_time_t present ) - { - if ( irq_pending ) - return present; + virtual nes_time_t next_irq(nes_time_t present) + { + if (irq_pending) + return present; - if ( !irq_enable ) - return no_irq; + if (!irq_enable) + return no_irq; - return 0x10000 - irq_counter + next_time; - } + return 0x10000 - irq_counter + next_time; + } - void write_irq_counter( int shift, int data ) - { - irq_latch &= ~( 0xF << shift ); - irq_latch |= data << shift; - } + void write_irq_counter(int shift, int data) + { + irq_latch &= ~(0xF << shift); + irq_latch |= data << shift; + } - void write( nes_time_t time, nes_addr_t addr, int data ) - { - data &= 0xF; + void write(nes_time_t time, nes_addr_t addr, int data) + { + data &= 0xF; - switch ( addr >> 12 ) - { - case 0xF: set_prg_bank( 0x8000, bank_16k, data ); break; - case 0x8: write_irq_counter( 0, data ); break; - case 0x9: write_irq_counter( 4, data ); break; - case 0xA: write_irq_counter( 8, data ); break; - case 0xB: write_irq_counter( 12, data ); break; - case 0xC: - irq_pending = false; - irq_awk = data & 1; - irq_enable = data & 2; + switch (addr >> 12) + { + case 0xF: set_prg_bank(0x8000, bank_16k, data); break; + case 0x8: write_irq_counter(0, data); break; + case 0x9: write_irq_counter(4, data); break; + case 0xA: write_irq_counter(8, data); break; + case 0xB: write_irq_counter(12, data); break; + case 0xC: + irq_pending = false; + irq_awk = data & 1; + irq_enable = data & 2; - if ( irq_enable ) - irq_counter = irq_latch; + if (irq_enable) + irq_counter = irq_latch; - break; - case 0xD: - irq_pending = false; - irq_enable = irq_awk; - break; - } + break; + case 0xD: + irq_pending = false; + irq_enable = irq_awk; + break; + } - irq_changed(); - } + irq_changed(); + } }; -// void register_vrc3_mapper(); -// void register_vrc3_mapper() -// { -// register_mapper< Mapper073> ( 73 ); -// } +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper075.hpp b/source/quickerNES/mappers/mapper075.hpp index dd0e62f..2d17169 100644 --- a/source/quickerNES/mappers/mapper075.hpp +++ b/source/quickerNES/mappers/mapper075.hpp @@ -17,93 +17,97 @@ * VRC-1 Konami */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct vrc1_state_t { - uint8_t prg_banks [ 3 ]; - uint8_t chr_banks [ 2 ]; - uint8_t chr_banks_hi [ 2 ]; - uint8_t mirroring; + uint8_t prg_banks[3]; + uint8_t chr_banks[2]; + uint8_t chr_banks_hi[2]; + uint8_t mirroring; }; -static_assert( sizeof ( vrc1_state_t ) == 8 ); +static_assert(sizeof(vrc1_state_t) == 8); -// VRC1 +// VRC1 -class Mapper075 : public Nes_Mapper, vrc1_state_t { -public: - Mapper075() - { - vrc1_state_t * state = this; - register_state( state, sizeof * state ); - } +class Mapper075 : public Mapper, vrc1_state_t +{ + public: + Mapper075() + { + vrc1_state_t *state = this; + register_state(state, sizeof *state); + } - void reset_state() - { - } + void reset_state() + { + } - void apply_mapping() - { - update_prg_banks(); - update_chr_banks(); - update_mirroring(); - } + void apply_mapping() + { + update_prg_banks(); + update_chr_banks(); + update_mirroring(); + } - void write( nes_time_t, nes_addr_t addr, int data ) - { - switch ( addr & 0xF000 ) - { - case 0x8000: - prg_banks [ 0 ] = data & 0xF; - update_prg_banks(); - break; - case 0x9000: - mirroring = data & 1; - chr_banks_hi [ 0 ] = ( data & 2 ) << 3; - chr_banks_hi [ 1 ] = ( data & 4 ) << 2; - update_chr_banks(); - update_mirroring(); - break; - case 0xa000: - prg_banks [ 1 ] = data & 0xF; - update_prg_banks(); - break; - case 0xc000: - prg_banks [ 2 ] = data & 0xF; - update_prg_banks(); - break; - case 0xe000: - chr_banks [ 0 ] = data & 0xF; - update_chr_banks(); - break; - case 0xf000: - chr_banks [ 1 ] = data & 0xF; - update_chr_banks(); - break; - } - } + void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr & 0xF000) + { + case 0x8000: + prg_banks[0] = data & 0xF; + update_prg_banks(); + break; + case 0x9000: + mirroring = data & 1; + chr_banks_hi[0] = (data & 2) << 3; + chr_banks_hi[1] = (data & 4) << 2; + update_chr_banks(); + update_mirroring(); + break; + case 0xa000: + prg_banks[1] = data & 0xF; + update_prg_banks(); + break; + case 0xc000: + prg_banks[2] = data & 0xF; + update_prg_banks(); + break; + case 0xe000: + chr_banks[0] = data & 0xF; + update_chr_banks(); + break; + case 0xf000: + chr_banks[1] = data & 0xF; + update_chr_banks(); + break; + } + } - void update_prg_banks() - { - set_prg_bank( 0x8000, bank_8k, prg_banks [ 0 ] ); - set_prg_bank( 0xa000, bank_8k, prg_banks [ 1 ] ); - set_prg_bank( 0xc000, bank_8k, prg_banks [ 2 ] ); - } + void update_prg_banks() + { + set_prg_bank(0x8000, bank_8k, prg_banks[0]); + set_prg_bank(0xa000, bank_8k, prg_banks[1]); + set_prg_bank(0xc000, bank_8k, prg_banks[2]); + } - void update_chr_banks() - { - set_chr_bank( 0x0000, bank_4k, chr_banks [ 0 ] | chr_banks_hi [ 0 ] ); - set_chr_bank( 0x1000, bank_4k, chr_banks [ 1 ] | chr_banks_hi [ 1 ] ); - } + void update_chr_banks() + { + set_chr_bank(0x0000, bank_4k, chr_banks[0] | chr_banks_hi[0]); + set_chr_bank(0x1000, bank_4k, chr_banks[1] | chr_banks_hi[1]); + } - void update_mirroring() - { - switch ( mirroring & 1 ) - { - case 1: mirror_horiz(); break; - case 0: mirror_vert(); break; - } - } + void update_mirroring() + { + switch (mirroring & 1) + { + case 1: mirror_horiz(); break; + case 0: mirror_vert(); break; + } + } }; - +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper078.hpp b/source/quickerNES/mappers/mapper078.hpp index 0590040..4b0d158 100644 --- a/source/quickerNES/mappers/mapper078.hpp +++ b/source/quickerNES/mappers/mapper078.hpp @@ -1,74 +1,80 @@ #pragma once -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // Holy Diver and Uchuusen - Cosmo Carrier. -class Mapper078 : public Nes_Mapper { - // lower 8 bits are the reg at 8000:ffff - // next two bits are autodetecting type - // 0 = unknown 1 = cosmo carrier 2 = holy diver - int reg; - void writeinternal(int data, int changed) - { - reg &= 0x300; - reg |= data; +namespace quickerNES +{ - if (changed & 0x07) - set_prg_bank(0x8000, bank_16k, reg & 0x07); - if (changed & 0xf0) - set_chr_bank(0x0000, bank_8k, (reg >> 4) & 0x0f); - if (changed & 0x08) - { - // set mirroring based on memorized board type - if (reg & 0x100) - { - mirror_single((reg >> 3) & 1); - } - else if (reg & 0x200) - { - if (reg & 0x08) - mirror_vert(); - else - mirror_horiz(); - } - else - { - // if you don't set something here, holy diver dumps with 4sc set will - // savestate as 4k NTRAM. then when you later set H\V mapping, state size mismatch. - mirror_single(1); - } - } - } +class Mapper078 : public Mapper +{ + // lower 8 bits are the reg at 8000:ffff + // next two bits are autodetecting type + // 0 = unknown 1 = cosmo carrier 2 = holy diver + int reg; + void writeinternal(int data, int changed) + { + reg &= 0x300; + reg |= data; -public: - Mapper078() - { - register_state(®, 4); - } + if (changed & 0x07) + set_prg_bank(0x8000, bank_16k, reg & 0x07); + if (changed & 0xf0) + set_chr_bank(0x0000, bank_8k, (reg >> 4) & 0x0f); + if (changed & 0x08) + { + // set mirroring based on memorized board type + if (reg & 0x100) + { + mirror_single((reg >> 3) & 1); + } + else if (reg & 0x200) + { + if (reg & 0x08) + mirror_vert(); + else + mirror_horiz(); + } + else + { + // if you don't set something here, holy diver dumps with 4sc set will + // savestate as 4k NTRAM. then when you later set H\V mapping, state size mismatch. + mirror_single(1); + } + } + } - virtual void reset_state() - { - reg = 0; - } + public: + Mapper078() + { + register_state(®, 4); + } - virtual void apply_mapping() - { - writeinternal(reg, 0xff); - } + virtual void reset_state() + { + reg = 0; + } - virtual void write( nes_time_t, nes_addr_t addr, int data) - { - // heuristic: if the first write ever to the register is 0, - // we're on holy diver, otherwise, carrier. it works for these two games... - if (!(reg & 0x300)) - { - reg |= data ? 0x100 : 0x200; - writeinternal(data, 0xff); - } - else - { - writeinternal(data, reg ^ data); - } - } + virtual void apply_mapping() + { + writeinternal(reg, 0xff); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + // heuristic: if the first write ever to the register is 0, + // we're on holy diver, otherwise, carrier. it works for these two games... + if (!(reg & 0x300)) + { + reg |= data ? 0x100 : 0x200; + writeinternal(data, 0xff); + } + else + { + writeinternal(data, reg ^ data); + } + } }; + +} // namespace quickNES diff --git a/source/quickerNES/mappers/mapper079.hpp b/source/quickerNES/mappers/mapper079.hpp index a7f752d..c3b9851 100644 --- a/source/quickerNES/mappers/mapper079.hpp +++ b/source/quickerNES/mappers/mapper079.hpp @@ -25,66 +25,74 @@ * Nina-03 / Nina-06 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -template < bool multicart > -class Mapper_AveNina : public Nes_Mapper { -public: - Mapper_AveNina() - { - register_state( ®s, 1 ); - } +namespace quickerNES +{ - void write_regs(); +template +class Mapper_AveNina : public Mapper +{ + public: + Mapper_AveNina() + { + register_state(®s, 1); + } - virtual void reset_state() - { - intercept_writes( 0x4000, 0x1000 ); - intercept_writes( 0x5000, 0x1000 ); - } + void write_regs(); - virtual void apply_mapping() - { - write_intercepted( 0, 0x4100, regs ); - } + virtual void reset_state() + { + intercept_writes(0x4000, 0x1000); + intercept_writes(0x5000, 0x1000); + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr , int data ) - { - if ( addr < 0x4100 || addr > 0x5FFF ) - return false; + virtual void apply_mapping() + { + write_intercepted(0, 0x4100, regs); + } - if ( addr & 0x100 ) - regs = data; + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr < 0x4100 || addr > 0x5FFF) + return false; - write_regs(); - return true; - } + if (addr & 0x100) + regs = data; - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( multicart == 0 && - ( ( addr == 0x8000 ) || ( addr & 0xFCB0 ) == 0xFCB0 ) ) - set_chr_bank( 0, bank_8k, data & 0x07 ); - } + write_regs(); + return true; + } - uint8_t regs; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (multicart == 0 && + ((addr == 0x8000) || (addr & 0xFCB0) == 0xFCB0)) + set_chr_bank(0, bank_8k, data & 0x07); + } + + uint8_t regs; }; -template < bool multicart > -void Mapper_AveNina< multicart >::write_regs() +template +void Mapper_AveNina::write_regs() { - if ( multicart == 0 ) - { - set_prg_bank ( 0x8000, bank_32k, ( regs >> 3 ) & 0x01 ); - set_chr_bank ( 0, bank_8k, regs & 0x07 ); - } - else - { - set_prg_bank ( 0x8000, bank_32k, ( regs >> 3 ) & 0x07 ); - set_chr_bank ( 0x0000, bank_8k, ( ( regs >> 3 ) & 0x08 ) | ( regs & 0x07 ) ); - if ( regs & 0x80 ) mirror_vert(); - else mirror_horiz(); - } + if (multicart == 0) + { + set_prg_bank(0x8000, bank_32k, (regs >> 3) & 0x01); + set_chr_bank(0, bank_8k, regs & 0x07); + } + else + { + set_prg_bank(0x8000, bank_32k, (regs >> 3) & 0x07); + set_chr_bank(0x0000, bank_8k, ((regs >> 3) & 0x08) | (regs & 0x07)); + if (regs & 0x80) + mirror_vert(); + else + mirror_horiz(); + } } typedef Mapper_AveNina Mapper079; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper085.hpp b/source/quickerNES/mappers/mapper085.hpp index a7fd31a..94e80e4 100644 --- a/source/quickerNES/mappers/mapper085.hpp +++ b/source/quickerNES/mappers/mapper085.hpp @@ -1,10 +1,10 @@ #pragma once -// Nes_Emu 0.5.4. http://www.slack.net/~ant/ +// Emu 0.5.4. http://www.slack.net/~ant/ +#include "apu/vrc7/apu.hpp" +#include "mappers/mapper.hpp" #include -#include "mappers/mapper.h" -#include "apu/vrc7/apu.h" /* Copyright (C) 2004-2005 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -17,201 +17,212 @@ more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +namespace quickerNES +{ + struct vrc7_state_t { - // written registers - uint8_t mirroring; - uint8_t prg_banks [3]; - uint8_t chr_banks [8]; - uint8_t irq_reload; - uint8_t irq_mode; - - // internal state - uint16_t next_time; - uint8_t irq_pending; - uint8_t unused; - - vrc7_snapshot_t sound_state; + // written registers + uint8_t mirroring; + uint8_t prg_banks[3]; + uint8_t chr_banks[8]; + uint8_t irq_reload; + uint8_t irq_mode; + + // internal state + uint16_t next_time; + uint8_t irq_pending; + uint8_t unused; + + vrc7_snapshot_t sound_state; }; -static_assert( sizeof (vrc7_state_t) == 20 + sizeof (vrc7_snapshot_t) ); +static_assert(sizeof(vrc7_state_t) == 20 + sizeof(vrc7_snapshot_t)); // Vrc7 -class Mapper085 : public Nes_Mapper, vrc7_state_t { -public: - Mapper085() - { - vrc7_state_t* state = this; - register_state( state, sizeof *state ); - } - - virtual int channel_count() const { return sound.osc_count; } - - virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } - - virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } - - virtual void save_state( mapper_state_t & out ) - { - sound.save_snapshot( &sound_state ); - Nes_Mapper::save_state( out ); - } - - virtual void load_state( mapper_state_t const& in ) - { - Nes_Mapper::load_state( in ); - sound.load_snapshot( sound_state, in.size ); - } - - virtual void reset_state() - { - mirroring = 0; - - memset( prg_banks, 0, sizeof prg_banks ); - memset( chr_banks, 0, sizeof chr_banks ); - memset( &sound_state, 0, sizeof sound_state ); - - irq_reload = 0; - irq_mode = 0; - irq_pending = false; - - next_time = 0; - sound.reset(); - - set_prg_bank( 0xE000, bank_8k, last_bank ); - apply_mapping(); - } +class Mapper085 : public Mapper, vrc7_state_t +{ + public: + Mapper085() + { + vrc7_state_t *state = this; + register_state(state, sizeof *state); + } - void write_prg_bank( int bank, int data ) - { - prg_banks [bank] = data; - set_prg_bank( 0x8000 | ( bank << bank_8k ), bank_8k, data ); - } - - void write_chr_bank( int bank, int data ) - { - //dprintf( "change chr bank %d\n", bank ); - chr_banks [bank] = data; - set_chr_bank( bank * 0x400, bank_1k, data ); - } - - void write_mirroring( int data ) - { - mirroring = data; - - //dprintf( "Change mirroring %d\n", data ); - enable_sram( data & 128, data & 64 ); + virtual int channel_count() const { return sound.osc_count; } - if ( data & 2 ) - mirror_single( data & 1 ); - else if ( data & 1 ) - mirror_horiz(); - else - mirror_vert(); - } + virtual void set_channel_buf(int i, Blip_Buffer *b) { sound.osc_output(i, b); } - void apply_mapping() - { - size_t i; + virtual void set_treble(blip_eq_t const &eq) { sound.treble_eq(eq); } - for ( i = 0; i < sizeof prg_banks; i++ ) - write_prg_bank( i, prg_banks [i] ); - - for ( i = 0; i < sizeof chr_banks; i++ ) - write_chr_bank( i, chr_banks [i] ); - - write_mirroring( mirroring ); - } - - void reset_timer( nes_time_t present ) - { - next_time = present + unsigned ((0x100 - irq_reload) * timer_period) / 4; - } - - virtual void run_until( nes_time_t end_time ) - { - if ( irq_mode & 2 ) - { - while ( next_time < end_time ) - { - //dprintf( "%d timer expired\n", next_time ); - irq_pending = true; - reset_timer( next_time ); - } - } - } - - virtual void end_frame( nes_time_t end_time ) - { - run_until( end_time ); - - next_time -= end_time; - - sound.end_frame( end_time ); - } - - virtual nes_time_t next_irq( nes_time_t present ) - { - if ( irq_pending ) - return present; - - if ( irq_mode & 2 ) - return next_time + 1; - - return no_irq; - } - - virtual void write( nes_time_t time, nes_addr_t addr, int data ) - { - addr |= ( addr & 8 ) << 1; + virtual void save_state(mapper_state_t &out) + { + sound.save_snapshot(&sound_state); + Mapper::save_state(out); + } - if ( addr >= 0xe010 ) - { - // IRQ - run_until( time ); - //dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); - switch ( addr & 0xf010 ) - { - case 0xe010: - irq_reload = data; - break; - - case 0xf000: - irq_pending = false; - irq_mode = data; - if ( data & 2 ) - reset_timer( time ); - break; - - case 0xf010: - irq_pending = false; - irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); - break; - } - irq_changed(); - } - else if ( ( unsigned ) ( addr - 0xa000 ) < 0x4000 ) - { - write_chr_bank( ((addr >> 4) & 1) | (((addr - 0xa000) >> 11)&~1), data ); - } - else switch ( addr & 0xf010 ) - { - case 0x8000: write_prg_bank( 0, data ); break; - case 0x8010: write_prg_bank( 1, data ); break; - case 0x9000: write_prg_bank( 2, data ); break; - - case 0xe000: - write_mirroring( data ); - break; + virtual void load_state(mapper_state_t const &in) + { + Mapper::load_state(in); + sound.load_snapshot(sound_state, in.size); + } - case 0x9010: - if ( addr & 0x20 ) sound.write_data( time, data ); - else sound.write_reg( data ); - break; - } - } + virtual void reset_state() + { + mirroring = 0; - Nes_Vrc7 sound; - enum { timer_period = 113 * 4 + 3 }; + memset(prg_banks, 0, sizeof prg_banks); + memset(chr_banks, 0, sizeof chr_banks); + memset(&sound_state, 0, sizeof sound_state); + + irq_reload = 0; + irq_mode = 0; + irq_pending = false; + + next_time = 0; + sound.reset(); + + set_prg_bank(0xE000, bank_8k, last_bank); + apply_mapping(); + } + + void write_prg_bank(int bank, int data) + { + prg_banks[bank] = data; + set_prg_bank(0x8000 | (bank << bank_8k), bank_8k, data); + } + + void write_chr_bank(int bank, int data) + { + // dprintf( "change chr bank %d\n", bank ); + chr_banks[bank] = data; + set_chr_bank(bank * 0x400, bank_1k, data); + } + + void write_mirroring(int data) + { + mirroring = data; + + // dprintf( "Change mirroring %d\n", data ); + enable_sram(data & 128, data & 64); + + if (data & 2) + mirror_single(data & 1); + else if (data & 1) + mirror_horiz(); + else + mirror_vert(); + } + + void apply_mapping() + { + size_t i; + + for (i = 0; i < sizeof prg_banks; i++) + write_prg_bank(i, prg_banks[i]); + + for (i = 0; i < sizeof chr_banks; i++) + write_chr_bank(i, chr_banks[i]); + + write_mirroring(mirroring); + } + + void reset_timer(nes_time_t present) + { + next_time = present + unsigned((0x100 - irq_reload) * timer_period) / 4; + } + + virtual void run_until(nes_time_t end_time) + { + if (irq_mode & 2) + { + while (next_time < end_time) + { + // dprintf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer(next_time); + } + } + } + + virtual void end_frame(nes_time_t end_time) + { + run_until(end_time); + + next_time -= end_time; + + sound.end_frame(end_time); + } + + virtual nes_time_t next_irq(nes_time_t present) + { + if (irq_pending) + return present; + + if (irq_mode & 2) + return next_time + 1; + + return no_irq; + } + + virtual void write(nes_time_t time, nes_addr_t addr, int data) + { + addr |= (addr & 8) << 1; + + if (addr >= 0xe010) + { + // IRQ + run_until(time); + // dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); + switch (addr & 0xf010) + { + case 0xe010: + irq_reload = data; + break; + + case 0xf000: + irq_pending = false; + irq_mode = data; + if (data & 2) + reset_timer(time); + break; + + case 0xf010: + irq_pending = false; + irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); + break; + } + irq_changed(); + } + else if ((unsigned)(addr - 0xa000) < 0x4000) + { + write_chr_bank(((addr >> 4) & 1) | (((addr - 0xa000) >> 11) & ~1), data); + } + else + switch (addr & 0xf010) + { + case 0x8000: write_prg_bank(0, data); break; + case 0x8010: write_prg_bank(1, data); break; + case 0x9000: write_prg_bank(2, data); break; + + case 0xe000: + write_mirroring(data); + break; + + case 0x9010: + if (addr & 0x20) + sound.write_data(time, data); + else + sound.write_reg(data); + break; + } + } + + Vrc7 sound; + enum + { + timer_period = 113 * 4 + 3 + }; }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper086.hpp b/source/quickerNES/mappers/mapper086.hpp index 6c35983..4d718e5 100644 --- a/source/quickerNES/mappers/mapper086.hpp +++ b/source/quickerNES/mappers/mapper086.hpp @@ -21,4 +21,9 @@ * */ -typedef Mapper_74x161x162x32<86> Mapper086; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_74x161x162x32<86> Mapper086; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper087.hpp b/source/quickerNES/mappers/mapper087.hpp index abf0d1e..64896bb 100644 --- a/source/quickerNES/mappers/mapper087.hpp +++ b/source/quickerNES/mappers/mapper087.hpp @@ -1,9 +1,9 @@ #pragma once // Optional less-common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -18,30 +18,36 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Jaleco/Konami/Taito -class Mapper087 : public Nes_Mapper { - uint8_t bank; -public: - Mapper087() - { - register_state( &bank, 1 ); - } - - void apply_mapping() - { - intercept_writes( 0x6000, 1 ); - write( 0, 0x6000, bank ); - } - - bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr != 0x6000 ) - return false; - - bank = data; - set_chr_bank( 0, bank_8k, data >> 1 ); - return true; - } - - void write( nes_time_t, nes_addr_t, int ) { } +namespace quickerNES +{ + +class Mapper087 : public Mapper +{ + uint8_t bank; + + public: + Mapper087() + { + register_state(&bank, 1); + } + + void apply_mapping() + { + intercept_writes(0x6000, 1); + write(0, 0x6000, bank); + } + + bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr != 0x6000) + return false; + + bank = data; + set_chr_bank(0, bank_8k, data >> 1); + return true; + } + + void write(nes_time_t, nes_addr_t, int) {} }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper088.hpp b/source/quickerNES/mappers/mapper088.hpp index bfa9e01..677a46e 100644 --- a/source/quickerNES/mappers/mapper088.hpp +++ b/source/quickerNES/mappers/mapper088.hpp @@ -25,87 +25,91 @@ * Mapper 206 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct namco_34x3_state_t { - uint8_t bank [ 8 ]; - uint8_t mirr; - uint8_t mode; + uint8_t bank[8]; + uint8_t mirr; + uint8_t mode; }; -static_assert( sizeof (namco_34x3_state_t) == 10 ); +static_assert(sizeof(namco_34x3_state_t) == 10); -template < bool _is154 > -class Mapper_Namco_34x3 : public Nes_Mapper, namco_34x3_state_t { -public: - Mapper_Namco_34x3() - { - namco_34x3_state_t *state = this; - register_state( state, sizeof *state ); - } +template +class Mapper_Namco_34x3 : public Mapper, namco_34x3_state_t +{ + public: + Mapper_Namco_34x3() + { + namco_34x3_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { } + virtual void reset_state() + { + } - virtual void apply_mapping() - { - set_chr_bank( 0x0000, bank_2k, bank [ 0 ] ); - set_chr_bank( 0x0800, bank_2k, bank [ 1 ] ); - for ( int i = 0; i < 4; i++ ) - set_chr_bank( 0x1000 + ( i << 10 ), bank_1k, bank [ i + 2 ] ); + virtual void apply_mapping() + { + set_chr_bank(0x0000, bank_2k, bank[0]); + set_chr_bank(0x0800, bank_2k, bank[1]); + for (int i = 0; i < 4; i++) + set_chr_bank(0x1000 + (i << 10), bank_1k, bank[i + 2]); - set_prg_bank( 0x8000, bank_8k, bank [ 6 ] ); - set_prg_bank( 0xA000, bank_8k, bank [ 7 ] ); - set_prg_bank( 0xC000, bank_8k, ~1 ); - set_prg_bank( 0xE000, bank_8k, ~0 ); + set_prg_bank(0x8000, bank_8k, bank[6]); + set_prg_bank(0xA000, bank_8k, bank[7]); + set_prg_bank(0xC000, bank_8k, ~1); + set_prg_bank(0xE000, bank_8k, ~0); - if ( _is154 ) - mirror_single( mirr ); - } + if (_is154) + mirror_single(mirr); + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - switch ( addr & 0xE001 ) - { - case 0x8000: - mode = data; - mirr = ( data >> 6 ) & 0x01; - if ( _is154 ) - mirror_single( mirr ); - break; - case 0x8001: - mode &= 0x07; - switch ( mode ) - { - case 0: case 1: - bank [ mode ] = data >> 1; - set_chr_bank( 0x0000 + ( mode << 11 ), bank_2k, bank [ mode ] ); - break; - case 2: case 3: case 4: case 5: - bank [ mode ] = data | 0x40; - set_chr_bank( 0x1000 + ( ( mode - 2 ) << 10 ), bank_1k, bank [ mode ] ); - break; - case 6: case 7: - bank [ mode ] = data; - set_prg_bank( 0x8000 + ( ( mode - 6 ) << 13 ), bank_8k, bank [ mode ] ); - break; - } - break; - case 0xC000: - mirr = ( data >> 6 ) & 0x01; - if ( _is154 ) - mirror_single( mirr ); - } - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr & 0xE001) + { + case 0x8000: + mode = data; + mirr = (data >> 6) & 0x01; + if (_is154) + mirror_single(mirr); + break; + case 0x8001: + mode &= 0x07; + switch (mode) + { + case 0: + case 1: + bank[mode] = data >> 1; + set_chr_bank(0x0000 + (mode << 11), bank_2k, bank[mode]); + break; + case 2: + case 3: + case 4: + case 5: + bank[mode] = data | 0x40; + set_chr_bank(0x1000 + ((mode - 2) << 10), bank_1k, bank[mode]); + break; + case 6: + case 7: + bank[mode] = data; + set_prg_bank(0x8000 + ((mode - 6) << 13), bank_8k, bank[mode]); + break; + } + break; + case 0xC000: + mirr = (data >> 6) & 0x01; + if (_is154) + mirror_single(mirr); + } + } }; typedef Mapper_Namco_34x3 Mapper088; -// void register_mapper_namco_34xx(); -// void register_mapper_namco_34xx() -// { -// register_mapper< Mapper_Namco_34x3 > ( 88 ); -// register_mapper< Mapper_Namco_34x3 > ( 154 ); -// register_mapper< Mapper_Namco_34xx > ( 206 ); -// } +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper089.hpp b/source/quickerNES/mappers/mapper089.hpp index 0937828..851474a 100644 --- a/source/quickerNES/mappers/mapper089.hpp +++ b/source/quickerNES/mappers/mapper089.hpp @@ -22,34 +22,41 @@ * Mapper 93 - Sunsoft-2 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // Sunsoft2b -class Mapper089 : public Nes_Mapper { -public: - Mapper089() - { - register_state( ®s, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - {} +class Mapper089 : public Mapper +{ + public: + Mapper089() + { + register_state(®s, 1); + } - virtual void apply_mapping() - { - set_prg_bank( 0xC000, bank_16k, last_bank ); - write( 0, 0x8000, regs ); - } + virtual void reset_state() + { + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - regs = handle_bus_conflict( addr, data ); + virtual void apply_mapping() + { + set_prg_bank(0xC000, bank_16k, last_bank); + write(0, 0x8000, regs); + } - set_chr_bank( 0x0000, bank_8k, ( ( data >> 4 ) & 0x08 ) | ( data & 0x07 ) ); - set_prg_bank( 0x8000, bank_16k, ( data >> 4 ) & 0x07 ); - mirror_single( ( data >> 3 ) & 1 ); - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + regs = handle_bus_conflict(addr, data); - uint8_t regs; + set_chr_bank(0x0000, bank_8k, ((data >> 4) & 0x08) | (data & 0x07)); + set_prg_bank(0x8000, bank_16k, (data >> 4) & 0x07); + mirror_single((data >> 3) & 1); + } + + uint8_t regs; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper093.hpp b/source/quickerNES/mappers/mapper093.hpp index 7d2284d..df06a3e 100644 --- a/source/quickerNES/mappers/mapper093.hpp +++ b/source/quickerNES/mappers/mapper093.hpp @@ -22,33 +22,40 @@ * Mapper 93 - Sunsoft-2 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -// Sunsoft2a +// Sunsoft2a -class Mapper093 : public Nes_Mapper { -public: - Mapper093() - { - register_state( ®s, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - {} +class Mapper093 : public Mapper +{ + public: + Mapper093() + { + register_state(®s, 1); + } - virtual void apply_mapping() - { - set_prg_bank( 0xC000, bank_16k, last_bank ); - write( 0, 0x8000, regs ); - } + virtual void reset_state() + { + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - regs = handle_bus_conflict( addr, data ); + virtual void apply_mapping() + { + set_prg_bank(0xC000, bank_16k, last_bank); + write(0, 0x8000, regs); + } - set_chr_bank( 0x0000, bank_8k, data & 0x0F ); - set_prg_bank( 0x8000, bank_16k, ( data >> 4 ) & 0x07 ); - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + regs = handle_bus_conflict(addr, data); - uint8_t regs; + set_chr_bank(0x0000, bank_8k, data & 0x0F); + set_prg_bank(0x8000, bank_16k, (data >> 4) & 0x07); + } + + uint8_t regs; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper094.hpp b/source/quickerNES/mappers/mapper094.hpp index cf64887..39e39ad 100644 --- a/source/quickerNES/mappers/mapper094.hpp +++ b/source/quickerNES/mappers/mapper094.hpp @@ -18,36 +18,43 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * This mapper was added by retrowertz for Libretro port of QuickNES. - * + * * Mapper 94 - HVC-UN1ROM * Senjou no Ookami (Japanese version of Commando) * */ -#include "mappers/mapper.h" - -// Un1rom +#include "mappers/mapper.hpp" -class Mapper094 : public Nes_Mapper { -public: - Mapper094() - { - register_state( &bank, 1 ); - } +// Un1rom - virtual void reset_state() - { } +namespace quickerNES +{ - virtual void apply_mapping() - { - write( 0, 0, bank ); - } +class Mapper094 : public Mapper +{ + public: + Mapper094() + { + register_state(&bank, 1); + } - virtual void write( nes_time_t, nes_addr_t, int data ) - { - bank = data; - set_prg_bank( 0x8000, bank_16k, bank >> 2 ); - } + virtual void reset_state() + { + } - uint8_t bank; + virtual void apply_mapping() + { + write(0, 0, bank); + } + + virtual void write(nes_time_t, nes_addr_t, int data) + { + bank = data; + set_prg_bank(0x8000, bank_16k, bank >> 2); + } + + uint8_t bank; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper097.hpp b/source/quickerNES/mappers/mapper097.hpp index a452089..375284d 100644 --- a/source/quickerNES/mappers/mapper097.hpp +++ b/source/quickerNES/mappers/mapper097.hpp @@ -18,46 +18,52 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * This mapper was added by retrowertz for Libretro port of QuickNES. - * + * * Mapper 97 - Kaiketsu Yanchamaru * */ -#include "mappers/mapper.h" - -// Irem_Tam_S1 +#include "mappers/mapper.hpp" -class Mapper097 : public Nes_Mapper { -public: - Mapper097() - { - register_state( &bank, 1 ); - } +// Irem_Tam_S1 - virtual void reset_state() - { - bank = ~0; - } +namespace quickerNES +{ - virtual void apply_mapping() - { - write( 0, 0, bank ); - } +class Mapper097 : public Mapper +{ + public: + Mapper097() + { + register_state(&bank, 1); + } - virtual void write( nes_time_t, nes_addr_t, int data ) - { - bank = data; - set_prg_bank( 0x8000, bank_16k, ~0 ); - set_prg_bank( 0xC000, bank_16k, bank & 0x0F ); + virtual void reset_state() + { + bank = ~0; + } - switch ( ( bank >> 6 ) & 0x03 ) - { - case 1: mirror_horiz(); break; - case 2: mirror_vert(); break; - case 0: - case 3: mirror_single( bank & 0x01 ); break; - } - } + virtual void apply_mapping() + { + write(0, 0, bank); + } - uint8_t bank; + virtual void write(nes_time_t, nes_addr_t, int data) + { + bank = data; + set_prg_bank(0x8000, bank_16k, ~0); + set_prg_bank(0xC000, bank_16k, bank & 0x0F); + + switch ((bank >> 6) & 0x03) + { + case 1: mirror_horiz(); break; + case 2: mirror_vert(); break; + case 0: + case 3: mirror_single(bank & 0x01); break; + } + } + + uint8_t bank; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper113.hpp b/source/quickerNES/mappers/mapper113.hpp index d43252c..d60b138 100644 --- a/source/quickerNES/mappers/mapper113.hpp +++ b/source/quickerNES/mappers/mapper113.hpp @@ -25,4 +25,9 @@ * Nina-03 / Nina-06 */ -typedef Mapper_AveNina Mapper113; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_AveNina Mapper113; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper140.hpp b/source/quickerNES/mappers/mapper140.hpp index ece1c8c..66bc113 100644 --- a/source/quickerNES/mappers/mapper140.hpp +++ b/source/quickerNES/mappers/mapper140.hpp @@ -24,40 +24,46 @@ * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" -// Jaleco_JF11 +// Jaleco_JF11 -class Mapper140 : public Nes_Mapper { -public: - Mapper140() - { - register_state( ®s, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - { - intercept_writes( 0x6000, 1 ); - } +class Mapper140 : public Mapper +{ + public: + Mapper140() + { + register_state(®s, 1); + } - virtual void apply_mapping() - { - write_intercepted(0, 0x6000, regs ); - } + virtual void reset_state() + { + intercept_writes(0x6000, 1); + } - bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) - { - if ( addr < 0x6000 || addr > 0x7FFF ) - return false; + virtual void apply_mapping() + { + write_intercepted(0, 0x6000, regs); + } - regs = data; - set_prg_bank( 0x8000, bank_32k, data >> 4); - set_chr_bank( 0, bank_8k, data ); + bool write_intercepted(nes_time_t time, nes_addr_t addr, int data) + { + if (addr < 0x6000 || addr > 0x7FFF) + return false; - return true; - } + regs = data; + set_prg_bank(0x8000, bank_32k, data >> 4); + set_chr_bank(0, bank_8k, data); - virtual void write( nes_time_t, nes_addr_t addr, int data ) { } + return true; + } - uint8_t regs; + virtual void write(nes_time_t, nes_addr_t addr, int data) {} + + uint8_t regs; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper152.hpp b/source/quickerNES/mappers/mapper152.hpp index 9cf4bcc..6b5071f 100644 --- a/source/quickerNES/mappers/mapper152.hpp +++ b/source/quickerNES/mappers/mapper152.hpp @@ -21,4 +21,9 @@ * */ -typedef Mapper_74x161x162x32<152> Mapper152; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_74x161x162x32<152> Mapper152; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper154.hpp b/source/quickerNES/mappers/mapper154.hpp index df363f6..d8e74c6 100644 --- a/source/quickerNES/mappers/mapper154.hpp +++ b/source/quickerNES/mappers/mapper154.hpp @@ -25,4 +25,9 @@ * Mapper 206 */ -typedef Mapper_Namco_34x3 Mapper154; \ No newline at end of file +namespace quickerNES +{ + +typedef Mapper_Namco_34x3 Mapper154; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper156.hpp b/source/quickerNES/mappers/mapper156.hpp index 2f93dde..73e648b 100644 --- a/source/quickerNES/mappers/mapper156.hpp +++ b/source/quickerNES/mappers/mapper156.hpp @@ -1,59 +1,65 @@ #pragma once -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // DIS23C01 DAOU ROM CONTROLLER +namespace quickerNES +{ + struct m156_state_t { - uint8_t prg_bank; - uint8_t chr_banks [8]; + uint8_t prg_bank; + uint8_t chr_banks[8]; }; -static_assert( sizeof (m156_state_t) == 9 ); +static_assert(sizeof(m156_state_t) == 9); -class Mapper156 : public Nes_Mapper, m156_state_t { -public: - Mapper156() - { - m156_state_t * state = this; - register_state( state, sizeof * state ); - } +class Mapper156 : public Mapper, m156_state_t +{ + public: + Mapper156() + { + m156_state_t *state = this; + register_state(state, sizeof *state); + } - void reset_state() - { - prg_bank = 0; - for ( unsigned i = 0; i < 8; i++ ) chr_banks [i] = i; - enable_sram(); - apply_mapping(); - } + void reset_state() + { + prg_bank = 0; + for (unsigned i = 0; i < 8; i++) chr_banks[i] = i; + enable_sram(); + apply_mapping(); + } - void apply_mapping() - { - mirror_single( 0 ); - set_prg_bank( 0x8000, bank_16k, prg_bank ); + void apply_mapping() + { + mirror_single(0); + set_prg_bank(0x8000, bank_16k, prg_bank); - for ( int i = 0; i < (int) sizeof chr_banks; i++ ) - set_chr_bank( i * 0x400, bank_1k, chr_banks [i] ); - } + for (int i = 0; i < (int)sizeof chr_banks; i++) + set_chr_bank(i * 0x400, bank_1k, chr_banks[i]); + } - void write( nes_time_t, nes_addr_t addr, int data ) - { - unsigned int reg = addr - 0xC000; - if ( addr == 0xC010 ) - { - prg_bank = data; - set_prg_bank( 0x8000, bank_16k, data ); - } - else if ( reg < 4 ) - { - chr_banks [reg] = data; - set_chr_bank( reg * 0x400, bank_1k, data ); - } - else if ( ( reg - 8 ) < 4 ) - { - reg -= 4; - chr_banks [reg] = data; - set_chr_bank( reg * 0x400, bank_1k, data ); - } - } + void write(nes_time_t, nes_addr_t addr, int data) + { + unsigned int reg = addr - 0xC000; + if (addr == 0xC010) + { + prg_bank = data; + set_prg_bank(0x8000, bank_16k, data); + } + else if (reg < 4) + { + chr_banks[reg] = data; + set_chr_bank(reg * 0x400, bank_1k, data); + } + else if ((reg - 8) < 4) + { + reg -= 4; + chr_banks[reg] = data; + set_chr_bank(reg * 0x400, bank_1k, data); + } + } }; + +} // namespace quickNES diff --git a/source/quickerNES/mappers/mapper180.hpp b/source/quickerNES/mappers/mapper180.hpp index f2c01c6..0c9e9f8 100644 --- a/source/quickerNES/mappers/mapper180.hpp +++ b/source/quickerNES/mappers/mapper180.hpp @@ -18,36 +18,43 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * This mapper was added by retrowertz for Libretro port of QuickNES. - * + * * Mapper 180 Crazy Climber * */ - -#include "mappers/mapper.h" - + +#include "mappers/mapper.hpp" + // UxROM (inverted) -class Mapper180 : public Nes_Mapper { -public: - Mapper180() - { - register_state( &bank, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - { } +class Mapper180 : public Mapper +{ + public: + Mapper180() + { + register_state(&bank, 1); + } - virtual void apply_mapping() - { - set_prg_bank( 0x8000, bank_16k, 0 ); - write( 0, 0, bank ); - } + virtual void reset_state() + { + } - virtual void write( nes_time_t, nes_addr_t, int data ) - { - bank = data; - set_prg_bank( 0xC000, bank_16k, data ); - } + virtual void apply_mapping() + { + set_prg_bank(0x8000, bank_16k, 0); + write(0, 0, bank); + } - uint8_t bank; + virtual void write(nes_time_t, nes_addr_t, int data) + { + bank = data; + set_prg_bank(0xC000, bank_16k, data); + } + + uint8_t bank; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper184.hpp b/source/quickerNES/mappers/mapper184.hpp index 96b8b97..19ac133 100644 --- a/source/quickerNES/mappers/mapper184.hpp +++ b/source/quickerNES/mappers/mapper184.hpp @@ -22,42 +22,49 @@ * Mapper 184 - Sunsoft-1 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // Sunsoft1 -class Mapper184 : public Nes_Mapper { -public: - Mapper184() - { - register_state( ®s, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - {} +class Mapper184 : public Mapper +{ + public: + Mapper184() + { + register_state(®s, 1); + } - virtual void apply_mapping() - { - set_prg_bank( 0x8000, bank_32k, 0 ); - intercept_writes( 0x6000, 1 ); - write_intercepted( 0, 0x6000, regs ); - } + virtual void reset_state() + { + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr != 0x6000 ) - return false; + virtual void apply_mapping() + { + set_prg_bank(0x8000, bank_32k, 0); + intercept_writes(0x6000, 1); + write_intercepted(0, 0x6000, regs); + } - regs = data; - set_chr_bank( 0x0000, bank_4k, data & 0x07 ); - set_chr_bank( 0x1000, bank_4k, ( data >> 4 ) & 0x07 ); + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr != 0x6000) + return false; - return true; - } + regs = data; + set_chr_bank(0x0000, bank_4k, data & 0x07); + set_chr_bank(0x1000, bank_4k, (data >> 4) & 0x07); - virtual void write( nes_time_t, nes_addr_t addr, int data ) - {} + return true; + } - uint8_t regs; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + } + + uint8_t regs; }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper190.hpp b/source/quickerNES/mappers/mapper190.hpp index 5d09320..4ca7ec6 100644 --- a/source/quickerNES/mappers/mapper190.hpp +++ b/source/quickerNES/mappers/mapper190.hpp @@ -1,55 +1,60 @@ #pragma once -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // Magic Kid Googoo -class Mapper190: public Nes_Mapper { -public: - Mapper190() - { - } +namespace quickerNES +{ - virtual void reset_state() - { - } +class Mapper190 : public Mapper +{ + public: + Mapper190() + { + } - virtual void apply_mapping() - { - mirror_vert(); - enable_sram(); - set_prg_bank( 0xc000, bank_16k, 0); - } + virtual void reset_state() + { + } - virtual void write(nes_time_t, nes_addr_t addr, int data) - { - switch ( addr >> 12 ) - { - case 0x8: - case 0x9: - case 0xc: - case 0xd: - set_prg_bank( 0x8000, bank_16k, ( ( ( addr >> 11 ) & 8 ) | ( data & 7 ) ) ); - break; - case 0xa: - case 0xb: - switch ( addr & 3 ) - { - case 0: - set_chr_bank( 0x0000, bank_2k, data ); - break; - case 1: - set_chr_bank( 0x0800, bank_2k, data ); - break; - case 2: - set_chr_bank( 0x1000, bank_2k, data ); - break; - case 3: - set_chr_bank( 0x1800, bank_2k, data ); - break; - } - break; - } - } + virtual void apply_mapping() + { + mirror_vert(); + enable_sram(); + set_prg_bank(0xc000, bank_16k, 0); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr >> 12) + { + case 0x8: + case 0x9: + case 0xc: + case 0xd: + set_prg_bank(0x8000, bank_16k, (((addr >> 11) & 8) | (data & 7))); + break; + case 0xa: + case 0xb: + switch (addr & 3) + { + case 0: + set_chr_bank(0x0000, bank_2k, data); + break; + case 1: + set_chr_bank(0x0800, bank_2k, data); + break; + case 2: + set_chr_bank(0x1000, bank_2k, data); + break; + case 3: + set_chr_bank(0x1800, bank_2k, data); + break; + } + break; + } + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper193.hpp b/source/quickerNES/mappers/mapper193.hpp index e2b2dfa..f32ca95 100644 --- a/source/quickerNES/mappers/mapper193.hpp +++ b/source/quickerNES/mappers/mapper193.hpp @@ -25,50 +25,57 @@ * */ -#include "mappers/mapper.h" - +#include "mappers/mapper.hpp" + // NTDEC's TC-112 mapper IC. -class Mapper193 : public Nes_Mapper { -public: - Mapper193() - { - register_state( regs, sizeof regs ); - } +namespace quickerNES +{ - virtual void reset_state() - { } +class Mapper193 : public Mapper +{ + public: + Mapper193() + { + register_state(regs, sizeof regs); + } - virtual void apply_mapping() - { - for ( size_t i = 0; i < sizeof regs; i++ ) - write_intercepted( 0, 0x6000 + i, regs [ i ] ); - set_prg_bank( 0xA000, bank_8k, ~2 ); - set_prg_bank( 0xC000, bank_8k, ~1 ); - set_prg_bank( 0xE000, bank_8k, ~0 ); - intercept_writes( 0x6000, 0x03 ); - } + virtual void reset_state() + { + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { } + virtual void apply_mapping() + { + for (size_t i = 0; i < sizeof regs; i++) + write_intercepted(0, 0x6000 + i, regs[i]); + set_prg_bank(0xA000, bank_8k, ~2); + set_prg_bank(0xC000, bank_8k, ~1); + set_prg_bank(0xE000, bank_8k, ~0); + intercept_writes(0x6000, 0x03); + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr < 0x6000 || addr > 0x6003 ) - return false; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + } - regs [ addr & 0x03 ] = data; - switch ( addr & 0x03 ) - { - case 0: set_chr_bank( 0x0000, bank_4k, regs [ 0 ] >> 2 ); break; - case 1: set_chr_bank( 0x1000, bank_2k, regs [ 1 ] >> 1 ); break; - case 2: set_chr_bank( 0x1800, bank_2k, regs [ 2 ] >> 1 ); break; - case 3: set_prg_bank( 0x8000, bank_8k, regs [ 3 ] ); break; - } + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr < 0x6000 || addr > 0x6003) + return false; - return true; - } + regs[addr & 0x03] = data; + switch (addr & 0x03) + { + case 0: set_chr_bank(0x0000, bank_4k, regs[0] >> 2); break; + case 1: set_chr_bank(0x1000, bank_2k, regs[1] >> 1); break; + case 2: set_chr_bank(0x1800, bank_2k, regs[2] >> 1); break; + case 3: set_prg_bank(0x8000, bank_8k, regs[3]); break; + } - uint8_t regs [ 4 ]; + return true; + } + + uint8_t regs[4]; }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper206.hpp b/source/quickerNES/mappers/mapper206.hpp index 35a00a3..c757335 100644 --- a/source/quickerNES/mappers/mapper206.hpp +++ b/source/quickerNES/mappers/mapper206.hpp @@ -25,68 +25,79 @@ * Mapper 206 */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct namco_34xx_state_t { - uint8_t bank [ 8 ]; - uint8_t mirr; - uint8_t mode; + uint8_t bank[8]; + uint8_t mirr; + uint8_t mode; }; -static_assert( sizeof (namco_34xx_state_t) == 10 ); +static_assert(sizeof(namco_34xx_state_t) == 10); // Namco_34xx -class Mapper206 : public Nes_Mapper, namco_34xx_state_t { -public: - Mapper206() - { - namco_34xx_state_t *state = this; - register_state( state, sizeof *state ); - } +class Mapper206 : public Mapper, namco_34xx_state_t +{ + public: + Mapper206() + { + namco_34xx_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { } + virtual void reset_state() + { + } - virtual void apply_mapping() - { - set_chr_bank( 0x0000, bank_2k, bank [ 0 ] ); - set_chr_bank( 0x0800, bank_2k, bank [ 1 ] ); - for ( int i = 0; i < 4; i++ ) - set_chr_bank( 0x1000 + ( i << 10 ), bank_1k, bank [ i + 2 ] ); + virtual void apply_mapping() + { + set_chr_bank(0x0000, bank_2k, bank[0]); + set_chr_bank(0x0800, bank_2k, bank[1]); + for (int i = 0; i < 4; i++) + set_chr_bank(0x1000 + (i << 10), bank_1k, bank[i + 2]); - set_prg_bank( 0x8000, bank_8k, bank [ 6 ] ); - set_prg_bank( 0xA000, bank_8k, bank [ 7 ] ); - set_prg_bank( 0xC000, bank_8k, ~1 ); - set_prg_bank( 0xE000, bank_8k, ~0 ); - } + set_prg_bank(0x8000, bank_8k, bank[6]); + set_prg_bank(0xA000, bank_8k, bank[7]); + set_prg_bank(0xC000, bank_8k, ~1); + set_prg_bank(0xE000, bank_8k, ~0); + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - switch ( addr & 0xE001 ) - { - case 0x8000: - mode = data; - break; - case 0x8001: - mode &= 0x07; - switch ( mode ) - { - case 0: case 1: - bank [ mode ] = data >> 1; - set_chr_bank( 0x0000 + ( mode << 11 ), bank_2k, bank [ mode ] ); - break; - case 2: case 3: case 4: case 5: - bank [ mode ] = data; - set_chr_bank( 0x1000 + ( ( mode - 2 ) << 10 ), bank_1k, bank [ mode ] ); - break; - case 6: case 7: - bank [ mode ] = data; - set_prg_bank( 0x8000 + ( ( mode - 6 ) << 13 ), bank_8k, bank [ mode ] ); - break; - } - break; - } - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + switch (addr & 0xE001) + { + case 0x8000: + mode = data; + break; + case 0x8001: + mode &= 0x07; + switch (mode) + { + case 0: + case 1: + bank[mode] = data >> 1; + set_chr_bank(0x0000 + (mode << 11), bank_2k, bank[mode]); + break; + case 2: + case 3: + case 4: + case 5: + bank[mode] = data; + set_chr_bank(0x1000 + ((mode - 2) << 10), bank_1k, bank[mode]); + break; + case 6: + case 7: + bank[mode] = data; + set_prg_bank(0x8000 + ((mode - 6) << 13), bank_8k, bank[mode]); + break; + } + break; + } + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper207.hpp b/source/quickerNES/mappers/mapper207.hpp index cb3c159..ac2677a 100644 --- a/source/quickerNES/mappers/mapper207.hpp +++ b/source/quickerNES/mappers/mapper207.hpp @@ -18,73 +18,79 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * This mapper was added by retrowertz for Libretro port of QuickNES. - * + * * Taito's X1-005 ( Rev. B ) Fudou Myouou Den * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" + +namespace quickerNES +{ struct taito_x1005_state_t { - uint8_t preg [ 3 ]; - uint8_t creg [ 6 ]; - uint8_t nametable [ 2 ]; + uint8_t preg[3]; + uint8_t creg[6]; + uint8_t nametable[2]; }; -static_assert( sizeof (taito_x1005_state_t) == 11 ); +static_assert(sizeof(taito_x1005_state_t) == 11); // TaitoX1005 -class Mapper207 : public Nes_Mapper, taito_x1005_state_t { -public: - Mapper207() - { - taito_x1005_state_t *state = this; - register_state( state, sizeof *state ); - } +class Mapper207 : public Mapper, taito_x1005_state_t +{ + public: + Mapper207() + { + taito_x1005_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { } + virtual void reset_state() + { + } - virtual void apply_mapping() - { - int i; - intercept_writes( 0x7EF0, 1 ); - for ( i = 0; i < 3; i++ ) - set_prg_bank( 0x8000 + ( i << 13 ), bank_8k, preg [ i ] ); - for ( i = 0; i < 2; i++ ) - set_chr_bank( 0x0000 + ( i << 11 ), bank_2k, creg [ i ] >> 1); - for ( i = 0; i < 4; i++ ) - set_chr_bank( 0x1000 + ( i << 10 ), bank_1k, creg [ 2 + i ] ); - mirror_manual( nametable [ 0 ], nametable [ 0 ], nametable [ 1 ], nametable [ 1 ] ); - } + virtual void apply_mapping() + { + int i; + intercept_writes(0x7EF0, 1); + for (i = 0; i < 3; i++) + set_prg_bank(0x8000 + (i << 13), bank_8k, preg[i]); + for (i = 0; i < 2; i++) + set_chr_bank(0x0000 + (i << 11), bank_2k, creg[i] >> 1); + for (i = 0; i < 4; i++) + set_chr_bank(0x1000 + (i << 10), bank_1k, creg[2 + i]); + mirror_manual(nametable[0], nametable[0], nametable[1], nametable[1]); + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr < 0x7EF0 || addr > 0x7EFF ) - return false; - - if ( ( addr & 0x0F ) < 6 ) - { - creg [ addr & 0x07 ] = data; - if ( ( addr & 0x0F ) < 2 ) - { - nametable [ addr & 0x01 ] = data >> 7; - mirror_manual( nametable [ 0 ], nametable [ 0 ], nametable [ 1 ], nametable [ 1 ] ); - set_chr_bank( ( addr << 11 ) & 0x800, bank_2k, creg [ addr & 0x01 ] >> 1 ); - return true; - } + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr < 0x7EF0 || addr > 0x7EFF) + return false; - set_chr_bank( 0x1000 | ( ( addr - 0x7EF2 ) << 10 ), bank_1k, creg [ addr & 0x07 ] ); - return true; - } - - addr = ( addr - 0x7EFA ) >> 1; - preg [ addr ] = data; - set_prg_bank( 0x8000 | ( addr << 13 ), bank_8k, preg [ addr ] ); - return true; - } + if ((addr & 0x0F) < 6) + { + creg[addr & 0x07] = data; + if ((addr & 0x0F) < 2) + { + nametable[addr & 0x01] = data >> 7; + mirror_manual(nametable[0], nametable[0], nametable[1], nametable[1]); + set_chr_bank((addr << 11) & 0x800, bank_2k, creg[addr & 0x01] >> 1); + return true; + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) { } + set_chr_bank(0x1000 | ((addr - 0x7EF2) << 10), bank_1k, creg[addr & 0x07]); + return true; + } + + addr = (addr - 0x7EFA) >> 1; + preg[addr] = data; + set_prg_bank(0x8000 | (addr << 13), bank_8k, preg[addr]); + return true; + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) {} }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper232.hpp b/source/quickerNES/mappers/mapper232.hpp index b8d465f..69a6cd0 100644 --- a/source/quickerNES/mappers/mapper232.hpp +++ b/source/quickerNES/mappers/mapper232.hpp @@ -3,9 +3,9 @@ // Optional less-common simple mappers -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ +// Emu 0.7.0. http://www.slack.net/~ant/ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" /* Copyright (C) 2004-2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -20,34 +20,40 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Quattro -class Mapper232 : public Nes_Mapper { - uint8_t regs [2]; -public: - Mapper232() - { - register_state( regs, sizeof regs ); - } - - virtual void reset_state() - { - regs [0] = 0; - regs [1] = 3; - } - - virtual void apply_mapping() - { - int bank = regs [0] >> 1 & 0x0c; - set_prg_bank( 0x8000, bank_16k, bank + (regs [1] & 3) ); - set_prg_bank( 0xC000, bank_16k, bank + 3 ); - } - - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr < 0xc000 ) - regs [0] = data; - else - regs [1] = data; - Mapper232::apply_mapping(); - } +namespace quickerNES +{ + +class Mapper232 : public Mapper +{ + uint8_t regs[2]; + + public: + Mapper232() + { + register_state(regs, sizeof regs); + } + + virtual void reset_state() + { + regs[0] = 0; + regs[1] = 3; + } + + virtual void apply_mapping() + { + int bank = regs[0] >> 1 & 0x0c; + set_prg_bank(0x8000, bank_16k, bank + (regs[1] & 3)); + set_prg_bank(0xC000, bank_16k, bank + 3); + } + + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (addr < 0xc000) + regs[0] = data; + else + regs[1] = data; + Mapper232::apply_mapping(); + } }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper240.hpp b/source/quickerNES/mappers/mapper240.hpp index f2817b3..05b4541 100644 --- a/source/quickerNES/mappers/mapper240.hpp +++ b/source/quickerNES/mappers/mapper240.hpp @@ -21,42 +21,49 @@ * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // https://www.nesdev.org/wiki/INES_Mapper240 -class Mapper240 : public Nes_Mapper { -public: - Mapper240() - { - register_state( ®s, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - { - } +class Mapper240 : public Mapper +{ + public: + Mapper240() + { + register_state(®s, 1); + } - virtual void apply_mapping() - { - enable_sram(); - intercept_writes( 0x4020, 1 ); - write_intercepted( 0, 0x4120, regs ); - } + virtual void reset_state() + { + } - virtual void write( nes_time_t, nes_addr_t, int data ) - { } + virtual void apply_mapping() + { + enable_sram(); + intercept_writes(0x4020, 1); + write_intercepted(0, 0x4120, regs); + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr < 0x4020 || addr > 0x5FFF ) - return false; + virtual void write(nes_time_t, nes_addr_t, int data) + { + } - regs = data; - set_chr_bank( 0x0000, bank_8k, data & 0x0F ); - set_prg_bank( 0x8000, bank_32k, data >> 4 ); + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + if (addr < 0x4020 || addr > 0x5FFF) + return false; - return true; - } + regs = data; + set_chr_bank(0x0000, bank_8k, data & 0x0F); + set_prg_bank(0x8000, bank_32k, data >> 4); - uint8_t regs; + return true; + } + + uint8_t regs; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper241.hpp b/source/quickerNES/mappers/mapper241.hpp index 02db4b7..628a282 100644 --- a/source/quickerNES/mappers/mapper241.hpp +++ b/source/quickerNES/mappers/mapper241.hpp @@ -21,31 +21,38 @@ * */ -#include "mappers/mapper.h" - +#include "mappers/mapper.hpp" + // https://www.nesdev.org/wiki/INES_Mapper241 -class Mapper241 : public Nes_Mapper { -public: - Mapper241() - { - register_state( &bank, 1 ); - } +namespace quickerNES +{ - virtual void reset_state() - { } +class Mapper241 : public Mapper +{ + public: + Mapper241() + { + register_state(&bank, 1); + } - virtual void apply_mapping() - { - enable_sram(); - write( 0, 0, bank ); - } + virtual void reset_state() + { + } - virtual void write( nes_time_t, nes_addr_t, int data ) - { - bank = data; - set_prg_bank( 0x8000, bank_32k, bank ); - } + virtual void apply_mapping() + { + enable_sram(); + write(0, 0, bank); + } - uint8_t bank; + virtual void write(nes_time_t, nes_addr_t, int data) + { + bank = data; + set_prg_bank(0x8000, bank_32k, bank); + } + + uint8_t bank; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper244.hpp b/source/quickerNES/mappers/mapper244.hpp index 038dfa3..167288c 100644 --- a/source/quickerNES/mappers/mapper244.hpp +++ b/source/quickerNES/mappers/mapper244.hpp @@ -23,46 +23,53 @@ * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // https://www.nesdev.org/wiki/INES_Mapper244 +namespace quickerNES +{ + struct mapper244_state_t { - uint8_t preg; - uint8_t creg; + uint8_t preg; + uint8_t creg; }; -static_assert( sizeof (mapper244_state_t) == 2 ); +static_assert(sizeof(mapper244_state_t) == 2); -class Mapper244 : public Nes_Mapper, mapper244_state_t { -public: - Mapper244() - { - mapper244_state_t *state = this; - register_state( state, sizeof *state ); - } +class Mapper244 : public Mapper, mapper244_state_t +{ + public: + Mapper244() + { + mapper244_state_t *state = this; + register_state(state, sizeof *state); + } - virtual void reset_state() - { } + virtual void reset_state() + { + } - virtual void apply_mapping() - { - set_prg_bank( 0x8000, bank_32k, preg ); - set_chr_bank( 0x0000, bank_8k, creg ); - } + virtual void apply_mapping() + { + set_prg_bank(0x8000, bank_32k, preg); + set_chr_bank(0x0000, bank_8k, creg); + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { - if ( addr >= 0x8065 && addr <= 0x80A4 ) - { - preg = ( addr - 0x8065 ) & 0x03; - set_prg_bank( 0x8000, bank_32k, preg ); - } + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + if (addr >= 0x8065 && addr <= 0x80A4) + { + preg = (addr - 0x8065) & 0x03; + set_prg_bank(0x8000, bank_32k, preg); + } - if ( addr >= 0x80A5 && addr <= 0x80E4 ) - { - creg = (addr - 0x80A5 ) & 0x07; - set_chr_bank( 0x0000, bank_8k, creg ); - } - } + if (addr >= 0x80A5 && addr <= 0x80E4) + { + creg = (addr - 0x80A5) & 0x07; + set_chr_bank(0x0000, bank_8k, creg); + } + } }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/mappers/mapper246.hpp b/source/quickerNES/mappers/mapper246.hpp index bd9d8a9..e1678ec 100644 --- a/source/quickerNES/mappers/mapper246.hpp +++ b/source/quickerNES/mappers/mapper246.hpp @@ -23,52 +23,58 @@ * */ -#include "mappers/mapper.h" +#include "mappers/mapper.hpp" // https://www.nesdev.org/wiki/INES_Mapper246 -class Mapper246 : public Nes_Mapper { -public: - Mapper246() - { - register_state( regs, sizeof regs ); - } +namespace quickerNES +{ - virtual void reset_state() - { - regs [ 3 ] = ~0; - } +class Mapper246 : public Mapper +{ + public: + Mapper246() + { + register_state(regs, sizeof regs); + } - virtual void apply_mapping() - { - enable_sram(); - intercept_writes( 0x6000, 0x07 ); - for ( size_t i = 0; i < sizeof regs; i++ ) - write_intercepted( 0, 0x6000 + i, regs [ i ] ); - } + virtual void reset_state() + { + regs[3] = ~0; + } - virtual void write( nes_time_t, nes_addr_t addr, int data ) - { } + virtual void apply_mapping() + { + enable_sram(); + intercept_writes(0x6000, 0x07); + for (size_t i = 0; i < sizeof regs; i++) + write_intercepted(0, 0x6000 + i, regs[i]); + } - virtual bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) - { - int bank = addr & 0x07; + virtual void write(nes_time_t, nes_addr_t addr, int data) + { + } - if ( addr < 0x6000 || addr > 0x67FF ) - return false; + virtual bool write_intercepted(nes_time_t, nes_addr_t addr, int data) + { + int bank = addr & 0x07; - regs [ bank ] = data; - if ( bank < 4 ) - { - set_prg_bank( 0x8000 + ( bank << 13 ), bank_8k, data ); - return true; - } + if (addr < 0x6000 || addr > 0x67FF) + return false; - set_chr_bank( 0x0000 + ( ( bank & 0x03 ) << 11 ) , bank_2k, data ); + regs[bank] = data; + if (bank < 4) + { + set_prg_bank(0x8000 + (bank << 13), bank_8k, data); + return true; + } - return true; - } + set_chr_bank(0x0000 + ((bank & 0x03) << 11), bank_2k, data); - uint8_t regs [ 8 ]; + return true; + } + + uint8_t regs[8]; }; +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/meson.build b/source/quickerNES/meson.build index d58a697..8e44de1 100644 --- a/source/quickerNES/meson.build +++ b/source/quickerNES/meson.build @@ -2,11 +2,11 @@ quickerNESAPUSrc = [ 'apu/apu.cpp', - 'apu/Nes_Oscs.cpp', - 'apu/Nes_Buffer.cpp', + 'apu/oscs.cpp', + 'apu/buffer.cpp', 'apu/Blip_Buffer.cpp', - 'apu/Effects_Buffer.cpp', - 'apu/Nes_Effects_Buffer.cpp', + 'apu/NESEffectsBuffer.cpp', + 'apu/effectsBuffer.cpp', 'apu/Multi_Buffer.cpp', 'apu/namco/apu.cpp', 'apu/vrc6/apu.cpp', @@ -17,15 +17,15 @@ quickerNESAPUSrc = [ ] quickerNESPPUSrc = [ - 'ppu/Nes_Ppu.cpp', - 'ppu/Nes_Ppu_Impl.cpp', - 'ppu/Nes_Ppu_Rendering.cpp', + 'ppu/ppu.cpp', + 'ppu/ppuImpl.cpp', + 'ppu/ppuRendering.cpp', ] quickerNESSrc = quickerNESAPUSrc + quickerNESPPUSrc + [ 'mappers/mapper.cpp', - 'Nes_Emu.cpp', - 'Nes_Cpu.cpp' + 'emu.cpp', + 'cpu.cpp' ] # quickerNES Core Configuration diff --git a/source/quickerNES/ppu/Nes_Ppu.cpp b/source/quickerNES/ppu/Nes_Ppu.cpp deleted file mode 100644 index d459fe3..0000000 --- a/source/quickerNES/ppu/Nes_Ppu.cpp +++ /dev/null @@ -1,652 +0,0 @@ - -// Timing and behavior of PPU - -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ - -#include -#include "Nes_Ppu.h" -#include "Nes_Core.h" - -/* Copyright (C) 2004-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -// to do: remove unnecessary run_until() calls - -// Timing - -ppu_time_t const scanline_len = Nes_Ppu::scanline_len; - -// if non-zero, report sprite max at fixed time rather than calculating it -nes_time_t const fixed_sprite_max_time = 0; // 1 * ((21 + 164) * scanline_len + 100) / ppu_overclock; -int const sprite_max_cpu_offset = 2420 + 3; - -ppu_time_t const t_to_v_time = 20 * scanline_len + 302; -ppu_time_t const even_odd_time = 20 * scanline_len + 328; - -ppu_time_t const first_scanline_time = 21 * scanline_len + 60; // this can be varied -ppu_time_t const first_hblank_time = 21 * scanline_len + 252; - -ppu_time_t const earliest_sprite_max = sprite_max_cpu_offset * ppu_overclock; -ppu_time_t const earliest_sprite_hit = 21 * scanline_len + 339; // needs to be 22 * scanline_len when fixed_sprite_max_time is set - -nes_time_t const vbl_end_time = 2272; -ppu_time_t const max_frame_length = 262 * scanline_len; -//ppu_time_t const max_frame_length = 320 * scanline_len; // longer frame for testing movie resync -nes_time_t const earliest_vbl_end_time = max_frame_length / ppu_overclock - 10; - -// Scanline rendering - -void Nes_Ppu::render_bg_until_( nes_time_t cpu_time ) -{ - ppu_time_t time = ppu_time( cpu_time ); - ppu_time_t const frame_duration = scanline_len * 261; - if ( time > frame_duration ) - time = frame_duration; - - // one-time events - if ( frame_phase <= 1 ) - { - if ( frame_phase < 1 ) - { - // vtemp->vaddr - frame_phase = 1; - if ( w2001 & 0x08 ) - vram_addr = vram_temp; - } - - // variable-length scanline - if ( time <= even_odd_time ) - { - next_bg_time = nes_time( even_odd_time ); - return; - } - frame_phase = 2; - if ( !(w2001 & 0x08) || emu.nes.frame_count & 1 ) - { - if ( --frame_length_extra < 0 ) - { - frame_length_extra = 2; - frame_length_++; - } - burst_phase--; - } - burst_phase = (burst_phase + 2) % 3; - } - - // scanlines - if ( scanline_time < time ) - { - int count = (time - scanline_time + scanline_len) / scanline_len; - - // hblank before next scanline - if ( hblank_time < scanline_time ) - { - hblank_time += scanline_len; - run_hblank( 1 ); - } - - scanline_time += count * scanline_len; - - hblank_time += scanline_len * (count - 1); - int saved_vaddr = vram_addr; - - int start = scanline_count; - scanline_count += count; - draw_background( start, count ); - - vram_addr = saved_vaddr; // to do: this is cheap - run_hblank( count - 1 ); - } - - // hblank after current scanline - ppu_time_t next_ppu_time = hblank_time; - if ( hblank_time < time ) - { - hblank_time += scanline_len; - run_hblank( 1 ); - next_ppu_time = scanline_time; // scanline will run next - } - // either hblank or scanline comes next - next_bg_time = nes_time( next_ppu_time ); -} - -void Nes_Ppu::render_until_( nes_time_t time ) -{ - // render bg scanlines then render sprite scanlines up to wherever bg was rendered to - - render_bg_until( time ); - next_sprites_time = nes_time( scanline_time ); - if ( host_pixels ) - { - int start = next_sprites_scanline; - int count = scanline_count - start; - if ( count > 0 ) - { - next_sprites_scanline += count; - draw_sprites( start, count ); - } - } -} - -// Frame events - -inline void Nes_Ppu::end_vblank() -{ - // clear VBL, sprite hit, and max sprites flags first time after 20 scanlines - r2002 &= end_vbl_mask; - end_vbl_mask = ~0; -} - -inline void Nes_Ppu::run_end_frame( nes_time_t time ) -{ - if ( !frame_ended ) - { - // update frame_length - render_bg_until( time ); - - // set VBL when end of frame is reached - nes_time_t len = frame_length(); - if ( time >= len ) - { - r2002 |= 0x80; - frame_ended = true; - if ( w2000 & 0x80 ) - nmi_time_ = len + 2 - (frame_length_extra >> 1); - } - } -} - -// Sprite max - -inline void Nes_Ppu::invalidate_sprite_max_() -{ - next_sprite_max_run = earliest_sprite_max / ppu_overclock; - sprite_max_set_time = 0; -} - -void Nes_Ppu::run_sprite_max_( nes_time_t cpu_time ) -{ - end_vblank(); // might get run outside $2002 handler - - // 577.0 / 0x10000 ~= 1.0 / 113.581, close enough to accurately calculate which scanline it is - int start_scanline = next_sprite_max_scanline; - next_sprite_max_scanline = unsigned ((cpu_time - sprite_max_cpu_offset) * 577) / 0x10000u; - - if ( !sprite_max_set_time ) - { - if ( !(w2001 & 0x18) ) - return; - - long t = recalc_sprite_max( start_scanline ); - sprite_max_set_time = indefinite_time; - if ( t > 0 ) - sprite_max_set_time = t / 3 + sprite_max_cpu_offset; - next_sprite_max_run = sprite_max_set_time; - //dprintf( "sprite_max_set_time: %d\n", sprite_max_set_time ); - } - - if ( cpu_time > sprite_max_set_time ) - { - r2002 |= 0x20; - //dprintf( "Sprite max flag set: %d\n", sprite_max_set_time ); - next_sprite_max_run = indefinite_time; - } -} - -inline void Nes_Ppu::run_sprite_max( nes_time_t t ) -{ - if ( !fixed_sprite_max_time && t > next_sprite_max_run ) - run_sprite_max_( t ); -} - -inline void Nes_Ppu::invalidate_sprite_max( nes_time_t t ) -{ - if ( !fixed_sprite_max_time && !(r2002 & 0x20) ) - { - run_sprite_max( t ); - invalidate_sprite_max_(); - } -} - -// Sprite 0 hit - -inline int Nes_Ppu_Impl::first_opaque_sprite_line() -{ - // advance earliest time if sprite has blank lines at beginning - uint8_t const* p = map_chr( sprite_tile_index( spr_ram ) * 16 ); - int twice = w2000 >> 5 & 1; // loop twice if double height is set - int line = 0; - do - { - for ( int n = 8; n--; p++ ) - { - if ( p [0] | p [8] ) - return line; - line++; - } - - p += 8; - } - while ( !--twice ); - return line; -} - -void Nes_Ppu::update_sprite_hit( nes_time_t cpu_time ) -{ - ppu_time_t earliest = earliest_sprite_hit + spr_ram [0] * scanline_len + spr_ram [3]; - //ppu_time_t latest = earliest + sprite_height() * scanline_len; - - earliest += first_opaque_sprite_line() * scanline_len; - - ppu_time_t time = ppu_time( cpu_time ); - next_sprite_hit_check = indefinite_time; - - if ( false ) - if ( earliest < time ) - { - r2002 |= 0x40; - return; - } - - if ( time < earliest ) - { - next_sprite_hit_check = nes_time( earliest ); - return; - } - - // within possible range; render scanline and compare pixels - int count_needed = 2 + (time - earliest_sprite_hit - spr_ram [3]) / scanline_len; - if ( count_needed > 240 ) - count_needed = 240; - while ( scanline_count < count_needed ) - render_bg_until( std::max( cpu_time, next_bg_time + 1 ) ); - - if ( sprite_hit_found < 0 ) - return; // sprite won't hit - - if ( !sprite_hit_found ) - { - // check again next scanline - next_sprite_hit_check = nes_time( earliest_sprite_hit + spr_ram [3] + - (scanline_count - 1) * scanline_len ); - } - else - { - // hit found - ppu_time_t hit_time = earliest_sprite_hit + sprite_hit_found - scanline_len; - - if ( time < hit_time ) - { - next_sprite_hit_check = nes_time( hit_time ); - return; - } - - //dprintf( "Sprite hit x: %d, y: %d, scanline_count: %d\n", - // sprite_hit_found % 341, sprite_hit_found / 341, scanline_count ); - - r2002 |= 0x40; - } -} - -// $2002 - -inline void Nes_Ppu::query_until( nes_time_t time ) -{ - end_vblank(); - - // sprite hit - if ( time > next_sprite_hit_check ) - update_sprite_hit( time ); - - // sprite max - if ( !fixed_sprite_max_time ) - run_sprite_max( time ); - else if ( time >= fixed_sprite_max_time ) - r2002 |= (w2001 << 1 & 0x20) | (w2001 << 2 & 0x20); -} - -int Nes_Ppu::read_2002( nes_time_t time ) -{ - nes_time_t next = next_status_event; - next_status_event = vbl_end_time; - int extra_clock = extra_clocks ? (extra_clocks - 1) >> 2 & 1 : 0; - if ( time > next && time > vbl_end_time + extra_clock ) - { - query_until( time ); - - next_status_event = next_sprite_hit_check; - nes_time_t const next_max = fixed_sprite_max_time ? - fixed_sprite_max_time : next_sprite_max_run; - if ( next_status_event > next_max ) - next_status_event = next_max; - - if ( time > earliest_open_bus_decay() ) - { - next_status_event = earliest_open_bus_decay(); - update_open_bus( time ); - } - - if ( time > earliest_vbl_end_time ) - { - if ( next_status_event > earliest_vbl_end_time ) - next_status_event = earliest_vbl_end_time; - run_end_frame( time ); - - // special vbl behavior when read is just before or at clock when it's set - if ( extra_clocks != 1 ) - { - if ( time == frame_length() ) - { - nmi_time_ = indefinite_time; - //dprintf( "Suppressed NMI\n" ); - } - } - else if ( time == frame_length() - 1 ) - { - r2002 &= ~0x80; - frame_ended = true; - nmi_time_ = indefinite_time; - //dprintf( "Suppressed NMI\n" ); - } - } - } - emu.set_ppu_2002_time( next_status_event ); - - int result = r2002; - second_write = false; - r2002 = result & ~0x80; - poke_open_bus( time, result, 0xE0 ); - update_open_bus( time ); - return ( result & 0xE0 ) | ( open_bus & 0x1F ); -} - -void Nes_Ppu::dma_sprites( nes_time_t time, void const* in ) -{ - //dprintf( "%d sprites written\n", time ); - render_until( time ); - - invalidate_sprite_max( time ); - - memcpy( spr_ram + w2003, in, 0x100 - w2003 ); - memcpy( spr_ram, (char*) in + 0x100 - w2003, w2003 ); -} - -// Read - -inline int Nes_Ppu_Impl::read_2007( int addr ) -{ - int result = r2007; - if ( addr < 0x2000 ) - { - r2007 = *map_chr( addr ); - } - else - { - r2007 = get_nametable( addr ) [addr & 0x3ff]; - if ( addr >= 0x3f00 ) - { - return palette [map_palette( addr )] | ( open_bus & 0xC0 ); - } - } - return result; -} - -int Nes_Ppu::read( unsigned addr, nes_time_t time ) -{ - switch ( addr & 7 ) - { - // status - case 2: // handled inline - return read_2002( time ); - - // sprite ram - case 4: { - int result = spr_ram [w2003]; - if ( (w2003 & 3) == 2 ) - result &= 0xe3; - poke_open_bus( time, result, ~0 ); - return result; - } - - // video ram - case 7: { - render_bg_until( time ); - int addr = vram_addr; - int new_addr = addr + addr_inc; - vram_addr = new_addr; - if ( ~addr & new_addr & vaddr_clock_mask ) - { - emu.mapper->a12_clocked(); - addr = vram_addr - addr_inc; // avoid having to save across func call - } - int result = read_2007( addr & 0x3fff ); - poke_open_bus( time, result, ( ( addr & 0x3fff ) >= 0x3f00 ) ? 0x3F : ~0 ); - return result; - } - - default: - /* Read from unimplemented PPU register */ - break; - } - - update_open_bus( time ); - - return open_bus; -} - -// Write - -void Nes_Ppu::write( nes_time_t time, unsigned addr, int data ) -{ - switch ( addr & 7 ) - { - case 0:{// control - int changed = w2000 ^ data; - - if ( changed & 0x28 ) - render_until( time ); // obj height or pattern addr changed - else if ( changed & 0x10 ) - render_bg_until( time ); // bg pattern addr changed - else if ( ((data << 10) ^ vram_temp) & 0x0C00 ) - render_bg_until( time ); // nametable changed - - if ( changed & 0x80 ) - { - if ( time > vbl_end_time + ((extra_clocks - 1) >> 2 & 1) ) - end_vblank(); // to do: clean this up - - if ( data & 0x80 & r2002 ) - { - nmi_time_ = time + 2; - emu.event_changed(); - } - if ( time >= earliest_vbl_end_time ) - run_end_frame( time - 1 + (extra_clocks & 1) ); - } - - // nametable select - vram_temp = (vram_temp & ~0x0C00) | ((data & 3) * 0x400); - - if ( changed & 0x20 ) // sprite height changed - invalidate_sprite_max( time ); - w2000 = data; - addr_inc = data & 4 ? 32 : 1; - - break; - } - - case 1:{// sprites, bg enable - int changed = w2001 ^ data; - - if ( changed & 0xE1 ) - { - render_until( time + 1 ); // emphasis/monochrome bits changed - palette_changed = 0x18; - } - - if ( changed & 0x14 ) - render_until( time + 1 ); // sprite enable/clipping changed - else if ( changed & 0x0A ) - render_bg_until( time + 1 ); // bg enable/clipping changed - - if ( changed & 0x08 ) // bg enabled changed - emu.mapper->run_until( time ); - - if ( !(w2001 & 0x18) != !(data & 0x18) ) - invalidate_sprite_max( time ); // all rendering just turned on or off - - w2001 = data; - - if ( changed & 0x08 ) - emu.irq_changed(); - - break; - } - - case 3: // spr addr - w2003 = data; - poke_open_bus( time, w2003, ~0 ); - break; - - case 4: - //dprintf( "%d sprites written\n", time ); - if ( time > first_scanline_time / ppu_overclock ) - { - render_until( time ); - invalidate_sprite_max( time ); - } - spr_ram [w2003] = data; - w2003 = (w2003 + 1) & 0xff; - break; - - case 5: - render_bg_until( time ); - if ( (second_write ^= 1) ) - { - pixel_x = data & 7; - vram_temp = (vram_temp & ~0x1f) | (data >> 3); - } - else - { - vram_temp = (vram_temp & ~0x73e0) | - (data << 12 & 0x7000) | (data << 2 & 0x03e0); - } - break; - - case 6: - render_bg_until( time ); - if ( (second_write ^= 1) ) - { - vram_temp = (vram_temp & 0xff) | (data << 8 & 0x3f00); - } - else - { - int changed = ~vram_addr & vram_temp; - vram_addr = vram_temp = (vram_temp & 0xff00) | data; - if ( changed & vaddr_clock_mask ) - emu.mapper->a12_clocked(); - } - break; - - default: - /* Wrote to unimplemented PPU register */ - break; - } - - poke_open_bus( time, data, ~0 ); -} - -// Frame begin/end - -nes_time_t Nes_Ppu::begin_frame( ppu_time_t timestamp ) -{ - // current time - int cpu_timestamp = timestamp / ppu_overclock; - extra_clocks = timestamp - cpu_timestamp * ppu_overclock; - - // frame end - ppu_time_t const frame_end = max_frame_length - 1 - extra_clocks; - frame_length_ = (frame_end + (ppu_overclock - 1)) / ppu_overclock; - frame_length_extra = frame_length_ * ppu_overclock - frame_end; - - // nmi - nmi_time_ = indefinite_time; - if ( w2000 & 0x80 & r2002 ) - nmi_time_ = 2 - (extra_clocks >> 1); - - // bg rendering - frame_phase = 0; - scanline_count = 0; - hblank_time = first_hblank_time; - scanline_time = first_scanline_time; - next_bg_time = nes_time( t_to_v_time ); - - // sprite rendering - next_sprites_scanline = 0; - next_sprites_time = 0; - - // status register - frame_ended = false; - end_vbl_mask = ~0xE0; - next_status_event = 0; - sprite_hit_found = 0; - next_sprite_hit_check = 0; - next_sprite_max_scanline = 0; - invalidate_sprite_max_(); - - decay_low += cpu_timestamp; - decay_high += cpu_timestamp; - - base::begin_frame(); - - //dprintf( "cpu_timestamp: %d\n", cpu_timestamp ); - return cpu_timestamp; -} - -ppu_time_t Nes_Ppu::end_frame( nes_time_t end_time ) -{ - render_bg_until( end_time ); - render_until( end_time ); - query_until( end_time ); - run_end_frame( end_time ); - - update_open_bus( end_time ); - decay_low -= end_time; - decay_high -= end_time; - - // to do: do more PPU RE to get exact behavior - if ( w2001 & 0x08 ) - { - unsigned a = vram_addr + 2; - if ( (vram_addr & 0xff) >= 0xfe ) - a = (vram_addr ^ 0x400) - 0x1e; - vram_addr = a; - } - - if ( w2001 & 0x10 ) - w2003 = 0; - - suspend_rendering(); - - return (end_time - frame_length_) * ppu_overclock + frame_length_extra; -} - -void Nes_Ppu::poke_open_bus( nes_time_t time, int data, int mask ) -{ - open_bus = ( open_bus & ~mask ) | ( data & mask ); - if ( mask & 0x1F ) decay_low = time + scanline_len * 100 / ppu_overclock; - if ( mask & 0xE0 ) decay_high = time + scanline_len * 100 / ppu_overclock; -} - -nes_time_t Nes_Ppu::earliest_open_bus_decay() -{ - return ( decay_low < decay_high ) ? decay_low : decay_high; -} diff --git a/source/quickerNES/ppu/Nes_Ppu.h b/source/quickerNES/ppu/Nes_Ppu.h deleted file mode 100644 index 134dd0e..0000000 --- a/source/quickerNES/ppu/Nes_Ppu.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once - -// NES PPU emulator - -// Nes_Emu 0.7.0 - -#include "Nes_Ppu_Rendering.h" -#include - -class Nes_Mapper; -class Nes_Core; - -typedef long nes_time_t; -typedef long ppu_time_t; // ppu_time_t = nes_time_t * ppu_overclock - -ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock - -class Nes_Ppu : public Nes_Ppu_Rendering { - typedef Nes_Ppu_Rendering base; -public: - Nes_Ppu( Nes_Core* ); - - // Begin PPU frame and return beginning CPU timestamp - nes_time_t begin_frame( ppu_time_t ); - - nes_time_t nmi_time() { return nmi_time_; } - void acknowledge_nmi() { nmi_time_ = LONG_MAX / 2 + 1; } - - int read_2002( nes_time_t ); - int read( unsigned addr, nes_time_t ); - void write( nes_time_t, unsigned addr, int ); - - void render_bg_until( nes_time_t ); - void render_until( nes_time_t ); - - // CPU time that frame will have ended by - int frame_length() const { return frame_length_; } - - // End frame rendering and return PPU timestamp for next frame - ppu_time_t end_frame( nes_time_t ); - - // Do direct memory copy to sprite RAM - void dma_sprites( nes_time_t, void const* in ); - - int burst_phase; - -private: - - Nes_Core& emu; - - enum { indefinite_time = LONG_MAX / 2 + 1 }; - - void suspend_rendering(); - int read_( unsigned addr, nes_time_t ); // note swapped arguments! - - // NES<->PPU time conversion - int extra_clocks; - ppu_time_t ppu_time( nes_time_t t ) const { return t * ppu_overclock + extra_clocks; } - nes_time_t nes_time( ppu_time_t t ) const { return (t - extra_clocks) / ppu_overclock; } - - // frame - nes_time_t nmi_time_; - int end_vbl_mask; - int frame_length_; - int frame_length_extra; - bool frame_ended; - void end_vblank(); - void run_end_frame( nes_time_t ); - - // bg rendering - nes_time_t next_bg_time; - ppu_time_t scanline_time; - ppu_time_t hblank_time; - int scanline_count; - int frame_phase; - void render_bg_until_( nes_time_t ); - void run_scanlines( int count ); - - // sprite rendering - ppu_time_t next_sprites_time; - int next_sprites_scanline; - void render_until_( nes_time_t ); - - // $2002 status register - nes_time_t next_status_event; - void query_until( nes_time_t ); - - // sprite hit - nes_time_t next_sprite_hit_check; - void update_sprite_hit( nes_time_t ); - - // open bus decay - void update_open_bus( nes_time_t ); - void poke_open_bus( nes_time_t, int, int mask ); - nes_time_t earliest_open_bus_decay(); - - // sprite max - nes_time_t next_sprite_max_run; // doesn't need to run until this time - nes_time_t sprite_max_set_time; // if 0, needs to be recalculated - int next_sprite_max_scanline; - void run_sprite_max_( nes_time_t ); - void run_sprite_max( nes_time_t ); - void invalidate_sprite_max_(); - void invalidate_sprite_max( nes_time_t ); - - friend int nes_cpu_read_likely_ppu( class Nes_Core*, unsigned, nes_time_t ); -}; - -inline void Nes_Ppu::suspend_rendering() -{ - next_bg_time = indefinite_time; - next_sprites_time = indefinite_time; - extra_clocks = 0; -} - -inline Nes_Ppu::Nes_Ppu( Nes_Core* e ) : emu( *e ) -{ - burst_phase = 0; - suspend_rendering(); -} - -inline void Nes_Ppu::render_until( nes_time_t t ) -{ - if ( t > next_sprites_time ) - render_until_( t ); -} - -inline void Nes_Ppu::render_bg_until( nes_time_t t ) -{ - if ( t > next_bg_time ) - render_bg_until_( t ); -} - -inline void Nes_Ppu::update_open_bus( nes_time_t time ) -{ - if ( time >= decay_low ) open_bus &= ~0x1F; - if ( time >= decay_high ) open_bus &= ~0xE0; -} diff --git a/source/quickerNES/ppu/Nes_Ppu_Impl.cpp b/source/quickerNES/ppu/Nes_Ppu_Impl.cpp deleted file mode 100644 index 786a33f..0000000 --- a/source/quickerNES/ppu/Nes_Ppu_Impl.cpp +++ /dev/null @@ -1,437 +0,0 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ - -#include -#include -#include -#include "Nes_Ppu_Impl.h" - -/* Copyright (C) 2004-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -int const cache_line_size = 128; // tile cache is kept aligned to this boundary - -inline void set_be32( void* p, unsigned long n ) { - ((unsigned char*) p) [0] = (unsigned char) (n >> 24); - ((unsigned char*) p) [1] = (unsigned char) (n >> 16); - ((unsigned char*) p) [2] = (unsigned char) (n >> 8); - ((unsigned char*) p) [3] = (unsigned char) n; -} - -#define SET_BE32( addr, data ) set_be32( addr, data ) - -Nes_Ppu_Impl::Nes_Ppu_Impl() -{ - impl = NULL; - chr_data = NULL; - chr_size = 0; - tile_cache = NULL; - host_palette = NULL; - max_palette_size = 0; - tile_cache_mem = NULL; - ppu_state_t::unused = 0; - - mmc24_enabled = false; - mmc24_latched[0] = 0; - mmc24_latched[1] = 0; - -} - -Nes_Ppu_Impl::~Nes_Ppu_Impl() -{ - close_chr(); - delete impl; -} - -void Nes_Ppu_Impl::all_tiles_modified() -{ - any_tiles_modified = true; - memset( modified_tiles, ~0, sizeof modified_tiles ); -} - -const char *Nes_Ppu_Impl::open_chr( uint8_t const* new_chr, long chr_data_size ) -{ - close_chr(); - - if ( !impl ) - { - impl = new impl_t; - chr_ram = impl->chr_ram; - } - - chr_data = new_chr; - chr_size = chr_data_size; - chr_is_writable = false; - - if ( chr_data_size == 0 ) - { - // CHR RAM - chr_data = impl->chr_ram; - chr_size = sizeof impl->chr_ram; - chr_is_writable = true; - } - - // allocate aligned memory for cache - long tile_count = chr_size / bytes_per_tile; - tile_cache_mem = new uint8_t [tile_count * sizeof (cached_tile_t) * 2 + cache_line_size]; - tile_cache = (cached_tile_t*) (tile_cache_mem + cache_line_size - - (uintptr_t) tile_cache_mem % cache_line_size); - flipped_tiles = tile_cache + tile_count; - - // rebuild cache - all_tiles_modified(); - if ( !chr_is_writable ) - { - any_tiles_modified = false; - rebuild_chr( 0, chr_size ); - } - - return 0; -} - -void Nes_Ppu_Impl::close_chr() -{ - delete [] tile_cache_mem; - tile_cache_mem = NULL; -} - -void Nes_Ppu_Impl::set_chr_bank( int addr, int size, long data ) -{ - if ( data + size > chr_size ) - data %= chr_size; - - int count = (unsigned) size / chr_page_size; - - int page = (unsigned) addr / chr_page_size; - while ( count-- ) - { - chr_pages [page] = data - page * chr_page_size; - page++; - data += chr_page_size; - } -} - -void Nes_Ppu_Impl::set_chr_bank_ex( int addr, int size, long data ) -{ - mmc24_enabled = true; - - //check( !chr_is_writable || addr == data ); // to do: is CHR RAM ever bank-switched? - //dprintf( "Tried to set CHR RAM bank at %04X to CHR+%04X\n", addr, data ); - - if ( data + size > chr_size ) - data %= chr_size; - - int count = (unsigned) size / chr_page_size; - //assert( chr_page_size * count == size ); - //assert( addr + size <= chr_addr_size ); - - int page = (unsigned) addr / chr_page_size; - while ( count-- ) - { - chr_pages_ex [page] = data - page * chr_page_size; - page++; - data += chr_page_size; - } -} - -static uint8_t const initial_palette [0x20] = -{ - 0x0f,0x01,0x00,0x01,0x00,0x02,0x02,0x0D,0x08,0x10,0x08,0x24,0x00,0x00,0x04,0x2C, - 0x00,0x01,0x34,0x03,0x00,0x04,0x00,0x14,0x00,0x3A,0x00,0x02,0x00,0x20,0x2C,0x08 -}; - -void Nes_Ppu_Impl::reset( bool full_reset ) -{ - w2000 = 0; - w2001 = 0; - r2002 = 0x80; - r2007 = 0; - open_bus = 0; - decay_low = 0; - decay_high = 0; - second_write = false; - vram_temp = 0; - pixel_x = 0; - - if ( full_reset ) - { - vram_addr = 0; - w2003 = 0; - memset( impl->chr_ram, 0xff, sizeof impl->chr_ram ); - memset( impl->nt_ram, 0xff, sizeof impl->nt_ram ); - memcpy( palette, initial_palette, sizeof palette ); - } - - set_nt_banks( 0, 0, 0, 0 ); - set_chr_bank( 0, chr_addr_size, 0 ); - memset( spr_ram, 0xff, sizeof spr_ram ); - all_tiles_modified(); - if ( max_palette_size > 0 ) - memset( host_palette, 0, max_palette_size * sizeof *host_palette ); -} - -void Nes_Ppu_Impl::capture_palette() -{ - if ( palette_size + palette_increment <= max_palette_size ) - { - palette_offset = (palette_begin + palette_size) * 0x01010101; - - short* out = host_palette + palette_size; - palette_size += palette_increment; - - int i; - - int emph = w2001 << 1 & 0x1C0; - int mono = (w2001 & 1 ? 0x30 : 0x3F); - - for ( i = 0; i < 32; i++ ) - out [i] = (palette [i] & mono) | emph; - - int bg = out [0]; - for ( i = 4; i < 32; i += 4 ) - out [i] = bg; - - memcpy( out + 32, out, 32 * sizeof *out ); - } -} - -void Nes_Ppu_Impl::run_hblank( int count ) -{ - long addr = (vram_addr & 0x7be0) + (vram_temp & 0x41f) + (count * 0x1000); - if ( w2001 & 0x08 ) - { - while ( addr >= 0x8000 ) - { - int y = (addr + 0x20) & 0x3e0; - addr = (addr - 0x8000) & ~0x3e0; - if ( y == 30 * 0x20 ) - y = 0x800; - addr ^= y; - } - vram_addr = addr; - } -} - -#ifdef __MWERKS__ - #pragma ppc_unroll_factor_limit 1 // messes up calc_sprite_max_scanlines loop - static int zero = 0; -#else - const int zero = 0; -#endif - -// Tile cache - -inline unsigned long reorder( unsigned long n ) -{ - n |= n << 7; - return ((n << 14) | n); -} - -inline void Nes_Ppu_Impl::update_tile( int index ) -{ - const uint8_t* in = chr_data + (index) * bytes_per_tile; - uint8_t* out = (uint8_t*) tile_cache [index]; - uint8_t* flipped_out = (uint8_t*) flipped_tiles [index]; - - unsigned long bit_mask = 0x11111111 + zero; - - for ( int n = 4; n--; ) - { - // Reorder two lines of two-bit pixels. No bits are wasted, so - // reordered version is also four bytes. - // - // 12345678 to A0E4B1F5C2G6D3H7 - // ABCDEFGH - unsigned long c = - ((reorder( in [0] ) & bit_mask) << 0) | - ((reorder( in [8] ) & bit_mask) << 1) | - ((reorder( in [1] ) & bit_mask) << 2) | - ((reorder( in [9] ) & bit_mask) << 3); - in += 2; - - SET_BE32( out, c ); - out += 4; - - // make horizontally-flipped version - c = ((c >> 28) & 0x000f) | - ((c >> 20) & 0x00f0) | - ((c >> 12) & 0x0f00) | - ((c >> 4) & 0xf000) | - ((c & 0xf000) << 4) | - ((c & 0x0f00) << 12) | - ((c & 0x00f0) << 20) | - ((c & 0x000f) << 28); - SET_BE32( flipped_out, c ); - flipped_out += 4; - } -} - -void Nes_Ppu_Impl::rebuild_chr( unsigned long begin, unsigned long end ) -{ - unsigned end_index = (end + bytes_per_tile - 1) / bytes_per_tile; - for ( unsigned index = begin / bytes_per_tile; index < end_index; index++ ) - update_tile( index ); -} - -void Nes_Ppu_Impl::update_tiles( int first_tile ) -{ - int chunk = 0; - do - { - if ( !(uint32_t&) modified_tiles [chunk] ) - { - chunk += 4; - } - else - { - do - { - int modified = modified_tiles [chunk]; - if ( modified ) - { - modified_tiles [chunk] = 0; - - int index = first_tile + chunk * 8; - do - { - if ( modified & 1 ) - update_tile( index ); - index++; - } - while ( (modified >>= 1) != 0 ); - } - } - while ( ++chunk & 3 ); - } - } - while ( chunk < chr_tile_count / 8 ); -} - -// Sprite max - -template -struct calc_sprite_max_scanlines -{ - static unsigned long func( uint8_t const* sprites, uint8_t* scanlines, int begin ) - { - unsigned long any_hits = 0; - unsigned long const offset = 0x01010101 + zero; - unsigned limit = 239 + height - begin; - for ( int n = 64; n; --n ) - { - int top = *sprites; - sprites += 4; - uint8_t* p = scanlines + top; - if ( (unsigned) (239 - top) < limit ) - { - unsigned long p0 = ((uint32_t*)p) [0] + offset; - unsigned long p4 = ((uint32_t*)p) [1] + offset; - ((uint32_t*)p) [0] = p0; - any_hits |= p0; - ((uint32_t*)p) [1] = p4; - any_hits |= p4; - if ( height > 8 ) - { - unsigned long p0 = ((uint32_t*)p) [2] + offset; - unsigned long p4 = ((uint32_t*)p) [3] + offset; - ((uint32_t*)p) [2] = p0; - any_hits |= p0; - ((uint32_t*)p) [3] = p4; - any_hits |= p4; - } - } - } - - return any_hits; - } -}; - -long Nes_Ppu_Impl::recalc_sprite_max( int scanline ) -{ - int const max_scanline_count = image_height; - - uint8_t sprite_max_scanlines [256 + 16]; - - // recalculate sprites per scanline - memset( sprite_max_scanlines + scanline, 0x78, last_sprite_max_scanline - scanline ); - unsigned long any_hits; - if ( w2000 & 0x20 ) - any_hits = calc_sprite_max_scanlines<16>::func( spr_ram, sprite_max_scanlines, scanline ); - else - any_hits = calc_sprite_max_scanlines<8 >::func( spr_ram, sprite_max_scanlines, scanline ); - - // cause search to terminate past max_scanline_count if none have 8 or more sprites - (uint32_t&) sprite_max_scanlines [max_scanline_count] = 0; - sprite_max_scanlines [max_scanline_count + 3] = 0x80; - - // avoid scan if no possible hits - if ( !(any_hits & 0x80808080) ) - return 0; - - // find soonest scanline with 8 or more sprites - while ( true ) - { - unsigned long const mask = 0x80808080 + zero; - - // check four at a time - uint8_t* pos = &sprite_max_scanlines [scanline]; - unsigned long n = *((uint32_t*)pos); - while ( 1 ) - { - unsigned long x = n & mask; - pos += 4; - n = *((uint32_t*)pos); - if ( x ) - break; - } - - int height = sprite_height(); - int remain = 8; - int i = 0; - - // find which of the four - pos -= 3 + (pos [-4] >> 7 & 1); - pos += 1 - (*pos >> 7 & 1); - pos += 1 - (*pos >> 7 & 1); - - scanline = pos - sprite_max_scanlines; - if ( scanline >= max_scanline_count ) - break; - - // find time that max sprites flag is set (or that it won't be set) - do - { - int relative = scanline - spr_ram [i]; - i += 4; - if ( (unsigned) relative < (unsigned) height && !--remain ) - { - // now use screwey search for 9th sprite - int offset = 0; - while ( i < 0x100 ) - { - int relative = scanline - spr_ram [i + offset]; - //dprintf( "Checking sprite %d [%d]\n", i / 4, offset ); - i += 4; - offset = (offset + 1) & 3; - if ( (unsigned) relative < (unsigned) height ) - { - //dprintf( "sprite max on scanline %d\n", scanline ); - return scanline * scanline_len + (unsigned) i / 2; - } - } - break; - } - } - while ( i < 0x100 ); - scanline++; - } - - return 0; -} diff --git a/source/quickerNES/ppu/Nes_Ppu_Impl.h b/source/quickerNES/ppu/Nes_Ppu_Impl.h deleted file mode 100644 index 13e0bac..0000000 --- a/source/quickerNES/ppu/Nes_Ppu_Impl.h +++ /dev/null @@ -1,240 +0,0 @@ -#pragma once - -// NES PPU misc functions and setup -// Nes_Emu 0.7.0 - -#include - - -struct ppu_state_t -{ - uint8_t w2000; // control - uint8_t w2001; // control - uint8_t r2002; // status - uint8_t w2003; // sprite ram addr - uint8_t r2007; // vram read buffer - uint8_t second_write; // next write to $2005/$2006 is second since last $2002 read - uint16_t vram_addr; // loopy_v - uint16_t vram_temp; // loopy_t - uint8_t pixel_x; // fine-scroll (0-7) - uint8_t unused; - uint8_t palette [0x20]; // entries $10, $14, $18, $1c should be ignored - uint16_t decay_low; - uint16_t decay_high; - uint8_t open_bus; - uint8_t unused2[3]; -}; -static_assert( sizeof (ppu_state_t) == 20 + 0x20 ); - -class Nes_Ppu_Impl : public ppu_state_t { -public: - Nes_Ppu_Impl(); - ~Nes_Ppu_Impl(); - - void reset( bool full_reset ); - - // Setup - const char * open_chr( const uint8_t*, long size ); - void rebuild_chr( unsigned long begin, unsigned long end ); - void close_chr(); - - static const uint16_t image_width = 256; - static const uint16_t image_height = 240; - static const uint16_t image_left = 8; - static const uint16_t buffer_width = image_width + 16; - static const uint16_t buffer_height = image_height; - enum { spr_ram_size = 0x100 }; - - uint8_t* nt_banks [4]; - bool chr_is_writable; - long chr_size; - - int write_2007( int ); - - // Host palette - static const uint8_t palette_increment = 64; - short* host_palette; - int palette_begin; - int max_palette_size; - int palette_size; // set after frame is rendered - - // Mapping - static const uint16_t vaddr_clock_mask = 0x1000; - void set_nt_banks( int bank0, int bank1, int bank2, int bank3 ); - void set_chr_bank( int addr, int size, long data ); - void set_chr_bank_ex( int addr, int size, long data ); - - // Nametable and CHR RAM - static const uint16_t nt_ram_size = 0x1000; - static const uint16_t chr_addr_size = 0x2000; - static const uint8_t bytes_per_tile = 16; - static const uint16_t chr_tile_count = chr_addr_size / bytes_per_tile; - static const uint8_t mini_offscreen_height = 16; // double-height sprite - - struct impl_t - { - uint8_t nt_ram [nt_ram_size]; - uint8_t chr_ram [chr_addr_size]; - union { - uint32_t clip_buf [256 * 2]; - uint8_t mini_offscreen [buffer_width * mini_offscreen_height]; - }; - }; - impl_t* impl; - - static const uint16_t scanline_len = 341; - - uint8_t* getSpriteRAM () { return spr_ram; } - uint16_t getSpriteRAMSize () { return spr_ram_size; } - uint8_t spr_ram [spr_ram_size]; - void all_tiles_modified(); -protected: - - void begin_frame(); - void run_hblank( int ); - int sprite_height() const { return (w2000 >> 2 & 8) + 8; } - -protected: //friend class Nes_Ppu; private: - - int addr_inc; // pre-calculated $2007 increment (based on w2001 & 0x04) - int read_2007( int addr ); - - static const uint16_t last_sprite_max_scanline = 240; - long recalc_sprite_max( int scanline ); - int first_opaque_sprite_line(); - -protected: //friend class Nes_Ppu_Rendering; private: - - unsigned long palette_offset; - int palette_changed; - void capture_palette(); - - bool any_tiles_modified; - void update_tiles( int first_tile ); - - typedef uint32_t cache_t; - typedef cache_t cached_tile_t [4]; - cached_tile_t const& get_bg_tile( int index ); - cached_tile_t const& get_sprite_tile( uint8_t const* sprite ); - uint8_t* get_nametable( int addr ) { return nt_banks [addr >> 10 & 3]; }; - -private: - - static int map_palette( int addr ); - int sprite_tile_index( uint8_t const* sprite ) const; - - // Mapping - static const uint16_t chr_page_size = 0x400; - long chr_pages [chr_addr_size / chr_page_size]; - long chr_pages_ex [chr_addr_size / chr_page_size]; - long map_chr_addr( unsigned a ) /*const*/ - { - if (!mmc24_enabled) - return chr_pages [a / chr_page_size] + a; - - int page = a >> 12 & 1; - int newval0 = (a & 0xff0) != 0xfd0; - int newval1 = (a & 0xff0) == 0xfe0; - - long ret; - if (mmc24_latched[page]) - ret = chr_pages_ex [a / chr_page_size] + a; - else - ret = chr_pages [a / chr_page_size] + a; - - mmc24_latched[page] &= newval0; - mmc24_latched[page] |= newval1; - - return ret; - } - - - bool mmc24_enabled; - uint8_t mmc24_latched [2]; - - // CHR data - uint8_t const* chr_data; // points to chr ram when there is no read-only data - uint8_t* chr_ram; // always points to impl->chr_ram; makes write_2007() faster - uint8_t const* map_chr( int addr ) { return &chr_data [map_chr_addr( addr )]; } - - // CHR cache - cached_tile_t* tile_cache; - cached_tile_t* flipped_tiles; - uint8_t* tile_cache_mem; - union { - uint8_t modified_tiles [chr_tile_count / 8]; - uint32_t align_; - }; - - void update_tile( int index ); -}; - -inline void Nes_Ppu_Impl::set_nt_banks( int bank0, int bank1, int bank2, int bank3 ) -{ - uint8_t* nt_ram = impl->nt_ram; - nt_banks [0] = &nt_ram [bank0 * 0x400]; - nt_banks [1] = &nt_ram [bank1 * 0x400]; - nt_banks [2] = &nt_ram [bank2 * 0x400]; - nt_banks [3] = &nt_ram [bank3 * 0x400]; -} - -inline int Nes_Ppu_Impl::map_palette( int addr ) -{ - if ( (addr & 3) == 0 ) - addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c - return addr & 0x1f; -} - -inline int Nes_Ppu_Impl::sprite_tile_index( uint8_t const* sprite ) const -{ - int tile = sprite [1] + (w2000 << 5 & 0x100); - if ( w2000 & 0x20 ) - tile = (tile & 1) * 0x100 + (tile & 0xfe); - return tile; -} - -inline int Nes_Ppu_Impl::write_2007( int data ) -{ - int addr = vram_addr; - uint8_t * chr_ram = this->chr_ram; // pre-read - int changed = addr + addr_inc; - unsigned const divisor = bytes_per_tile * 8; - int mod_index = (unsigned) addr / divisor % (0x4000 / divisor); - vram_addr = changed; - changed ^= addr; - addr &= 0x3fff; - - // use index into modified_tiles [] since it's calculated sooner than addr is masked - if ( (unsigned) mod_index < 0x2000 / divisor ) - { - // Avoid overhead of checking for read-only CHR; if that is the case, - // this modification will be ignored. - int mod = modified_tiles [mod_index]; - chr_ram [addr] = data; - any_tiles_modified = true; - modified_tiles [mod_index] = mod | (1 << ((unsigned) addr / bytes_per_tile % 8)); - } - else if ( addr < 0x3f00 ) - { - get_nametable( addr ) [addr & 0x3ff] = data; - } - else - { - data &= 0x3f; - uint8_t& entry = palette [map_palette( addr )]; - int changed = entry ^ data; - entry = data; - if ( changed ) - palette_changed = 0x18; - } - - return changed; -} - -inline void Nes_Ppu_Impl::begin_frame() -{ - palette_changed = 0x18; - palette_size = 0; - palette_offset = palette_begin * 0x01010101; - addr_inc = w2000 & 4 ? 32 : 1; -} diff --git a/source/quickerNES/ppu/Nes_Ppu_Rendering.cpp b/source/quickerNES/ppu/Nes_Ppu_Rendering.cpp deleted file mode 100644 index ce4476b..0000000 --- a/source/quickerNES/ppu/Nes_Ppu_Rendering.cpp +++ /dev/null @@ -1,510 +0,0 @@ -// Nes_Emu 0.7.0. http://www.slack.net/~ant/ - -#include "Nes_Ppu_Rendering.h" -#include -#include -#include - -/* Copyright (C) 2004-2006 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for -more details. You should have received a copy of the GNU Lesser General -Public License along with this module; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -// Nes_Ppu_Impl - -static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants - -inline Nes_Ppu_Impl::cached_tile_t const& - Nes_Ppu_Impl::get_sprite_tile( uint8_t const* sprite ) -{ - cached_tile_t* tiles = tile_cache; - if ( sprite [2] & 0x40 ) - tiles = flipped_tiles; - int index = sprite_tile_index( sprite ); - - // use index directly, since cached tile is same size as native tile - static_assert( sizeof (cached_tile_t) == bytes_per_tile ); - return *(Nes_Ppu_Impl::cached_tile_t*) - ((uint8_t*) tiles + map_chr_addr( index * bytes_per_tile )); -} - -inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index ) -{ - // use index directly, since cached tile is same size as native tile - static_assert( sizeof (cached_tile_t) == bytes_per_tile ); - return *(Nes_Ppu_Impl::cached_tile_t*) - ((uint8_t*) tile_cache + map_chr_addr( index * bytes_per_tile )); -} - -// Fill - -void Nes_Ppu_Rendering::fill_background( int count ) -{ - ptrdiff_t const next_line = scanline_row_bytes - image_width; - uint32_t* pixels = (uint32_t*) scanline_pixels; - - unsigned long fill = palette_offset; - if ( (vram_addr & 0x3f00) == 0x3f00 ) - { - // PPU uses current palette entry if addr is within palette ram - int color = vram_addr & 0x1f; - if ( !(color & 3) ) - color &= 0x0f; - fill += color * 0x01010101; - } - - for ( int n = count; n--; ) - { - for ( int n = image_width / 16; n--; ) - { - pixels [0] = fill; - pixels [1] = fill; - pixels [2] = fill; - pixels [3] = fill; - pixels += 4; - } - pixels = (uint32_t*) ((uint8_t*) pixels + next_line); - } -} - -void Nes_Ppu_Rendering::clip_left( int count ) -{ - ptrdiff_t next_line = scanline_row_bytes; - uint8_t* p = scanline_pixels; - unsigned long fill = palette_offset; - - for ( int n = count; n--; ) - { - ((uint32_t*) p) [0] = fill; - ((uint32_t*) p) [1] = fill; - p += next_line; - } -} - -void Nes_Ppu_Rendering::save_left( int count ) -{ - ptrdiff_t next_line = scanline_row_bytes; - uint8_t* in = scanline_pixels; - uint32_t* out = impl->clip_buf; - - for ( int n = count; n--; ) - { - unsigned long in0 = ((uint32_t*) in) [0]; - unsigned long in1 = ((uint32_t*) in) [1]; - in += next_line; - out [0] = in0; - out [1] = in1; - out += 2; - } -} - -void Nes_Ppu_Rendering::restore_left( int count ) -{ - ptrdiff_t next_line = scanline_row_bytes; - uint8_t* out = scanline_pixels; - uint32_t* in = impl->clip_buf; - - for ( int n = count; n--; ) - { - unsigned long in0 = in [0]; - unsigned long in1 = in [1]; - in += 2; - ((uint32_t*) out) [0] = in0; - ((uint32_t*) out) [1] = in1; - out += next_line; - } -} - -// Background - -void Nes_Ppu_Rendering::draw_background_( int remain ) -{ - // Draws 'remain' background scanlines. Does not modify vram_addr. - - int vram_addr = this->vram_addr & 0x7fff; - uint8_t* row_pixels = scanline_pixels - pixel_x; - int left_clip = (w2001 >> 1 & 1) ^ 1; - row_pixels += left_clip * 8; - do - { - // scanlines until next row - int height = 8 - (vram_addr >> 12); - if ( height > remain ) - height = remain; - - // handle hscroll change before next scanline - int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f; - int addr = vram_addr; - if ( hscroll_changed ) - { - vram_addr ^= hscroll_changed; - height = 1; // hscroll will change after first line - } - remain -= height; - - // increment address for next row - vram_addr += height << 12; - if ( vram_addr & 0x8000 ) - { - int y = (vram_addr + 0x20) & 0x3e0; - vram_addr &= 0x7fff & ~0x3e0; - if ( y == 30 * 0x20 ) - y = 0x800; // toggle vertical nametable - vram_addr ^= y; - } - - // nametable change usually occurs in middle of row - uint8_t const* nametable = get_nametable( addr ); - uint8_t const* nametable2 = get_nametable( addr ^ 0x400 ); - int count2 = addr & 31; - int count = 32 - count2 - left_clip; - //if ( pixel_x ) - count2++; - - uint8_t const* attr_table = &nametable [0x3c0 | (addr >> 4 & 0x38)]; - int bg_bank = (w2000 << 4) & 0x100; - addr += left_clip; - - // output pixels - ptrdiff_t const row_bytes = scanline_row_bytes; - uint8_t* pixels = row_pixels; - row_pixels += height * row_bytes; - - unsigned long const mask = 0x03030303 + zero; - unsigned long const attrib_factor = 0x04040404 + zero; - - const int fine_y = (height == 8) ? 0 : addr >> 12; - const int clipped = (height == 8) ? false : true; - addr &= 0x03ff; - if (height == 8) height -= fine_y & 1; - - while ( true ) - { - while ( count-- ) - { - int attrib = attr_table [addr >> 2 & 0x07]; - attrib >>= (addr >> 4 & 4) | (addr & 2); - unsigned long offset = (attrib & 3) * attrib_factor + this->palette_offset; - - // draw one tile - cache_t const* lines = this->get_bg_tile( nametable [addr] + bg_bank ); - uint8_t* p = pixels; - addr++; - pixels += 8; // next tile - - if ( !clipped ) - { - // optimal case: no clipping - for ( int n = 4; n--; ) - { - unsigned long line = *lines++; - ((uint32_t*) p) [0] = (line >> 4 & mask) + offset; - ((uint32_t*) p) [1] = (line & mask) + offset; - p += row_bytes; - ((uint32_t*) p) [0] = (line >> 6 & mask) + offset; - ((uint32_t*) p) [1] = (line >> 2 & mask) + offset; - p += row_bytes; - } - } - else - { - lines += fine_y >> 1; - - if ( fine_y & 1 ) - { - unsigned long line = *lines++; - ((uint32_t*) p) [0] = (line >> 6 & mask) + offset; - ((uint32_t*) p) [1] = (line >> 2 & mask) + offset; - p += row_bytes; - } - - for ( int n = height >> 1; n--; ) - { - unsigned long line = *lines++; - ((uint32_t*) p) [0] = (line >> 4 & mask) + offset; - ((uint32_t*) p) [1] = (line & mask) + offset; - p += row_bytes; - ((uint32_t*) p) [0] = (line >> 6 & mask) + offset; - ((uint32_t*) p) [1] = (line >> 2 & mask) + offset; - p += row_bytes; - } - - if ( height & 1 ) - { - unsigned long line = *lines; - ((uint32_t*) p) [0] = (line >> 4 & mask) + offset; - ((uint32_t*) p) [1] = (line & mask) + offset; - } - } - } - - count = count2; - count2 = 0; - addr -= 32; - attr_table = attr_table - nametable + nametable2; - nametable = nametable2; - if ( !count ) - break; - } - - } - while ( remain ); -} - -// Sprites - -void Nes_Ppu_Rendering::draw_sprites_( int begin, int end ) -{ - // Draws sprites on scanlines begin through end - 1. Handles clipping. - - int const sprite_height = this->sprite_height(); - int end_minus_one = end - 1; - int begin_minus_one = begin - 1; - int index = 0; - do - { - uint8_t const* sprite = &spr_ram [index]; - index += 4; - - // find if sprite is visible - int top_minus_one = sprite [0]; - int visible = end_minus_one - top_minus_one; - if ( visible <= 0 ) - continue; // off bottom - - // quickly determine whether sprite is unclipped - int neg_vis = visible - sprite_height; - int neg_skip = top_minus_one - begin_minus_one; - if ( (neg_skip | neg_vis) >= 0 ) // neg_skip >= 0 && neg_vis >= 0 - { - int const skip = 0; - int visible = sprite_height; - - #define CLIPPED 0 - #include "Nes_Ppu_Sprites.h" - } - else - { - // clipped - if ( neg_vis > 0 ) - visible -= neg_vis; - - if ( neg_skip > 0 ) - neg_skip = 0; - visible += neg_skip; - - if ( visible <= 0 ) - continue; // off top - - int skip = -neg_skip; - - //dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n", - // begin, end, top_minus_one + 1, skip, visible ); - - #define CLIPPED 1 - #include "Nes_Ppu_Sprites.h" - } - } - while ( index < 0x100 ); -} - -void Nes_Ppu_Rendering::check_sprite_hit( int begin, int end ) -{ - // Checks for sprite 0 hit on scanlines begin through end - 1. - // Updates sprite_hit_found. Background (but not sprites) must have - // already been rendered for the scanlines. - - // clip - int top = spr_ram [0] + 1; - int skip = begin - top; - if ( skip < 0 ) - skip = 0; - - top += skip; - int visible = end - top; - if ( visible <= 0 ) - return; // not visible - - int height = sprite_height(); - if ( visible >= height ) - { - visible = height; - sprite_hit_found = -1; // signal that no more hit checking will take place - } - - // pixels - ptrdiff_t next_row = this->scanline_row_bytes; - uint8_t const* bg = this->scanline_pixels + spr_ram [3] + (top - begin) * next_row; - cache_t const* lines = get_sprite_tile( spr_ram ); - - // left edge clipping - int start_x = 0; - if ( spr_ram [3] < 8 && (w2001 & 0x01e) != 0x1e ) - { - if ( spr_ram [3] == 0 ) - return; // won't hit - start_x = 8 - spr_ram [3]; - } - - // vertical flip - int final = skip + visible; - if ( spr_ram [2] & 0x80 ) - { - skip += height - 1; - final = skip - visible; - } - - // check each line - unsigned long const mask = 0x01010101 + zero; - do - { - // get pixels for line - unsigned long line = lines [skip >> 1]; - unsigned long hit0 = ((uint32_t*) bg) [0]; - unsigned long hit1 = ((uint32_t*) bg) [1]; - bg += next_row; - line >>= skip << 1 & 2; - line |= line >> 1; - - // check for hits - hit0 = ((hit0 >> 1) | hit0) & (line >> 4); - hit1 = ((hit1 >> 1) | hit1) & line; - if ( (hit0 | hit1) & mask ) - { - // write to memory to avoid endian issues - uint32_t quads [3]; - quads [0] = hit0; - quads [1] = hit1; - - // find which pixel hit - int x = start_x; - do - { - if ( ((uint8_t*) quads) [x] & 1 ) - { - x += spr_ram [3]; - if ( x >= 255 ) - break; // ignore right edge - - if ( spr_ram [2] & 0x80 ) - skip = height - 1 - skip; // vertical flip - int y = spr_ram [0] + 1 + skip; - sprite_hit_found = y * scanline_len + x; - - return; - } - } - while ( x++ < 7 ); - } - if ( skip > final ) - skip -= 2; - skip++; - } - while ( skip != final ); -} - -// Draw scanlines - -inline bool Nes_Ppu_Rendering::sprite_hit_possible( int scanline ) const -{ - return !sprite_hit_found && spr_ram [0] <= scanline && (w2001 & 0x18) == 0x18; -} - -void Nes_Ppu_Rendering::draw_scanlines( int start, int count, - uint8_t* pixels, long pitch, int mode ) -{ - scanline_pixels = pixels + image_left; - scanline_row_bytes = pitch; - - int const obj_mask = 2; - int const bg_mask = 1; - int draw_mode = (w2001 >> 3) & 3; - int clip_mode = (~w2001 >> 1) & draw_mode; - - if ( !(draw_mode & bg_mask) ) - { - // no background - clip_mode |= bg_mask; // avoid unnecessary save/restore - if ( mode & bg_mask ) - fill_background( count ); - } - - if ( start == 0 && mode & 1 ) - memset( sprite_scanlines, max_sprites - sprite_limit, 240 ); - - if ( (draw_mode &= mode) ) - { - // sprites and/or background are being rendered - - if ( any_tiles_modified && chr_is_writable ) - { - any_tiles_modified = false; - update_tiles( 0 ); - } - - if ( draw_mode & bg_mask ) - { - //dprintf( "bg %3d-%3d\n", start, start + count - 1 ); - draw_background_( count ); - - if ( clip_mode == bg_mask ) - clip_left( count ); - - if ( sprite_hit_possible( start + count ) ) - check_sprite_hit( start, start + count ); - } - - if ( draw_mode & obj_mask ) - { - // when clipping just sprites, save left strip then restore after drawing them - if ( clip_mode == obj_mask ) - save_left( count ); - - //dprintf( "obj %3d-%3d\n", start, start + count - 1 ); - - draw_sprites_( start, start + count ); - - if ( clip_mode == obj_mask ) - restore_left( count ); - - if ( clip_mode == (obj_mask | bg_mask) ) - clip_left( count ); - } - } - - scanline_pixels = NULL; -} - -void Nes_Ppu_Rendering::draw_background( int start, int count ) -{ - // always capture palette at least once per frame - if ( (start + count >= 240 && !palette_size) || (w2001 & palette_changed) ) - { - palette_changed = false; - capture_palette(); - } - - if ( host_pixels ) - { - draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1 ); - } - else if ( sprite_hit_possible( start + count ) ) - { - // not rendering, but still handle sprite hit using mini graphics buffer - int y = spr_ram [0] + 1; - int skip = std::min( count, std::max( y - start, 0 ) ); - int visible = std::min( count - skip, sprite_height() ); - - if ( visible > 0 ) - { - run_hblank( skip ); - draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 ); - } - } -} diff --git a/source/quickerNES/ppu/Nes_Ppu_Rendering.h b/source/quickerNES/ppu/Nes_Ppu_Rendering.h deleted file mode 100644 index fbb1506..0000000 --- a/source/quickerNES/ppu/Nes_Ppu_Rendering.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -// NES PPU emulator graphics rendering - -// Nes_Emu 0.7.0 - -#include "Nes_Ppu_Impl.h" - -class Nes_Ppu_Rendering : public Nes_Ppu_Impl { - typedef Nes_Ppu_Impl base; -public: - Nes_Ppu_Rendering(); - - int sprite_limit; - - uint8_t* host_pixels; - long host_row_bytes; - -protected: - - long sprite_hit_found; // -1: sprite 0 didn't hit, 0: no hit so far, > 0: y * 341 + x - void draw_background( int start, int count ); - void draw_sprites( int start, int count ); - -private: - - void draw_scanlines( int start, int count, uint8_t* pixels, long pitch, int mode ); - void draw_background_( int count ); - - // destination for draw functions; avoids extra parameters - uint8_t* scanline_pixels; - long scanline_row_bytes; - - // fill/copy - void fill_background( int count ); - void clip_left( int count ); - void save_left( int count ); - void restore_left( int count ); - - // sprites - enum { max_sprites = 64 }; - uint8_t sprite_scanlines [image_height]; // number of sprites on each scanline - void draw_sprites_( int start, int count ); - bool sprite_hit_possible( int scanline ) const; - void check_sprite_hit( int begin, int end ); -}; - -inline Nes_Ppu_Rendering::Nes_Ppu_Rendering() -{ - sprite_limit = 8; - host_pixels = nullptr; -} - -inline void Nes_Ppu_Rendering::draw_sprites( int start, int count ) -{ - draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 2 ); -} - diff --git a/source/quickerNES/ppu/Nes_Ppu_Sprites.h b/source/quickerNES/ppu/Nes_Ppu_Sprites.h deleted file mode 100644 index ad23a0b..0000000 --- a/source/quickerNES/ppu/Nes_Ppu_Sprites.h +++ /dev/null @@ -1,130 +0,0 @@ -int sprite_2 = sprite [2]; - -// pixels -ptrdiff_t next_row = this->scanline_row_bytes; -uint8_t* out = this->scanline_pixels + sprite [3] + - (top_minus_one + skip - begin_minus_one) * next_row; -cache_t const* lines = get_sprite_tile( sprite ); - -int dir = 1; -uint8_t* scanlines = this->sprite_scanlines + 1 + top_minus_one + skip; - -if ( sprite_2 & 0x80 ) -{ - // vertical flip - out -= next_row; - out += visible * next_row; - next_row = -next_row; - dir = -1; - scanlines += visible - 1; - #if CLIPPED - int height = this->sprite_height(); - skip = height - skip - visible; - #endif -} - -// attributes -unsigned long offset = (sprite_2 & 3) * 0x04040404 + (this->palette_offset + 0x10101010); - -unsigned long const mask = 0x03030303 + zero; -unsigned long const maskgen = 0x80808080 + zero; - -#define DRAW_PAIR( shift ) { \ - int sprite_count = *scanlines; \ - CALC_FOUR( ((uint32_t*) out) [0], (line >> (shift + 4)), out0 ) \ - CALC_FOUR( ((uint32_t*) out) [1], (line >> shift), out1 ) \ - if ( sprite_count < this->max_sprites ) { \ - ((uint32_t*) out) [0] = out0; \ - ((uint32_t*) out) [1] = out1; \ - } \ - if ( CLIPPED ) visible--; \ - out += next_row; \ - *scanlines = sprite_count + 1; \ - scanlines += dir; \ - if ( CLIPPED && !visible ) break; \ -} - -if ( !(sprite_2 & 0x20) ) -{ - // front - unsigned long const maskgen2 = 0x7f7f7f7f + zero; - - #define CALC_FOUR( in, line, out ) \ - unsigned long out; \ - { \ - unsigned long bg = in; \ - unsigned long sp = line & mask; \ - unsigned long bgm = maskgen2 + ((bg >> 4) & mask); \ - unsigned long spm = (maskgen - sp) & maskgen2; \ - unsigned long m = (bgm & spm) >> 2; \ - out = (bg & ~m) | ((sp + offset) & m); \ - } - - #if CLIPPED - lines += skip >> 1; - unsigned long line = *lines++; - if ( skip & 1 ) - goto front_skip; - - while ( true ) - { - DRAW_PAIR( 0 ) - front_skip: - DRAW_PAIR( 2 ) - line = *lines++; - } - #else - for ( int n = visible >> 1; n--; ) - { - unsigned long line = *lines++; - DRAW_PAIR( 0 ) - DRAW_PAIR( 2 ) - } - #endif - - #undef CALC_FOUR -} -else -{ - // behind - unsigned long const omask = 0x20202020 + zero; - unsigned long const bg_or = 0xc3c3c3c3 + zero; - - #define CALC_FOUR( in, line, out ) \ - unsigned long out; \ - { \ - unsigned long bg = in; \ - unsigned long sp = line & mask; \ - unsigned long bgm = maskgen - (bg & mask); \ - unsigned long spm = maskgen - sp; \ - out = (bg & (bgm | bg_or)) | (spm & omask) | \ - (((offset & spm) + sp) & ~(bgm >> 2)); \ - } - - #if CLIPPED - lines += skip >> 1; - unsigned long line = *lines++; - if ( skip & 1 ) - goto back_skip; - - while ( true ) - { - DRAW_PAIR( 0 ) - back_skip: - DRAW_PAIR( 2 ) - line = *lines++; - } - #else - for ( int n = visible >> 1; n--; ) - { - unsigned long line = *lines++; - DRAW_PAIR( 0 ) - DRAW_PAIR( 2 ) - } - #endif - - #undef CALC_FOUR -} - -#undef CLIPPED -#undef DRAW_PAIR diff --git a/source/quickerNES/ppu/ppu.cpp b/source/quickerNES/ppu/ppu.cpp new file mode 100644 index 0000000..00b32f8 --- /dev/null +++ b/source/quickerNES/ppu/ppu.cpp @@ -0,0 +1,659 @@ + +// Timing and behavior of PPU + +// Emu 0.7.0. http://www.slack.net/~ant/ + +#include +#include "ppu.hpp" +#include "core.hpp" + +namespace quickerNES +{ + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// to do: remove unnecessary run_until() calls + +// Timing + +ppu_time_t const scanline_len = Ppu::scanline_len; + +// if non-zero, report sprite max at fixed time rather than calculating it +nes_time_t const fixed_sprite_max_time = 0; // 1 * ((21 + 164) * scanline_len + 100) / ppu_overclock; +int const sprite_max_cpu_offset = 2420 + 3; + +ppu_time_t const t_to_v_time = 20 * scanline_len + 302; +ppu_time_t const even_odd_time = 20 * scanline_len + 328; + +ppu_time_t const first_scanline_time = 21 * scanline_len + 60; // this can be varied +ppu_time_t const first_hblank_time = 21 * scanline_len + 252; + +ppu_time_t const earliest_sprite_max = sprite_max_cpu_offset * ppu_overclock; +ppu_time_t const earliest_sprite_hit = 21 * scanline_len + 339; // needs to be 22 * scanline_len when fixed_sprite_max_time is set + +nes_time_t const vbl_end_time = 2272; +ppu_time_t const max_frame_length = 262 * scanline_len; +// ppu_time_t const max_frame_length = 320 * scanline_len; // longer frame for testing movie resync +nes_time_t const earliest_vbl_end_time = max_frame_length / ppu_overclock - 10; + +// Scanline rendering + +void Ppu::render_bg_until_(nes_time_t cpu_time) +{ + ppu_time_t time = ppu_time(cpu_time); + ppu_time_t const frame_duration = scanline_len * 261; + if (time > frame_duration) + time = frame_duration; + + // one-time events + if (frame_phase <= 1) + { + if (frame_phase < 1) + { + // vtemp->vaddr + frame_phase = 1; + if (w2001 & 0x08) + vram_addr = vram_temp; + } + + // variable-length scanline + if (time <= even_odd_time) + { + next_bg_time = nes_time(even_odd_time); + return; + } + frame_phase = 2; + if (!(w2001 & 0x08) || emu.nes.frame_count & 1) + { + if (--frame_length_extra < 0) + { + frame_length_extra = 2; + frame_length_++; + } + burst_phase--; + } + burst_phase = (burst_phase + 2) % 3; + } + + // scanlines + if (scanline_time < time) + { + int count = (time - scanline_time + scanline_len) / scanline_len; + + // hblank before next scanline + if (hblank_time < scanline_time) + { + hblank_time += scanline_len; + run_hblank(1); + } + + scanline_time += count * scanline_len; + + hblank_time += scanline_len * (count - 1); + int saved_vaddr = vram_addr; + + int start = scanline_count; + scanline_count += count; + draw_background(start, count); + + vram_addr = saved_vaddr; // to do: this is cheap + run_hblank(count - 1); + } + + // hblank after current scanline + ppu_time_t next_ppu_time = hblank_time; + if (hblank_time < time) + { + hblank_time += scanline_len; + run_hblank(1); + next_ppu_time = scanline_time; // scanline will run next + } + // either hblank or scanline comes next + next_bg_time = nes_time(next_ppu_time); +} + +void Ppu::render_until_(nes_time_t time) +{ + // render bg scanlines then render sprite scanlines up to wherever bg was rendered to + + render_bg_until(time); + next_sprites_time = nes_time(scanline_time); + if (host_pixels) + { + int start = next_sprites_scanline; + int count = scanline_count - start; + if (count > 0) + { + next_sprites_scanline += count; + draw_sprites(start, count); + } + } +} + +// Frame events + +inline void Ppu::end_vblank() +{ + // clear VBL, sprite hit, and max sprites flags first time after 20 scanlines + r2002 &= end_vbl_mask; + end_vbl_mask = ~0; +} + +inline void Ppu::run_end_frame(nes_time_t time) +{ + if (!frame_ended) + { + // update frame_length + render_bg_until(time); + + // set VBL when end of frame is reached + nes_time_t len = frame_length(); + if (time >= len) + { + r2002 |= 0x80; + frame_ended = true; + if (w2000 & 0x80) + nmi_time_ = len + 2 - (frame_length_extra >> 1); + } + } +} + +// Sprite max + +inline void Ppu::invalidate_sprite_max_() +{ + next_sprite_max_run = earliest_sprite_max / ppu_overclock; + sprite_max_set_time = 0; +} + +void Ppu::run_sprite_max_(nes_time_t cpu_time) +{ + end_vblank(); // might get run outside $2002 handler + + // 577.0 / 0x10000 ~= 1.0 / 113.581, close enough to accurately calculate which scanline it is + int start_scanline = next_sprite_max_scanline; + next_sprite_max_scanline = unsigned((cpu_time - sprite_max_cpu_offset) * 577) / 0x10000u; + + if (!sprite_max_set_time) + { + if (!(w2001 & 0x18)) + return; + + long t = recalc_sprite_max(start_scanline); + sprite_max_set_time = indefinite_time; + if (t > 0) + sprite_max_set_time = t / 3 + sprite_max_cpu_offset; + next_sprite_max_run = sprite_max_set_time; + // dprintf( "sprite_max_set_time: %d\n", sprite_max_set_time ); + } + + if (cpu_time > sprite_max_set_time) + { + r2002 |= 0x20; + // dprintf( "Sprite max flag set: %d\n", sprite_max_set_time ); + next_sprite_max_run = indefinite_time; + } +} + +inline void Ppu::run_sprite_max(nes_time_t t) +{ + if (!fixed_sprite_max_time && t > next_sprite_max_run) + run_sprite_max_(t); +} + +inline void Ppu::invalidate_sprite_max(nes_time_t t) +{ + if (!fixed_sprite_max_time && !(r2002 & 0x20)) + { + run_sprite_max(t); + invalidate_sprite_max_(); + } +} + +// Sprite 0 hit + +inline int Ppu_Impl::first_opaque_sprite_line() +{ + // advance earliest time if sprite has blank lines at beginning + uint8_t const *p = map_chr(sprite_tile_index(spr_ram) * 16); + int twice = w2000 >> 5 & 1; // loop twice if double height is set + int line = 0; + do + { + for (int n = 8; n--; p++) + { + if (p[0] | p[8]) + return line; + line++; + } + + p += 8; + } while (!--twice); + return line; +} + +void Ppu::update_sprite_hit(nes_time_t cpu_time) +{ + ppu_time_t earliest = earliest_sprite_hit + spr_ram[0] * scanline_len + spr_ram[3]; + // ppu_time_t latest = earliest + sprite_height() * scanline_len; + + earliest += first_opaque_sprite_line() * scanline_len; + + ppu_time_t time = ppu_time(cpu_time); + next_sprite_hit_check = indefinite_time; + + if (false) + if (earliest < time) + { + r2002 |= 0x40; + return; + } + + if (time < earliest) + { + next_sprite_hit_check = nes_time(earliest); + return; + } + + // within possible range; render scanline and compare pixels + int count_needed = 2 + (time - earliest_sprite_hit - spr_ram[3]) / scanline_len; + if (count_needed > 240) + count_needed = 240; + while (scanline_count < count_needed) + render_bg_until(std::max(cpu_time, next_bg_time + 1)); + + if (sprite_hit_found < 0) + return; // sprite won't hit + + if (!sprite_hit_found) + { + // check again next scanline + next_sprite_hit_check = nes_time(earliest_sprite_hit + spr_ram[3] + + (scanline_count - 1) * scanline_len); + } + else + { + // hit found + ppu_time_t hit_time = earliest_sprite_hit + sprite_hit_found - scanline_len; + + if (time < hit_time) + { + next_sprite_hit_check = nes_time(hit_time); + return; + } + + // dprintf( "Sprite hit x: %d, y: %d, scanline_count: %d\n", + // sprite_hit_found % 341, sprite_hit_found / 341, scanline_count ); + + r2002 |= 0x40; + } +} + +// $2002 + +inline void Ppu::query_until(nes_time_t time) +{ + end_vblank(); + + // sprite hit + if (time > next_sprite_hit_check) + update_sprite_hit(time); + + // sprite max + if (!fixed_sprite_max_time) + run_sprite_max(time); + else if (time >= fixed_sprite_max_time) + r2002 |= (w2001 << 1 & 0x20) | (w2001 << 2 & 0x20); +} + +int Ppu::read_2002(nes_time_t time) +{ + nes_time_t next = next_status_event; + next_status_event = vbl_end_time; + int extra_clock = extra_clocks ? (extra_clocks - 1) >> 2 & 1 : 0; + if (time > next && time > vbl_end_time + extra_clock) + { + query_until(time); + + next_status_event = next_sprite_hit_check; + nes_time_t const next_max = fixed_sprite_max_time ? fixed_sprite_max_time : next_sprite_max_run; + if (next_status_event > next_max) + next_status_event = next_max; + + if (time > earliest_open_bus_decay()) + { + next_status_event = earliest_open_bus_decay(); + update_open_bus(time); + } + + if (time > earliest_vbl_end_time) + { + if (next_status_event > earliest_vbl_end_time) + next_status_event = earliest_vbl_end_time; + run_end_frame(time); + + // special vbl behavior when read is just before or at clock when it's set + if (extra_clocks != 1) + { + if (time == frame_length()) + { + nmi_time_ = indefinite_time; + // dprintf( "Suppressed NMI\n" ); + } + } + else if (time == frame_length() - 1) + { + r2002 &= ~0x80; + frame_ended = true; + nmi_time_ = indefinite_time; + // dprintf( "Suppressed NMI\n" ); + } + } + } + emu.set_ppu_2002_time(next_status_event); + + int result = r2002; + second_write = false; + r2002 = result & ~0x80; + poke_open_bus(time, result, 0xE0); + update_open_bus(time); + return (result & 0xE0) | (open_bus & 0x1F); +} + +void Ppu::dma_sprites(nes_time_t time, void const *in) +{ + // dprintf( "%d sprites written\n", time ); + render_until(time); + + invalidate_sprite_max(time); + + memcpy(spr_ram + w2003, in, 0x100 - w2003); + memcpy(spr_ram, (char *)in + 0x100 - w2003, w2003); +} + +// Read + +inline int Ppu_Impl::read_2007(int addr) +{ + int result = r2007; + if (addr < 0x2000) + { + r2007 = *map_chr(addr); + } + else + { + r2007 = get_nametable(addr)[addr & 0x3ff]; + if (addr >= 0x3f00) + { + return palette[map_palette(addr)] | (open_bus & 0xC0); + } + } + return result; +} + +int Ppu::read(unsigned addr, nes_time_t time) +{ + switch (addr & 7) + { + // status + case 2: // handled inline + return read_2002(time); + + // sprite ram + case 4: + { + int result = spr_ram[w2003]; + if ((w2003 & 3) == 2) + result &= 0xe3; + poke_open_bus(time, result, ~0); + return result; + } + + // video ram + case 7: + { + render_bg_until(time); + int addr = vram_addr; + int new_addr = addr + addr_inc; + vram_addr = new_addr; + if (~addr & new_addr & vaddr_clock_mask) + { + emu.mapper->a12_clocked(); + addr = vram_addr - addr_inc; // avoid having to save across func call + } + int result = read_2007(addr & 0x3fff); + poke_open_bus(time, result, ((addr & 0x3fff) >= 0x3f00) ? 0x3F : ~0); + return result; + } + + default: + /* Read from unimplemented PPU register */ + break; + } + + update_open_bus(time); + + return open_bus; +} + +// Write + +void Ppu::write(nes_time_t time, unsigned addr, int data) +{ + switch (addr & 7) + { + case 0: + { // control + int changed = w2000 ^ data; + + if (changed & 0x28) + render_until(time); // obj height or pattern addr changed + else if (changed & 0x10) + render_bg_until(time); // bg pattern addr changed + else if (((data << 10) ^ vram_temp) & 0x0C00) + render_bg_until(time); // nametable changed + + if (changed & 0x80) + { + if (time > vbl_end_time + ((extra_clocks - 1) >> 2 & 1)) + end_vblank(); // to do: clean this up + + if (data & 0x80 & r2002) + { + nmi_time_ = time + 2; + emu.event_changed(); + } + if (time >= earliest_vbl_end_time) + run_end_frame(time - 1 + (extra_clocks & 1)); + } + + // nametable select + vram_temp = (vram_temp & ~0x0C00) | ((data & 3) * 0x400); + + if (changed & 0x20) // sprite height changed + invalidate_sprite_max(time); + w2000 = data; + addr_inc = data & 4 ? 32 : 1; + + break; + } + + case 1: + { // sprites, bg enable + int changed = w2001 ^ data; + + if (changed & 0xE1) + { + render_until(time + 1); // emphasis/monochrome bits changed + palette_changed = 0x18; + } + + if (changed & 0x14) + render_until(time + 1); // sprite enable/clipping changed + else if (changed & 0x0A) + render_bg_until(time + 1); // bg enable/clipping changed + + if (changed & 0x08) // bg enabled changed + emu.mapper->run_until(time); + + if (!(w2001 & 0x18) != !(data & 0x18)) + invalidate_sprite_max(time); // all rendering just turned on or off + + w2001 = data; + + if (changed & 0x08) + emu.irq_changed(); + + break; + } + + case 3: // spr addr + w2003 = data; + poke_open_bus(time, w2003, ~0); + break; + + case 4: + // dprintf( "%d sprites written\n", time ); + if (time > first_scanline_time / ppu_overclock) + { + render_until(time); + invalidate_sprite_max(time); + } + spr_ram[w2003] = data; + w2003 = (w2003 + 1) & 0xff; + break; + + case 5: + render_bg_until(time); + if ((second_write ^= 1)) + { + pixel_x = data & 7; + vram_temp = (vram_temp & ~0x1f) | (data >> 3); + } + else + { + vram_temp = (vram_temp & ~0x73e0) | + (data << 12 & 0x7000) | (data << 2 & 0x03e0); + } + break; + + case 6: + render_bg_until(time); + if ((second_write ^= 1)) + { + vram_temp = (vram_temp & 0xff) | (data << 8 & 0x3f00); + } + else + { + int changed = ~vram_addr & vram_temp; + vram_addr = vram_temp = (vram_temp & 0xff00) | data; + if (changed & vaddr_clock_mask) + emu.mapper->a12_clocked(); + } + break; + + default: + /* Wrote to unimplemented PPU register */ + break; + } + + poke_open_bus(time, data, ~0); +} + +// Frame begin/end + +nes_time_t Ppu::begin_frame(ppu_time_t timestamp) +{ + // current time + int cpu_timestamp = timestamp / ppu_overclock; + extra_clocks = timestamp - cpu_timestamp * ppu_overclock; + + // frame end + ppu_time_t const frame_end = max_frame_length - 1 - extra_clocks; + frame_length_ = (frame_end + (ppu_overclock - 1)) / ppu_overclock; + frame_length_extra = frame_length_ * ppu_overclock - frame_end; + + // nmi + nmi_time_ = indefinite_time; + if (w2000 & 0x80 & r2002) + nmi_time_ = 2 - (extra_clocks >> 1); + + // bg rendering + frame_phase = 0; + scanline_count = 0; + hblank_time = first_hblank_time; + scanline_time = first_scanline_time; + next_bg_time = nes_time(t_to_v_time); + + // sprite rendering + next_sprites_scanline = 0; + next_sprites_time = 0; + + // status register + frame_ended = false; + end_vbl_mask = ~0xE0; + next_status_event = 0; + sprite_hit_found = 0; + next_sprite_hit_check = 0; + next_sprite_max_scanline = 0; + invalidate_sprite_max_(); + + decay_low += cpu_timestamp; + decay_high += cpu_timestamp; + + base::begin_frame(); + + // dprintf( "cpu_timestamp: %d\n", cpu_timestamp ); + return cpu_timestamp; +} + +ppu_time_t Ppu::end_frame(nes_time_t end_time) +{ + render_bg_until(end_time); + render_until(end_time); + query_until(end_time); + run_end_frame(end_time); + + update_open_bus(end_time); + decay_low -= end_time; + decay_high -= end_time; + + // to do: do more PPU RE to get exact behavior + if (w2001 & 0x08) + { + unsigned a = vram_addr + 2; + if ((vram_addr & 0xff) >= 0xfe) + a = (vram_addr ^ 0x400) - 0x1e; + vram_addr = a; + } + + if (w2001 & 0x10) + w2003 = 0; + + suspend_rendering(); + + return (end_time - frame_length_) * ppu_overclock + frame_length_extra; +} + +void Ppu::poke_open_bus(nes_time_t time, int data, int mask) +{ + open_bus = (open_bus & ~mask) | (data & mask); + if (mask & 0x1F) decay_low = time + scanline_len * 100 / ppu_overclock; + if (mask & 0xE0) decay_high = time + scanline_len * 100 / ppu_overclock; +} + +nes_time_t Ppu::earliest_open_bus_decay() +{ + return (decay_low < decay_high) ? decay_low : decay_high; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/ppu/ppu.hpp b/source/quickerNES/ppu/ppu.hpp new file mode 100644 index 0000000..9eee0b0 --- /dev/null +++ b/source/quickerNES/ppu/ppu.hpp @@ -0,0 +1,146 @@ +#pragma once + +// NES PPU emulator +// Emu 0.7.0 + +#include +#include "ppuRendering.hpp" + +namespace quickerNES +{ + +class Mapper; +class Core; + +typedef long nes_time_t; +typedef long ppu_time_t; // ppu_time_t = nes_time_t * ppu_overclock + +ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock + +class Ppu : public Ppu_Rendering +{ + typedef Ppu_Rendering base; + + public: + Ppu(Core *); + + // Begin PPU frame and return beginning CPU timestamp + nes_time_t begin_frame(ppu_time_t); + + nes_time_t nmi_time() { return nmi_time_; } + void acknowledge_nmi() { nmi_time_ = LONG_MAX / 2 + 1; } + + int read_2002(nes_time_t); + int read(unsigned addr, nes_time_t); + void write(nes_time_t, unsigned addr, int); + + void render_bg_until(nes_time_t); + void render_until(nes_time_t); + + // CPU time that frame will have ended by + int frame_length() const { return frame_length_; } + + // End frame rendering and return PPU timestamp for next frame + ppu_time_t end_frame(nes_time_t); + + // Do direct memory copy to sprite RAM + void dma_sprites(nes_time_t, void const *in); + + int burst_phase; + + private: + Core &emu; + + enum + { + indefinite_time = LONG_MAX / 2 + 1 + }; + + void suspend_rendering(); + int read_(unsigned addr, nes_time_t); // note swapped arguments! + + // NES<->PPU time conversion + int extra_clocks; + ppu_time_t ppu_time(nes_time_t t) const { return t * ppu_overclock + extra_clocks; } + nes_time_t nes_time(ppu_time_t t) const { return (t - extra_clocks) / ppu_overclock; } + + // frame + nes_time_t nmi_time_; + int end_vbl_mask; + int frame_length_; + int frame_length_extra; + bool frame_ended; + void end_vblank(); + void run_end_frame(nes_time_t); + + // bg rendering + nes_time_t next_bg_time; + ppu_time_t scanline_time; + ppu_time_t hblank_time; + int scanline_count; + int frame_phase; + void render_bg_until_(nes_time_t); + void run_scanlines(int count); + + // sprite rendering + ppu_time_t next_sprites_time; + int next_sprites_scanline; + void render_until_(nes_time_t); + + // $2002 status register + nes_time_t next_status_event; + void query_until(nes_time_t); + + // sprite hit + nes_time_t next_sprite_hit_check; + void update_sprite_hit(nes_time_t); + + // open bus decay + void update_open_bus(nes_time_t); + void poke_open_bus(nes_time_t, int, int mask); + nes_time_t earliest_open_bus_decay(); + + // sprite max + nes_time_t next_sprite_max_run; // doesn't need to run until this time + nes_time_t sprite_max_set_time; // if 0, needs to be recalculated + int next_sprite_max_scanline; + void run_sprite_max_(nes_time_t); + void run_sprite_max(nes_time_t); + void invalidate_sprite_max_(); + void invalidate_sprite_max(nes_time_t); + + friend int nes_cpu_read_likely_ppu(class Core *, unsigned, nes_time_t); +}; + +inline void Ppu::suspend_rendering() +{ + next_bg_time = indefinite_time; + next_sprites_time = indefinite_time; + extra_clocks = 0; +} + +inline Ppu::Ppu(Core *e) : emu(*e) +{ + burst_phase = 0; + suspend_rendering(); +} + +inline void Ppu::render_until(nes_time_t t) +{ + if (t > next_sprites_time) + render_until_(t); +} + +inline void Ppu::render_bg_until(nes_time_t t) +{ + if (t > next_bg_time) + render_bg_until_(t); +} + +inline void Ppu::update_open_bus(nes_time_t time) +{ + if (time >= decay_low) open_bus &= ~0x1F; + if (time >= decay_high) open_bus &= ~0xE0; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/ppu/ppuImpl.cpp b/source/quickerNES/ppu/ppuImpl.cpp new file mode 100644 index 0000000..ffe48d9 --- /dev/null +++ b/source/quickerNES/ppu/ppuImpl.cpp @@ -0,0 +1,436 @@ +// Emu 0.7.0. http://www.slack.net/~ant/ + +#include "ppuImpl.hpp" +#include +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +namespace quickerNES +{ + +int const cache_line_size = 128; // tile cache is kept aligned to this boundary + +inline void set_be32(void *p, unsigned long n) +{ + ((unsigned char *)p)[0] = (unsigned char)(n >> 24); + ((unsigned char *)p)[1] = (unsigned char)(n >> 16); + ((unsigned char *)p)[2] = (unsigned char)(n >> 8); + ((unsigned char *)p)[3] = (unsigned char)n; +} + +#define SET_BE32(addr, data) set_be32(addr, data) + +Ppu_Impl::Ppu_Impl() +{ + impl = NULL; + chr_data = NULL; + chr_size = 0; + tile_cache = NULL; + host_palette = NULL; + max_palette_size = 0; + tile_cache_mem = NULL; + ppu_state_t::unused = 0; + + mmc24_enabled = false; + mmc24_latched[0] = 0; + mmc24_latched[1] = 0; +} + +Ppu_Impl::~Ppu_Impl() +{ + close_chr(); + delete impl; +} + +void Ppu_Impl::all_tiles_modified() +{ + any_tiles_modified = true; + memset(modified_tiles, ~0, sizeof modified_tiles); +} + +const char *Ppu_Impl::open_chr(uint8_t const *new_chr, long chr_data_size) +{ + close_chr(); + + if (!impl) + { + impl = new impl_t; + chr_ram = impl->chr_ram; + } + + chr_data = new_chr; + chr_size = chr_data_size; + chr_is_writable = false; + + if (chr_data_size == 0) + { + // CHR RAM + chr_data = impl->chr_ram; + chr_size = sizeof impl->chr_ram; + chr_is_writable = true; + } + + // allocate aligned memory for cache + long tile_count = chr_size / bytes_per_tile; + tile_cache_mem = new uint8_t[tile_count * sizeof(cached_tile_t) * 2 + cache_line_size]; + tile_cache = (cached_tile_t *)(tile_cache_mem + cache_line_size - + (uintptr_t)tile_cache_mem % cache_line_size); + flipped_tiles = tile_cache + tile_count; + + // rebuild cache + all_tiles_modified(); + if (!chr_is_writable) + { + any_tiles_modified = false; + rebuild_chr(0, chr_size); + } + + return 0; +} + +void Ppu_Impl::close_chr() +{ + delete[] tile_cache_mem; + tile_cache_mem = NULL; +} + +void Ppu_Impl::set_chr_bank(int addr, int size, long data) +{ + if (data + size > chr_size) + data %= chr_size; + + int count = (unsigned)size / chr_page_size; + + int page = (unsigned)addr / chr_page_size; + while (count--) + { + chr_pages[page] = data - page * chr_page_size; + page++; + data += chr_page_size; + } +} + +void Ppu_Impl::set_chr_bank_ex(int addr, int size, long data) +{ + mmc24_enabled = true; + + // check( !chr_is_writable || addr == data ); // to do: is CHR RAM ever bank-switched? + // dprintf( "Tried to set CHR RAM bank at %04X to CHR+%04X\n", addr, data ); + + if (data + size > chr_size) + data %= chr_size; + + int count = (unsigned)size / chr_page_size; + // assert( chr_page_size * count == size ); + // assert( addr + size <= chr_addr_size ); + + int page = (unsigned)addr / chr_page_size; + while (count--) + { + chr_pages_ex[page] = data - page * chr_page_size; + page++; + data += chr_page_size; + } +} + +static uint8_t const initial_palette[0x20] = + { + 0x0f, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, 0x00, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 0x00, 0x3A, 0x00, 0x02, 0x00, 0x20, 0x2C, 0x08}; + +void Ppu_Impl::reset(bool full_reset) +{ + w2000 = 0; + w2001 = 0; + r2002 = 0x80; + r2007 = 0; + open_bus = 0; + decay_low = 0; + decay_high = 0; + second_write = false; + vram_temp = 0; + pixel_x = 0; + + if (full_reset) + { + vram_addr = 0; + w2003 = 0; + memset(impl->chr_ram, 0xff, sizeof impl->chr_ram); + memset(impl->nt_ram, 0xff, sizeof impl->nt_ram); + memcpy(palette, initial_palette, sizeof palette); + } + + set_nt_banks(0, 0, 0, 0); + set_chr_bank(0, chr_addr_size, 0); + memset(spr_ram, 0xff, sizeof spr_ram); + all_tiles_modified(); + if (max_palette_size > 0) + memset(host_palette, 0, max_palette_size * sizeof *host_palette); +} + +void Ppu_Impl::capture_palette() +{ + if (palette_size + palette_increment <= max_palette_size) + { + palette_offset = (palette_begin + palette_size) * 0x01010101; + + short *out = host_palette + palette_size; + palette_size += palette_increment; + + int i; + + int emph = w2001 << 1 & 0x1C0; + int mono = (w2001 & 1 ? 0x30 : 0x3F); + + for (i = 0; i < 32; i++) + out[i] = (palette[i] & mono) | emph; + + int bg = out[0]; + for (i = 4; i < 32; i += 4) + out[i] = bg; + + memcpy(out + 32, out, 32 * sizeof *out); + } +} + +void Ppu_Impl::run_hblank(int count) +{ + long addr = (vram_addr & 0x7be0) + (vram_temp & 0x41f) + (count * 0x1000); + if (w2001 & 0x08) + { + while (addr >= 0x8000) + { + int y = (addr + 0x20) & 0x3e0; + addr = (addr - 0x8000) & ~0x3e0; + if (y == 30 * 0x20) + y = 0x800; + addr ^= y; + } + vram_addr = addr; + } +} + +#ifdef __MWERKS__ + #pragma ppc_unroll_factor_limit 1 // messes up calc_sprite_max_scanlines loop +static int zero = 0; +#else +const int zero = 0; +#endif + +// Tile cache + +inline unsigned long reorder(unsigned long n) +{ + n |= n << 7; + return ((n << 14) | n); +} + +inline void Ppu_Impl::update_tile(int index) +{ + const uint8_t *in = chr_data + (index)*bytes_per_tile; + uint8_t *out = (uint8_t *)tile_cache[index]; + uint8_t *flipped_out = (uint8_t *)flipped_tiles[index]; + + unsigned long bit_mask = 0x11111111 + zero; + + for (int n = 4; n--;) + { + // Reorder two lines of two-bit pixels. No bits are wasted, so + // reordered version is also four bytes. + // + // 12345678 to A0E4B1F5C2G6D3H7 + // ABCDEFGH + unsigned long c = + ((reorder(in[0]) & bit_mask) << 0) | + ((reorder(in[8]) & bit_mask) << 1) | + ((reorder(in[1]) & bit_mask) << 2) | + ((reorder(in[9]) & bit_mask) << 3); + in += 2; + + SET_BE32(out, c); + out += 4; + + // make horizontally-flipped version + c = ((c >> 28) & 0x000f) | + ((c >> 20) & 0x00f0) | + ((c >> 12) & 0x0f00) | + ((c >> 4) & 0xf000) | + ((c & 0xf000) << 4) | + ((c & 0x0f00) << 12) | + ((c & 0x00f0) << 20) | + ((c & 0x000f) << 28); + SET_BE32(flipped_out, c); + flipped_out += 4; + } +} + +void Ppu_Impl::rebuild_chr(unsigned long begin, unsigned long end) +{ + unsigned end_index = (end + bytes_per_tile - 1) / bytes_per_tile; + for (unsigned index = begin / bytes_per_tile; index < end_index; index++) + update_tile(index); +} + +void Ppu_Impl::update_tiles(int first_tile) +{ + int chunk = 0; + do + { + if (!(uint32_t &)modified_tiles[chunk]) + { + chunk += 4; + } + else + { + do + { + int modified = modified_tiles[chunk]; + if (modified) + { + modified_tiles[chunk] = 0; + + int index = first_tile + chunk * 8; + do + { + if (modified & 1) + update_tile(index); + index++; + } while ((modified >>= 1) != 0); + } + } while (++chunk & 3); + } + } while (chunk < chr_tile_count / 8); +} + +// Sprite max + +template +struct calc_sprite_max_scanlines +{ + static unsigned long func(uint8_t const *sprites, uint8_t *scanlines, int begin) + { + unsigned long any_hits = 0; + unsigned long const offset = 0x01010101 + zero; + unsigned limit = 239 + height - begin; + for (int n = 64; n; --n) + { + int top = *sprites; + sprites += 4; + uint8_t *p = scanlines + top; + if ((unsigned)(239 - top) < limit) + { + unsigned long p0 = ((uint32_t *)p)[0] + offset; + unsigned long p4 = ((uint32_t *)p)[1] + offset; + ((uint32_t *)p)[0] = p0; + any_hits |= p0; + ((uint32_t *)p)[1] = p4; + any_hits |= p4; + if (height > 8) + { + unsigned long p0 = ((uint32_t *)p)[2] + offset; + unsigned long p4 = ((uint32_t *)p)[3] + offset; + ((uint32_t *)p)[2] = p0; + any_hits |= p0; + ((uint32_t *)p)[3] = p4; + any_hits |= p4; + } + } + } + + return any_hits; + } +}; + +long Ppu_Impl::recalc_sprite_max(int scanline) +{ + int const max_scanline_count = image_height; + + uint8_t sprite_max_scanlines[256 + 16]; + + // recalculate sprites per scanline + memset(sprite_max_scanlines + scanline, 0x78, last_sprite_max_scanline - scanline); + unsigned long any_hits; + if (w2000 & 0x20) + any_hits = calc_sprite_max_scanlines<16>::func(spr_ram, sprite_max_scanlines, scanline); + else + any_hits = calc_sprite_max_scanlines<8>::func(spr_ram, sprite_max_scanlines, scanline); + + // cause search to terminate past max_scanline_count if none have 8 or more sprites + (uint32_t &)sprite_max_scanlines[max_scanline_count] = 0; + sprite_max_scanlines[max_scanline_count + 3] = 0x80; + + // avoid scan if no possible hits + if (!(any_hits & 0x80808080)) + return 0; + + // find soonest scanline with 8 or more sprites + while (true) + { + unsigned long const mask = 0x80808080 + zero; + + // check four at a time + uint8_t *pos = &sprite_max_scanlines[scanline]; + unsigned long n = *((uint32_t *)pos); + while (1) + { + unsigned long x = n & mask; + pos += 4; + n = *((uint32_t *)pos); + if (x) + break; + } + + int height = sprite_height(); + int remain = 8; + int i = 0; + + // find which of the four + pos -= 3 + (pos[-4] >> 7 & 1); + pos += 1 - (*pos >> 7 & 1); + pos += 1 - (*pos >> 7 & 1); + + scanline = pos - sprite_max_scanlines; + if (scanline >= max_scanline_count) + break; + + // find time that max sprites flag is set (or that it won't be set) + do + { + int relative = scanline - spr_ram[i]; + i += 4; + if ((unsigned)relative < (unsigned)height && !--remain) + { + // now use screwey search for 9th sprite + int offset = 0; + while (i < 0x100) + { + int relative = scanline - spr_ram[i + offset]; + // dprintf( "Checking sprite %d [%d]\n", i / 4, offset ); + i += 4; + offset = (offset + 1) & 3; + if ((unsigned)relative < (unsigned)height) + { + // dprintf( "sprite max on scanline %d\n", scanline ); + return scanline * scanline_len + (unsigned)i / 2; + } + } + break; + } + } while (i < 0x100); + scanline++; + } + + return 0; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/ppu/ppuImpl.hpp b/source/quickerNES/ppu/ppuImpl.hpp new file mode 100644 index 0000000..0b8bb82 --- /dev/null +++ b/source/quickerNES/ppu/ppuImpl.hpp @@ -0,0 +1,246 @@ +#pragma once + +// NES PPU misc functions and setup +// Emu 0.7.0 + +#include + +namespace quickerNES +{ + +struct ppu_state_t +{ + uint8_t w2000; // control + uint8_t w2001; // control + uint8_t r2002; // status + uint8_t w2003; // sprite ram addr + uint8_t r2007; // vram read buffer + uint8_t second_write; // next write to $2005/$2006 is second since last $2002 read + uint16_t vram_addr; // loopy_v + uint16_t vram_temp; // loopy_t + uint8_t pixel_x; // fine-scroll (0-7) + uint8_t unused; + uint8_t palette[0x20]; // entries $10, $14, $18, $1c should be ignored + uint16_t decay_low; + uint16_t decay_high; + uint8_t open_bus; + uint8_t unused2[3]; +}; +static_assert(sizeof(ppu_state_t) == 20 + 0x20); + +class Ppu_Impl : public ppu_state_t +{ + public: + Ppu_Impl(); + ~Ppu_Impl(); + + void reset(bool full_reset); + + // Setup + const char *open_chr(const uint8_t *, long size); + void rebuild_chr(unsigned long begin, unsigned long end); + void close_chr(); + + static const uint16_t image_width = 256; + static const uint16_t image_height = 240; + static const uint16_t image_left = 8; + static const uint16_t buffer_width = image_width + 16; + static const uint16_t buffer_height = image_height; + enum + { + spr_ram_size = 0x100 + }; + + uint8_t *nt_banks[4]; + bool chr_is_writable; + long chr_size; + + int write_2007(int); + + // Host palette + static const uint8_t palette_increment = 64; + short *host_palette; + int palette_begin; + int max_palette_size; + int palette_size; // set after frame is rendered + + // Mapping + static const uint16_t vaddr_clock_mask = 0x1000; + void set_nt_banks(int bank0, int bank1, int bank2, int bank3); + void set_chr_bank(int addr, int size, long data); + void set_chr_bank_ex(int addr, int size, long data); + + // Nametable and CHR RAM + static const uint16_t nt_ram_size = 0x1000; + static const uint16_t chr_addr_size = 0x2000; + static const uint8_t bytes_per_tile = 16; + static const uint16_t chr_tile_count = chr_addr_size / bytes_per_tile; + static const uint8_t mini_offscreen_height = 16; // double-height sprite + + struct impl_t + { + uint8_t nt_ram[nt_ram_size]; + uint8_t chr_ram[chr_addr_size]; + union + { + uint32_t clip_buf[256 * 2]; + uint8_t mini_offscreen[buffer_width * mini_offscreen_height]; + }; + }; + impl_t *impl; + + static const uint16_t scanline_len = 341; + + uint8_t *getSpriteRAM() { return spr_ram; } + uint16_t getSpriteRAMSize() { return spr_ram_size; } + uint8_t spr_ram[spr_ram_size]; + void all_tiles_modified(); + + protected: + void begin_frame(); + void run_hblank(int); + int sprite_height() const { return (w2000 >> 2 & 8) + 8; } + + protected: // friend class Ppu; private: + int addr_inc; // pre-calculated $2007 increment (based on w2001 & 0x04) + int read_2007(int addr); + + static const uint16_t last_sprite_max_scanline = 240; + long recalc_sprite_max(int scanline); + int first_opaque_sprite_line(); + + protected: // friend class Ppu_Rendering; private: + unsigned long palette_offset; + int palette_changed; + void capture_palette(); + + bool any_tiles_modified; + void update_tiles(int first_tile); + + typedef uint32_t cache_t; + typedef cache_t cached_tile_t[4]; + cached_tile_t const &get_bg_tile(int index); + cached_tile_t const &get_sprite_tile(uint8_t const *sprite); + uint8_t *get_nametable(int addr) { return nt_banks[addr >> 10 & 3]; }; + + private: + static int map_palette(int addr); + int sprite_tile_index(uint8_t const *sprite) const; + + // Mapping + static const uint16_t chr_page_size = 0x400; + long chr_pages[chr_addr_size / chr_page_size]; + long chr_pages_ex[chr_addr_size / chr_page_size]; + long map_chr_addr(unsigned a) /*const*/ + { + if (!mmc24_enabled) + return chr_pages[a / chr_page_size] + a; + + int page = a >> 12 & 1; + int newval0 = (a & 0xff0) != 0xfd0; + int newval1 = (a & 0xff0) == 0xfe0; + + long ret; + if (mmc24_latched[page]) + ret = chr_pages_ex[a / chr_page_size] + a; + else + ret = chr_pages[a / chr_page_size] + a; + + mmc24_latched[page] &= newval0; + mmc24_latched[page] |= newval1; + + return ret; + } + + bool mmc24_enabled; + uint8_t mmc24_latched[2]; + + // CHR data + uint8_t const *chr_data; // points to chr ram when there is no read-only data + uint8_t *chr_ram; // always points to impl->chr_ram; makes write_2007() faster + uint8_t const *map_chr(int addr) { return &chr_data[map_chr_addr(addr)]; } + + // CHR cache + cached_tile_t *tile_cache; + cached_tile_t *flipped_tiles; + uint8_t *tile_cache_mem; + union + { + uint8_t modified_tiles[chr_tile_count / 8]; + uint32_t align_; + }; + + void update_tile(int index); +}; + +inline void Ppu_Impl::set_nt_banks(int bank0, int bank1, int bank2, int bank3) +{ + uint8_t *nt_ram = impl->nt_ram; + nt_banks[0] = &nt_ram[bank0 * 0x400]; + nt_banks[1] = &nt_ram[bank1 * 0x400]; + nt_banks[2] = &nt_ram[bank2 * 0x400]; + nt_banks[3] = &nt_ram[bank3 * 0x400]; +} + +inline int Ppu_Impl::map_palette(int addr) +{ + if ((addr & 3) == 0) + addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c + return addr & 0x1f; +} + +inline int Ppu_Impl::sprite_tile_index(uint8_t const *sprite) const +{ + int tile = sprite[1] + (w2000 << 5 & 0x100); + if (w2000 & 0x20) + tile = (tile & 1) * 0x100 + (tile & 0xfe); + return tile; +} + +inline int Ppu_Impl::write_2007(int data) +{ + int addr = vram_addr; + uint8_t *chr_ram = this->chr_ram; // pre-read + int changed = addr + addr_inc; + unsigned const divisor = bytes_per_tile * 8; + int mod_index = (unsigned)addr / divisor % (0x4000 / divisor); + vram_addr = changed; + changed ^= addr; + addr &= 0x3fff; + + // use index into modified_tiles [] since it's calculated sooner than addr is masked + if ((unsigned)mod_index < 0x2000 / divisor) + { + // Avoid overhead of checking for read-only CHR; if that is the case, + // this modification will be ignored. + int mod = modified_tiles[mod_index]; + chr_ram[addr] = data; + any_tiles_modified = true; + modified_tiles[mod_index] = mod | (1 << ((unsigned)addr / bytes_per_tile % 8)); + } + else if (addr < 0x3f00) + { + get_nametable(addr)[addr & 0x3ff] = data; + } + else + { + data &= 0x3f; + uint8_t &entry = palette[map_palette(addr)]; + int changed = entry ^ data; + entry = data; + if (changed) + palette_changed = 0x18; + } + + return changed; +} + +inline void Ppu_Impl::begin_frame() +{ + palette_changed = 0x18; + palette_size = 0; + palette_offset = palette_begin * 0x01010101; + addr_inc = w2000 & 4 ? 32 : 1; +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/ppu/ppuRendering.cpp b/source/quickerNES/ppu/ppuRendering.cpp new file mode 100644 index 0000000..0fef109 --- /dev/null +++ b/source/quickerNES/ppu/ppuRendering.cpp @@ -0,0 +1,506 @@ +// Emu 0.7.0. http://www.slack.net/~ant/ + +#include "ppuRendering.hpp" +#include +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +namespace quickerNES +{ + +static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants + +inline Ppu_Impl::cached_tile_t const & +Ppu_Impl::get_sprite_tile(uint8_t const *sprite) +{ + cached_tile_t *tiles = tile_cache; + if (sprite[2] & 0x40) + tiles = flipped_tiles; + int index = sprite_tile_index(sprite); + + // use index directly, since cached tile is same size as native tile + static_assert(sizeof(cached_tile_t) == bytes_per_tile); + return *(Ppu_Impl::cached_tile_t *)((uint8_t *)tiles + map_chr_addr(index * bytes_per_tile)); +} + +inline Ppu_Impl::cached_tile_t const &Ppu_Impl::get_bg_tile(int index) +{ + // use index directly, since cached tile is same size as native tile + static_assert(sizeof(cached_tile_t) == bytes_per_tile); + return *(Ppu_Impl::cached_tile_t *)((uint8_t *)tile_cache + map_chr_addr(index * bytes_per_tile)); +} + +// Fill + +void Ppu_Rendering::fill_background(int count) +{ + ptrdiff_t const next_line = scanline_row_bytes - image_width; + uint32_t *pixels = (uint32_t *)scanline_pixels; + + unsigned long fill = palette_offset; + if ((vram_addr & 0x3f00) == 0x3f00) + { + // PPU uses current palette entry if addr is within palette ram + int color = vram_addr & 0x1f; + if (!(color & 3)) + color &= 0x0f; + fill += color * 0x01010101; + } + + for (int n = count; n--;) + { + for (int n = image_width / 16; n--;) + { + pixels[0] = fill; + pixels[1] = fill; + pixels[2] = fill; + pixels[3] = fill; + pixels += 4; + } + pixels = (uint32_t *)((uint8_t *)pixels + next_line); + } +} + +void Ppu_Rendering::clip_left(int count) +{ + ptrdiff_t next_line = scanline_row_bytes; + uint8_t *p = scanline_pixels; + unsigned long fill = palette_offset; + + for (int n = count; n--;) + { + ((uint32_t *)p)[0] = fill; + ((uint32_t *)p)[1] = fill; + p += next_line; + } +} + +void Ppu_Rendering::save_left(int count) +{ + ptrdiff_t next_line = scanline_row_bytes; + uint8_t *in = scanline_pixels; + uint32_t *out = impl->clip_buf; + + for (int n = count; n--;) + { + unsigned long in0 = ((uint32_t *)in)[0]; + unsigned long in1 = ((uint32_t *)in)[1]; + in += next_line; + out[0] = in0; + out[1] = in1; + out += 2; + } +} + +void Ppu_Rendering::restore_left(int count) +{ + ptrdiff_t next_line = scanline_row_bytes; + uint8_t *out = scanline_pixels; + uint32_t *in = impl->clip_buf; + + for (int n = count; n--;) + { + unsigned long in0 = in[0]; + unsigned long in1 = in[1]; + in += 2; + ((uint32_t *)out)[0] = in0; + ((uint32_t *)out)[1] = in1; + out += next_line; + } +} + +// Background + +void Ppu_Rendering::draw_background_(int remain) +{ + // Draws 'remain' background scanlines. Does not modify vram_addr. + + int vram_addr = this->vram_addr & 0x7fff; + uint8_t *row_pixels = scanline_pixels - pixel_x; + int left_clip = (w2001 >> 1 & 1) ^ 1; + row_pixels += left_clip * 8; + do + { + // scanlines until next row + int height = 8 - (vram_addr >> 12); + if (height > remain) + height = remain; + + // handle hscroll change before next scanline + int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f; + int addr = vram_addr; + if (hscroll_changed) + { + vram_addr ^= hscroll_changed; + height = 1; // hscroll will change after first line + } + remain -= height; + + // increment address for next row + vram_addr += height << 12; + if (vram_addr & 0x8000) + { + int y = (vram_addr + 0x20) & 0x3e0; + vram_addr &= 0x7fff & ~0x3e0; + if (y == 30 * 0x20) + y = 0x800; // toggle vertical nametable + vram_addr ^= y; + } + + // nametable change usually occurs in middle of row + uint8_t const *nametable = get_nametable(addr); + uint8_t const *nametable2 = get_nametable(addr ^ 0x400); + int count2 = addr & 31; + int count = 32 - count2 - left_clip; + // if ( pixel_x ) + count2++; + + uint8_t const *attr_table = &nametable[0x3c0 | (addr >> 4 & 0x38)]; + int bg_bank = (w2000 << 4) & 0x100; + addr += left_clip; + + // output pixels + ptrdiff_t const row_bytes = scanline_row_bytes; + uint8_t *pixels = row_pixels; + row_pixels += height * row_bytes; + + unsigned long const mask = 0x03030303 + zero; + unsigned long const attrib_factor = 0x04040404 + zero; + + const int fine_y = (height == 8) ? 0 : addr >> 12; + const int clipped = (height == 8) ? false : true; + addr &= 0x03ff; + if (height == 8) height -= fine_y & 1; + + while (true) + { + while (count--) + { + int attrib = attr_table[addr >> 2 & 0x07]; + attrib >>= (addr >> 4 & 4) | (addr & 2); + unsigned long offset = (attrib & 3) * attrib_factor + this->palette_offset; + + // draw one tile + cache_t const *lines = this->get_bg_tile(nametable[addr] + bg_bank); + uint8_t *p = pixels; + addr++; + pixels += 8; // next tile + + if (!clipped) + { + // optimal case: no clipping + for (int n = 4; n--;) + { + unsigned long line = *lines++; + ((uint32_t *)p)[0] = (line >> 4 & mask) + offset; + ((uint32_t *)p)[1] = (line & mask) + offset; + p += row_bytes; + ((uint32_t *)p)[0] = (line >> 6 & mask) + offset; + ((uint32_t *)p)[1] = (line >> 2 & mask) + offset; + p += row_bytes; + } + } + else + { + lines += fine_y >> 1; + + if (fine_y & 1) + { + unsigned long line = *lines++; + ((uint32_t *)p)[0] = (line >> 6 & mask) + offset; + ((uint32_t *)p)[1] = (line >> 2 & mask) + offset; + p += row_bytes; + } + + for (int n = height >> 1; n--;) + { + unsigned long line = *lines++; + ((uint32_t *)p)[0] = (line >> 4 & mask) + offset; + ((uint32_t *)p)[1] = (line & mask) + offset; + p += row_bytes; + ((uint32_t *)p)[0] = (line >> 6 & mask) + offset; + ((uint32_t *)p)[1] = (line >> 2 & mask) + offset; + p += row_bytes; + } + + if (height & 1) + { + unsigned long line = *lines; + ((uint32_t *)p)[0] = (line >> 4 & mask) + offset; + ((uint32_t *)p)[1] = (line & mask) + offset; + } + } + } + + count = count2; + count2 = 0; + addr -= 32; + attr_table = attr_table - nametable + nametable2; + nametable = nametable2; + if (!count) + break; + } + + } while (remain); +} + +// Sprites + +void Ppu_Rendering::draw_sprites_(int begin, int end) +{ + // Draws sprites on scanlines begin through end - 1. Handles clipping. + + int const sprite_height = this->sprite_height(); + int end_minus_one = end - 1; + int begin_minus_one = begin - 1; + int index = 0; + do + { + uint8_t const *sprite = &spr_ram[index]; + index += 4; + + // find if sprite is visible + int top_minus_one = sprite[0]; + int visible = end_minus_one - top_minus_one; + if (visible <= 0) + continue; // off bottom + + // quickly determine whether sprite is unclipped + int neg_vis = visible - sprite_height; + int neg_skip = top_minus_one - begin_minus_one; + if ((neg_skip | neg_vis) >= 0) // neg_skip >= 0 && neg_vis >= 0 + { + int const skip = 0; + int visible = sprite_height; + +#define CLIPPED 0 +#include "ppuSprites.hpp" + } + else + { + // clipped + if (neg_vis > 0) + visible -= neg_vis; + + if (neg_skip > 0) + neg_skip = 0; + visible += neg_skip; + + if (visible <= 0) + continue; // off top + + int skip = -neg_skip; + + // dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n", + // begin, end, top_minus_one + 1, skip, visible ); + +#define CLIPPED 1 +#include "ppuSprites.hpp" + } + } while (index < 0x100); +} + +void Ppu_Rendering::check_sprite_hit(int begin, int end) +{ + // Checks for sprite 0 hit on scanlines begin through end - 1. + // Updates sprite_hit_found. Background (but not sprites) must have + // already been rendered for the scanlines. + + // clip + int top = spr_ram[0] + 1; + int skip = begin - top; + if (skip < 0) + skip = 0; + + top += skip; + int visible = end - top; + if (visible <= 0) + return; // not visible + + int height = sprite_height(); + if (visible >= height) + { + visible = height; + sprite_hit_found = -1; // signal that no more hit checking will take place + } + + // pixels + ptrdiff_t next_row = this->scanline_row_bytes; + uint8_t const *bg = this->scanline_pixels + spr_ram[3] + (top - begin) * next_row; + cache_t const *lines = get_sprite_tile(spr_ram); + + // left edge clipping + int start_x = 0; + if (spr_ram[3] < 8 && (w2001 & 0x01e) != 0x1e) + { + if (spr_ram[3] == 0) + return; // won't hit + start_x = 8 - spr_ram[3]; + } + + // vertical flip + int final = skip + visible; + if (spr_ram[2] & 0x80) + { + skip += height - 1; + final = skip - visible; + } + + // check each line + unsigned long const mask = 0x01010101 + zero; + do + { + // get pixels for line + unsigned long line = lines[skip >> 1]; + unsigned long hit0 = ((uint32_t *)bg)[0]; + unsigned long hit1 = ((uint32_t *)bg)[1]; + bg += next_row; + line >>= skip << 1 & 2; + line |= line >> 1; + + // check for hits + hit0 = ((hit0 >> 1) | hit0) & (line >> 4); + hit1 = ((hit1 >> 1) | hit1) & line; + if ((hit0 | hit1) & mask) + { + // write to memory to avoid endian issues + uint32_t quads[3]; + quads[0] = hit0; + quads[1] = hit1; + + // find which pixel hit + int x = start_x; + do + { + if (((uint8_t *)quads)[x] & 1) + { + x += spr_ram[3]; + if (x >= 255) + break; // ignore right edge + + if (spr_ram[2] & 0x80) + skip = height - 1 - skip; // vertical flip + int y = spr_ram[0] + 1 + skip; + sprite_hit_found = y * scanline_len + x; + + return; + } + } while (x++ < 7); + } + if (skip > final) + skip -= 2; + skip++; + } while (skip != final); +} + +// Draw scanlines + +inline bool Ppu_Rendering::sprite_hit_possible(int scanline) const +{ + return !sprite_hit_found && spr_ram[0] <= scanline && (w2001 & 0x18) == 0x18; +} + +void Ppu_Rendering::draw_scanlines(int start, int count, uint8_t *pixels, long pitch, int mode) +{ + scanline_pixels = pixels + image_left; + scanline_row_bytes = pitch; + + int const obj_mask = 2; + int const bg_mask = 1; + int draw_mode = (w2001 >> 3) & 3; + int clip_mode = (~w2001 >> 1) & draw_mode; + + if (!(draw_mode & bg_mask)) + { + // no background + clip_mode |= bg_mask; // avoid unnecessary save/restore + if (mode & bg_mask) + fill_background(count); + } + + if (start == 0 && mode & 1) + memset(sprite_scanlines, max_sprites - sprite_limit, 240); + + if ((draw_mode &= mode)) + { + // sprites and/or background are being rendered + + if (any_tiles_modified && chr_is_writable) + { + any_tiles_modified = false; + update_tiles(0); + } + + if (draw_mode & bg_mask) + { + // dprintf( "bg %3d-%3d\n", start, start + count - 1 ); + draw_background_(count); + + if (clip_mode == bg_mask) + clip_left(count); + + if (sprite_hit_possible(start + count)) + check_sprite_hit(start, start + count); + } + + if (draw_mode & obj_mask) + { + // when clipping just sprites, save left strip then restore after drawing them + if (clip_mode == obj_mask) + save_left(count); + + // dprintf( "obj %3d-%3d\n", start, start + count - 1 ); + + draw_sprites_(start, start + count); + + if (clip_mode == obj_mask) + restore_left(count); + + if (clip_mode == (obj_mask | bg_mask)) + clip_left(count); + } + } + + scanline_pixels = NULL; +} + +void Ppu_Rendering::draw_background(int start, int count) +{ + // always capture palette at least once per frame + if ((start + count >= 240 && !palette_size) || (w2001 & palette_changed)) + { + palette_changed = false; + capture_palette(); + } + + if (host_pixels) + { + draw_scanlines(start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1); + } + else if (sprite_hit_possible(start + count)) + { + // not rendering, but still handle sprite hit using mini graphics buffer + int y = spr_ram[0] + 1; + int skip = std::min(count, std::max(y - start, 0)); + int visible = std::min(count - skip, sprite_height()); + + if (visible > 0) + { + run_hblank(skip); + draw_scanlines(start + skip, visible, impl->mini_offscreen, buffer_width, 3); + } + } +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/ppu/ppuRendering.hpp b/source/quickerNES/ppu/ppuRendering.hpp new file mode 100644 index 0000000..e2e8cf1 --- /dev/null +++ b/source/quickerNES/ppu/ppuRendering.hpp @@ -0,0 +1,64 @@ +#pragma once + +// NES PPU emulator graphics rendering +// Emu 0.7.0 + +#include "ppuImpl.hpp" + +namespace quickerNES +{ + +class Ppu_Rendering : public Ppu_Impl +{ + typedef Ppu_Impl base; + + public: + Ppu_Rendering(); + + int sprite_limit; + + uint8_t *host_pixels; + long host_row_bytes; + + protected: + long sprite_hit_found; // -1: sprite 0 didn't hit, 0: no hit so far, > 0: y * 341 + x + void draw_background(int start, int count); + void draw_sprites(int start, int count); + + private: + void draw_scanlines(int start, int count, uint8_t *pixels, long pitch, int mode); + void draw_background_(int count); + + // destination for draw functions; avoids extra parameters + uint8_t *scanline_pixels; + long scanline_row_bytes; + + // fill/copy + void fill_background(int count); + void clip_left(int count); + void save_left(int count); + void restore_left(int count); + + // sprites + enum + { + max_sprites = 64 + }; + uint8_t sprite_scanlines[image_height]; // number of sprites on each scanline + void draw_sprites_(int start, int count); + bool sprite_hit_possible(int scanline) const; + void check_sprite_hit(int begin, int end); +}; + +inline Ppu_Rendering::Ppu_Rendering() +{ + sprite_limit = 8; + host_pixels = nullptr; +} + +inline void Ppu_Rendering::draw_sprites(int start, int count) +{ + draw_scanlines(start, count, host_pixels + host_row_bytes * start, host_row_bytes, 2); +} + +} // namespace quickNES \ No newline at end of file diff --git a/source/quickerNES/ppu/ppuSprites.hpp b/source/quickerNES/ppu/ppuSprites.hpp new file mode 100644 index 0000000..737b92b --- /dev/null +++ b/source/quickerNES/ppu/ppuSprites.hpp @@ -0,0 +1,132 @@ +int sprite_2 = sprite[2]; + +// pixels +ptrdiff_t next_row = this->scanline_row_bytes; +uint8_t *out = this->scanline_pixels + sprite[3] + + (top_minus_one + skip - begin_minus_one) * next_row; +cache_t const *lines = get_sprite_tile(sprite); + +int dir = 1; +uint8_t *scanlines = this->sprite_scanlines + 1 + top_minus_one + skip; + +if (sprite_2 & 0x80) +{ + // vertical flip + out -= next_row; + out += visible * next_row; + next_row = -next_row; + dir = -1; + scanlines += visible - 1; +#if CLIPPED + int height = this->sprite_height(); + skip = height - skip - visible; +#endif +} + +// attributes +unsigned long offset = (sprite_2 & 3) * 0x04040404 + (this->palette_offset + 0x10101010); + +unsigned long const mask = 0x03030303 + zero; +unsigned long const maskgen = 0x80808080 + zero; + +#define DRAW_PAIR(shift) \ + { \ + int sprite_count = *scanlines; \ + CALC_FOUR(((uint32_t *)out)[0], (line >> (shift + 4)), out0) \ + CALC_FOUR(((uint32_t *)out)[1], (line >> shift), out1) \ + if (sprite_count < this->max_sprites) \ + { \ + ((uint32_t *)out)[0] = out0; \ + ((uint32_t *)out)[1] = out1; \ + } \ + if (CLIPPED) visible--; \ + out += next_row; \ + *scanlines = sprite_count + 1; \ + scanlines += dir; \ + if (CLIPPED && !visible) break; \ + } + +if (!(sprite_2 & 0x20)) +{ + // front + unsigned long const maskgen2 = 0x7f7f7f7f + zero; + +#define CALC_FOUR(in, line, out) \ + unsigned long out; \ + { \ + unsigned long bg = in; \ + unsigned long sp = line & mask; \ + unsigned long bgm = maskgen2 + ((bg >> 4) & mask); \ + unsigned long spm = (maskgen - sp) & maskgen2; \ + unsigned long m = (bgm & spm) >> 2; \ + out = (bg & ~m) | ((sp + offset) & m); \ + } + +#if CLIPPED + lines += skip >> 1; + unsigned long line = *lines++; + if (skip & 1) + goto front_skip; + + while (true) + { + DRAW_PAIR(0) + front_skip: + DRAW_PAIR(2) + line = *lines++; + } +#else + for (int n = visible >> 1; n--;) + { + unsigned long line = *lines++; + DRAW_PAIR(0) + DRAW_PAIR(2) + } +#endif + +#undef CALC_FOUR +} +else +{ + // behind + unsigned long const omask = 0x20202020 + zero; + unsigned long const bg_or = 0xc3c3c3c3 + zero; + +#define CALC_FOUR(in, line, out) \ + unsigned long out; \ + { \ + unsigned long bg = in; \ + unsigned long sp = line & mask; \ + unsigned long bgm = maskgen - (bg & mask); \ + unsigned long spm = maskgen - sp; \ + out = (bg & (bgm | bg_or)) | (spm & omask) | \ + (((offset & spm) + sp) & ~(bgm >> 2)); \ + } + +#if CLIPPED + lines += skip >> 1; + unsigned long line = *lines++; + if (skip & 1) + goto back_skip; + + while (true) + { + DRAW_PAIR(0) + back_skip: + DRAW_PAIR(2) + line = *lines++; + } +#else + for (int n = visible >> 1; n--;) + { + unsigned long line = *lines++; + DRAW_PAIR(0) + DRAW_PAIR(2) + } +#endif + +#undef CALC_FOUR +} + +#undef CLIPPED +#undef DRAW_PAIR diff --git a/source/quickerNES/quickerNESInstance.hpp b/source/quickerNES/quickerNESInstance.hpp index 8d504c5..e395ad1 100644 --- a/source/quickerNES/quickerNESInstance.hpp +++ b/source/quickerNES/quickerNESInstance.hpp @@ -1,76 +1,79 @@ #pragma once -#include +#include #include +namespace quickerNES +{ + class QuickerNESInstance : public EmuInstance { - public: + public: + QuickerNESInstance() : EmuInstance() + { + // Creating new emulator + _nes = new Emu; - QuickerNESInstance() : EmuInstance() - { - // Creating new emulator - _nes = new Nes_Emu; + // Allocating video buffer + video_buffer = (uint8_t *)malloc(image_width * image_height); - // Allocating video buffer - video_buffer = (uint8_t*) malloc(image_width * image_height); + // Setting video buffer + _nes->set_pixels(video_buffer, image_width + 8); + } - // Setting video buffer - _nes->set_pixels(video_buffer, image_width+8); - } - - ~QuickerNESInstance() = default; + ~QuickerNESInstance() = default; - virtual bool loadROMFileImpl(const std::string& romData) override - { - // Loading rom data - _nes->load_ines((uint8_t*)romData.data()); - return true; - } + virtual bool loadROMFileImpl(const std::string &romData) override + { + // Loading rom data + _nes->load_ines((uint8_t *)romData.data()); + return true; + } - uint8_t* getLowMem() const override { return _nes->low_mem(); }; - uint8_t* getNametableMem() const override { return _nes->nametable_mem(); }; - uint8_t* getHighMem() const override { return _nes->high_mem();}; - const uint8_t* getChrMem() const override { return _nes->chr_mem();}; - size_t getChrMemSize() const override { return _nes->chr_size();}; + uint8_t *getLowMem() const override { return _nes->low_mem(); }; + uint8_t *getNametableMem() const override { return _nes->nametable_mem(); }; + uint8_t *getHighMem() const override { return _nes->high_mem(); }; + const uint8_t *getChrMem() const override { return _nes->chr_mem(); }; + size_t getChrMemSize() const override { return _nes->chr_size(); }; - void serializeState(uint8_t* state) const override - { + void serializeState(uint8_t *state) const override + { _nes->serializeState(state); - } + } - void deserializeState(const uint8_t* state) override - { - _nes->deserializeState(state); - } + void deserializeState(const uint8_t *state) override + { + _nes->deserializeState(state); + } - void advanceStateImpl(const inputType controller1, const inputType controller2) override - { - if (_doRendering == true) _nes->emulate_frame(controller1, controller2); - if (_doRendering == false) _nes->emulate_skip_frame(controller1, controller2); - } + void advanceStateImpl(const inputType controller1, const inputType controller2) override + { + if (_doRendering == true) _nes->emulate_frame(controller1, controller2); + if (_doRendering == false) _nes->emulate_skip_frame(controller1, controller2); + } - std::string getCoreName() const override { return "QuickerNES"; } - void doSoftReset() override { _nes->reset(false); } - void doHardReset() override { _nes->reset(true); } + std::string getCoreName() const override { return "QuickerNES"; } + void doSoftReset() override { _nes->reset(false); } + void doHardReset() override { _nes->reset(true); } - void* getInternalEmulatorPointer() const override { return _nes; } + void *getInternalEmulatorPointer() const override { return _nes; } - private: - - inline size_t getStateSizeImpl() const override - { + private: + inline size_t getStateSizeImpl() const override + { return _nes->getStateSize(); - } + } - inline size_t getLiteStateSizeImpl() const override - { + inline size_t getLiteStateSizeImpl() const override + { return _nes->getLiteStateSize(); - } + } - // Video buffer - uint8_t* video_buffer; + // Video buffer + uint8_t *video_buffer; - // Emulator instance - Nes_Emu* _nes; + // Emulator instance + Emu *_nes; }; + +} // namespace quickNES \ No newline at end of file diff --git a/source/tester.cpp b/source/tester.cpp index 89a6376..b0c31dc 100644 --- a/source/tester.cpp +++ b/source/tester.cpp @@ -1,21 +1,21 @@ -#include -#include #include "argparse/argparse.hpp" +#include "nlohmann/json.hpp" #include "sha1/sha1.hpp" #include "utils.hpp" -#include "nlohmann/json.hpp" +#include +#include #ifdef _USE_QUICKNES -#include "quickNESInstance.hpp" + #include "quickNESInstance.hpp" #endif #ifdef _USE_QUICKERNES -#include "quickerNESInstance.hpp" + #include "quickerNESInstance.hpp" #endif int main(int argc, char *argv[]) { - // Parsing command line arguments + // Parsing command line arguments argparse::ArgumentParser program("tester", "1.0"); program.add_argument("scriptFile") @@ -31,8 +31,14 @@ int main(int argc, char *argv[]) .default_value(std::string("")); // Try to parse arguments - try { program.parse_args(argc, argv); } - catch (const std::runtime_error &err) { EXIT_WITH_ERROR("%s\n%s", err.what(), program.help().str().c_str()); } + try + { + program.parse_args(argc, argv); + } + catch (const std::runtime_error &err) + { + EXIT_WITH_ERROR("%s\n%s", err.what(), program.help().str().c_str()); + } // Getting test script file path std::string scriptFilePath = program.get("scriptFile"); @@ -45,9 +51,9 @@ int main(int argc, char *argv[]) // Loading script file std::string scriptJsonRaw; - if (loadStringFromFile(scriptJsonRaw, scriptFilePath) == false) EXIT_WITH_ERROR("Could not find/read script file: %s\n", scriptFilePath.c_str()); + if (loadStringFromFile(scriptJsonRaw, scriptFilePath) == false) EXIT_WITH_ERROR("Could not find/read script file: %s\n", scriptFilePath.c_str()); - // Parsing script + // Parsing script const auto scriptJson = nlohmann::json::parse(scriptJsonRaw); // Getting rom file path @@ -70,21 +76,21 @@ int main(int argc, char *argv[]) if (scriptJson["Expected ROM SHA1"].is_string() == false) EXIT_WITH_ERROR("Script file 'Expected ROM SHA1' entry is not a string\n"); std::string expectedROMSHA1 = scriptJson["Expected ROM SHA1"].get(); - // Creating emulator instance - #ifdef _USE_QUICKNES +// Creating emulator instance +#ifdef _USE_QUICKNES auto e = QuickNESInstance(); - #endif +#endif - #ifdef _USE_QUICKERNES - auto e = QuickerNESInstance(); - #endif +#ifdef _USE_QUICKERNES + auto e = quickerNES::QuickerNESInstance(); +#endif // Loading ROM File e.loadROMFile(romFilePath); // If an initial state is provided, load it now if (initialStateFilePath != "") e.loadStateFile(initialStateFilePath); - + // Disable rendering e.disableRendering(); @@ -129,11 +135,11 @@ int main(int argc, char *argv[]) printf("[] Full State Size: %lu bytes\n", stateSize); printf("[] Lite State Size: %lu bytes\n", liteStateSize); printf("[] ********** Running Test **********\n"); - + fflush(stdout); - + // Serializing initial state - uint8_t* currentState = (uint8_t*) malloc (stateSize); + uint8_t *currentState = (uint8_t *)malloc(stateSize); e.serializeState(currentState); // Check whether to perform each action @@ -143,13 +149,13 @@ int main(int argc, char *argv[]) // Actually running the sequence auto t0 = std::chrono::high_resolution_clock::now(); - for (const std::string& input : sequence) + for (const std::string &input : sequence) { if (doPreAdvance == true) e.advanceState(input); if (doDeserialize == true) e.deserializeState(currentState); e.advanceState(input); if (doSerialize == true) e.serializeState(currentState); - } + } auto tf = std::chrono::high_resolution_clock::now(); // Calculating running time @@ -174,4 +180,3 @@ int main(int argc, char *argv[]) // If reached this point, everything ran ok return 0; } - diff --git a/source/utils.hpp b/source/utils.hpp index c17046d..c2b75e5 100644 --- a/source/utils.hpp +++ b/source/utils.hpp @@ -1,21 +1,21 @@ #pragma once +#include #include +#include #include -#include #include #include #include #include -#include -#include +#include // If we use NCurses, we need to use the appropriate printing function #ifdef NCURSES - #include - #define LOG printw + #include + #define LOG printw #else - #define LOG printf + #define LOG printf #endif // If we use NCurses, define the following useful functions @@ -82,12 +82,10 @@ void refreshTerminal() refresh(); } - #endif // NCURSES - typedef _uint128_t hash_t; -inline hash_t calculateMetroHash(uint8_t* data, size_t size) +inline hash_t calculateMetroHash(uint8_t *data, size_t size) { MetroHash128 hash; hash.Update(data, size); @@ -100,7 +98,8 @@ inline hash_t calculateMetroHash(uint8_t* data, size_t size) // Taken from stack overflow answer to https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string // By Evan Teran -template inline void split(const std::string &s, char delim, Out result) +template +inline void split(const std::string &s, char delim, Out result) { std::istringstream iss(s); std::string item; @@ -112,11 +111,11 @@ template inline void split(const std::string &s, char delim, Out inline std::vector split(const std::string &s, char delim) { - std::string newString = s; - std::replace(newString.begin(), newString.end(), '\n', ' '); - std::vector elems; - split(newString, delim, std::back_inserter(elems)); - return elems; + std::string newString = s; + std::replace(newString.begin(), newString.end(), '\n', ' '); + std::vector elems; + split(newString, delim, std::back_inserter(elems)); + return elems; } // Taken from https://stackoverflow.com/questions/116038/how-do-i-read-an-entire-file-into-a-stdstring-in-c/116220#116220 @@ -127,8 +126,8 @@ inline std::string slurp(std::ifstream &in) return sstr.str(); } -#define EXIT_WITH_ERROR(...) exitWithError(__FILE__, __LINE__, __VA_ARGS__) -inline void exitWithError [[noreturn]] (const char *fileName, const int lineNumber, const char *format, ...) +#define EXIT_WITH_ERROR(...) exitWithError(__FILE__, __LINE__, __VA_ARGS__) +inline void exitWithError [[noreturn]] (const char *fileName, const int lineNumber, const char *format, ...) { char *outstr = 0; va_list ap; @@ -178,7 +177,8 @@ inline bool saveStringToFile(const std::string &src, const char *fileName) } // Function to split a vector into n mostly fair chunks -template inline std::vector splitVector(const T size, const T n) +template +inline std::vector splitVector(const T size, const T n) { std::vector subSizes(n); @@ -191,14 +191,19 @@ template inline std::vector splitVector(const T size, const T n) return subSizes; } -inline std::string simplifyMove(const std::string& move) +inline std::string simplifyMove(const std::string &move) { - std::string simpleMove; + std::string simpleMove; - bool isEmptyMove = true; - for (size_t i = 0; i < move.size(); i++) if (move[i] != '.' && move[i] != '|') { simpleMove += move[i]; isEmptyMove = false; } - if (isEmptyMove) return "."; - return simpleMove; + bool isEmptyMove = true; + for (size_t i = 0; i < move.size(); i++) + if (move[i] != '.' && move[i] != '|') + { + simpleMove += move[i]; + isEmptyMove = false; + } + if (isEmptyMove) return "."; + return simpleMove; } inline bool getBitFlag(const uint8_t value, const uint8_t idx) @@ -214,29 +219,42 @@ inline bool getBitFlag(const uint8_t value, const uint8_t idx) return false; } - -inline size_t countButtonsPressedString(const std::string& input) { size_t count = 0; for (size_t i = 0; i < input.size(); i++) if (input[i] != '.') count++; return count; }; - -template inline uint16_t countButtonsPressedNumber(const T& input) { - uint16_t count = 0; - if (input & 0b0000000000000001) count++; - if (input & 0b0000000000000010) count++; - if (input & 0b0000000000000100) count++; - if (input & 0b0000000000001000) count++; - if (input & 0b0000000000010000) count++; - if (input & 0b0000000000100000) count++; - if (input & 0b0000000001000000) count++; - if (input & 0b0000000010000000) count++; - if (input & 0b0000000100000000) count++; - if (input & 0b0000001000000000) count++; - if (input & 0b0000010000000000) count++; - if (input & 0b0000100000000000) count++; - if (input & 0b0001000000000000) count++; - if (input & 0b0010000000000000) count++; - if (input & 0b0100000000000000) count++; - if (input & 0b1000000000000000) count++; - return count; +inline size_t countButtonsPressedString(const std::string &input) +{ + size_t count = 0; + for (size_t i = 0; i < input.size(); i++) + if (input[i] != '.') count++; + return count; }; -static auto moveCountComparerString = [](const std::string& a, const std::string& b) { return countButtonsPressedString(a) < countButtonsPressedString(b); }; -static auto moveCountComparerNumber = [](const uint8_t a, const uint8_t b) { return countButtonsPressedNumber(a) < countButtonsPressedNumber(b); }; +template +inline uint16_t countButtonsPressedNumber(const T &input) +{ + uint16_t count = 0; + if (input & 0b0000000000000001) count++; + if (input & 0b0000000000000010) count++; + if (input & 0b0000000000000100) count++; + if (input & 0b0000000000001000) count++; + if (input & 0b0000000000010000) count++; + if (input & 0b0000000000100000) count++; + if (input & 0b0000000001000000) count++; + if (input & 0b0000000010000000) count++; + if (input & 0b0000000100000000) count++; + if (input & 0b0000001000000000) count++; + if (input & 0b0000010000000000) count++; + if (input & 0b0000100000000000) count++; + if (input & 0b0001000000000000) count++; + if (input & 0b0010000000000000) count++; + if (input & 0b0100000000000000) count++; + if (input & 0b1000000000000000) count++; + return count; +}; + +static auto moveCountComparerString = [](const std::string &a, const std::string &b) +{ + return countButtonsPressedString(a) < countButtonsPressedString(b); +}; +static auto moveCountComparerNumber = [](const uint8_t a, const uint8_t b) +{ + return countButtonsPressedNumber(a) < countButtonsPressedNumber(b); +}; diff --git a/tests/arkanoid/warpless.test b/tests/arkanoid/warpless.test index 480806e..98fe513 100644 --- a/tests/arkanoid/warpless.test +++ b/tests/arkanoid/warpless.test @@ -1,5 +1,5 @@ { - "Rom File": "Arkanoid (U) [!].nes", + "Rom File": "../roms/Arkanoid (U) [!].nes", "Expected ROM SHA1": "B2B30C4F30DD853C215C17B0C67CFE63D61A3062", "Initial State File": "", "Sequence File": "warpless.sol" diff --git a/tests/arkanoid/warps.test b/tests/arkanoid/warps.test index 4b2344e..2cf1843 100644 --- a/tests/arkanoid/warps.test +++ b/tests/arkanoid/warps.test @@ -1,5 +1,5 @@ { - "Rom File": "Arkanoid (U) [!].nes", + "Rom File": "../roms/Arkanoid (U) [!].nes", "Expected ROM SHA1": "B2B30C4F30DD853C215C17B0C67CFE63D61A3062", "Initial State File": "", "Sequence File": "warps.sol" diff --git a/tests/castlevania1/anyPercent.test b/tests/castlevania1/anyPercent.test index 2254d11..ef42c08 100644 --- a/tests/castlevania1/anyPercent.test +++ b/tests/castlevania1/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Castlevania (U) (PRG0) [!].nes", + "Rom File": "../roms/Castlevania (U) (PRG0) [!].nes", "Expected ROM SHA1": "A31B8BD5B370A9103343C866F3C2B2998E889341", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/castlevania1/pacifist.test b/tests/castlevania1/pacifist.test index e03dc39..cfef218 100644 --- a/tests/castlevania1/pacifist.test +++ b/tests/castlevania1/pacifist.test @@ -1,5 +1,5 @@ { - "Rom File": "Castlevania (U) (PRG0) [!].nes", + "Rom File": "../roms/Castlevania (U) (PRG0) [!].nes", "Expected ROM SHA1": "A31B8BD5B370A9103343C866F3C2B2998E889341", "Initial State File": "", "Verification State File": "pacifist.final.state", diff --git a/tests/galaga/anyPercent.test b/tests/galaga/anyPercent.test index 973d82b..fa768b1 100644 --- a/tests/galaga/anyPercent.test +++ b/tests/galaga/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Galaga - Demons of Death (U) [!].nes", + "Rom File": "../roms/Galaga - Demons of Death (U) [!].nes", "Expected ROM SHA1": "DA54C223D79FA59EB95437854B677CF69B5CAC8A", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/ironSword/anyPercent.test b/tests/ironSword/anyPercent.test index c1f525b..c72fe69 100644 --- a/tests/ironSword/anyPercent.test +++ b/tests/ironSword/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Ironsword - Wizards & Warriors II (U) [!].nes", + "Rom File": "../roms/Ironsword - Wizards & Warriors II (U) [!].nes", "Expected ROM SHA1": "97B79E432F62403FB9F877090850C41112A9A168", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/nigelMansell/anyPercent.test b/tests/nigelMansell/anyPercent.test index 72209c6..dfdaade 100644 --- a/tests/nigelMansell/anyPercent.test +++ b/tests/nigelMansell/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Nigel Mansell's World Championship Challenge (U) [!].nes", + "Rom File": "../roms/Nigel Mansell's World Championship Challenge (U) [!].nes", "Expected ROM SHA1": "BBE5CF2DFA0B5422776A530D6F1B617238A8569F", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/ninjaGaiden/anyPercent.test b/tests/ninjaGaiden/anyPercent.test index 090c42c..29e4331 100644 --- a/tests/ninjaGaiden/anyPercent.test +++ b/tests/ninjaGaiden/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Ninja Gaiden (U) [!].nes", + "Rom File": "../roms/Ninja Gaiden (U) [!].nes", "Expected ROM SHA1": "CA513F841D75EFEB33BB8099FB02BEEB39F6BB9C", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/ninjaGaiden/pacifist.test b/tests/ninjaGaiden/pacifist.test index 3c06133..fceaa97 100644 --- a/tests/ninjaGaiden/pacifist.test +++ b/tests/ninjaGaiden/pacifist.test @@ -1,5 +1,5 @@ { - "Rom File": "Ninja Gaiden (U) [!].nes", + "Rom File": "../roms/Ninja Gaiden (U) [!].nes", "Expected ROM SHA1": "CA513F841D75EFEB33BB8099FB02BEEB39F6BB9C", "Initial State File": "", "Sequence File": "pacifist.sol" diff --git a/tests/ninjaGaiden2/anyPercent.test b/tests/ninjaGaiden2/anyPercent.test index 21d24f7..587249b 100644 --- a/tests/ninjaGaiden2/anyPercent.test +++ b/tests/ninjaGaiden2/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Ninja Gaiden II - The Dark Sword of Chaos (U) [!].nes", + "Rom File": "../roms/Ninja Gaiden II - The Dark Sword of Chaos (U) [!].nes", "Expected ROM SHA1": "B1796660E4A4CEFC72181D4BF4F97999BC048A77", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/ninjaGaiden2/pacifist.test b/tests/ninjaGaiden2/pacifist.test index b19685a..dee408d 100644 --- a/tests/ninjaGaiden2/pacifist.test +++ b/tests/ninjaGaiden2/pacifist.test @@ -1,5 +1,5 @@ { - "Rom File": "Ninja Gaiden II - The Dark Sword of Chaos (U) [!].nes", + "Rom File": "../roms/Ninja Gaiden II - The Dark Sword of Chaos (U) [!].nes", "Expected ROM SHA1": "B1796660E4A4CEFC72181D4BF4F97999BC048A77", "Initial State File": "", "Sequence File": "pacifist.sol" diff --git a/tests/novaTheSquirrel/anyPercent.test b/tests/novaTheSquirrel/anyPercent.test index 0162425..e7085ab 100644 --- a/tests/novaTheSquirrel/anyPercent.test +++ b/tests/novaTheSquirrel/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "nova.nes", + "Rom File": "../roms/nova.nes", "Expected ROM SHA1": "B6B07EE76492ED475F39167C89B342353F999231", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/princeOfPersia/anyPercent.test b/tests/princeOfPersia/anyPercent.test index d093658..82d1af0 100644 --- a/tests/princeOfPersia/anyPercent.test +++ b/tests/princeOfPersia/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Prince of Persia (U) [!].nes", + "Rom File": "../roms/Prince of Persia (U) [!].nes", "Expected ROM SHA1": "6B58F149F34FA829135619C58700CAAA95B9CDE3", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/roms/README.md b/tests/roms/README.md new file mode 100644 index 0000000..cb04bbc --- /dev/null +++ b/tests/roms/README.md @@ -0,0 +1,3 @@ +Here go all the roms needed for testing + +To prevent copyright violations, only free licensed roms shall be uploaded. The rest should be provisioned locally by the runners. diff --git a/tests/novaTheSquirrel/nova.nes b/tests/roms/nova.nes similarity index 100% rename from tests/novaTheSquirrel/nova.nes rename to tests/roms/nova.nes diff --git a/tests/sprilo/sprilo.nes b/tests/roms/sprilo.nes similarity index 100% rename from tests/sprilo/sprilo.nes rename to tests/roms/sprilo.nes diff --git a/tests/saintSeiyaKanketsuHen/anyPercent.test b/tests/saintSeiyaKanketsuHen/anyPercent.test index 60eb89d..431e6f5 100644 --- a/tests/saintSeiyaKanketsuHen/anyPercent.test +++ b/tests/saintSeiyaKanketsuHen/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Saint Seiya - Ougon Densetsu Kanketsu Hen (J) [!].nes", + "Rom File": "../roms/Saint Seiya - Ougon Densetsu Kanketsu Hen (J) [!].nes", "Expected ROM SHA1": "F871D9B3DAFDDCDAD5F2ACD71044292E5169064E", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/saintSeiyaOugonDensetsu/anyPercent.test b/tests/saintSeiyaOugonDensetsu/anyPercent.test index 6fddec6..d69b8a7 100644 --- a/tests/saintSeiyaOugonDensetsu/anyPercent.test +++ b/tests/saintSeiyaOugonDensetsu/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Saint Seiya - Ougon Densetsu (J) [!].nes", + "Rom File": "../roms/Saint Seiya - Ougon Densetsu (J) [!].nes", "Expected ROM SHA1": "3F3B499CF50386084E053BCA096AE8E52330CFAE", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/saiyuukiWorld/anyPercent.test b/tests/saiyuukiWorld/anyPercent.test index 8dbb390..493fd3f 100644 --- a/tests/saiyuukiWorld/anyPercent.test +++ b/tests/saiyuukiWorld/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Saiyuuki World (J).nes", + "Rom File": "../roms/Saiyuuki World (J).nes", "Expected ROM SHA1": "C2F12D915A4D0B1FFDF8A64AE1092CE6A2D08770", "Initial State File": "", "Sequence File": "anyPercent.sol", diff --git a/tests/saiyuukiWorld/lastHalf.test b/tests/saiyuukiWorld/lastHalf.test index 02e6265..ae3d654 100644 --- a/tests/saiyuukiWorld/lastHalf.test +++ b/tests/saiyuukiWorld/lastHalf.test @@ -1,5 +1,5 @@ { - "Rom File": "Saiyuuki World (J).nes", + "Rom File": "../roms/Saiyuuki World (J).nes", "Expected ROM SHA1": "C2F12D915A4D0B1FFDF8A64AE1092CE6A2D08770", "Initial State File": "lastHalf.state", "Sequence File": "anyPercent.sol", diff --git a/tests/solarJetman/anyPercent.test b/tests/solarJetman/anyPercent.test index 573af2f..9d3bae5 100644 --- a/tests/solarJetman/anyPercent.test +++ b/tests/solarJetman/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Solar Jetman - Hunt for the Golden Warpship (U) [!].nes", + "Rom File": "../roms/Solar Jetman - Hunt for the Golden Warpship (U) [!].nes", "Expected ROM SHA1": "872B91A2F7A2F635061EF43F79E7F7E9F59F5C50", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/sprilo/anyPercent.test b/tests/sprilo/anyPercent.test index d33d626..c8e3328 100644 --- a/tests/sprilo/anyPercent.test +++ b/tests/sprilo/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "sprilo.nes", + "Rom File": "../roms/sprilo.nes", "Expected ROM SHA1": "6EC09B9B51320A536A786D3D4719432B714C5779", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/superMarioBros/warpless.test b/tests/superMarioBros/warpless.test index 0b98499..0838d00 100644 --- a/tests/superMarioBros/warpless.test +++ b/tests/superMarioBros/warpless.test @@ -1,5 +1,5 @@ { - "Rom File": "Super Mario Bros. (W) [!].nes", + "Rom File": "../roms/Super Mario Bros. (W) [!].nes", "Expected ROM SHA1": "EA343F4E445A9050D4B4FBAC2C77D0693B1D0922", "Initial State File": "", "Sequence File": "warpless.sol" diff --git a/tests/superMarioBros/warps.test b/tests/superMarioBros/warps.test index 486c514..d591ad3 100644 --- a/tests/superMarioBros/warps.test +++ b/tests/superMarioBros/warps.test @@ -1,5 +1,5 @@ { - "Rom File": "Super Mario Bros. (W) [!].nes", + "Rom File": "../roms/Super Mario Bros. (W) [!].nes", "Expected ROM SHA1": "EA343F4E445A9050D4B4FBAC2C77D0693B1D0922", "Initial State File": "", "Sequence File": "warps.sol" diff --git a/tests/superMarioBros3/warps.test b/tests/superMarioBros3/warps.test index 77869fb..a242afa 100644 --- a/tests/superMarioBros3/warps.test +++ b/tests/superMarioBros3/warps.test @@ -1,5 +1,5 @@ { - "Rom File": "Super Mario Bros. 3 (U) (PRG0) [!].nes", + "Rom File": "../roms/Super Mario Bros. 3 (U) (PRG0) [!].nes", "Expected ROM SHA1": "A03E7E526E79DF222E048AE22214BCA2BC49C449", "Initial State File": "", "Sequence File": "warps.sol" diff --git a/tests/superOffroad/anyPercent.test b/tests/superOffroad/anyPercent.test index 54fc1f1..c69074e 100644 --- a/tests/superOffroad/anyPercent.test +++ b/tests/superOffroad/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Ivan 'Ironman' Stewart's Super Off Road (USA).nes", + "Rom File": "../roms/Ivan 'Ironman' Stewart's Super Off Road (USA).nes", "Expected ROM SHA1": "57919B685B55EE3ED3AD98FB1D25626B98BE7D39", "Initial State File": "", "Sequence File": "anyPercent.sol" diff --git a/tests/tennis/anyPercent.test b/tests/tennis/anyPercent.test index ed98ccf..8a8816f 100644 --- a/tests/tennis/anyPercent.test +++ b/tests/tennis/anyPercent.test @@ -1,5 +1,5 @@ { - "Rom File": "Tennis (JU) [!].nes", + "Rom File": "../roms/Tennis (JU) [!].nes", "Expected ROM SHA1": "80D99C035E6A5AB9718E413EC25CBE094F085962", "Initial State File": "", "Sequence File": "anyPercent.sol"