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,115 @@
1
+ /*
2
+ * assembler.hpp - 65C02 multi-pass assembler
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <cstdint>
11
+ #include <cstring>
12
+ #include <string>
13
+ #include <vector>
14
+ #include <unordered_map>
15
+
16
+ namespace a2e {
17
+
18
+ // Maximum error message length
19
+ static constexpr int ASM_MAX_ERROR_MSG = 80;
20
+
21
+ // Assembly error
22
+ struct AsmError {
23
+ int lineNumber;
24
+ char message[ASM_MAX_ERROR_MSG];
25
+ };
26
+
27
+ // Symbol entry for exposing the symbol table
28
+ struct AsmSymbol {
29
+ char name[64];
30
+ int32_t value;
31
+ };
32
+
33
+ // Assembly result
34
+ struct AsmResult {
35
+ std::vector<uint8_t> output;
36
+ std::vector<AsmError> errors;
37
+ std::vector<AsmSymbol> symbols;
38
+ uint16_t origin;
39
+ uint16_t endAddress;
40
+ bool success;
41
+ };
42
+
43
+ class Assembler {
44
+ public:
45
+ Assembler();
46
+
47
+ /**
48
+ * Assemble source text into machine code.
49
+ * @param source Null-terminated source string
50
+ * @return Assembly result with output bytes and errors
51
+ */
52
+ AsmResult assemble(const char* source);
53
+
54
+ private:
55
+ // Parsed source line
56
+ struct ParsedLine {
57
+ std::string label;
58
+ std::string mnemonic;
59
+ std::string operand;
60
+ int lineNumber;
61
+ };
62
+
63
+ // Reverse opcode table: reverseOpcodes[mnemonicIndex][addrMode] = opcode byte
64
+ // 0xFF = invalid combination
65
+ uint8_t reverseOpcodes[99][16];
66
+ bool reverseTableBuilt;
67
+
68
+ // Symbol table: label -> address
69
+ std::unordered_map<std::string, int32_t> symbols;
70
+
71
+ void buildReverseOpcodeTable();
72
+ std::vector<ParsedLine> parseSource(const char* source);
73
+ ParsedLine parseLine(const char* line, int lineNumber);
74
+
75
+ // Expression evaluation
76
+ int32_t evaluateExpression(const std::string& expr, bool& error,
77
+ std::string& errorMsg, int lineNumber);
78
+ int32_t evalAddSub(const char*& p, bool& error, std::string& errorMsg,
79
+ int lineNumber);
80
+ int32_t evalMulDiv(const char*& p, bool& error, std::string& errorMsg,
81
+ int lineNumber);
82
+ int32_t evalUnary(const char*& p, bool& error, std::string& errorMsg,
83
+ int lineNumber);
84
+ int32_t evalPrimary(const char*& p, bool& error, std::string& errorMsg,
85
+ int lineNumber);
86
+
87
+ // Addressing mode detection
88
+ uint8_t detectAddressingMode(const std::string& mnemonic,
89
+ const std::string& operand,
90
+ int32_t value, bool valueKnown);
91
+
92
+ // Instruction sizing
93
+ int getInstructionSize(const std::string& mnemonic,
94
+ const std::string& operand,
95
+ bool labelsComplete);
96
+
97
+ // Directive processing
98
+ int getDirectiveSize(const std::string& directive, const std::string& operand,
99
+ bool& error, std::string& errorMsg, int lineNumber);
100
+ void emitDirective(const std::string& directive, const std::string& operand,
101
+ std::vector<uint8_t>& output,
102
+ bool& error, std::string& errorMsg, int lineNumber);
103
+
104
+ // Mnemonic lookup
105
+ int findMnemonicIndex(const std::string& mnemonic);
106
+
107
+ // Helper to check if a mnemonic is a branch instruction
108
+ bool isBranchMnemonic(int mnemonicIndex);
109
+ bool isZPRMnemonic(int mnemonicIndex);
110
+
111
+ // Current PC during assembly
112
+ uint16_t pc;
113
+ };
114
+
115
+ } // namespace a2e
@@ -0,0 +1,160 @@
1
+ /*
2
+ * audio.cpp - Speaker audio emulation implementation
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #include "audio.hpp"
9
+ #include "../cards/mockingboard_card.hpp"
10
+ #include <cmath>
11
+
12
+ namespace a2e {
13
+
14
+ Audio::Audio() {
15
+ toggleCycles_.reserve(MAX_TOGGLES);
16
+ reset();
17
+ }
18
+
19
+ void Audio::reset() {
20
+ speakerState_ = false;
21
+ toggleCycles_.clear();
22
+ toggleReadIndex_ = 0;
23
+ lastSampleCycle_ = 0;
24
+ filterState_ = 0.0f;
25
+ dcOffset_ = 0.0f;
26
+ }
27
+
28
+ void Audio::toggleSpeaker(uint64_t cycleCount) {
29
+ // Record the toggle event
30
+ if (toggleCycles_.size() < MAX_TOGGLES) {
31
+ toggleCycles_.push_back(cycleCount);
32
+ }
33
+ }
34
+
35
+ int Audio::generateStereoSamples(float *buffer, int sampleCount,
36
+ uint64_t currentCycle) {
37
+ if (sampleCount <= 0) {
38
+ return sampleCount;
39
+ }
40
+
41
+ // First generate mono speaker samples into a temp buffer
42
+ std::vector<float> speakerBuffer(sampleCount);
43
+
44
+ uint64_t startCycle = lastSampleCycle_;
45
+ uint64_t endCycle = currentCycle;
46
+ uint64_t totalCycles = endCycle - startCycle;
47
+
48
+ // Sanity check: if cycle range is too large (more than 2x expected), clamp it
49
+ uint64_t expectedCycles = static_cast<uint64_t>(sampleCount * CYCLES_PER_SAMPLE);
50
+ if (totalCycles == 0 || totalCycles > expectedCycles * 2) {
51
+ totalCycles = expectedCycles;
52
+ startCycle = endCycle - totalCycles;
53
+ }
54
+
55
+ double cyclesPerSample = static_cast<double>(totalCycles) / sampleCount;
56
+
57
+ // Process each sample for speaker
58
+ for (int i = 0; i < sampleCount; i++) {
59
+ uint64_t sampleCycleStart =
60
+ startCycle + static_cast<uint64_t>(i * cyclesPerSample);
61
+ uint64_t sampleCycleEnd =
62
+ startCycle + static_cast<uint64_t>((i + 1) * cyclesPerSample);
63
+
64
+ float highTime = 0.0f;
65
+ float lowTime = 0.0f;
66
+ uint64_t lastCycle = sampleCycleStart;
67
+ bool currentState = speakerState_;
68
+
69
+ while (toggleReadIndex_ < toggleCycles_.size() &&
70
+ toggleCycles_[toggleReadIndex_] < sampleCycleEnd) {
71
+ uint64_t toggleCycle = toggleCycles_[toggleReadIndex_];
72
+
73
+ if (toggleCycle >= sampleCycleStart) {
74
+ float cycles = static_cast<float>(toggleCycle - lastCycle);
75
+ if (currentState) {
76
+ highTime += cycles;
77
+ } else {
78
+ lowTime += cycles;
79
+ }
80
+ lastCycle = toggleCycle;
81
+ }
82
+
83
+ currentState = !currentState;
84
+ toggleReadIndex_++;
85
+ }
86
+
87
+ float cycles = static_cast<float>(sampleCycleEnd - lastCycle);
88
+ if (currentState) {
89
+ highTime += cycles;
90
+ } else {
91
+ lowTime += cycles;
92
+ }
93
+
94
+ speakerState_ = currentState;
95
+
96
+ float totalTime = highTime + lowTime;
97
+ float rawValue = 0.0f;
98
+ if (totalTime > 0) {
99
+ rawValue = (highTime - lowTime) / totalTime;
100
+ }
101
+
102
+ filterState_ = filterState_ + FILTER_ALPHA * (rawValue - filterState_);
103
+ dcOffset_ = DC_ALPHA * dcOffset_ + (1.0f - DC_ALPHA) * filterState_;
104
+ float dcCorrected = filterState_ - dcOffset_;
105
+ float sample = std::max(-1.0f, std::min(1.0f, dcCorrected));
106
+
107
+ speakerBuffer[i] = sample;
108
+ }
109
+
110
+ if (toggleReadIndex_ > 0) {
111
+ toggleCycles_.erase(toggleCycles_.begin(),
112
+ toggleCycles_.begin() + toggleReadIndex_);
113
+ toggleReadIndex_ = 0;
114
+ }
115
+
116
+ lastSampleCycle_ = currentCycle;
117
+
118
+ // When muted, zero the output but keep speaker state tracking intact
119
+ if (muted_) {
120
+ for (int i = 0; i < sampleCount * 2; i++) {
121
+ buffer[i] = 0.0f;
122
+ }
123
+ // Still consume Mockingboard samples to keep them in sync
124
+ if (mockingboard_) {
125
+ std::vector<float> mbDiscard(sampleCount * 2);
126
+ mockingboard_->consumeStereoSamples(mbDiscard.data(), sampleCount);
127
+ }
128
+ return sampleCount;
129
+ }
130
+
131
+ // Get stereo Mockingboard samples (incrementally generated during CPU execution)
132
+ std::vector<float> mbBuffer(sampleCount * 2, 0.0f);
133
+ if (mockingboard_) {
134
+ mockingboard_->consumeStereoSamples(mbBuffer.data(), sampleCount);
135
+ }
136
+
137
+ // Mix speaker (center) with Mockingboard stereo
138
+ // Scale both sources by 0.5 to prevent clipping when both are active
139
+ constexpr float MIX_SCALE = 0.5f;
140
+
141
+ for (int i = 0; i < sampleCount; i++) {
142
+ float speakerSample = speakerBuffer[i] * MIX_SCALE;
143
+
144
+ // Mockingboard: PSG1 left, PSG2 right (already properly normalized)
145
+ float mbLeft = mbBuffer[i * 2] * MIX_SCALE;
146
+ float mbRight = mbBuffer[i * 2 + 1] * MIX_SCALE;
147
+
148
+ // Mix: speaker goes to both channels
149
+ float left = speakerSample + mbLeft;
150
+ float right = speakerSample + mbRight;
151
+
152
+ // Clamp to valid range (should rarely clip now)
153
+ buffer[i * 2] = std::max(-1.0f, std::min(1.0f, left));
154
+ buffer[i * 2 + 1] = std::max(-1.0f, std::min(1.0f, right));
155
+ }
156
+
157
+ return sampleCount;
158
+ }
159
+
160
+ } // namespace a2e
@@ -0,0 +1,81 @@
1
+ /*
2
+ * audio.hpp - Speaker audio emulation
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include "../types.hpp"
11
+ #include <array>
12
+ #include <cstdint>
13
+ #include <vector>
14
+
15
+ namespace a2e {
16
+
17
+ // Forward declaration
18
+ class MockingboardCard;
19
+
20
+ class Audio {
21
+ public:
22
+ static constexpr int BUFFER_SIZE = 4096;
23
+ static constexpr int MAX_TOGGLES = 8192;
24
+
25
+ Audio();
26
+
27
+ // Speaker toggle (called when $C030 is accessed)
28
+ void toggleSpeaker(uint64_t cycleCount);
29
+
30
+ // Generate stereo audio samples (interleaved L/R)
31
+ // Mockingboard: PSG1 on left, PSG2 on right
32
+ // Speaker: centered (both channels)
33
+ // Returns the number of sample frames generated
34
+ int generateStereoSamples(float *buffer, int sampleCount, uint64_t currentCycle);
35
+
36
+ // Reset
37
+ void reset();
38
+
39
+ // Volume control (0.0 - 1.0)
40
+ void setVolume(float volume) { volume_ = volume; }
41
+ float getVolume() const { return volume_; }
42
+
43
+ // Mute control
44
+ void setMuted(bool muted) { muted_ = muted; }
45
+ bool isMuted() const { return muted_; }
46
+
47
+ // Speaker state (for state serialization)
48
+ bool getSpeakerState() const { return speakerState_; }
49
+
50
+ // Mockingboard connection
51
+ void setMockingboard(MockingboardCard* mb) { mockingboard_ = mb; }
52
+
53
+ private:
54
+ // Speaker state
55
+ bool speakerState_ = false;
56
+
57
+ // Toggle event recording
58
+ std::vector<uint64_t> toggleCycles_;
59
+ size_t toggleReadIndex_ = 0;
60
+
61
+ // Audio generation state
62
+ uint64_t lastSampleCycle_ = 0;
63
+
64
+ // Simple low-pass filter state
65
+ // Alpha ~0.15 gives cutoff ~7.8kHz at 48kHz, preserving speaker harmonics
66
+ float filterState_ = 0.0f;
67
+ static constexpr float FILTER_ALPHA = 0.15f;
68
+
69
+ // Volume
70
+ float volume_ = 0.5f;
71
+ bool muted_ = false;
72
+
73
+ // DC offset removal - fast enough to track speaker state changes
74
+ float dcOffset_ = 0.0f;
75
+ static constexpr float DC_ALPHA = 0.995f;
76
+
77
+ // Mockingboard
78
+ MockingboardCard* mockingboard_ = nullptr;
79
+ };
80
+
81
+ } // namespace a2e