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,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
|