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,196 @@
1
+ /*
2
+ * test_audio.cpp - Unit tests for Audio (speaker) emulation
3
+ */
4
+
5
+ #define CATCH_CONFIG_MAIN
6
+ #include "catch.hpp"
7
+
8
+ #include "audio.hpp"
9
+
10
+ #include <cmath>
11
+ #include <vector>
12
+
13
+ using namespace a2e;
14
+
15
+ // ============================================================================
16
+ // Constructor
17
+ // ============================================================================
18
+
19
+ TEST_CASE("Audio constructor creates a valid instance", "[audio][ctor]") {
20
+ Audio audio;
21
+ // Default state: speaker off, volume 0.5, not muted
22
+ CHECK(audio.getSpeakerState() == false);
23
+ CHECK(audio.getVolume() == Approx(0.5f));
24
+ CHECK(audio.isMuted() == false);
25
+ }
26
+
27
+ // ============================================================================
28
+ // toggleSpeaker
29
+ // ============================================================================
30
+
31
+ TEST_CASE("toggleSpeaker records events and state updates on sample generation", "[audio][toggle]") {
32
+ Audio audio;
33
+ CHECK(audio.getSpeakerState() == false);
34
+
35
+ // toggleSpeaker only records toggle events; it does NOT immediately
36
+ // change speakerState_. The state is updated when generateStereoSamples
37
+ // processes the recorded toggle events.
38
+ audio.toggleSpeaker(100);
39
+
40
+ // State is still false until samples are generated
41
+ CHECK(audio.getSpeakerState() == false);
42
+
43
+ // Generate samples to process the toggle events
44
+ const int count = 128;
45
+ std::vector<float> buffer(count * 2, 0.0f);
46
+ audio.generateStereoSamples(buffer.data(), count, 10000);
47
+
48
+ // After generating samples with one toggle, speaker state should be true
49
+ CHECK(audio.getSpeakerState() == true);
50
+
51
+ // Toggle twice more and generate samples
52
+ audio.toggleSpeaker(11000);
53
+ audio.toggleSpeaker(12000);
54
+ audio.generateStereoSamples(buffer.data(), count, 20000);
55
+
56
+ // Two more toggles from true: true->false->true
57
+ CHECK(audio.getSpeakerState() == true);
58
+ }
59
+
60
+ // ============================================================================
61
+ // generateStereoSamples
62
+ // ============================================================================
63
+
64
+ TEST_CASE("generateStereoSamples returns sample count", "[audio][generate]") {
65
+ Audio audio;
66
+ const int count = 128;
67
+ std::vector<float> buffer(count * 2, 0.0f); // stereo interleaved
68
+
69
+ int generated = audio.generateStereoSamples(buffer.data(), count, 10000);
70
+ CHECK(generated == count);
71
+ }
72
+
73
+ TEST_CASE("Silence: no toggles produces near-zero output", "[audio][silence]") {
74
+ Audio audio;
75
+ const int count = 256;
76
+ std::vector<float> buffer(count * 2, 1.0f); // Fill with non-zero
77
+
78
+ audio.generateStereoSamples(buffer.data(), count, 50000);
79
+
80
+ // After generating samples with no toggles, output should be near zero
81
+ float maxAbs = 0.0f;
82
+ for (int i = 0; i < count * 2; i++) {
83
+ float absVal = std::fabs(buffer[i]);
84
+ if (absVal > maxAbs) maxAbs = absVal;
85
+ }
86
+ // With DC removal and no toggles, output should settle toward zero
87
+ CHECK(maxAbs < 0.5f);
88
+ }
89
+
90
+ TEST_CASE("Toggle produces non-zero output", "[audio][toggle_output]") {
91
+ Audio audio;
92
+ audio.setVolume(1.0f);
93
+
94
+ // Toggle the speaker rapidly to create audio signal
95
+ for (int i = 0; i < 100; i++) {
96
+ audio.toggleSpeaker(i * 50);
97
+ }
98
+
99
+ const int count = 512;
100
+ std::vector<float> buffer(count * 2, 0.0f);
101
+ audio.generateStereoSamples(buffer.data(), count, 5100);
102
+
103
+ // Check there is at least some non-zero output
104
+ float maxAbs = 0.0f;
105
+ for (int i = 0; i < count * 2; i++) {
106
+ float absVal = std::fabs(buffer[i]);
107
+ if (absVal > maxAbs) maxAbs = absVal;
108
+ }
109
+ CHECK(maxAbs > 0.0f);
110
+ }
111
+
112
+ // ============================================================================
113
+ // Volume control
114
+ // ============================================================================
115
+
116
+ TEST_CASE("setVolume/getVolume round-trips correctly", "[audio][volume]") {
117
+ Audio audio;
118
+
119
+ audio.setVolume(0.0f);
120
+ CHECK(audio.getVolume() == Approx(0.0f));
121
+
122
+ audio.setVolume(1.0f);
123
+ CHECK(audio.getVolume() == Approx(1.0f));
124
+
125
+ audio.setVolume(0.75f);
126
+ CHECK(audio.getVolume() == Approx(0.75f));
127
+ }
128
+
129
+ // ============================================================================
130
+ // Mute control
131
+ // ============================================================================
132
+
133
+ TEST_CASE("setMuted/isMuted round-trips correctly", "[audio][mute]") {
134
+ Audio audio;
135
+
136
+ CHECK(audio.isMuted() == false);
137
+
138
+ audio.setMuted(true);
139
+ CHECK(audio.isMuted() == true);
140
+
141
+ audio.setMuted(false);
142
+ CHECK(audio.isMuted() == false);
143
+ }
144
+
145
+ TEST_CASE("Muted audio produces zero output", "[audio][mute_output]") {
146
+ Audio audio;
147
+ audio.setMuted(true);
148
+ audio.setVolume(1.0f);
149
+
150
+ // Generate toggles
151
+ for (int i = 0; i < 100; i++) {
152
+ audio.toggleSpeaker(i * 50);
153
+ }
154
+
155
+ const int count = 256;
156
+ std::vector<float> buffer(count * 2, 1.0f); // Fill with non-zero
157
+ audio.generateStereoSamples(buffer.data(), count, 5100);
158
+
159
+ // All samples should be zero when muted
160
+ for (int i = 0; i < count * 2; i++) {
161
+ CHECK(buffer[i] == Approx(0.0f));
162
+ }
163
+ }
164
+
165
+ // ============================================================================
166
+ // reset
167
+ // ============================================================================
168
+
169
+ TEST_CASE("reset clears speaker state", "[audio][reset]") {
170
+ Audio audio;
171
+
172
+ // Toggle speaker and generate samples so state is updated
173
+ audio.toggleSpeaker(100);
174
+ const int count = 128;
175
+ std::vector<float> buffer(count * 2, 0.0f);
176
+ audio.generateStereoSamples(buffer.data(), count, 10000);
177
+ CHECK(audio.getSpeakerState() == true);
178
+
179
+ audio.reset();
180
+
181
+ CHECK(audio.getSpeakerState() == false);
182
+ }
183
+
184
+ TEST_CASE("reset preserves volume and mute settings", "[audio][reset]") {
185
+ Audio audio;
186
+
187
+ audio.setVolume(0.8f);
188
+ audio.setMuted(true);
189
+
190
+ audio.reset();
191
+
192
+ // Volume and mute are user settings, typically preserved across reset
193
+ // (though this depends on implementation; verify actual behavior)
194
+ // The speaker state itself should be reset
195
+ CHECK(audio.getSpeakerState() == false);
196
+ }
@@ -0,0 +1,311 @@
1
+ /*
2
+ * test_ay8910.cpp - Unit tests for AY-3-8910 sound chip emulation
3
+ */
4
+
5
+ #define CATCH_CONFIG_MAIN
6
+ #include "catch.hpp"
7
+
8
+ #include "ay8910.hpp"
9
+
10
+ #include <vector>
11
+ #include <cmath>
12
+
13
+ using namespace a2e;
14
+
15
+ // Helper: write a value to a register
16
+ static void writeReg(AY8910& psg, uint8_t reg, uint8_t value) {
17
+ psg.setRegisterAddress(reg);
18
+ psg.writeRegister(value);
19
+ }
20
+
21
+ // Helper: read a register value
22
+ static uint8_t readReg(AY8910& psg, uint8_t reg) {
23
+ psg.setRegisterAddress(reg);
24
+ return psg.readRegister();
25
+ }
26
+
27
+ // ============================================================================
28
+ // Constructor
29
+ // ============================================================================
30
+
31
+ TEST_CASE("AY8910 constructor creates valid instance", "[ay8910][ctor]") {
32
+ AY8910 psg;
33
+ // Registers should be initialized to 0, except register 7 (mixer)
34
+ // which defaults to 0x3F (all tone and noise channels disabled for silence)
35
+ for (int r = 0; r < 16; r++) {
36
+ if (r == 7) {
37
+ CHECK(psg.getRegister(r) == 0x3F);
38
+ } else {
39
+ CHECK(psg.getRegister(r) == 0);
40
+ }
41
+ }
42
+ }
43
+
44
+ // ============================================================================
45
+ // Register access cycle
46
+ // ============================================================================
47
+
48
+ TEST_CASE("setRegisterAddress/writeRegister/readRegister cycle", "[ay8910][reg]") {
49
+ AY8910 psg;
50
+
51
+ // Write to register 0 (Tone A fine)
52
+ psg.setRegisterAddress(0);
53
+ psg.writeRegister(0x5A);
54
+
55
+ // Read back
56
+ psg.setRegisterAddress(0);
57
+ uint8_t val = psg.readRegister();
58
+ CHECK(val == 0x5A);
59
+ }
60
+
61
+ // ============================================================================
62
+ // Tone period registers (0-5)
63
+ // ============================================================================
64
+
65
+ TEST_CASE("Tone period registers write and read back", "[ay8910][tone]") {
66
+ AY8910 psg;
67
+
68
+ SECTION("Channel A tone period") {
69
+ writeReg(psg, 0, 0xAB); // Fine
70
+ writeReg(psg, 1, 0x0C); // Coarse (4-bit)
71
+ CHECK(readReg(psg, 0) == 0xAB);
72
+ CHECK(readReg(psg, 1) == 0x0C); // Only low 4 bits
73
+ }
74
+
75
+ SECTION("Channel B tone period") {
76
+ writeReg(psg, 2, 0x34);
77
+ writeReg(psg, 3, 0x05);
78
+ CHECK(readReg(psg, 2) == 0x34);
79
+ CHECK(readReg(psg, 3) == 0x05);
80
+ }
81
+
82
+ SECTION("Channel C tone period") {
83
+ writeReg(psg, 4, 0xFF);
84
+ writeReg(psg, 5, 0x0F);
85
+ CHECK(readReg(psg, 4) == 0xFF);
86
+ CHECK(readReg(psg, 5) == 0x0F);
87
+ }
88
+ }
89
+
90
+ // ============================================================================
91
+ // Noise register (6) - 5 bit
92
+ // ============================================================================
93
+
94
+ TEST_CASE("Noise period register is 5-bit", "[ay8910][noise]") {
95
+ AY8910 psg;
96
+ writeReg(psg, 6, 0xFF);
97
+ // Only lower 5 bits should be stored
98
+ CHECK(readReg(psg, 6) == 0x1F);
99
+ }
100
+
101
+ TEST_CASE("Noise register write and read back", "[ay8910][noise]") {
102
+ AY8910 psg;
103
+ writeReg(psg, 6, 0x0A);
104
+ CHECK(readReg(psg, 6) == 0x0A);
105
+ }
106
+
107
+ // ============================================================================
108
+ // Mixer register (7)
109
+ // ============================================================================
110
+
111
+ TEST_CASE("Mixer register controls tone/noise per channel", "[ay8910][mixer]") {
112
+ AY8910 psg;
113
+ // Bit layout: B7=IOB B6=IOA B5=NoiseC B4=NoiseB B3=NoiseA B2=ToneC B1=ToneB B0=ToneA
114
+ // 1 = disabled, 0 = enabled
115
+ writeReg(psg, 7, 0x38); // Disable all noise, enable all tone
116
+ CHECK(readReg(psg, 7) == 0x38);
117
+
118
+ writeReg(psg, 7, 0x3F); // Disable everything
119
+ CHECK(readReg(psg, 7) == 0x3F);
120
+
121
+ writeReg(psg, 7, 0x00); // Enable everything
122
+ CHECK(readReg(psg, 7) == 0x00);
123
+ }
124
+
125
+ // ============================================================================
126
+ // Volume registers (8-10)
127
+ // ============================================================================
128
+
129
+ TEST_CASE("Volume registers write and read back", "[ay8910][volume]") {
130
+ AY8910 psg;
131
+
132
+ SECTION("Channel A volume") {
133
+ writeReg(psg, 8, 0x0F); // Max volume, no envelope
134
+ CHECK(readReg(psg, 8) == 0x0F);
135
+ }
136
+
137
+ SECTION("Channel B volume with envelope mode") {
138
+ writeReg(psg, 9, 0x10); // Bit 4 = use envelope
139
+ CHECK(readReg(psg, 9) == 0x10);
140
+ }
141
+
142
+ SECTION("Channel C volume") {
143
+ writeReg(psg, 10, 0x0A);
144
+ CHECK(readReg(psg, 10) == 0x0A);
145
+ }
146
+ }
147
+
148
+ // ============================================================================
149
+ // Envelope registers (11-13)
150
+ // ============================================================================
151
+
152
+ TEST_CASE("Envelope period registers", "[ay8910][envelope]") {
153
+ AY8910 psg;
154
+
155
+ writeReg(psg, 11, 0xCD); // Envelope fine
156
+ writeReg(psg, 12, 0xAB); // Envelope coarse
157
+ CHECK(readReg(psg, 11) == 0xCD);
158
+ CHECK(readReg(psg, 12) == 0xAB);
159
+ }
160
+
161
+ TEST_CASE("Envelope shape register", "[ay8910][envelope]") {
162
+ AY8910 psg;
163
+
164
+ // 4-bit shape control
165
+ writeReg(psg, 13, 0x0E);
166
+ CHECK(readReg(psg, 13) == 0x0E);
167
+ }
168
+
169
+ // ============================================================================
170
+ // reset
171
+ // ============================================================================
172
+
173
+ TEST_CASE("reset clears all registers", "[ay8910][reset]") {
174
+ AY8910 psg;
175
+
176
+ // Set various registers
177
+ writeReg(psg, 0, 0xFF);
178
+ writeReg(psg, 7, 0x00);
179
+ writeReg(psg, 8, 0x0F);
180
+ writeReg(psg, 13, 0x0E);
181
+
182
+ psg.reset();
183
+
184
+ for (int r = 0; r < 16; r++) {
185
+ INFO("Register " << r);
186
+ if (r == 7) {
187
+ // Mixer register resets to 0x3F (all channels disabled for silence)
188
+ CHECK(psg.getRegister(r) == 0x3F);
189
+ } else {
190
+ CHECK(psg.getRegister(r) == 0);
191
+ }
192
+ }
193
+ }
194
+
195
+ // ============================================================================
196
+ // Channel muting
197
+ // ============================================================================
198
+
199
+ TEST_CASE("Channel muting defaults to unmuted", "[ay8910][mute]") {
200
+ AY8910 psg;
201
+ CHECK(psg.isChannelMuted(0) == false);
202
+ CHECK(psg.isChannelMuted(1) == false);
203
+ CHECK(psg.isChannelMuted(2) == false);
204
+ }
205
+
206
+ TEST_CASE("setChannelMute/isChannelMuted round-trips", "[ay8910][mute]") {
207
+ AY8910 psg;
208
+
209
+ psg.setChannelMute(0, true);
210
+ CHECK(psg.isChannelMuted(0) == true);
211
+ CHECK(psg.isChannelMuted(1) == false);
212
+ CHECK(psg.isChannelMuted(2) == false);
213
+
214
+ psg.setChannelMute(1, true);
215
+ CHECK(psg.isChannelMuted(1) == true);
216
+
217
+ psg.setChannelMute(0, false);
218
+ CHECK(psg.isChannelMuted(0) == false);
219
+ }
220
+
221
+ // ============================================================================
222
+ // generateSamples
223
+ // ============================================================================
224
+
225
+ TEST_CASE("generateSamples produces output", "[ay8910][generate]") {
226
+ AY8910 psg;
227
+
228
+ // Enable tone on channel A, set frequency and volume
229
+ writeReg(psg, 0, 0x10); // Tone A fine = low period for audible freq
230
+ writeReg(psg, 1, 0x00); // Tone A coarse
231
+ writeReg(psg, 7, 0x3E); // Enable tone A only (disable noise all, tone B+C)
232
+ writeReg(psg, 8, 0x0F); // Channel A volume max
233
+
234
+ const int count = 256;
235
+ std::vector<float> buffer(count, 0.0f);
236
+ psg.generateSamples(buffer.data(), count, 48000);
237
+
238
+ // With a short tone period and max volume, there should be non-zero output
239
+ float maxAbs = 0.0f;
240
+ for (int i = 0; i < count; i++) {
241
+ float absVal = std::fabs(buffer[i]);
242
+ if (absVal > maxAbs) maxAbs = absVal;
243
+ }
244
+ CHECK(maxAbs > 0.0f);
245
+ }
246
+
247
+ TEST_CASE("generateSamples all channels muted produces silence", "[ay8910][generate]") {
248
+ AY8910 psg;
249
+
250
+ // Set up tone but mute all channels
251
+ writeReg(psg, 0, 0x10);
252
+ writeReg(psg, 7, 0x3E);
253
+ writeReg(psg, 8, 0x0F);
254
+
255
+ psg.setChannelMute(0, true);
256
+ psg.setChannelMute(1, true);
257
+ psg.setChannelMute(2, true);
258
+
259
+ const int count = 128;
260
+ std::vector<float> buffer(count, 1.0f);
261
+ psg.generateSamples(buffer.data(), count, 48000);
262
+
263
+ for (int i = 0; i < count; i++) {
264
+ CHECK(buffer[i] == Approx(0.0f));
265
+ }
266
+ }
267
+
268
+ // ============================================================================
269
+ // Debug counters
270
+ // ============================================================================
271
+
272
+ TEST_CASE("Write count tracks register writes", "[ay8910][debug]") {
273
+ AY8910 psg;
274
+ CHECK(psg.getWriteCount() == 0);
275
+
276
+ writeReg(psg, 0, 0x42);
277
+ CHECK(psg.getWriteCount() == 1);
278
+
279
+ writeReg(psg, 1, 0x03);
280
+ CHECK(psg.getWriteCount() == 2);
281
+ }
282
+
283
+ TEST_CASE("Last write tracking", "[ay8910][debug]") {
284
+ AY8910 psg;
285
+
286
+ writeReg(psg, 5, 0xAB);
287
+ CHECK(psg.getLastWriteReg() == 5);
288
+ CHECK(psg.getLastWriteVal() == 0xAB);
289
+ }
290
+
291
+ // ============================================================================
292
+ // State serialization size
293
+ // ============================================================================
294
+
295
+ TEST_CASE("State serialization exports and imports", "[ay8910][state]") {
296
+ AY8910 psg1;
297
+ writeReg(psg1, 0, 0x55);
298
+ writeReg(psg1, 7, 0x38);
299
+ writeReg(psg1, 8, 0x0F);
300
+
301
+ uint8_t stateBuffer[AY8910::STATE_SIZE];
302
+ size_t written = psg1.exportState(stateBuffer);
303
+ REQUIRE(written == AY8910::STATE_SIZE);
304
+
305
+ AY8910 psg2;
306
+ psg2.importState(stateBuffer);
307
+
308
+ CHECK(psg2.getRegister(0) == 0x55);
309
+ CHECK(psg2.getRegister(7) == 0x38);
310
+ CHECK(psg2.getRegister(8) == 0x0F);
311
+ }