diff --git a/src/cadmium.cpp b/src/cadmium.cpp index 076b1a1..4c5f90c 100644 --- a/src/cadmium.cpp +++ b/src/cadmium.cpp @@ -514,12 +514,11 @@ class Cadmium : public emu::Chip8EmuHostEx , _screenHeight(MIN_SCREEN_HEIGHT) { SetTraceLogCallback(LogHandler); - #ifdef WITH_FLAG_COCOA_GRAPHICS_SWITCHING #ifdef RESIZABLE_GUI SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_COCOA_GRAPHICS_SWITCHING); #else - SetConfigFlags(FLAG_COCOA_GRAPHICS_SWITCHING/*|FLAG_VSYNC_HINT*/); + //SetConfigFlags(FLAG_COCOA_GRAPHICS_SWITCHING/*|FLAG_VSYNC_HINT*/); #endif #else #ifdef RESIZABLE_GUI @@ -632,7 +631,12 @@ class Cadmium : public emu::Chip8EmuHostEx */ #ifdef PLATFORM_WEB JsClipboard_AddJsHook(); +#else + _volume = _volumeSlider = _cfg.volume; #endif + if(_volume > 1.0f) + _volume = _volumeSlider = 1.0f; + SetMasterVolume(_volume); } ~Cadmium() override @@ -706,40 +710,15 @@ class Cadmium : public emu::Chip8EmuHostEx void renderAudio(int16_t *samples, unsigned int frames) { std::scoped_lock lock(_audioMutex); + _audioCallbackAvgFrames = _audioCallbackAvgFrames ? (_audioCallbackAvgFrames + frames)/2 : frames; if(_chipEmu) { - if(_options.behaviorBase == emu::Chip8EmulatorOptions::eMEGACHIP) { - while(frames--) { - *samples++ = ((int16_t)_chipEmu->getNextMCSample() - 128) * 256; - } - return; - } - else { - auto st = _chipEmu->soundTimer(); - if(st && _chipEmu->getExecMode() == emu::GenericCpu::eRUNNING) { - auto samplesLeftToPlay = std::min(st * (44100 / 60) / g_frameBoost, (int)frames); - float phase = _chipEmu->getAudioPhase(); - if (!_options.optXOChipSound) { - const float step = _chipEmu->getAudioFrequency() / 44100; - for (int i = 0; i < samplesLeftToPlay; ++i, --frames) { - *samples++ = (phase > 0.5f) ? 16384 : -16384; - phase = std::fmod(phase + step, 1.0f); - } - _chipEmu->setAudioPhase(phase); - } - else { - auto len = _audioBuffer.read(samples, frames); - frames -= len; - if (frames > 0) { - //TraceLog(LOG_WARNING, "AudioBuffer underrun: %d frames", frames); - auto step = 4000 * std::pow(2.0f, (float(_chipEmu->getXOPitch()) - 64) / 48.0f) / 128 / 44100; - for (; frames > 0; --frames) { - auto pos = int(std::clamp(phase * 128.0f, 0.0f, 127.0f)); - *samples++ = _chipEmu->getXOAudioPattern()[pos >> 3] & (1 << (7 - (pos & 7))) ? 16384 : -16384; - phase = std::fmod(phase + step, 1.0f); - } - _chipEmu->setAudioPhase(phase); - } - } + if(_chipEmu->getExecMode() == emu::GenericCpu::eRUNNING) { + auto len = _audioBuffer.read(samples, frames); + frames -= len; + samples += len; + if(frames) { + _chipEmu->renderAudio(samples, frames, 44100); + frames = 0; } } } @@ -748,26 +727,22 @@ class Cadmium : public emu::Chip8EmuHostEx } } - void pushAudio(float deltaT = 1.0f/60) + void pushAudio(int frames) { static int16_t sampleBuffer[44100]; - auto st = _chipEmu->soundTimer(); - if(_chipEmu->getExecMode() == emu::IChip8Emulator::eRUNNING && st && _options.optXOChipSound) { - auto samples = int(44100 * deltaT + 0.75f); - if(samples > 44100) samples = 44100; - auto step = 4000 * std::pow(2.0f, (float(_chipEmu->getXOPitch()) - 64) / 48.0f) / 128 / 44100; - float phase = st ? _chipEmu->getAudioPhase() : 0.0f; - auto* dest = sampleBuffer; - for (int i = 0; i < samples; ++i) { - auto pos = int(std::clamp(phase * 128.0f, 0.0f, 127.0f)); - *dest++ = _chipEmu->getXOAudioPattern()[pos >> 3] & (1 << (7 - (pos & 7))) ? 16384 : -16384; - phase = std::fmod(phase + step, 1.0f); - } - _audioBuffer.write(sampleBuffer, samples); - _chipEmu->setAudioPhase(phase); + if(_chipEmu->getExecMode() == emu::IChip8Emulator::eRUNNING) { + //if(_audioBuffer.dataAvailable() < _audioCallbackAvgFrames) ++frames; + if(frames > _audioBuffer.spaceAvailable()) frames = _audioBuffer.spaceAvailable(); + _chipEmu->renderAudio(sampleBuffer, frames, 44100); + _audioBuffer.write(sampleBuffer, frames); } } + void vblank() override + { + pushAudio(44100 / _options.frameRate); + } + uint8_t getKeyPressed() override { static uint32_t instruction = 0; @@ -1066,7 +1041,6 @@ class Cadmium : public emu::Chip8EmuHostEx _chipEmu->tick(getInstrPerFrame()); g_soundTimer.store(_chipEmu->soundTimer()); } - pushAudio(deltaT); } else { static int cntx = 0; @@ -1079,7 +1053,6 @@ class Cadmium : public emu::Chip8EmuHostEx else { excessTime = 0; } - pushAudio(deltaT); } if(_chipEmu->needsScreenUpdate()) @@ -1214,6 +1187,7 @@ class Cadmium : public emu::Chip8EmuHostEx static uint32_t* selectedColor = nullptr; static std::string colorText; static uint32_t previousColor{}; + static std::chrono::steady_clock::time_point volumeClick{}; #ifdef RESIZABLE_GUI auto screenScale = std::min(std::clamp(int(GetScreenWidth() / _screenWidth), 1, 8), std::clamp(int(GetScreenHeight() / _screenHeight), 1, 8)); @@ -1443,7 +1417,7 @@ class Cadmium : public emu::Chip8EmuHostEx } } SetTooltip("RESTART"); - int buttonsRight = 6; + int buttonsRight = 7; #ifdef WITH_EDITOR ++buttonsRight; #endif @@ -1479,6 +1453,9 @@ class Cadmium : public emu::Chip8EmuHostEx if (iconButton(ICON_GEAR, _mainView == eSETTINGS)) _mainView = eSETTINGS; SetTooltip("SETTINGS"); + if (iconButton(ICON_AUDIO, false)) + volumeClick = std::chrono::steady_clock::now(); + SetTooltip("VOLUME"); static Vector2 versionSize = MeasureTextEx(GuiGetFont(), "v" CADMIUM_VERSION, 8, 0); DrawTextEx(GuiGetFont(), "v" CADMIUM_VERSION, {spacePos.x + (spaceWidth - versionSize.x) / 2, spacePos.y + 6}, 8, 0, WHITE); @@ -1551,11 +1528,11 @@ class Cadmium : public emu::Chip8EmuHostEx SetRowHeight(20); if(!_chipEmu->isGenericEmulation() || _options.behaviorBase == emu::Chip8EmulatorOptions::eCHIP8TE) GuiDisable(); - Spinner("Instructions per frame", &_options.instructionsPerFrame, 0, 500000); + Spinner("Instructions per frame", &_options.instructionsPerFrame, 0, 1000000); Spinner("Frame rate", &_options.frameRate, 10, 120); if(!_chipEmu->isGenericEmulation() || _options.behaviorBase == emu::Chip8EmulatorOptions::eCHIP8TE) GuiEnable(); - if (!_options.instructionsPerFrame) { + if (true/*!_options.instructionsPerFrame*/) { static int _fb1{1}; GuiDisable(); Spinner("Frame boost", &_fb1, 1, 100000); @@ -1882,6 +1859,27 @@ class Cadmium : public emu::Chip8EmuHostEx EndColumns(); EndWindowBox(); } + if(IsKeyDown(KEY_ESCAPE)) + volumeClick = std::chrono::steady_clock::time_point{}; + if(volumeClick != std::chrono::steady_clock::time_point{}) { + if (std::chrono::duration_cast(std::chrono::steady_clock::now() - volumeClick).count() < 2) { + Rectangle bounds{430.0, 21.0f, 80.0f, 14.0f}; + DrawRectangleRec({bounds.x-56, bounds.y-2, bounds.width+58, bounds.height+4}, {0,0,0,128}); + GuiSliderBar(bounds, "Volume: ", "", &_volumeSlider, 0.0001f, 1.0f); + if (_volumeSlider != _volume) + SetMasterVolume(_volumeSlider); + if (CheckCollisionPointRec(GetMousePosition(), bounds)) { + volumeClick = std::chrono::steady_clock::now(); + } + } + else { + if (_volumeSlider != _volume) { + _volume = _volumeSlider; + _cfg.volume = _volume; + saveConfig(); + } + } + } EndGui(); } if(_chipEmu->getExecMode() != ExecMode::ePAUSED) { @@ -2291,12 +2289,15 @@ class Cadmium : public emu::Chip8EmuHostEx std::string _screenShotSha1sum; RenderTexture _keyboardOverlay{}; CircularBuffer _audioBuffer; + int64_t _audioGaps{}; bool _shouldClose{false}; bool _showKeyMap{false}; int _screenWidth{}; int _screenHeight{}; RenderTexture _renderTexture{}; AudioStream _audioStream{}; + float _volumeSlider{0.5f}; + float _volume{0.5f}; SMA<60,uint64_t> _ipfAverage; SMA<120,uint32_t> _frameTimeAverage_us; SMA<120,int> _frameDelta; @@ -2311,6 +2312,7 @@ class Cadmium : public emu::Chip8EmuHostEx int _frameBoost{1}; int _memoryOffset{-1}; int _instructionOffset{-1}; + std::atomic_int _audioCallbackAvgFrames{}; //std::string _romName; //std::vector _romImage; diff --git a/src/chip8emuhostex.cpp b/src/chip8emuhostex.cpp index 2f7736f..5c8df14 100644 --- a/src/chip8emuhostex.cpp +++ b/src/chip8emuhostex.cpp @@ -125,7 +125,7 @@ bool Chip8EmuHostEx::loadRom(const char* filename, bool andRun) _editor.setText(""); _editor.setFilename(""); #endif - auto fileData = loadFile(filename); + auto fileData = loadFile(filename, Librarian::MAX_ROM_SIZE); return loadBinary(filename, fileData.data(), fileData.size(), andRun); //memory[0x1FF] = 3; } diff --git a/src/chip8emuhostex.hpp b/src/chip8emuhostex.hpp index c93d149..586c53d 100644 --- a/src/chip8emuhostex.hpp +++ b/src/chip8emuhostex.hpp @@ -89,6 +89,7 @@ class Chip8HeadlessHostEx : public Chip8EmuHostEx bool isKeyDown(uint8_t key) override { return false; } const std::array& getKeyStates() const override { static const std::array keys{}; return keys; } void updateScreen() override {} + void vblank() override {} void updatePalette(const std::array& palette) override {} void updatePalette(const std::vector& palette, size_t offset) override {} }; diff --git a/src/configuration.cpp b/src/configuration.cpp index 3b18dc3..0b68346 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -30,6 +30,7 @@ void to_json(nlohmann::json& j, const CadmiumConfiguration& cc) { j = nlohmann::json{ + {"volume", cc.volume}, {"workingDirectory", cc.workingDirectory}, {"databaseDirectory", cc.databaseDirectory}, {"emuOptions", cc.emuOptions}, @@ -40,6 +41,7 @@ void to_json(nlohmann::json& j, const CadmiumConfiguration& cc) { void from_json(const nlohmann::json& j, CadmiumConfiguration& cc) { j.at("workingDirectory").get_to(cc.workingDirectory); cc.databaseDirectory = j.value("databaseDirectory", ""); + cc.volume = j.value("volume", 0.5f); try { j.at("emuOptions").get_to(cc.emuOptions); } diff --git a/src/configuration.hpp b/src/configuration.hpp index 780f771..aec8398 100644 --- a/src/configuration.hpp +++ b/src/configuration.hpp @@ -33,6 +33,7 @@ struct CadmiumConfiguration { + float volume; std::string workingDirectory; std::string databaseDirectory; emu::Chip8EmulatorOptions emuOptions; diff --git a/src/editor.cpp b/src/editor.cpp index 241c243..414b219 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -271,7 +271,7 @@ void Editor::update() setFocus(); if(_mouseDownInText || IsMouseButtonPressed(0)) { auto clickPos = GetMousePosition(); - auto cx = (clickPos.x - _textArea.x - 6 * COLUMN_WIDTH) / COLUMN_WIDTH; + auto cx = (clickPos.x - _textArea.x - _lineNumberWidth) / COLUMN_WIDTH; auto cy = (clickPos.y - _textArea.y - 4) / LINE_SIZE; if (cx > 0 && cy + _tosLine >= 0) { _cursorX = cx + _losCol; @@ -423,11 +423,14 @@ void Editor::update() } } } + _lineNumberCols = _lines.empty() ? 5 : std::max(3,int(std::log10(_lines.size())) + 3); + _lineNumberWidth = _lineNumberCols * COLUMN_WIDTH; } void Editor::recompile() { try { + auto start = std::chrono::steady_clock::now(); _compiler.reset(); if(_text.empty() || _text.back() != '\n') { auto text = _text + '\n'; @@ -436,6 +439,8 @@ void Editor::recompile() else { _compiler.compile(_filename, _text.data(), _text.data() + _text.size() + 1); } + auto time_ms = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + TraceLog(LOG_INFO, "Recompilation took %dms", (int)time_ms); } catch(std::exception& ex) {} @@ -457,17 +462,18 @@ void Editor::draw(Font& font, Rectangle rect) _textArea = {_totalArea.x, _totalArea.y + _toolArea.height, _totalArea.width, _totalArea.height - _toolArea.height - _messageArea.height}; float ypos = _textArea.y - 4; _visibleLines = uint32_t((_textArea.height - 6) / LINE_SIZE); - _visibleCols = uint32_t(_textArea.width - 6*COLUMN_WIDTH - 6) / COLUMN_WIDTH; + _visibleCols = uint32_t(_textArea.width - _lineNumberWidth - 6) / COLUMN_WIDTH; _scrollPos = {-(float)_losCol * COLUMN_WIDTH, -(float)_tosLine * LINE_SIZE}; gui::SetStyle(DEFAULT, BORDER_WIDTH, 0); gui::BeginScrollPanel(_textArea.height, {0,0,std::max(_textArea.width, (float)(_longestLineSize+8) * COLUMN_WIDTH), (float)std::max((uint32_t)_textArea.height, uint32_t(_lines.size()+1)*LINE_SIZE)}, &_scrollPos); gui::SetStyle(DEFAULT, BORDER_WIDTH, 1); //gui::Space(rect.height -50); - DrawRectangle(5*COLUMN_WIDTH, _textArea.y, 1, _textArea.height, GetColor(0x2f7486ff)); + DrawRectangle(_lineNumberWidth - COLUMN_WIDTH/2, _textArea.y, 1, _textArea.height, GetColor(0x2f7486ff)); + std::string lineNumberFormat = fmt::format("%{}d", _lineNumberCols - 1); while(lineNumber < int(_lines.size()) && ypos < _textArea.y + _textArea.height) { if(lineNumber >= 0) { - DrawTextEx(font, TextFormat("%4d", lineNumber + 1), {_textArea.x, ypos}, 8, 0, LIGHTGRAY); - drawTextLine(font, lineStart(lineNumber), lineEnd(lineNumber), {_textArea.x + 6 * COLUMN_WIDTH, ypos}, _textArea.width - 6 * COLUMN_WIDTH, _losCol); + DrawTextEx(font, TextFormat(lineNumberFormat.c_str(), lineNumber + 1), {_textArea.x, ypos}, 8, 0, LIGHTGRAY); + drawTextLine(font, lineStart(lineNumber), lineEnd(lineNumber), {_textArea.x + _lineNumberWidth, ypos}, _textArea.width - _lineNumberWidth, _losCol); } ++lineNumber; ypos += LINE_SIZE; @@ -478,8 +484,8 @@ void Editor::draw(Font& font, Rectangle rect) if(hasFocus() && _blinkTimer >= BLINK_RATE/2) { auto cx = (_cursorX - _losCol) * COLUMN_WIDTH; auto cy = (_cursorY - _tosLine) * LINE_SIZE + LINE_SIZE - 4; - if(cx >= 0 && cx < _textArea.width - 6*COLUMN_WIDTH - 3 && cy >= 0 && cy + 8 < _textArea.height) - DrawRectangle(_textArea.x + 6*COLUMN_WIDTH + cx, _textArea.y + cy - 2, 2, LINE_SIZE, WHITE); + if(cx >= 0 && cx < _textArea.width - _lineNumberWidth - 3 && cy >= 0 && cy + 8 < _textArea.height) + DrawRectangle(_textArea.x + _lineNumberWidth + cx, _textArea.y + cy - 2, 2, LINE_SIZE, WHITE); } auto handle = verticalScrollHandle(); @@ -918,4 +924,4 @@ void Editor::updateFindResults() } if(_findCurrentResult > _findResults) _findCurrentResult = _findResults; -} \ No newline at end of file +} diff --git a/src/editor.hpp b/src/editor.hpp index a5ab84e..80d773b 100644 --- a/src/editor.hpp +++ b/src/editor.hpp @@ -183,6 +183,8 @@ class Editor int _cursorY{0}; uint32_t _visibleLines{0}; uint32_t _visibleCols{0}; + int _lineNumberWidth{6*COLUMN_WIDTH}; + uint32_t _lineNumberCols{6}; uint32_t _selectionStart{0}; uint32_t _selectionEnd{0}; uint32_t _editId{0}; diff --git a/src/emulation/chip8cores.cpp b/src/emulation/chip8cores.cpp index ee98343..e833f11 100644 --- a/src/emulation/chip8cores.cpp +++ b/src/emulation/chip8cores.cpp @@ -377,12 +377,12 @@ uint8_t Chip8EmulatorFP::getNextMCSample() if(_sampleLoop) pos -= _sampleLength; else - pos = _sampleLength = 0, val = 127; + pos = _sampleLength = 0, val = 128; } _mcSamplePos.store(pos); return val; } - return 127; + return 128; } void Chip8EmulatorFP::on(uint16_t mask, uint16_t opcode, OpcodeHandler handler) @@ -684,8 +684,6 @@ void Chip8EmulatorFP::op02nn(uint16_t opcode) auto g = _memory[address++ & ADDRESS_MASK]; auto b = _memory[address++ & ADDRESS_MASK]; _mcPalette[i + 1] = be32((r << 24) | (g << 16) | (b << 8) | a); - if(i == 250) - std::cout << "i: " << i << " - " << std::endl; cols.push_back(be32((r << 24) | (g << 16) | (b << 8) | a)); } _host.updatePalette(cols, 1); @@ -1231,9 +1229,12 @@ void Chip8EmulatorFP::opF000(uint16_t opcode) void Chip8EmulatorFP::opF002(uint16_t opcode) { + uint8_t anyBit = 0; for(int i = 0; i < 16; ++i) { _xoAudioPattern[i] = _memory[(_rI + i) & ADDRESS_MASK]; + anyBit |= _xoAudioPattern[i]; } + _xoSilencePattern = anyBit != 0; } void Chip8EmulatorFP::opFx01(uint16_t opcode) @@ -1395,4 +1396,36 @@ void Chip8EmulatorFP::opFxFB_c8x(uint16_t opcode) // still nop } +void Chip8EmulatorFP::renderAudio(int16_t* samples, size_t frames, int sampleFrequency) +{ + if(_isMegaChipMode && _sampleLength) { + while(frames--) { + *samples++ = ((int16_t)getNextMCSample() - 128) * 256; + } + } + else if(_rST) { + if (_options.optXOChipSound) { + auto step = 4000 * std::pow(2.0f, (float(_xoPitch) - 64) / 48.0f) / 128 / sampleFrequency; + for (int i = 0; i < frames; ++i) { + auto pos = int(std::clamp(_wavePhase * 128.0f, 0.0f, 127.0f)); + *samples++ = _xoAudioPattern[pos >> 3] & (1 << (7 - (pos & 7))) ? 16384 : -16384; + _wavePhase = std::fmod(_wavePhase + step, 1.0f); + } + } + else { + auto audioFrequency = _options.behaviorBase == Chip8EmulatorOptions::eCHIP8X ? 27535.0f / ((unsigned)_vp595Frequency + 1) : 1400.0f; + const float step = audioFrequency / sampleFrequency; + for (int i = 0; i < frames; ++i) { + *samples++ = (_wavePhase > 0.5f) ? 16384 : -16384; + _wavePhase = std::fmod(_wavePhase + step, 1.0f); + } + } + } + else { + // Default is silence + _wavePhase = 0; + IChip8Emulator::renderAudio(samples, frames, sampleFrequency); + } +} + } diff --git a/src/emulation/chip8cores.hpp b/src/emulation/chip8cores.hpp index 2ca2bb2..2e03f61 100644 --- a/src/emulation/chip8cores.hpp +++ b/src/emulation/chip8cores.hpp @@ -798,10 +798,13 @@ class Chip8EmulatorFP : public Chip8EmulatorBase return (bool)collision; } - float getAudioFrequency() const override + /*float getAudioFrequency() const override { return _options.behaviorBase == Chip8EmulatorOptions::eCHIP8X ? 27535.0f / ((unsigned)_vp595Frequency + 1) : 1400.0f; - } + }*/ + + void renderAudio(int16_t* samples, size_t frames, int sampleFrequency) override; + private: inline uint16_t readWord(uint32_t addr) const { @@ -825,6 +828,7 @@ class Chip8HeadlessHost : public Chip8EmulatorHost bool isKeyDown(uint8_t key) override { return false; } const std::array& getKeyStates() const override { static const std::array keys{}; return keys; } void updateScreen() override {} + void vblank() override {} void updatePalette(const std::array& palette) override {} void updatePalette(const std::vector& palette, size_t offset) override {} Chip8EmulatorOptions options; diff --git a/src/emulation/chip8dream.cpp b/src/emulation/chip8dream.cpp index 7b28914..8ffc104 100644 --- a/src/emulation/chip8dream.cpp +++ b/src/emulation/chip8dream.cpp @@ -280,6 +280,7 @@ int Chip8Dream::executeVDG() _impl->_pia.pinCB1(true); _impl->_pia.pinCB1(false); _impl->_keyMatrix.updateKeys(_host.getKeyStates()); + _host.vblank(); } lastFC = fc; return fc; @@ -416,7 +417,7 @@ uint8_t Chip8Dream::soundTimer() const return (_impl->_pia.portB() & 64) ? _state.st : 0; } -float Chip8Dream::getAudioPhase() const +/*float Chip8Dream::getAudioPhase() const { return _impl->_wavePhase; } @@ -424,6 +425,21 @@ float Chip8Dream::getAudioPhase() const void Chip8Dream::setAudioPhase(float phase) { _impl->_wavePhase = phase; +}*/ + +void Chip8Dream::renderAudio(int16_t* samples, size_t frames, int sampleFrequency) +{ + if(_state.st) { + const float step = 1000.0f / sampleFrequency; + for (int i = 0; i < frames; ++i) { + *samples++ = (_impl->_wavePhase > 0.5f) ? 16384 : -16384; + _impl->_wavePhase = std::fmod(_impl->_wavePhase + step, 1.0f); + } + } + else { + // Default is silence + IChip8Emulator::renderAudio(samples, frames, sampleFrequency); + } } uint16_t Chip8Dream::getCurrentScreenWidth() const diff --git a/src/emulation/chip8dream.hpp b/src/emulation/chip8dream.hpp index b5d51dc..a62ba9a 100644 --- a/src/emulation/chip8dream.hpp +++ b/src/emulation/chip8dream.hpp @@ -61,8 +61,9 @@ class Chip8Dream : public Chip8RealCoreBase, public M6800Bus<> const VideoType* getScreen() const override; uint8_t soundTimer() const override; - float getAudioPhase() const override; - void setAudioPhase(float phase) override; + //float getAudioPhase() const override; + //void setAudioPhase(float phase) override; + void renderAudio(int16_t* samples, size_t frames, int sampleFrequency) override; // M6800-Bus uint8_t readByte(uint16_t addr) const override; diff --git a/src/emulation/chip8emulatorbase.cpp b/src/emulation/chip8emulatorbase.cpp index 71a5cd0..56ba680 100644 --- a/src/emulation/chip8emulatorbase.cpp +++ b/src/emulation/chip8emulatorbase.cpp @@ -333,6 +333,7 @@ void Chip8EmulatorBase::reset() std::memcpy(_memory.data() + 16*5, bigFont, bigSize); std::memcpy(_xxoPalette.data(), defaultPalette, 16); std::memset(_xoAudioPattern.data(), 0, 16); + _xoSilencePattern = true; _xoPitch = 64; _planes = 0xff; clearScreen(); @@ -399,7 +400,7 @@ void Chip8EmulatorBase::tick(int instructionsPerFrame) do { executeInstructions(487); } - while(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count() < 14); + while(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count() < 12); } else { executeInstructions(instructionsPerFrame); diff --git a/src/emulation/chip8emulatorbase.hpp b/src/emulation/chip8emulatorbase.hpp index cf313a0..8410365 100644 --- a/src/emulation/chip8emulatorbase.hpp +++ b/src/emulation/chip8emulatorbase.hpp @@ -71,8 +71,8 @@ class Chip8EmulatorBase : public Chip8OpcodeDisassembler std::memcpy(_stack.data(), iother->getStackElements(), sizeof(uint16_t) * iother->stackSize()); _rSP = iother->getSP(); _rDT = iother->delayTimer(); - _rST.store(iother->soundTimer()); - _wavePhase.store(iother->getAudioPhase()); + _rST = iother->soundTimer(); + _wavePhase = 0; //iother->getAudioPhase(); for (int i = 0; i < 16; ++i) { _rV[i] = iother->getV(i); } @@ -91,6 +91,7 @@ class Chip8EmulatorBase : public Chip8OpcodeDisassembler _screenRGBA = other->_screenRGBA; _xoAudioPattern = other->_xoAudioPattern; _xoPitch.store(other->_xoPitch); + _xoSilencePattern = other->_xoSilencePattern; _sampleStep.store(other->_sampleStep); _sampleStart.store(other->_sampleStart); _sampleLength.store(other->_sampleLength); @@ -246,8 +247,11 @@ class Chip8EmulatorBase : public Chip8OpcodeDisassembler --_rST; if (!_rST) _wavePhase = 0; - if(_screenNeedsUpdate) + if(_screenNeedsUpdate) { _host.updateScreen(); + _screenNeedsUpdate = false; + } + _host.vblank(); } } @@ -264,8 +268,8 @@ class Chip8EmulatorBase : public Chip8OpcodeDisassembler const VideoRGBAType* getScreenRGBA() const override { return _isMegaChipMode ? &_screenRGBA : nullptr; } void setPalette(std::array& palette) override { _screen.setPalette(palette); } - float getAudioPhase() const override { return _wavePhase; } - void setAudioPhase(float phase) override { _wavePhase = phase; } + //float getAudioPhase() const override { return _wavePhase; } + //void setAudioPhase(float phase) override { _wavePhase = phase; } const uint8_t* getXOAudioPattern() const override { return _xoAudioPattern.data(); } uint8_t getXOPitch() const override { return _xoPitch; } @@ -298,11 +302,12 @@ class Chip8EmulatorBase : public Chip8OpcodeDisassembler std::array _stack{}; uint8_t _rSP{}; uint8_t _rDT{}; - std::atomic_uint8_t _rST{}; - std::atomic _wavePhase{0}; + uint8_t _rST{}; + float _wavePhase{0}; VideoScreen _screen; VideoScreen _screenRGBA{}; std::array _xoAudioPattern{}; + bool _xoSilencePattern{true}; std::atomic_uint8_t _xoPitch{}; std::atomic _sampleStep{0}; std::atomic_uint32_t _sampleStart{0}; diff --git a/src/emulation/chip8emulatorhost.hpp b/src/emulation/chip8emulatorhost.hpp index b9d0588..6a9482f 100644 --- a/src/emulation/chip8emulatorhost.hpp +++ b/src/emulation/chip8emulatorhost.hpp @@ -42,6 +42,7 @@ class Chip8EmulatorHost virtual bool isKeyUp(uint8_t key) { return !isKeyDown(key); } virtual const std::array& getKeyStates() const = 0; virtual void updateScreen() = 0; + virtual void vblank() = 0; virtual void updatePalette(const std::array& palette) = 0; virtual void updatePalette(const std::vector& palette, size_t offset) = 0; virtual void preClear() {} diff --git a/src/emulation/chip8strict.hpp b/src/emulation/chip8strict.hpp index 3f0ced7..b8606f7 100644 --- a/src/emulation/chip8strict.hpp +++ b/src/emulation/chip8strict.hpp @@ -461,6 +461,21 @@ class Chip8StrictEmulator : public Chip8EmulatorBase _screenNeedsUpdate = true; return collision; } + + void renderAudio(int16_t* samples, size_t frames, int sampleFrequency) override + { + if(_rST) { + const float step = 1000.0f / 44100; + for (int i = 0; i < frames; ++i) { + *samples++ = (_wavePhase > 0.5f) ? 16384 : -16384; + _wavePhase = std::fmod(_wavePhase + step, 1.0f); + } + } + else { + // Default is silence + IChip8Emulator::renderAudio(samples, frames, sampleFrequency); + } + } protected: void wait(int instructionCycles = 0) { @@ -500,8 +515,10 @@ class Chip8StrictEmulator : public Chip8EmulatorBase --_rST; if (!_rST) _wavePhase = 0; - if(_screenNeedsUpdate) + if(_screenNeedsUpdate) { _host.updateScreen(); + } + _host.vblank(); } inline void addCycles(emu::cycles_t cycles) { diff --git a/src/emulation/chip8vip.cpp b/src/emulation/chip8vip.cpp index b3f45c8..036fb54 100644 --- a/src/emulation/chip8vip.cpp +++ b/src/emulation/chip8vip.cpp @@ -39,7 +39,7 @@ class Chip8VIP::Private { uint16_t _initialChip8SP{0}; uint16_t _colorRamMask{0xff}; uint16_t _colorRamMaskLores{0xe7}; - std::atomic _wavePhase{0}; + float _wavePhase{0}; std::array _ram{}; std::array _colorRam{}; std::array _rom{}; @@ -375,6 +375,7 @@ void Chip8VIP::reset() _impl->_lastOpcode = 0; _impl->_initialChip8SP = 0; _impl->_frequencyLatch = 0x80; + _impl->_wavePhase = 0; _cpuState = eNORMAL; setExecMode(eRUNNING); while(_impl->_cpu.getExecMode() == eRUNNING && (!executeCdp1802() || getPC() != _options.startAddress)); // fast-forward to fetch/decode loop @@ -441,7 +442,9 @@ bool Chip8VIP::executeCdp1802() { static int lastFC = 0; static int endlessLoops = 0; - auto fc = _impl->_video.executeStep(); + auto [fc,vsync] = _impl->_video.executeStep(); + if(vsync) + _host.vblank(); if(_options.optTraceLog && _impl->_cpu.getCpuState() != Cdp1802::eIDLE) Logger::log(Logger::eBACKEND_EMU, _impl->_cpu.getCycles(), {_frames, fc}, fmt::format("{:24} ; {}", _impl->_cpu.disassembleInstructionWithBytes(-1, nullptr), _impl->_cpu.dumpStateLine()).c_str()); if(_impl->_cpu.PC() == Private::FETCH_LOOP_ENTRY) { @@ -589,6 +592,7 @@ int64_t Chip8VIP::frames() const return _impl->_video.frames(); } +/* float Chip8VIP::getAudioPhase() const { return _impl->_wavePhase; @@ -603,6 +607,23 @@ float Chip8VIP::getAudioFrequency() const { return _impl->_video.getType() == Cdp186x::eVP590 ? 27535.0f / ((unsigned)_impl->_frequencyLatch + 1) : 1400.0f; } +*/ +void Chip8VIP::renderAudio(int16_t* samples, size_t frames, int sampleFrequency) +{ + if(_impl->_cpu.getQ()) { + auto audioFrequency = _impl->_video.getType() == Cdp186x::eVP590 ? 27535.0f / ((unsigned)_impl->_frequencyLatch + 1) : 1400.0f; + const float step = audioFrequency / sampleFrequency; + for (int i = 0; i < frames; ++i) { + *samples++ = (_impl->_wavePhase > 0.5f) ? 16384 : -16384; + _impl->_wavePhase = std::fmod(_impl->_wavePhase + step, 1.0f); + } + } + else { + // Default is silence + _impl->_wavePhase = 0; + IChip8Emulator::renderAudio(samples, frames, sampleFrequency); + } +} uint16_t Chip8VIP::getCurrentScreenWidth() const { diff --git a/src/emulation/chip8vip.hpp b/src/emulation/chip8vip.hpp index df57209..95d28ff 100644 --- a/src/emulation/chip8vip.hpp +++ b/src/emulation/chip8vip.hpp @@ -68,9 +68,10 @@ class Chip8VIP : public Chip8RealCoreBase, public Cdp1802Bus bool isDisplayEnabled() const override; - float getAudioPhase() const override; - void setAudioPhase(float phase) override; - float getAudioFrequency() const override; + //float getAudioPhase() const override; + //void setAudioPhase(float phase) override; + //float getAudioFrequency() const override; + void renderAudio(int16_t* samples, size_t frames, int sampleFrequency) override; // CDP1802-Bus uint8_t readByte(uint16_t addr) const override; diff --git a/src/emulation/hardware/cdp186x.cpp b/src/emulation/hardware/cdp186x.cpp index e68bbf9..8f43f7e 100644 --- a/src/emulation/hardware/cdp186x.cpp +++ b/src/emulation/hardware/cdp186x.cpp @@ -89,7 +89,7 @@ const Cdp186x::VideoType& Cdp186x::getScreen() const return _screen; } -int Cdp186x::executeStep() +std::pair Cdp186x::executeStep() { auto fc = (_cpu.getCycles() >> 3) % 3668; bool vsync = false; @@ -106,7 +106,7 @@ int Cdp186x::executeStep() Logger::log(Logger::eBACKEND_EMU, _cpu.getCycles(), {_frameCounter, _frameCycle}, fmt::format("{:24} ; {}", "--- HSYNC ---", _cpu.dumpStateLine()).c_str()); } if(_frameCycle > VIDEO_FIRST_INVISIBLE_LINE * 14 || _frameCycle < (VIDEO_FIRST_VISIBLE_LINE - 2) * 14) - return _frameCycle; + return {_frameCycle,vsync}; if(_frameCycle < VIDEO_FIRST_VISIBLE_LINE * 14 && _frameCycle >= (VIDEO_FIRST_VISIBLE_LINE - 2) * 14 + 2 && _cpu.getIE()) { _displayEnabledLatch = _displayEnabled; if(_displayEnabled) { @@ -138,7 +138,7 @@ int Cdp186x::executeStep() } } } - return (_cpu.getCycles() >> 3) % 3668; + return {(_cpu.getCycles() >> 3) % 3668, vsync}; } void Cdp186x::incrementBackground() diff --git a/src/emulation/hardware/cdp186x.hpp b/src/emulation/hardware/cdp186x.hpp index c04b550..22eacb8 100644 --- a/src/emulation/hardware/cdp186x.hpp +++ b/src/emulation/hardware/cdp186x.hpp @@ -30,6 +30,7 @@ #include #include +#include namespace emu { @@ -48,7 +49,7 @@ class Cdp186x void reset(); bool getNEFX() const; Type getType() const { return _type; } - int executeStep(); + std::pair executeStep(); void enableDisplay(); void disableDisplay(); bool isDisplayEnabled() const { return _displayEnabled; } diff --git a/src/emulation/ichip8.hpp b/src/emulation/ichip8.hpp index 644f06b..dd533d7 100644 --- a/src/emulation/ichip8.hpp +++ b/src/emulation/ichip8.hpp @@ -119,9 +119,10 @@ class IChip8Emulator : public GenericCpu virtual void setPalette(std::array& palette) {} // optional interfaces for audio and/or modern CHIP-8 variant properties - virtual float getAudioPhase() const { return 0.0f; } - virtual void setAudioPhase(float) { } - virtual float getAudioFrequency() const { return 1400.0f; } + //virtual float getAudioPhase() const { return 0.0f; } + //virtual void setAudioPhase(float) { } + //virtual float getAudioFrequency() const { return 1400.0f; } + virtual void renderAudio(int16_t* samples, size_t frames, int sampleFrequency) { while (frames--) *samples++ = 0; } virtual const uint8_t* getXOAudioPattern() const { return nullptr; } virtual uint8_t getXOPitch() const { return 0; } virtual uint8_t getNextMCSample() { return 0; } diff --git a/src/emulation/octocartridge.cpp b/src/emulation/octocartridge.cpp index bd21036..59f6d38 100644 --- a/src/emulation/octocartridge.cpp +++ b/src/emulation/octocartridge.cpp @@ -64,7 +64,7 @@ static Chip8EmulatorOptions optionsFromOctoOptions(const octo_options& octo) OctoCartridge emu::OctoCartridge::load(std::string filename) { - auto data = loadFile(filename); + auto data = loadFile(filename, 65536); octo_options options; octo_default_options(&options); octo_str source; diff --git a/src/emulation/utility.hpp b/src/emulation/utility.hpp index 9237a8b..6fff92f 100644 --- a/src/emulation/utility.hpp +++ b/src/emulation/utility.hpp @@ -101,12 +101,14 @@ inline std::vector split(const std::string& s, char delimiter) return result; } -inline std::vector loadFile(const std::string& file) +inline std::vector loadFile(const std::string& file, size_t maxSize) { std::ifstream is(file, std::ios::binary | std::ios::ate); auto size = is.tellg(); is.seekg(0, std::ios::beg); - + if(size > maxSize) { + return {}; + } std::vector buffer(size); if (is.read((char*)buffer.data(), size)) { return buffer; diff --git a/src/librarian.cpp b/src/librarian.cpp index 1e6a664..67acd3c 100644 --- a/src/librarian.cpp +++ b/src/librarian.cpp @@ -76,7 +76,7 @@ static KnownRomInfo g_knownRoms[] = { //{"044021b046cf207c0b555ea884d61a726f7a3c22", {emu::Chip8EmulatorOptions::eSCHIP11}}, {"044021b046cf207c0b555ea884d61a726f7a3c22", emu::chip8::Variant::SCHIPC, "Ded-Lok (parityb1t, 2015)", nullptr, nullptr}, {"048659b97e0cf9506eba85ef7baaf21ada22c6f2", emu::chip8::Variant::CHIP_8, "Astro Dodge (Revival Studios)", nullptr, nullptr}, - {"04e18ff4ae42e3056c502e0c99d4740ecea65966", emu::chip8::Variant::XO_CHIP, "Nyan Cat (Kouzerumatsu, 2022)", nullptr, nullptr}, + {"04e18ff4ae42e3056c502e0c99d4740ecea65966", emu::chip8::Variant::XO_CHIP, "Nyan Cat 2 (Kouzerumatsu, 2022)", R"({"instructionsPerFrame": 10000})", nullptr}, {"050f07a54371da79f924dd0227b89d07b4f2aed0", emu::chip8::Variant::CHIP_8, "Hidden (David Winter, 1996)", nullptr, nullptr}, {"0572f188fc25ccda14b0c306c4156fe4b1d21ae1", emu::chip8::Variant::GENERIC_CHIP_8, "4-Flags (Timendus, 2023-04-12)", nullptr, "@GH/Timendus/chip8-test-suite/v4.0/bin/4-flags.ch8"}, {"064492173cf4ccac3cce8fe307fc164b397013b9", emu::chip8::Variant::CHIP_8, "Division Test (Sergey Naydenov, 2010)", nullptr, nullptr}, @@ -520,6 +520,7 @@ static KnownRomInfo g_knownRoms[] = { {"a9bf29597674c39b4e11d964b352b1e52c4ebb2f", emu::chip8::Variant::SCHIPC, "Line Demo (unknown aauthor)", nullptr, nullptr}, {"a9d3c975a5e733646a04f6e61deebcd0ad50f700", emu::chip8::Variant::CHIP_8, "Outlaw (JohnEarnest, 2014-07-17)", R"({"instructionsPerFrame": 15, "optWrapSprites": true, "advanced": {"col0": "#664400", "col1": "#AA4400", "buzzColor": "#FF7F50", "quietColor": "#000000"}})", nullptr}, {"aa4f1a282bd64a2364102abf5737a4205365a2b4", emu::chip8::Variant::CHIP_8, "Space Flight", nullptr, nullptr}, // Space Flight.ch8 + {"aae22735122c1e15df1dde4ef19e7b4968d88f6f", emu::chip8::Variant::XO_CHIP, "Nyan Cat 1 (Kouzerumatsu, 2022)", R"({"instructionsPerFrame": 100000})", nullptr}, {"ab36ced6e34affacd57b2874ede3f95b669a424c", emu::chip8::Variant::XO_CHIP, "Jub8 Song 1 (your name here, 2016-08-31)", R"({"instructionsPerFrame": 1000, "advanced": {"col1": "#000000", "col2": "#FDFFD5", "col3": "#BA5A1A", "col0": "#353C41", "buzzColor": "#353C41", "quietColor": "#353C41", "screenRotation": 0}})", nullptr}, {"ab5cbf267d74c168e174041b9594ae856cbd671d", emu::chip8::Variant::CHIP_8, "Chipwar (JohnEarnest, 2014-06-06)", R"({"instructionsPerFrame": 15, "advanced": {"col0": "#6699FF", "col1": "#000066", "buzzColor": "#FFAA00", "quietColor": "#000000"}})", nullptr}, {"abfce04ddd0f72838dd887f3db3106066fd675b3", emu::chip8::Variant::SCHIP_1_1, "Squad (JohnEarnest, 2020-10-25)", R"({"instructionsPerFrame": 500, "advanced": {"col1": "#FFAF00", "col2": "#FD8100", "col3": "#FD8100", "col0": "#663300", "buzzColor": "#F9FFB3", "quietColor": "#000000", "screenRotation": 0, "fontStyle": "octo"}})", nullptr}, diff --git a/src/librarian.hpp b/src/librarian.hpp index d162ce3..b054cb0 100644 --- a/src/librarian.hpp +++ b/src/librarian.hpp @@ -36,6 +36,7 @@ class Librarian { public: + static constexpr size_t MAX_ROM_SIZE = 16 * 1024 * 1024 - 512; struct Info { enum Type { eDIRECTORY, eUNKNOWN_FILE, eROM_FILE, eOCTO_SOURCE };