web-a2e 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ay8910.hpp - AY-3-8910 sound chip emulation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <array>
|
|
12
|
+
#include <vector>
|
|
13
|
+
#include <functional>
|
|
14
|
+
|
|
15
|
+
namespace a2e {
|
|
16
|
+
|
|
17
|
+
// AY-3-8910 Programmable Sound Generator emulation
|
|
18
|
+
// Used in the Mockingboard sound card (2 chips per card)
|
|
19
|
+
class AY8910 {
|
|
20
|
+
public:
|
|
21
|
+
static constexpr int NUM_CHANNELS = 3;
|
|
22
|
+
// Mockingboard uses the Apple II CPU clock divided down
|
|
23
|
+
// Apple IIe runs at 1.023 MHz (actually 1.0227272... MHz from 14.31818 MHz / 14)
|
|
24
|
+
static constexpr int PSG_CLOCK = 1023000; // ~1.023 MHz for accuracy
|
|
25
|
+
|
|
26
|
+
using CycleCallback = std::function<uint64_t()>;
|
|
27
|
+
|
|
28
|
+
AY8910();
|
|
29
|
+
|
|
30
|
+
// Set PSG ID for debug logging (1 or 2)
|
|
31
|
+
void setPsgId(int id);
|
|
32
|
+
|
|
33
|
+
// Enable/disable console debug logging
|
|
34
|
+
static void setDebugLogging(bool enabled);
|
|
35
|
+
|
|
36
|
+
// Set callback to get current CPU cycle (for timestamping register writes)
|
|
37
|
+
void setCycleCallback(CycleCallback callback) { cycleCallback_ = std::move(callback); }
|
|
38
|
+
|
|
39
|
+
// Register access via 6522 VIA
|
|
40
|
+
void setRegisterAddress(uint8_t address);
|
|
41
|
+
void writeRegister(uint8_t value);
|
|
42
|
+
uint8_t readRegister() const;
|
|
43
|
+
|
|
44
|
+
// Audio generation - pass cycle range for proper timing
|
|
45
|
+
void generateSamples(float* buffer, int count, int sampleRate, uint64_t startCycle, uint64_t endCycle);
|
|
46
|
+
// Legacy version without timing (uses immediate register values)
|
|
47
|
+
void generateSamples(float* buffer, int count, int sampleRate);
|
|
48
|
+
void generateChannelSamples(float* buffer, int count, int sampleRate, int channel);
|
|
49
|
+
|
|
50
|
+
// Generate a single audio sample at 48kHz using current register state.
|
|
51
|
+
// Advances PSG internal state by the appropriate number of ticks.
|
|
52
|
+
// Used for per-instruction incremental audio generation.
|
|
53
|
+
float generateSingleSample();
|
|
54
|
+
|
|
55
|
+
// Channel muting (for debug/mixing purposes)
|
|
56
|
+
void setChannelMute(int channel, bool muted);
|
|
57
|
+
bool isChannelMuted(int channel) const;
|
|
58
|
+
|
|
59
|
+
// Reset
|
|
60
|
+
void reset();
|
|
61
|
+
|
|
62
|
+
// State access for debugging
|
|
63
|
+
uint8_t getRegister(int reg) const {
|
|
64
|
+
return (reg >= 0 && reg < 16) ? registers_[reg] : 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Debug: track writes
|
|
68
|
+
uint32_t getWriteCount() const { return writeCount_; }
|
|
69
|
+
uint8_t getLastWriteReg() const { return lastWriteReg_; }
|
|
70
|
+
uint8_t getLastWriteVal() const { return lastWriteVal_; }
|
|
71
|
+
uint8_t getCurrentRegister() const { return currentRegister_; }
|
|
72
|
+
|
|
73
|
+
// State serialization
|
|
74
|
+
size_t exportState(uint8_t* buffer) const;
|
|
75
|
+
void importState(const uint8_t* buffer);
|
|
76
|
+
static constexpr size_t STATE_SIZE = 48; // Expanded to include noise/envelope counters
|
|
77
|
+
|
|
78
|
+
private:
|
|
79
|
+
// Registers
|
|
80
|
+
static constexpr int REG_TONE_A_FINE = 0;
|
|
81
|
+
static constexpr int REG_TONE_A_COARSE = 1;
|
|
82
|
+
static constexpr int REG_TONE_B_FINE = 2;
|
|
83
|
+
static constexpr int REG_TONE_B_COARSE = 3;
|
|
84
|
+
static constexpr int REG_TONE_C_FINE = 4;
|
|
85
|
+
static constexpr int REG_TONE_C_COARSE = 5;
|
|
86
|
+
static constexpr int REG_NOISE_PERIOD = 6;
|
|
87
|
+
static constexpr int REG_MIXER = 7;
|
|
88
|
+
static constexpr int REG_AMP_A = 8;
|
|
89
|
+
static constexpr int REG_AMP_B = 9;
|
|
90
|
+
static constexpr int REG_AMP_C = 10;
|
|
91
|
+
static constexpr int REG_ENV_FINE = 11;
|
|
92
|
+
static constexpr int REG_ENV_COARSE = 12;
|
|
93
|
+
static constexpr int REG_ENV_SHAPE = 13;
|
|
94
|
+
static constexpr int REG_IO_PORT_A = 14;
|
|
95
|
+
static constexpr int REG_IO_PORT_B = 15;
|
|
96
|
+
|
|
97
|
+
// Register array
|
|
98
|
+
std::array<uint8_t, 16> registers_{};
|
|
99
|
+
uint8_t currentRegister_ = 0;
|
|
100
|
+
|
|
101
|
+
// Tone generator state (3 channels)
|
|
102
|
+
std::array<uint32_t, 3> toneCounters_{};
|
|
103
|
+
std::array<bool, 3> toneOutput_{};
|
|
104
|
+
|
|
105
|
+
// Channel mute state (for debug/visualization)
|
|
106
|
+
std::array<bool, 3> channelMuted_{};
|
|
107
|
+
|
|
108
|
+
// Noise generator state
|
|
109
|
+
uint32_t noiseCounter_ = 0;
|
|
110
|
+
uint32_t noiseShiftReg_ = 1; // 17-bit LFSR, must not be 0
|
|
111
|
+
bool noiseToggle_ = false; // Legacy (kept for state serialization compat)
|
|
112
|
+
|
|
113
|
+
// Envelope generator state
|
|
114
|
+
uint32_t envCounter_ = 0;
|
|
115
|
+
uint8_t envVolume_ = 0;
|
|
116
|
+
bool envHolding_ = false;
|
|
117
|
+
bool envContinue_ = false; // Bit 3: Continue after first cycle
|
|
118
|
+
bool envAttack_ = false; // Bit 2: Attack direction (1=up, 0=down)
|
|
119
|
+
bool envAlternate_ = false; // Bit 1: Alternate direction each cycle
|
|
120
|
+
bool envHold_ = false; // Bit 0: Hold final value
|
|
121
|
+
|
|
122
|
+
// Fractional accumulator for sample rate conversion
|
|
123
|
+
double phaseAccumulator_ = 0.0;
|
|
124
|
+
|
|
125
|
+
// Volume table (4-bit to amplitude)
|
|
126
|
+
static const float volumeTable_[16];
|
|
127
|
+
|
|
128
|
+
// Debug counters
|
|
129
|
+
uint32_t writeCount_ = 0;
|
|
130
|
+
uint8_t lastWriteReg_ = 0;
|
|
131
|
+
uint8_t lastWriteVal_ = 0;
|
|
132
|
+
int psgId_ = 1; // PSG identifier for logging
|
|
133
|
+
|
|
134
|
+
// Timestamped register writes for accurate sample generation
|
|
135
|
+
struct RegisterWrite {
|
|
136
|
+
uint64_t cycle;
|
|
137
|
+
uint8_t reg;
|
|
138
|
+
uint8_t value;
|
|
139
|
+
};
|
|
140
|
+
std::vector<RegisterWrite> pendingWrites_;
|
|
141
|
+
CycleCallback cycleCallback_;
|
|
142
|
+
|
|
143
|
+
// Apply a register write (internal, doesn't timestamp)
|
|
144
|
+
void applyRegisterWrite(uint8_t reg, uint8_t value);
|
|
145
|
+
|
|
146
|
+
// Helper methods
|
|
147
|
+
uint16_t getTonePeriod(int channel) const;
|
|
148
|
+
uint8_t getNoisePeriod() const;
|
|
149
|
+
uint16_t getEnvPeriod() const;
|
|
150
|
+
void updateToneGenerator(int channel);
|
|
151
|
+
void updateNoiseGenerator();
|
|
152
|
+
void updateEnvelopeGenerator();
|
|
153
|
+
void handleEnvelopeCycleEnd();
|
|
154
|
+
float getChannelOutput(int channel) const;
|
|
155
|
+
// Compute raw mixer output (sum of all unmuted channels, normalized)
|
|
156
|
+
float computeMixerOutput() const;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
} // namespace a2e
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* via6522.cpp - VIA 6522 timer chip emulation implementation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "via6522.hpp"
|
|
9
|
+
#include "ay8910.hpp"
|
|
10
|
+
#ifdef __EMSCRIPTEN__
|
|
11
|
+
#include <emscripten.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
namespace a2e {
|
|
15
|
+
|
|
16
|
+
// Debug logging flag
|
|
17
|
+
static bool viaDebugLogging_ = false;
|
|
18
|
+
|
|
19
|
+
void VIA6522::setDebugLogging(bool enabled) {
|
|
20
|
+
viaDebugLogging_ = enabled;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void VIA6522::setViaId(int id) {
|
|
24
|
+
viaId_ = id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
VIA6522::VIA6522() {
|
|
28
|
+
reset();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
void VIA6522::reset() {
|
|
32
|
+
ora_ = 0;
|
|
33
|
+
orb_ = 0;
|
|
34
|
+
ddra_ = 0;
|
|
35
|
+
ddrb_ = 0;
|
|
36
|
+
ira_ = 0xFF; // Floating high
|
|
37
|
+
irb_ = 0xFF;
|
|
38
|
+
|
|
39
|
+
t1Counter_ = 0xFFFF;
|
|
40
|
+
t1Latch_ = 0xFFFF;
|
|
41
|
+
t1Running_ = false;
|
|
42
|
+
t1Fired_ = false;
|
|
43
|
+
|
|
44
|
+
t2Counter_ = 0xFFFF;
|
|
45
|
+
t2LatchLow_ = 0xFF;
|
|
46
|
+
t2Running_ = false;
|
|
47
|
+
t2Fired_ = false;
|
|
48
|
+
|
|
49
|
+
sr_ = 0;
|
|
50
|
+
acr_ = 0;
|
|
51
|
+
pcr_ = 0;
|
|
52
|
+
ifr_ = 0;
|
|
53
|
+
ier_ = 0;
|
|
54
|
+
|
|
55
|
+
prevPsgControl_ = 0;
|
|
56
|
+
psgAddressLatched_ = false;
|
|
57
|
+
psgState_ = PSG_INACTIVE;
|
|
58
|
+
busDriven_ = false;
|
|
59
|
+
prevIrqActive_ = false;
|
|
60
|
+
t1IrqDelay_ = 0;
|
|
61
|
+
t2IrqDelay_ = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
uint8_t VIA6522::read(uint8_t reg) {
|
|
65
|
+
reg &= 0x0F;
|
|
66
|
+
|
|
67
|
+
switch (reg) {
|
|
68
|
+
case REG_ORB:
|
|
69
|
+
// Reading ORB clears CB1/CB2 interrupt flags
|
|
70
|
+
ifr_ &= ~(IRQ_CB1 | IRQ_CB2);
|
|
71
|
+
checkIRQ();
|
|
72
|
+
// Return output bits where direction is output, input where direction is input
|
|
73
|
+
return (orb_ & ddrb_) | (irb_ & ~ddrb_);
|
|
74
|
+
|
|
75
|
+
case REG_ORA:
|
|
76
|
+
case REG_ORA_NH:
|
|
77
|
+
// Reading ORA clears CA1/CA2 interrupt flags (unless ORA_NH)
|
|
78
|
+
if (reg == REG_ORA) {
|
|
79
|
+
ifr_ &= ~(IRQ_CA1 | IRQ_CA2);
|
|
80
|
+
checkIRQ();
|
|
81
|
+
}
|
|
82
|
+
{
|
|
83
|
+
// Input pins: driven by PSG if in READ mode (use ira_), else float high
|
|
84
|
+
uint8_t inputBits = busDriven_ ? ira_ : 0xFF;
|
|
85
|
+
return (ora_ & ddra_) | (inputBits & ~ddra_);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case REG_DDRB:
|
|
89
|
+
return ddrb_;
|
|
90
|
+
|
|
91
|
+
case REG_DDRA:
|
|
92
|
+
return ddra_;
|
|
93
|
+
|
|
94
|
+
case REG_T1CL:
|
|
95
|
+
// Reading T1CL clears T1 interrupt flag
|
|
96
|
+
ifr_ &= ~IRQ_T1;
|
|
97
|
+
checkIRQ();
|
|
98
|
+
return t1Counter_ & 0xFF;
|
|
99
|
+
|
|
100
|
+
case REG_T1CH:
|
|
101
|
+
return (t1Counter_ >> 8) & 0xFF;
|
|
102
|
+
|
|
103
|
+
case REG_T1LL:
|
|
104
|
+
return t1Latch_ & 0xFF;
|
|
105
|
+
|
|
106
|
+
case REG_T1LH:
|
|
107
|
+
return (t1Latch_ >> 8) & 0xFF;
|
|
108
|
+
|
|
109
|
+
case REG_T2CL:
|
|
110
|
+
// Reading T2CL clears T2 interrupt flag
|
|
111
|
+
ifr_ &= ~IRQ_T2;
|
|
112
|
+
checkIRQ();
|
|
113
|
+
return t2Counter_ & 0xFF;
|
|
114
|
+
|
|
115
|
+
case REG_T2CH:
|
|
116
|
+
return (t2Counter_ >> 8) & 0xFF;
|
|
117
|
+
|
|
118
|
+
case REG_SR:
|
|
119
|
+
return sr_;
|
|
120
|
+
|
|
121
|
+
case REG_ACR:
|
|
122
|
+
return acr_;
|
|
123
|
+
|
|
124
|
+
case REG_PCR:
|
|
125
|
+
return pcr_;
|
|
126
|
+
|
|
127
|
+
case REG_IFR:
|
|
128
|
+
// Bit 7 is set if any enabled interrupt flag is set
|
|
129
|
+
if (ifr_ & ier_ & 0x7F) {
|
|
130
|
+
return ifr_ | IRQ_ANY;
|
|
131
|
+
}
|
|
132
|
+
return ifr_;
|
|
133
|
+
|
|
134
|
+
case REG_IER:
|
|
135
|
+
// Reading IER returns bit 7 = 1
|
|
136
|
+
return ier_ | 0x80;
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
return 0xFF;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
void VIA6522::write(uint8_t reg, uint8_t value) {
|
|
144
|
+
reg &= 0x0F;
|
|
145
|
+
|
|
146
|
+
switch (reg) {
|
|
147
|
+
case REG_ORB:
|
|
148
|
+
orb_ = value;
|
|
149
|
+
// Writing ORB clears CB1/CB2 interrupt flags
|
|
150
|
+
ifr_ &= ~(IRQ_CB1 | IRQ_CB2);
|
|
151
|
+
checkIRQ();
|
|
152
|
+
updatePSG();
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case REG_ORA:
|
|
156
|
+
case REG_ORA_NH:
|
|
157
|
+
ora_ = value;
|
|
158
|
+
// Writing ORA clears CA1/CA2 interrupt flags (unless ORA_NH)
|
|
159
|
+
if (reg == REG_ORA) {
|
|
160
|
+
ifr_ &= ~(IRQ_CA1 | IRQ_CA2);
|
|
161
|
+
checkIRQ();
|
|
162
|
+
}
|
|
163
|
+
updatePSG();
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case REG_DDRB:
|
|
167
|
+
ddrb_ = value;
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case REG_DDRA:
|
|
171
|
+
ddra_ = value;
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case REG_T1CL:
|
|
175
|
+
case REG_T1LL:
|
|
176
|
+
t1Latch_ = (t1Latch_ & 0xFF00) | value;
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case REG_T1CH:
|
|
180
|
+
t1Latch_ = (t1Latch_ & 0x00FF) | (value << 8);
|
|
181
|
+
// Writing T1CH also loads counter and clears interrupt
|
|
182
|
+
// Real 6522 period = latch + 2 (Rockwell datasheet Fig.16)
|
|
183
|
+
// Counter starts at latch + 1 (the +2nd cycle is the write itself)
|
|
184
|
+
t1Counter_ = t1Latch_ + 1;
|
|
185
|
+
t1Running_ = true;
|
|
186
|
+
t1Fired_ = false;
|
|
187
|
+
ifr_ &= ~IRQ_T1;
|
|
188
|
+
checkIRQ();
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case REG_T1LH:
|
|
192
|
+
t1Latch_ = (t1Latch_ & 0x00FF) | (value << 8);
|
|
193
|
+
// Just updates latch, doesn't affect counter
|
|
194
|
+
ifr_ &= ~IRQ_T1; // Clears interrupt flag
|
|
195
|
+
checkIRQ();
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case REG_T2CL:
|
|
199
|
+
t2LatchLow_ = value;
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case REG_T2CH:
|
|
203
|
+
t2Counter_ = t2LatchLow_ | (value << 8);
|
|
204
|
+
t2Counter_ += 1; // +1 extra cycle for proper period (total = latch + 2)
|
|
205
|
+
t2Running_ = true;
|
|
206
|
+
t2Fired_ = false;
|
|
207
|
+
ifr_ &= ~IRQ_T2;
|
|
208
|
+
checkIRQ();
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case REG_SR:
|
|
212
|
+
sr_ = value;
|
|
213
|
+
break;
|
|
214
|
+
|
|
215
|
+
case REG_ACR: {
|
|
216
|
+
uint8_t oldAcr = acr_;
|
|
217
|
+
acr_ = value;
|
|
218
|
+
// If Timer 1 mode changed (one-shot <-> free-running), reset fired flag
|
|
219
|
+
// so the timer can fire again after a mode transition
|
|
220
|
+
if ((oldAcr & 0x40) != (value & 0x40)) {
|
|
221
|
+
t1Fired_ = false;
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
case REG_PCR:
|
|
227
|
+
pcr_ = value;
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
case REG_IFR:
|
|
231
|
+
// Writing 1 to a bit clears that interrupt flag
|
|
232
|
+
ifr_ &= ~(value & 0x7F);
|
|
233
|
+
checkIRQ();
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case REG_IER:
|
|
237
|
+
// Bit 7 controls set/clear mode
|
|
238
|
+
if (value & 0x80) {
|
|
239
|
+
// Set bits
|
|
240
|
+
ier_ |= (value & 0x7F);
|
|
241
|
+
} else {
|
|
242
|
+
// Clear bits
|
|
243
|
+
ier_ &= ~(value & 0x7F);
|
|
244
|
+
}
|
|
245
|
+
checkIRQ();
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
void VIA6522::update(int cycles) {
|
|
251
|
+
if (cycles <= 0) return;
|
|
252
|
+
|
|
253
|
+
uint32_t cyclesToProcess = static_cast<uint32_t>(cycles);
|
|
254
|
+
|
|
255
|
+
// Update Timer 1
|
|
256
|
+
// Counter always decrements (needed for detection to verify card presence)
|
|
257
|
+
// But interrupts and proper reload only happen when timer is "armed" (T1CH written)
|
|
258
|
+
if (cyclesToProcess > t1Counter_) {
|
|
259
|
+
// Timer 1 underflowed
|
|
260
|
+
uint32_t overflow = cyclesToProcess - t1Counter_ - 1;
|
|
261
|
+
|
|
262
|
+
if (t1Running_) {
|
|
263
|
+
// Timer is armed - generate interrupt and handle reload
|
|
264
|
+
if (!t1Fired_) {
|
|
265
|
+
ifr_ |= IRQ_T1;
|
|
266
|
+
checkIRQ();
|
|
267
|
+
t1Fired_ = true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check ACR for timer mode
|
|
271
|
+
if (acr_ & 0x40) {
|
|
272
|
+
// Free-running mode - reload from latch and continue
|
|
273
|
+
// Real 6522 period = latch + 2 (Rockwell datasheet)
|
|
274
|
+
uint32_t period = static_cast<uint32_t>(t1Latch_) + 2;
|
|
275
|
+
overflow = overflow % period;
|
|
276
|
+
t1Counter_ = static_cast<uint16_t>(t1Latch_ + 1 - overflow);
|
|
277
|
+
t1Fired_ = false; // Can fire again next time
|
|
278
|
+
} else {
|
|
279
|
+
// One-shot mode - counter wraps but doesn't reload or re-fire
|
|
280
|
+
t1Counter_ = static_cast<uint16_t>(0xFFFF - (overflow % 0x10000));
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
// Timer not armed - just wrap counter naturally (for detection)
|
|
284
|
+
t1Counter_ = static_cast<uint16_t>(0xFFFF - (overflow % 0x10000));
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
t1Counter_ -= static_cast<uint16_t>(cyclesToProcess);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Update Timer 2
|
|
291
|
+
// Timer 2 also always decrements (in timed mode)
|
|
292
|
+
if (!(acr_ & 0x20)) { // Timed mode (not pulse counting)
|
|
293
|
+
if (cyclesToProcess > t2Counter_) {
|
|
294
|
+
// Timer 2 underflowed
|
|
295
|
+
uint32_t overflow = cyclesToProcess - t2Counter_ - 1;
|
|
296
|
+
|
|
297
|
+
if (t2Running_ && !t2Fired_) {
|
|
298
|
+
ifr_ |= IRQ_T2;
|
|
299
|
+
checkIRQ();
|
|
300
|
+
t2Fired_ = true;
|
|
301
|
+
}
|
|
302
|
+
// Timer 2 is one-shot only - wraps but doesn't reload or re-fire
|
|
303
|
+
t2Counter_ = static_cast<uint16_t>(0xFFFF - (overflow % 0x10000));
|
|
304
|
+
} else {
|
|
305
|
+
t2Counter_ -= static_cast<uint16_t>(cyclesToProcess);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
void VIA6522::updatePSG() {
|
|
311
|
+
if (!psg_) return;
|
|
312
|
+
|
|
313
|
+
// Port B controls the PSG via BC1, BDIR, and RESET lines
|
|
314
|
+
// BC1 = bit 0, BDIR = bit 1, ~RESET = bit 2 (active low)
|
|
315
|
+
// Note: Only look at bits that are configured as outputs
|
|
316
|
+
uint8_t control = orb_ & ddrb_ & 0x07;
|
|
317
|
+
|
|
318
|
+
// Check for PSG reset - bit 2 going low resets the PSG
|
|
319
|
+
// This is used by software to silence the PSG when done playing
|
|
320
|
+
bool resetActive = (control & 0x04) == 0; // Bit 2 = 0 means reset active
|
|
321
|
+
bool wasResetActive = (prevPsgControl_ & 0x04) == 0;
|
|
322
|
+
|
|
323
|
+
if (resetActive && !wasResetActive) {
|
|
324
|
+
// Reset just became active - reset the PSG
|
|
325
|
+
psg_->reset();
|
|
326
|
+
#ifdef __EMSCRIPTEN__
|
|
327
|
+
if (viaDebugLogging_) {
|
|
328
|
+
EM_ASM({
|
|
329
|
+
console.log("VIA" + $0 + ": PSG RESET asserted");
|
|
330
|
+
}, viaId_);
|
|
331
|
+
}
|
|
332
|
+
#endif
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Only perform PSG operations on state TRANSITIONS
|
|
336
|
+
if (control == prevPsgControl_) {
|
|
337
|
+
return; // No change in control state
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
uint8_t prevControl = prevPsgControl_;
|
|
341
|
+
prevPsgControl_ = control;
|
|
342
|
+
|
|
343
|
+
#ifdef __EMSCRIPTEN__
|
|
344
|
+
if (viaDebugLogging_) {
|
|
345
|
+
EM_ASM({
|
|
346
|
+
console.log("VIA" + $4 + ": ctrl " + $0 + "->" + $1 + " ORA=0x" + $2.toString(16) + " DDRA=0x" + $3.toString(16));
|
|
347
|
+
}, prevControl, control, ora_, ddra_, viaId_);
|
|
348
|
+
}
|
|
349
|
+
#endif
|
|
350
|
+
|
|
351
|
+
// Compute PSG function from control bits: BC1=bit0, BDIR=bit1
|
|
352
|
+
// 0b00=INACTIVE, 0b01=READ, 0b10=WRITE, 0b11=LATCH
|
|
353
|
+
uint8_t psgFunc = control & 0x03; // Mask out reset bit
|
|
354
|
+
PsgState newState;
|
|
355
|
+
switch (psgFunc) {
|
|
356
|
+
case 0x00: newState = PSG_INACTIVE; break;
|
|
357
|
+
case 0x01: newState = PSG_READ; break;
|
|
358
|
+
case 0x02: newState = PSG_WRITE; break;
|
|
359
|
+
case 0x03: newState = PSG_LATCH; break;
|
|
360
|
+
default: newState = PSG_INACTIVE; break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// AppleWin-style: operations only execute when transitioning FROM inactive
|
|
364
|
+
if (psgState_ != PSG_INACTIVE) {
|
|
365
|
+
// Not inactive - just update state and return, no operation
|
|
366
|
+
busDriven_ = (newState == PSG_READ && psgAddressLatched_);
|
|
367
|
+
psgState_ = newState;
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Was inactive - execute the operation, then update state
|
|
372
|
+
psgState_ = newState;
|
|
373
|
+
|
|
374
|
+
if (newState == PSG_INACTIVE) {
|
|
375
|
+
busDriven_ = false;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (newState == PSG_LATCH) {
|
|
380
|
+
busDriven_ = false;
|
|
381
|
+
uint8_t addr = ora_ & ddra_;
|
|
382
|
+
if (addr <= 0x0F) {
|
|
383
|
+
psg_->setRegisterAddress(addr);
|
|
384
|
+
psgAddressLatched_ = true;
|
|
385
|
+
} else {
|
|
386
|
+
psgAddressLatched_ = false;
|
|
387
|
+
#ifdef __EMSCRIPTEN__
|
|
388
|
+
if (viaDebugLogging_) {
|
|
389
|
+
EM_ASM({
|
|
390
|
+
console.log("VIA: Rejected invalid address 0x" + $0.toString(16).toUpperCase());
|
|
391
|
+
}, addr);
|
|
392
|
+
}
|
|
393
|
+
#endif
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (newState == PSG_WRITE) {
|
|
399
|
+
busDriven_ = false;
|
|
400
|
+
if (psgAddressLatched_) {
|
|
401
|
+
psg_->writeRegister(ora_ & ddra_);
|
|
402
|
+
} else {
|
|
403
|
+
#ifdef __EMSCRIPTEN__
|
|
404
|
+
if (viaDebugLogging_) {
|
|
405
|
+
EM_ASM({
|
|
406
|
+
console.log("VIA: Write rejected - no address latched, data=0x" + $0.toString(16).toUpperCase());
|
|
407
|
+
}, ora_ & ddra_);
|
|
408
|
+
}
|
|
409
|
+
#endif
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (newState == PSG_READ) {
|
|
415
|
+
if (psgAddressLatched_) {
|
|
416
|
+
ira_ = psg_->readRegister();
|
|
417
|
+
busDriven_ = true;
|
|
418
|
+
} else {
|
|
419
|
+
busDriven_ = false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
void VIA6522::checkIRQ() {
|
|
425
|
+
bool irqActive = (ifr_ & ier_ & 0x7F) != 0;
|
|
426
|
+
|
|
427
|
+
// Only trigger callback on transition from inactive to active
|
|
428
|
+
// This prevents multiple IRQ assertions during a single interrupt handler
|
|
429
|
+
if (irqActive && !prevIrqActive_) {
|
|
430
|
+
if (irqCallback_) {
|
|
431
|
+
irqCallback_();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
prevIrqActive_ = irqActive;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
size_t VIA6522::exportState(uint8_t* buffer) const {
|
|
439
|
+
size_t offset = 0;
|
|
440
|
+
|
|
441
|
+
// Port registers
|
|
442
|
+
buffer[offset++] = ora_;
|
|
443
|
+
buffer[offset++] = orb_;
|
|
444
|
+
buffer[offset++] = ddra_;
|
|
445
|
+
buffer[offset++] = ddrb_;
|
|
446
|
+
buffer[offset++] = ira_;
|
|
447
|
+
buffer[offset++] = irb_;
|
|
448
|
+
|
|
449
|
+
// Timer 1
|
|
450
|
+
buffer[offset++] = t1Counter_ & 0xFF;
|
|
451
|
+
buffer[offset++] = (t1Counter_ >> 8) & 0xFF;
|
|
452
|
+
buffer[offset++] = t1Latch_ & 0xFF;
|
|
453
|
+
buffer[offset++] = (t1Latch_ >> 8) & 0xFF;
|
|
454
|
+
buffer[offset++] = (t1Running_ ? 1 : 0) | (t1Fired_ ? 2 : 0);
|
|
455
|
+
|
|
456
|
+
// Timer 2
|
|
457
|
+
buffer[offset++] = t2Counter_ & 0xFF;
|
|
458
|
+
buffer[offset++] = (t2Counter_ >> 8) & 0xFF;
|
|
459
|
+
buffer[offset++] = t2LatchLow_;
|
|
460
|
+
buffer[offset++] = (t2Running_ ? 1 : 0) | (t2Fired_ ? 2 : 0);
|
|
461
|
+
|
|
462
|
+
// Control registers
|
|
463
|
+
buffer[offset++] = sr_;
|
|
464
|
+
buffer[offset++] = acr_;
|
|
465
|
+
buffer[offset++] = pcr_;
|
|
466
|
+
buffer[offset++] = ifr_;
|
|
467
|
+
buffer[offset++] = ier_;
|
|
468
|
+
|
|
469
|
+
// PSG control state
|
|
470
|
+
buffer[offset++] = prevPsgControl_;
|
|
471
|
+
buffer[offset++] = psgAddressLatched_ ? 1 : 0;
|
|
472
|
+
buffer[offset++] = static_cast<uint8_t>(psgState_);
|
|
473
|
+
buffer[offset++] = busDriven_ ? 1 : 0;
|
|
474
|
+
|
|
475
|
+
// Pad to STATE_SIZE for consistent serialization
|
|
476
|
+
while (offset < STATE_SIZE) {
|
|
477
|
+
buffer[offset++] = 0;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return offset; // Exactly STATE_SIZE bytes
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
void VIA6522::importState(const uint8_t* buffer) {
|
|
484
|
+
size_t offset = 0;
|
|
485
|
+
|
|
486
|
+
// Port registers
|
|
487
|
+
ora_ = buffer[offset++];
|
|
488
|
+
orb_ = buffer[offset++];
|
|
489
|
+
ddra_ = buffer[offset++];
|
|
490
|
+
ddrb_ = buffer[offset++];
|
|
491
|
+
ira_ = buffer[offset++];
|
|
492
|
+
irb_ = buffer[offset++];
|
|
493
|
+
|
|
494
|
+
// Timer 1
|
|
495
|
+
t1Counter_ = buffer[offset] | (buffer[offset + 1] << 8);
|
|
496
|
+
offset += 2;
|
|
497
|
+
t1Latch_ = buffer[offset] | (buffer[offset + 1] << 8);
|
|
498
|
+
offset += 2;
|
|
499
|
+
uint8_t t1Flags = buffer[offset++];
|
|
500
|
+
t1Running_ = (t1Flags & 1) != 0;
|
|
501
|
+
t1Fired_ = (t1Flags & 2) != 0;
|
|
502
|
+
|
|
503
|
+
// Timer 2
|
|
504
|
+
t2Counter_ = buffer[offset] | (buffer[offset + 1] << 8);
|
|
505
|
+
offset += 2;
|
|
506
|
+
t2LatchLow_ = buffer[offset++];
|
|
507
|
+
uint8_t t2Flags = buffer[offset++];
|
|
508
|
+
t2Running_ = (t2Flags & 1) != 0;
|
|
509
|
+
t2Fired_ = (t2Flags & 2) != 0;
|
|
510
|
+
|
|
511
|
+
// Control registers
|
|
512
|
+
sr_ = buffer[offset++];
|
|
513
|
+
acr_ = buffer[offset++];
|
|
514
|
+
pcr_ = buffer[offset++];
|
|
515
|
+
ifr_ = buffer[offset++];
|
|
516
|
+
ier_ = buffer[offset++];
|
|
517
|
+
|
|
518
|
+
// PSG control state
|
|
519
|
+
prevPsgControl_ = buffer[offset++];
|
|
520
|
+
psgAddressLatched_ = buffer[offset++] != 0;
|
|
521
|
+
psgState_ = static_cast<PsgState>(buffer[offset++]);
|
|
522
|
+
busDriven_ = buffer[offset++] != 0;
|
|
523
|
+
|
|
524
|
+
// Restore IRQ state
|
|
525
|
+
prevIrqActive_ = (ifr_ & ier_ & 0x7F) != 0;
|
|
526
|
+
t1IrqDelay_ = 0;
|
|
527
|
+
t2IrqDelay_ = 0;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
} // namespace a2e
|