web-a2e 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,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
|
+
}
|