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.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. 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