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,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* via6522.hpp - VIA 6522 timer chip emulation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <functional>
|
|
12
|
+
|
|
13
|
+
namespace a2e {
|
|
14
|
+
|
|
15
|
+
// Forward declaration
|
|
16
|
+
class AY8910;
|
|
17
|
+
|
|
18
|
+
// 6522 VIA (Versatile Interface Adapter) emulation
|
|
19
|
+
// Two of these are used in the Mockingboard, each controlling one AY-3-8910
|
|
20
|
+
class VIA6522 {
|
|
21
|
+
public:
|
|
22
|
+
using IRQCallback = std::function<void()>;
|
|
23
|
+
|
|
24
|
+
VIA6522();
|
|
25
|
+
|
|
26
|
+
// Set VIA ID for debug logging (1 or 2)
|
|
27
|
+
void setViaId(int id);
|
|
28
|
+
|
|
29
|
+
// Enable/disable console debug logging
|
|
30
|
+
static void setDebugLogging(bool enabled);
|
|
31
|
+
|
|
32
|
+
// Register access
|
|
33
|
+
uint8_t read(uint8_t reg);
|
|
34
|
+
void write(uint8_t reg, uint8_t value);
|
|
35
|
+
|
|
36
|
+
// Timer update (call with CPU cycles elapsed)
|
|
37
|
+
void update(int cycles);
|
|
38
|
+
|
|
39
|
+
// Connect to PSG chip
|
|
40
|
+
void connectPSG(AY8910* psg) { psg_ = psg; }
|
|
41
|
+
|
|
42
|
+
// IRQ callback
|
|
43
|
+
void setIRQCallback(IRQCallback cb) { irqCallback_ = std::move(cb); }
|
|
44
|
+
|
|
45
|
+
// Reset
|
|
46
|
+
void reset();
|
|
47
|
+
|
|
48
|
+
// Check if IRQ is active
|
|
49
|
+
bool isIRQActive() const { return (ifr_ & ier_ & 0x7F) != 0; }
|
|
50
|
+
|
|
51
|
+
// Debug accessors
|
|
52
|
+
uint8_t getORA() const { return ora_; }
|
|
53
|
+
uint8_t getORB() const { return orb_; }
|
|
54
|
+
uint8_t getDDRA() const { return ddra_; }
|
|
55
|
+
uint8_t getDDRB() const { return ddrb_; }
|
|
56
|
+
|
|
57
|
+
// State serialization
|
|
58
|
+
size_t exportState(uint8_t* buffer) const;
|
|
59
|
+
void importState(const uint8_t* buffer);
|
|
60
|
+
static constexpr size_t STATE_SIZE = 32;
|
|
61
|
+
|
|
62
|
+
// Timer debug accessors
|
|
63
|
+
uint16_t getT1Counter() const { return t1Counter_; }
|
|
64
|
+
uint16_t getT1Latch() const { return t1Latch_; }
|
|
65
|
+
bool isT1Running() const { return t1Running_; }
|
|
66
|
+
bool hasT1Fired() const { return t1Fired_; }
|
|
67
|
+
uint8_t getACR() const { return acr_; }
|
|
68
|
+
uint8_t getIFR() const { return ifr_; }
|
|
69
|
+
uint8_t getIER() const { return ier_; }
|
|
70
|
+
|
|
71
|
+
private:
|
|
72
|
+
// Register addresses
|
|
73
|
+
static constexpr int REG_ORB = 0x00; // Output Register B / Input Register B
|
|
74
|
+
static constexpr int REG_ORA = 0x01; // Output Register A / Input Register A
|
|
75
|
+
static constexpr int REG_DDRB = 0x02; // Data Direction Register B
|
|
76
|
+
static constexpr int REG_DDRA = 0x03; // Data Direction Register A
|
|
77
|
+
static constexpr int REG_T1CL = 0x04; // Timer 1 Counter Low
|
|
78
|
+
static constexpr int REG_T1CH = 0x05; // Timer 1 Counter High
|
|
79
|
+
static constexpr int REG_T1LL = 0x06; // Timer 1 Latch Low
|
|
80
|
+
static constexpr int REG_T1LH = 0x07; // Timer 1 Latch High
|
|
81
|
+
static constexpr int REG_T2CL = 0x08; // Timer 2 Counter Low
|
|
82
|
+
static constexpr int REG_T2CH = 0x09; // Timer 2 Counter High
|
|
83
|
+
static constexpr int REG_SR = 0x0A; // Shift Register
|
|
84
|
+
static constexpr int REG_ACR = 0x0B; // Auxiliary Control Register
|
|
85
|
+
static constexpr int REG_PCR = 0x0C; // Peripheral Control Register
|
|
86
|
+
static constexpr int REG_IFR = 0x0D; // Interrupt Flag Register
|
|
87
|
+
static constexpr int REG_IER = 0x0E; // Interrupt Enable Register
|
|
88
|
+
static constexpr int REG_ORA_NH = 0x0F; // Output Register A (no handshake)
|
|
89
|
+
|
|
90
|
+
// IRQ flag bits
|
|
91
|
+
static constexpr uint8_t IRQ_CA2 = 0x01;
|
|
92
|
+
static constexpr uint8_t IRQ_CA1 = 0x02;
|
|
93
|
+
static constexpr uint8_t IRQ_SR = 0x04;
|
|
94
|
+
static constexpr uint8_t IRQ_CB2 = 0x08;
|
|
95
|
+
static constexpr uint8_t IRQ_CB1 = 0x10;
|
|
96
|
+
static constexpr uint8_t IRQ_T2 = 0x20;
|
|
97
|
+
static constexpr uint8_t IRQ_T1 = 0x40;
|
|
98
|
+
static constexpr uint8_t IRQ_ANY = 0x80;
|
|
99
|
+
|
|
100
|
+
// Port registers
|
|
101
|
+
uint8_t ora_ = 0; // Output Register A
|
|
102
|
+
uint8_t orb_ = 0; // Output Register B
|
|
103
|
+
uint8_t ddra_ = 0; // Data Direction Register A (1 = output)
|
|
104
|
+
uint8_t ddrb_ = 0; // Data Direction Register B (1 = output)
|
|
105
|
+
uint8_t ira_ = 0; // Input Register A (external input)
|
|
106
|
+
uint8_t irb_ = 0; // Input Register B (external input)
|
|
107
|
+
|
|
108
|
+
// Timer 1
|
|
109
|
+
uint16_t t1Counter_ = 0xFFFF;
|
|
110
|
+
uint16_t t1Latch_ = 0xFFFF;
|
|
111
|
+
bool t1Running_ = false;
|
|
112
|
+
bool t1Fired_ = false;
|
|
113
|
+
|
|
114
|
+
// Timer 2
|
|
115
|
+
uint16_t t2Counter_ = 0xFFFF;
|
|
116
|
+
uint8_t t2LatchLow_ = 0xFF;
|
|
117
|
+
bool t2Running_ = false;
|
|
118
|
+
bool t2Fired_ = false;
|
|
119
|
+
|
|
120
|
+
// Shift register
|
|
121
|
+
uint8_t sr_ = 0;
|
|
122
|
+
|
|
123
|
+
// Control registers
|
|
124
|
+
uint8_t acr_ = 0; // Auxiliary Control Register
|
|
125
|
+
uint8_t pcr_ = 0; // Peripheral Control Register
|
|
126
|
+
uint8_t ifr_ = 0; // Interrupt Flag Register
|
|
127
|
+
uint8_t ier_ = 0; // Interrupt Enable Register
|
|
128
|
+
|
|
129
|
+
// Connected PSG
|
|
130
|
+
AY8910* psg_ = nullptr;
|
|
131
|
+
|
|
132
|
+
// IRQ callback
|
|
133
|
+
IRQCallback irqCallback_;
|
|
134
|
+
|
|
135
|
+
// Helper methods
|
|
136
|
+
void updatePSG();
|
|
137
|
+
void checkIRQ();
|
|
138
|
+
|
|
139
|
+
// Previous PSG control state for edge detection
|
|
140
|
+
uint8_t prevPsgControl_ = 0;
|
|
141
|
+
|
|
142
|
+
// Track if a valid PSG address was latched (AppleWin-style)
|
|
143
|
+
bool psgAddressLatched_ = false;
|
|
144
|
+
|
|
145
|
+
// PSG state machine: operations only execute from INACTIVE state
|
|
146
|
+
enum PsgState { PSG_INACTIVE, PSG_READ, PSG_WRITE, PSG_LATCH };
|
|
147
|
+
PsgState psgState_ = PSG_INACTIVE;
|
|
148
|
+
|
|
149
|
+
// Bus-driven flag: true when PSG is driving port A data (READ state)
|
|
150
|
+
bool busDriven_ = false;
|
|
151
|
+
|
|
152
|
+
// Previous IRQ state for edge detection (only trigger on 0->1 transition)
|
|
153
|
+
bool prevIrqActive_ = false;
|
|
154
|
+
|
|
155
|
+
// IRQ delay - matches AppleWin's CheckTimerUnderflow behavior
|
|
156
|
+
int t1IrqDelay_ = 0;
|
|
157
|
+
int t2IrqDelay_ = 0;
|
|
158
|
+
|
|
159
|
+
// VIA identifier for debug logging
|
|
160
|
+
int viaId_ = 1;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
} // namespace a2e
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* mockingboard_card.cpp - Mockingboard sound card implementation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "mockingboard_card.hpp"
|
|
9
|
+
#include <cstring>
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
|
|
12
|
+
namespace a2e {
|
|
13
|
+
|
|
14
|
+
MockingboardCard::MockingboardCard() {
|
|
15
|
+
// Set IDs for debug logging
|
|
16
|
+
psg1_.setPsgId(1);
|
|
17
|
+
psg2_.setPsgId(2);
|
|
18
|
+
via1_.setViaId(1);
|
|
19
|
+
via2_.setViaId(2);
|
|
20
|
+
|
|
21
|
+
// Connect PSGs to VIAs
|
|
22
|
+
via1_.connectPSG(&psg1_);
|
|
23
|
+
via2_.connectPSG(&psg2_);
|
|
24
|
+
|
|
25
|
+
reset();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void MockingboardCard::setDebugLogging(bool enabled) {
|
|
29
|
+
AY8910::setDebugLogging(enabled);
|
|
30
|
+
VIA6522::setDebugLogging(enabled);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
uint8_t MockingboardCard::readIO(uint8_t offset) {
|
|
34
|
+
// Mockingboard doesn't use I/O space ($C0C0-$C0CF)
|
|
35
|
+
// It uses ROM space for VIA registers
|
|
36
|
+
(void)offset;
|
|
37
|
+
return 0xFF;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void MockingboardCard::writeIO(uint8_t offset, uint8_t value) {
|
|
41
|
+
// Mockingboard doesn't use I/O space
|
|
42
|
+
(void)offset;
|
|
43
|
+
(void)value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
uint8_t MockingboardCard::peekIO(uint8_t offset) const {
|
|
47
|
+
(void)offset;
|
|
48
|
+
return 0xFF;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
uint8_t MockingboardCard::readROM(uint8_t offset) {
|
|
52
|
+
if (!enabled_) return 0xFF;
|
|
53
|
+
|
|
54
|
+
// Address decoding for slot ROM space
|
|
55
|
+
// VIA 1 is mirrored at $C400-$C47F (active when bit 7 = 0)
|
|
56
|
+
// VIA 2 is mirrored at $C480-$C4FF (active when bit 7 = 1)
|
|
57
|
+
// Register is determined by bits 0-3
|
|
58
|
+
|
|
59
|
+
uint8_t reg = offset & 0x0F;
|
|
60
|
+
|
|
61
|
+
if ((offset & 0x80) == 0) {
|
|
62
|
+
// VIA 1 ($C400-$C47F)
|
|
63
|
+
return via1_.read(reg);
|
|
64
|
+
} else {
|
|
65
|
+
// VIA 2 ($C480-$C4FF)
|
|
66
|
+
return via2_.read(reg);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void MockingboardCard::writeROM(uint8_t offset, uint8_t value) {
|
|
71
|
+
if (!enabled_) return;
|
|
72
|
+
|
|
73
|
+
uint8_t reg = offset & 0x0F;
|
|
74
|
+
|
|
75
|
+
if ((offset & 0x80) == 0) {
|
|
76
|
+
// VIA 1 ($C400-$C47F)
|
|
77
|
+
via1_.write(reg, value);
|
|
78
|
+
} else {
|
|
79
|
+
// VIA 2 ($C480-$C4FF)
|
|
80
|
+
via2_.write(reg, value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
void MockingboardCard::reset() {
|
|
85
|
+
via1_.reset();
|
|
86
|
+
via2_.reset();
|
|
87
|
+
psg1_.reset();
|
|
88
|
+
psg2_.reset();
|
|
89
|
+
cycleAccum_ = 0.0;
|
|
90
|
+
sampleAccum_.clear();
|
|
91
|
+
sampleReadPos_ = 0;
|
|
92
|
+
dcStateL_ = 0.0f;
|
|
93
|
+
dcStateR_ = 0.0f;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bool MockingboardCard::arePsgsIdentical() const {
|
|
97
|
+
// Compare sound registers 0-13 (skip I/O ports 14-15)
|
|
98
|
+
for (int i = 0; i < 14; i++) {
|
|
99
|
+
if (psg1_.getRegister(i) != psg2_.getRegister(i)) return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void MockingboardCard::update(int cycles) {
|
|
105
|
+
if (!enabled_) return;
|
|
106
|
+
|
|
107
|
+
via1_.update(cycles);
|
|
108
|
+
via2_.update(cycles);
|
|
109
|
+
|
|
110
|
+
// Incremental audio generation: accumulate CPU cycles and generate
|
|
111
|
+
// audio samples at 48kHz rate. This ensures PSG register changes
|
|
112
|
+
// from VIA timer IRQ handlers are immediately reflected in output.
|
|
113
|
+
cycleAccum_ += cycles;
|
|
114
|
+
while (cycleAccum_ >= CYCLES_PER_SAMPLE) {
|
|
115
|
+
cycleAccum_ -= CYCLES_PER_SAMPLE;
|
|
116
|
+
|
|
117
|
+
float left = psg1_.generateSingleSample();
|
|
118
|
+
float right = psg2_.generateSingleSample();
|
|
119
|
+
|
|
120
|
+
// When both PSGs are programmed identically, use PSG1's output
|
|
121
|
+
// for both channels to eliminate phase cancellation from
|
|
122
|
+
// independent tone counters
|
|
123
|
+
if (arePsgsIdentical()) {
|
|
124
|
+
right = left;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
sampleAccum_.push_back(left);
|
|
128
|
+
sampleAccum_.push_back(right);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void MockingboardCard::setIRQCallback(IRQCallback callback) {
|
|
133
|
+
// Both VIAs can trigger IRQ
|
|
134
|
+
via1_.setIRQCallback(callback);
|
|
135
|
+
via2_.setIRQCallback(callback);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
bool MockingboardCard::isIRQActive() const {
|
|
139
|
+
return enabled_ && (via1_.isIRQActive() || via2_.isIRQActive());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
size_t MockingboardCard::getStateSize() const {
|
|
143
|
+
return STATE_SIZE;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
size_t MockingboardCard::serialize(uint8_t* buffer, size_t maxSize) const {
|
|
147
|
+
if (maxSize < STATE_SIZE) return 0;
|
|
148
|
+
|
|
149
|
+
size_t offset = 0;
|
|
150
|
+
|
|
151
|
+
// Enabled flag
|
|
152
|
+
buffer[offset++] = enabled_ ? 1 : 0;
|
|
153
|
+
|
|
154
|
+
// VIA1 and PSG1
|
|
155
|
+
offset += via1_.exportState(buffer + offset);
|
|
156
|
+
offset += psg1_.exportState(buffer + offset);
|
|
157
|
+
|
|
158
|
+
// VIA2 and PSG2
|
|
159
|
+
offset += via2_.exportState(buffer + offset);
|
|
160
|
+
offset += psg2_.exportState(buffer + offset);
|
|
161
|
+
|
|
162
|
+
return offset;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
size_t MockingboardCard::deserialize(const uint8_t* buffer, size_t size) {
|
|
166
|
+
if (size < STATE_SIZE) return 0;
|
|
167
|
+
|
|
168
|
+
size_t offset = 0;
|
|
169
|
+
|
|
170
|
+
// Enabled flag
|
|
171
|
+
enabled_ = buffer[offset++] != 0;
|
|
172
|
+
|
|
173
|
+
// VIA1 and PSG1
|
|
174
|
+
via1_.importState(buffer + offset);
|
|
175
|
+
offset += VIA6522::STATE_SIZE;
|
|
176
|
+
psg1_.importState(buffer + offset);
|
|
177
|
+
offset += AY8910::STATE_SIZE;
|
|
178
|
+
|
|
179
|
+
// VIA2 and PSG2
|
|
180
|
+
via2_.importState(buffer + offset);
|
|
181
|
+
offset += VIA6522::STATE_SIZE;
|
|
182
|
+
psg2_.importState(buffer + offset);
|
|
183
|
+
offset += AY8910::STATE_SIZE;
|
|
184
|
+
|
|
185
|
+
// Reset DC filter state for clean audio restart
|
|
186
|
+
dcStateL_ = 0.0f;
|
|
187
|
+
dcStateR_ = 0.0f;
|
|
188
|
+
|
|
189
|
+
return offset;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
void MockingboardCard::generateStereoSamples(float* buffer, int count, int sampleRate) {
|
|
193
|
+
if (!enabled_ || count <= 0) {
|
|
194
|
+
for (int i = 0; i < count * 2; i++) {
|
|
195
|
+
buffer[i] = 0.0f;
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (static_cast<int>(audioBuffer1_.size()) < count) {
|
|
201
|
+
audioBuffer1_.resize(count);
|
|
202
|
+
}
|
|
203
|
+
if (static_cast<int>(audioBuffer2_.size()) < count) {
|
|
204
|
+
audioBuffer2_.resize(count);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
psg1_.generateSamples(audioBuffer1_.data(), count, sampleRate);
|
|
208
|
+
psg2_.generateSamples(audioBuffer2_.data(), count, sampleRate);
|
|
209
|
+
|
|
210
|
+
bool identical = arePsgsIdentical();
|
|
211
|
+
for (int i = 0; i < count; i++) {
|
|
212
|
+
float left = audioBuffer1_[i];
|
|
213
|
+
float right = identical ? left : audioBuffer2_[i];
|
|
214
|
+
|
|
215
|
+
// DC offset removal
|
|
216
|
+
dcStateL_ = DC_ALPHA * dcStateL_ + (1.0f - DC_ALPHA) * left;
|
|
217
|
+
dcStateR_ = DC_ALPHA * dcStateR_ + (1.0f - DC_ALPHA) * right;
|
|
218
|
+
buffer[i * 2] = left - dcStateL_;
|
|
219
|
+
buffer[i * 2 + 1] = right - dcStateR_;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
void MockingboardCard::generateStereoSamples(float* buffer, int count, int sampleRate, uint64_t startCycle, uint64_t endCycle) {
|
|
224
|
+
if (!enabled_ || count <= 0) {
|
|
225
|
+
for (int i = 0; i < count * 2; i++) {
|
|
226
|
+
buffer[i] = 0.0f;
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (static_cast<int>(audioBuffer1_.size()) < count) {
|
|
232
|
+
audioBuffer1_.resize(count);
|
|
233
|
+
}
|
|
234
|
+
if (static_cast<int>(audioBuffer2_.size()) < count) {
|
|
235
|
+
audioBuffer2_.resize(count);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Generate with proper timing
|
|
239
|
+
psg1_.generateSamples(audioBuffer1_.data(), count, sampleRate, startCycle, endCycle);
|
|
240
|
+
psg2_.generateSamples(audioBuffer2_.data(), count, sampleRate, startCycle, endCycle);
|
|
241
|
+
|
|
242
|
+
bool identical = arePsgsIdentical();
|
|
243
|
+
for (int i = 0; i < count; i++) {
|
|
244
|
+
float left = audioBuffer1_[i];
|
|
245
|
+
float right = identical ? left : audioBuffer2_[i];
|
|
246
|
+
|
|
247
|
+
// DC offset removal
|
|
248
|
+
dcStateL_ = DC_ALPHA * dcStateL_ + (1.0f - DC_ALPHA) * left;
|
|
249
|
+
dcStateR_ = DC_ALPHA * dcStateR_ + (1.0f - DC_ALPHA) * right;
|
|
250
|
+
buffer[i * 2] = left - dcStateL_;
|
|
251
|
+
buffer[i * 2 + 1] = right - dcStateR_;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
int MockingboardCard::consumeStereoSamples(float* buffer, int frameCount) {
|
|
256
|
+
if (!enabled_ || frameCount <= 0) {
|
|
257
|
+
for (int i = 0; i < frameCount * 2; i++) {
|
|
258
|
+
buffer[i] = 0.0f;
|
|
259
|
+
}
|
|
260
|
+
return frameCount;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
int availableFrames = static_cast<int>((sampleAccum_.size() - sampleReadPos_) / 2);
|
|
264
|
+
int framesToCopy = std::min(frameCount, availableFrames);
|
|
265
|
+
|
|
266
|
+
// Copy available accumulated samples
|
|
267
|
+
if (framesToCopy > 0) {
|
|
268
|
+
std::memcpy(buffer, sampleAccum_.data() + sampleReadPos_,
|
|
269
|
+
framesToCopy * 2 * sizeof(float));
|
|
270
|
+
sampleReadPos_ += framesToCopy * 2;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// If we need more samples than accumulated, generate the remainder on the spot
|
|
274
|
+
// (handles slight timing drift between CPU execution and audio requests)
|
|
275
|
+
if (framesToCopy < frameCount) {
|
|
276
|
+
bool identical = arePsgsIdentical();
|
|
277
|
+
for (int i = framesToCopy; i < frameCount; i++) {
|
|
278
|
+
float left = psg1_.generateSingleSample();
|
|
279
|
+
float right = identical ? left : psg2_.generateSingleSample();
|
|
280
|
+
buffer[i * 2] = left;
|
|
281
|
+
buffer[i * 2 + 1] = right;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// DC offset removal converts unipolar PSG output to bipolar for audio playback.
|
|
286
|
+
// Identity check was already applied during accumulation in update() and
|
|
287
|
+
// in the overflow path above.
|
|
288
|
+
for (int i = 0; i < frameCount; i++) {
|
|
289
|
+
float left = buffer[i * 2];
|
|
290
|
+
float right = buffer[i * 2 + 1];
|
|
291
|
+
|
|
292
|
+
dcStateL_ = DC_ALPHA * dcStateL_ + (1.0f - DC_ALPHA) * left;
|
|
293
|
+
dcStateR_ = DC_ALPHA * dcStateR_ + (1.0f - DC_ALPHA) * right;
|
|
294
|
+
buffer[i * 2] = left - dcStateL_;
|
|
295
|
+
buffer[i * 2 + 1] = right - dcStateR_;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Compact the buffer: remove consumed samples
|
|
299
|
+
if (sampleReadPos_ > 0) {
|
|
300
|
+
size_t remaining = sampleAccum_.size() - sampleReadPos_;
|
|
301
|
+
if (remaining > 0) {
|
|
302
|
+
std::memmove(sampleAccum_.data(), sampleAccum_.data() + sampleReadPos_,
|
|
303
|
+
remaining * sizeof(float));
|
|
304
|
+
}
|
|
305
|
+
sampleAccum_.resize(remaining);
|
|
306
|
+
sampleReadPos_ = 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return frameCount;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
} // namespace a2e
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* mockingboard_card.hpp - Mockingboard sound card
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "expansion_card.hpp"
|
|
11
|
+
#include "mockingboard/via6522.hpp"
|
|
12
|
+
#include "mockingboard/ay8910.hpp"
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
namespace a2e {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* MockingboardCard - Mockingboard Sound Card
|
|
19
|
+
*
|
|
20
|
+
* Implements the ExpansionCard interface with direct ownership of VIA/PSG chips.
|
|
21
|
+
* The Mockingboard typically occupies slot 4, providing:
|
|
22
|
+
* - I/O space: $C0C0-$C0CF (unused - Mockingboard uses ROM space for VIA access)
|
|
23
|
+
*
|
|
24
|
+
* Note: The Mockingboard is unusual in that it uses the slot ROM space
|
|
25
|
+
* ($C400-$C4FF) for its VIA registers rather than the I/O space ($C0C0-$C0CF).
|
|
26
|
+
* This is because it needs more than 16 bytes of address space.
|
|
27
|
+
*
|
|
28
|
+
* VIA 1: $C400-$C47F (bit 7 = 0) - left channel PSG
|
|
29
|
+
* VIA 2: $C480-$C4FF (bit 7 = 1) - right channel PSG
|
|
30
|
+
*/
|
|
31
|
+
class MockingboardCard : public ExpansionCard {
|
|
32
|
+
public:
|
|
33
|
+
using CycleCallback = std::function<uint64_t()>;
|
|
34
|
+
|
|
35
|
+
// State size for serialization: enabled(1) + VIA1(32) + PSG1(48) + VIA2(32) + PSG2(48) = 161
|
|
36
|
+
static constexpr size_t STATE_SIZE = 161;
|
|
37
|
+
|
|
38
|
+
MockingboardCard();
|
|
39
|
+
~MockingboardCard() override = default;
|
|
40
|
+
|
|
41
|
+
// Delete copy
|
|
42
|
+
MockingboardCard(const MockingboardCard&) = delete;
|
|
43
|
+
MockingboardCard& operator=(const MockingboardCard&) = delete;
|
|
44
|
+
|
|
45
|
+
// Allow move
|
|
46
|
+
MockingboardCard(MockingboardCard&&) = default;
|
|
47
|
+
MockingboardCard& operator=(MockingboardCard&&) = default;
|
|
48
|
+
|
|
49
|
+
// ===== ExpansionCard Interface =====
|
|
50
|
+
|
|
51
|
+
// I/O space ($C0C0-$C0CF) - Mockingboard doesn't use this
|
|
52
|
+
uint8_t readIO(uint8_t offset) override;
|
|
53
|
+
void writeIO(uint8_t offset, uint8_t value) override;
|
|
54
|
+
uint8_t peekIO(uint8_t offset) const override;
|
|
55
|
+
|
|
56
|
+
// ROM space ($C400-$C4FF) - Contains VIA registers
|
|
57
|
+
uint8_t readROM(uint8_t offset) override;
|
|
58
|
+
void writeROM(uint8_t offset, uint8_t value) override;
|
|
59
|
+
bool hasROM() const override { return true; }
|
|
60
|
+
|
|
61
|
+
bool hasExpansionROM() const override { return false; }
|
|
62
|
+
|
|
63
|
+
void reset() override;
|
|
64
|
+
void update(int cycles) override;
|
|
65
|
+
|
|
66
|
+
void setIRQCallback(IRQCallback callback) override;
|
|
67
|
+
void setCycleCallback(CycleCallback callback) override {
|
|
68
|
+
cycleCallback_ = callback;
|
|
69
|
+
// Pass to PSGs for timestamped register writes
|
|
70
|
+
psg1_.setCycleCallback(callback);
|
|
71
|
+
psg2_.setCycleCallback(callback);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
bool isIRQActive() const override;
|
|
75
|
+
|
|
76
|
+
size_t getStateSize() const override;
|
|
77
|
+
size_t serialize(uint8_t* buffer, size_t maxSize) const override;
|
|
78
|
+
size_t deserialize(const uint8_t* buffer, size_t size) override;
|
|
79
|
+
|
|
80
|
+
const char* getName() const override { return "Mockingboard"; }
|
|
81
|
+
uint8_t getPreferredSlot() const override { return 4; }
|
|
82
|
+
|
|
83
|
+
bool isEnabled() const override { return enabled_; }
|
|
84
|
+
void setEnabled(bool enabled) override { enabled_ = enabled; }
|
|
85
|
+
|
|
86
|
+
// ===== Audio Generation =====
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate stereo audio samples (legacy, no timing)
|
|
90
|
+
*/
|
|
91
|
+
void generateStereoSamples(float* buffer, int count, int sampleRate);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate stereo audio samples with proper timing
|
|
95
|
+
*/
|
|
96
|
+
void generateStereoSamples(float* buffer, int count, int sampleRate, uint64_t startCycle, uint64_t endCycle);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Consume accumulated stereo samples (from incremental generation).
|
|
100
|
+
* Returns actual number of sample frames consumed.
|
|
101
|
+
* If fewer samples available than requested, generates remaining on the spot.
|
|
102
|
+
*/
|
|
103
|
+
int consumeStereoSamples(float* buffer, int frameCount);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Enable/disable debug logging
|
|
107
|
+
* @param enabled true to enable
|
|
108
|
+
*/
|
|
109
|
+
void setDebugLogging(bool enabled);
|
|
110
|
+
|
|
111
|
+
// ===== Debug Access =====
|
|
112
|
+
const VIA6522& getVIA1() const { return via1_; }
|
|
113
|
+
const VIA6522& getVIA2() const { return via2_; }
|
|
114
|
+
const AY8910& getPSG1() const { return psg1_; }
|
|
115
|
+
const AY8910& getPSG2() const { return psg2_; }
|
|
116
|
+
AY8910& getPSG1() { return psg1_; }
|
|
117
|
+
AY8910& getPSG2() { return psg2_; }
|
|
118
|
+
|
|
119
|
+
private:
|
|
120
|
+
// Two VIA chips
|
|
121
|
+
VIA6522 via1_; // $C400-$C47F (bit 7 = 0)
|
|
122
|
+
VIA6522 via2_; // $C480-$C4FF (bit 7 = 1)
|
|
123
|
+
|
|
124
|
+
// Two PSG chips
|
|
125
|
+
AY8910 psg1_; // Connected to VIA1 (left channel)
|
|
126
|
+
AY8910 psg2_; // Connected to VIA2 (right channel)
|
|
127
|
+
|
|
128
|
+
// Enabled state
|
|
129
|
+
bool enabled_ = true;
|
|
130
|
+
|
|
131
|
+
// Callbacks
|
|
132
|
+
CycleCallback cycleCallback_;
|
|
133
|
+
|
|
134
|
+
// Preallocated audio buffers to avoid heap allocations in audio hot path
|
|
135
|
+
mutable std::vector<float> audioBuffer1_;
|
|
136
|
+
mutable std::vector<float> audioBuffer2_;
|
|
137
|
+
|
|
138
|
+
// Phase coherence: when both PSGs have identical sound registers (0-13),
|
|
139
|
+
// use PSG1's output for both channels. This eliminates phase cancellation
|
|
140
|
+
// caused by independent tone counters producing anti-phase waveforms
|
|
141
|
+
// when Mockingboard music mirrors content to both PSGs.
|
|
142
|
+
bool arePsgsIdentical() const;
|
|
143
|
+
|
|
144
|
+
// Per-channel DC offset removal (high-pass filter)
|
|
145
|
+
// Converts unipolar PSG output to bipolar for audio playback.
|
|
146
|
+
// Slow time constant (~200ms at 48kHz) avoids tracking musical content.
|
|
147
|
+
static constexpr float DC_ALPHA = 0.9999f;
|
|
148
|
+
float dcStateL_ = 0.0f;
|
|
149
|
+
float dcStateR_ = 0.0f;
|
|
150
|
+
|
|
151
|
+
// Incremental audio generation state
|
|
152
|
+
// CPU cycles per audio sample at 48kHz: 1,023,000 / 48,000 ≈ 21.3125
|
|
153
|
+
static constexpr double CYCLES_PER_SAMPLE = 1023000.0 / 48000.0;
|
|
154
|
+
double cycleAccum_ = 0.0; // Fractional CPU cycle accumulator
|
|
155
|
+
std::vector<float> sampleAccum_; // Accumulated stereo samples (interleaved L/R)
|
|
156
|
+
size_t sampleReadPos_ = 0; // Read position in accumulated buffer
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
} // namespace a2e
|