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,616 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ay8910.cpp - AY-3-8910 sound chip emulation implementation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "ay8910.hpp"
|
|
9
|
+
#include <cmath>
|
|
10
|
+
#ifdef __EMSCRIPTEN__
|
|
11
|
+
#include <emscripten.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
namespace a2e {
|
|
15
|
+
|
|
16
|
+
// Debug logging flag - set via setDebugLogging()
|
|
17
|
+
static bool debugLogging_ = false;
|
|
18
|
+
|
|
19
|
+
void AY8910::setDebugLogging(bool enabled) {
|
|
20
|
+
debugLogging_ = enabled;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void AY8910::setPsgId(int id) {
|
|
24
|
+
psgId_ = id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static const char* getRegisterName(int reg) {
|
|
28
|
+
static const char* names[] = {
|
|
29
|
+
"ToneA_Fine", "ToneA_Coarse", "ToneB_Fine", "ToneB_Coarse",
|
|
30
|
+
"ToneC_Fine", "ToneC_Coarse", "NoisePeriod", "Mixer",
|
|
31
|
+
"AmpA", "AmpB", "AmpC", "EnvFine", "EnvCoarse", "EnvShape",
|
|
32
|
+
"IOPortA", "IOPortB"
|
|
33
|
+
};
|
|
34
|
+
return (reg >= 0 && reg < 16) ? names[reg] : "Unknown";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Volume table based on AppleWin/MAME measurements
|
|
38
|
+
// Values represent amplitude levels for 4-bit volume (0-15)
|
|
39
|
+
// Converted from 16-bit values: 0x0000, 0x0385, 0x053D, 0x0770, etc.
|
|
40
|
+
const float AY8910::volumeTable_[16] = {
|
|
41
|
+
0.0000f, 0.0137f, 0.0205f, 0.0291f,
|
|
42
|
+
0.0423f, 0.0618f, 0.0847f, 0.1369f,
|
|
43
|
+
0.1691f, 0.2647f, 0.3527f, 0.4499f,
|
|
44
|
+
0.5704f, 0.6873f, 0.8482f, 1.0000f
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
AY8910::AY8910() {
|
|
48
|
+
reset();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void AY8910::reset() {
|
|
52
|
+
registers_.fill(0);
|
|
53
|
+
currentRegister_ = 0;
|
|
54
|
+
|
|
55
|
+
// Set mixer to 0x3F - all tone and noise DISABLED for silence
|
|
56
|
+
// Bits 0-2: tone disable (1=disabled), Bits 3-5: noise disable (1=disabled)
|
|
57
|
+
registers_[REG_MIXER] = 0x3F;
|
|
58
|
+
|
|
59
|
+
toneCounters_.fill(0);
|
|
60
|
+
toneOutput_.fill(false);
|
|
61
|
+
|
|
62
|
+
noiseCounter_ = 0;
|
|
63
|
+
noiseShiftReg_ = 1; // Must be non-zero
|
|
64
|
+
noiseToggle_ = false;
|
|
65
|
+
|
|
66
|
+
envCounter_ = 0;
|
|
67
|
+
envVolume_ = 0;
|
|
68
|
+
envHolding_ = false;
|
|
69
|
+
envContinue_ = false;
|
|
70
|
+
envAttack_ = false;
|
|
71
|
+
envAlternate_ = false;
|
|
72
|
+
envHold_ = false;
|
|
73
|
+
|
|
74
|
+
phaseAccumulator_ = 0.0;
|
|
75
|
+
|
|
76
|
+
// Clear any pending register writes
|
|
77
|
+
pendingWrites_.clear();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void AY8910::setRegisterAddress(uint8_t address) {
|
|
81
|
+
currentRegister_ = address & 0x0F;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
void AY8910::writeRegister(uint8_t value) {
|
|
85
|
+
// Track writes for debugging
|
|
86
|
+
writeCount_++;
|
|
87
|
+
lastWriteReg_ = currentRegister_;
|
|
88
|
+
lastWriteVal_ = value;
|
|
89
|
+
|
|
90
|
+
// Apply register write immediately
|
|
91
|
+
applyRegisterWrite(currentRegister_, value);
|
|
92
|
+
|
|
93
|
+
#ifdef __EMSCRIPTEN__
|
|
94
|
+
if (debugLogging_) {
|
|
95
|
+
const char* regName = getRegisterName(currentRegister_);
|
|
96
|
+
EM_ASM({
|
|
97
|
+
const reg = $0;
|
|
98
|
+
const val = $1;
|
|
99
|
+
const regName = UTF8ToString($2);
|
|
100
|
+
const psgId = $3;
|
|
101
|
+
console.log(`PSG${psgId}: R${reg} (${regName}) = $${val.toString(16).toUpperCase().padStart(2,'0')} (${val})`);
|
|
102
|
+
}, currentRegister_, value, regName, psgId_);
|
|
103
|
+
}
|
|
104
|
+
#endif
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
void AY8910::applyRegisterWrite(uint8_t reg, uint8_t value) {
|
|
108
|
+
// Apply masks based on register
|
|
109
|
+
switch (reg) {
|
|
110
|
+
case REG_TONE_A_COARSE:
|
|
111
|
+
case REG_TONE_B_COARSE:
|
|
112
|
+
case REG_TONE_C_COARSE:
|
|
113
|
+
value &= 0x0F; // 4-bit coarse tune
|
|
114
|
+
break;
|
|
115
|
+
case REG_NOISE_PERIOD:
|
|
116
|
+
value &= 0x1F; // 5-bit noise period
|
|
117
|
+
break;
|
|
118
|
+
case REG_AMP_A:
|
|
119
|
+
case REG_AMP_B:
|
|
120
|
+
case REG_AMP_C:
|
|
121
|
+
value &= 0x1F; // 5-bit (bit 4 = envelope mode)
|
|
122
|
+
break;
|
|
123
|
+
case REG_ENV_SHAPE:
|
|
124
|
+
value &= 0x0F; // 4-bit envelope shape
|
|
125
|
+
// Writing to envelope shape resets the envelope
|
|
126
|
+
envCounter_ = 0;
|
|
127
|
+
envHolding_ = false;
|
|
128
|
+
// Decode envelope shape bits: CONT ATT ALT HOLD (bits 3-0)
|
|
129
|
+
envContinue_ = (value & 0x08) != 0;
|
|
130
|
+
envAttack_ = (value & 0x04) != 0;
|
|
131
|
+
envAlternate_ = (value & 0x02) != 0;
|
|
132
|
+
envHold_ = (value & 0x01) != 0;
|
|
133
|
+
// Set initial volume based on direction
|
|
134
|
+
if (envAttack_) {
|
|
135
|
+
envVolume_ = 0; // Start at 0 for attack (rising)
|
|
136
|
+
} else {
|
|
137
|
+
envVolume_ = 15; // Start at 15 for decay (falling)
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
registers_[reg] = value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
uint8_t AY8910::readRegister() const {
|
|
146
|
+
return registers_[currentRegister_];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
uint16_t AY8910::getTonePeriod(int channel) const {
|
|
150
|
+
int fineReg = channel * 2;
|
|
151
|
+
int coarseReg = channel * 2 + 1;
|
|
152
|
+
return registers_[fineReg] | ((registers_[coarseReg] & 0x0F) << 8);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
uint8_t AY8910::getNoisePeriod() const {
|
|
156
|
+
return registers_[REG_NOISE_PERIOD] & 0x1F;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
uint16_t AY8910::getEnvPeriod() const {
|
|
160
|
+
return registers_[REG_ENV_FINE] | (registers_[REG_ENV_COARSE] << 8);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
void AY8910::updateToneGenerator(int channel) {
|
|
164
|
+
uint16_t period = getTonePeriod(channel);
|
|
165
|
+
if (period == 0) period = 1; // Avoid division by zero
|
|
166
|
+
|
|
167
|
+
toneCounters_[channel]++;
|
|
168
|
+
if (toneCounters_[channel] >= period) {
|
|
169
|
+
toneCounters_[channel] = 0;
|
|
170
|
+
toneOutput_[channel] = !toneOutput_[channel];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void AY8910::updateNoiseGenerator() {
|
|
175
|
+
uint8_t period = getNoisePeriod();
|
|
176
|
+
|
|
177
|
+
// Period 0 acts as period 1 (highest frequency noise)
|
|
178
|
+
if (period == 0) period = 1;
|
|
179
|
+
|
|
180
|
+
noiseCounter_++;
|
|
181
|
+
// Noise runs at clock/16 while tones run at clock/8
|
|
182
|
+
// Since we step at clock/8 rate, double the period comparison
|
|
183
|
+
if (noiseCounter_ >= static_cast<uint32_t>(period) * 2) {
|
|
184
|
+
noiseCounter_ = 0;
|
|
185
|
+
|
|
186
|
+
// MAME-style 17-bit LFSR: feedback = bit0 XOR bit3, inject at bit16, shift right
|
|
187
|
+
// Noise output is directly bit 0 of the shift register (no toggle mechanism)
|
|
188
|
+
uint32_t feedback = (noiseShiftReg_ & 1) ^ ((noiseShiftReg_ >> 3) & 1);
|
|
189
|
+
noiseShiftReg_ = (noiseShiftReg_ >> 1) | (feedback << 16);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
void AY8910::updateEnvelopeGenerator() {
|
|
194
|
+
if (envHolding_) return;
|
|
195
|
+
|
|
196
|
+
uint16_t period = getEnvPeriod();
|
|
197
|
+
|
|
198
|
+
envCounter_++;
|
|
199
|
+
// Envelope counter runs at the same rate as tone counters (master/8).
|
|
200
|
+
// FUSE/AppleWin compare directly against the period value — no multiplier.
|
|
201
|
+
// The datasheet's fE = fCLOCK/(256*EP) refers to a full 32-step triangle
|
|
202
|
+
// (16 up + 16 down), so each individual step = EP ticks at master/8.
|
|
203
|
+
// Period 0 should be treated same as period 1 (minimum period, highest frequency)
|
|
204
|
+
// per MAME/AppleWin implementations - avoids undefined behavior.
|
|
205
|
+
uint32_t effectivePeriod = (period == 0) ? 1 : period;
|
|
206
|
+
uint32_t threshold = static_cast<uint32_t>(effectivePeriod);
|
|
207
|
+
if (envCounter_ >= threshold) {
|
|
208
|
+
envCounter_ = 0;
|
|
209
|
+
|
|
210
|
+
// Update envelope volume based on current direction
|
|
211
|
+
if (envAttack_) {
|
|
212
|
+
// Attack (rising)
|
|
213
|
+
if (envVolume_ < 15) {
|
|
214
|
+
envVolume_++;
|
|
215
|
+
} else {
|
|
216
|
+
// Reached max (15) - handle end of cycle
|
|
217
|
+
handleEnvelopeCycleEnd();
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Decay (falling)
|
|
221
|
+
if (envVolume_ > 0) {
|
|
222
|
+
envVolume_--;
|
|
223
|
+
} else {
|
|
224
|
+
// Reached min (0) - handle end of cycle
|
|
225
|
+
handleEnvelopeCycleEnd();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
void AY8910::handleEnvelopeCycleEnd() {
|
|
232
|
+
// Called when envelope reaches its limit (0 or 15)
|
|
233
|
+
// Envelope shape bits: CONT(3) ATT(2) ALT(1) HOLD(0)
|
|
234
|
+
|
|
235
|
+
if (!envContinue_) {
|
|
236
|
+
// CONT=0: After first cycle, always hold at 0
|
|
237
|
+
envVolume_ = 0;
|
|
238
|
+
envHolding_ = true;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// CONT=1: Continue behavior depends on ALT and HOLD
|
|
243
|
+
if (envHold_) {
|
|
244
|
+
// HOLD=1: Stop after this cycle
|
|
245
|
+
if (envAlternate_) {
|
|
246
|
+
// ALT=1, HOLD=1: Hold at opposite extreme
|
|
247
|
+
// If we were attacking (going up), hold at 0
|
|
248
|
+
// If we were decaying (going down), hold at 15
|
|
249
|
+
envVolume_ = envAttack_ ? 0 : 15;
|
|
250
|
+
}
|
|
251
|
+
// else ALT=0, HOLD=1: Hold at current extreme (already there)
|
|
252
|
+
envHolding_ = true;
|
|
253
|
+
} else {
|
|
254
|
+
// HOLD=0: Continue cycling
|
|
255
|
+
if (envAlternate_) {
|
|
256
|
+
// ALT=1: Reverse direction (triangle wave)
|
|
257
|
+
envAttack_ = !envAttack_;
|
|
258
|
+
} else {
|
|
259
|
+
// ALT=0: Reset to start (sawtooth wave)
|
|
260
|
+
envVolume_ = envAttack_ ? 0 : 15;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
float AY8910::getChannelOutput(int channel) const {
|
|
266
|
+
uint8_t mixer = registers_[REG_MIXER];
|
|
267
|
+
uint8_t ampReg = registers_[REG_AMP_A + channel];
|
|
268
|
+
|
|
269
|
+
uint8_t volume;
|
|
270
|
+
if (ampReg & 0x10) {
|
|
271
|
+
volume = envVolume_;
|
|
272
|
+
} else {
|
|
273
|
+
volume = ampReg & 0x0F;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (volume == 0) return 0.0f;
|
|
277
|
+
|
|
278
|
+
float level = volumeTable_[volume];
|
|
279
|
+
|
|
280
|
+
// MAME mixer: output = (tone_out | tone_disable) & (noise_out | noise_disable)
|
|
281
|
+
// Register bit = 1 means disabled (bypassed/always high)
|
|
282
|
+
// Unipolar output matching real hardware: 0 or +level (never negative)
|
|
283
|
+
bool toneDisable = (mixer & (1 << channel)) != 0;
|
|
284
|
+
bool noiseDisable = (mixer & (1 << (channel + 3))) != 0;
|
|
285
|
+
|
|
286
|
+
bool toneOut = toneOutput_[channel] || toneDisable;
|
|
287
|
+
bool noiseOut = ((noiseShiftReg_ & 1) != 0) || noiseDisable;
|
|
288
|
+
|
|
289
|
+
return (toneOut && noiseOut) ? level : 0.0f;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
void AY8910::setChannelMute(int channel, bool muted) {
|
|
293
|
+
if (channel >= 0 && channel < NUM_CHANNELS) {
|
|
294
|
+
channelMuted_[channel] = muted;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
bool AY8910::isChannelMuted(int channel) const {
|
|
299
|
+
if (channel >= 0 && channel < NUM_CHANNELS) {
|
|
300
|
+
return channelMuted_[channel];
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
float AY8910::computeMixerOutput() const {
|
|
306
|
+
uint8_t mixer = registers_[REG_MIXER];
|
|
307
|
+
float sample = 0.0f;
|
|
308
|
+
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
|
309
|
+
if (channelMuted_[ch]) continue;
|
|
310
|
+
|
|
311
|
+
uint8_t ampReg = registers_[REG_AMP_A + ch];
|
|
312
|
+
uint8_t volume = (ampReg & 0x10) ? envVolume_ : (ampReg & 0x0F);
|
|
313
|
+
if (volume == 0) continue;
|
|
314
|
+
|
|
315
|
+
float level = volumeTable_[volume];
|
|
316
|
+
|
|
317
|
+
bool toneDisable = (mixer & (1 << ch)) != 0;
|
|
318
|
+
bool noiseDisable = (mixer & (1 << (ch + 3))) != 0;
|
|
319
|
+
bool toneOut = toneOutput_[ch] || toneDisable;
|
|
320
|
+
bool noiseOut = ((noiseShiftReg_ & 1) != 0) || noiseDisable;
|
|
321
|
+
|
|
322
|
+
sample += (toneOut && noiseOut) ? level : 0.0f;
|
|
323
|
+
}
|
|
324
|
+
return sample / 3.0f;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
void AY8910::generateSamples(float* buffer, int count, int sampleRate) {
|
|
328
|
+
// Legacy version - apply any pending writes immediately and generate
|
|
329
|
+
for (const auto& write : pendingWrites_) {
|
|
330
|
+
applyRegisterWrite(write.reg, write.value);
|
|
331
|
+
}
|
|
332
|
+
pendingWrites_.clear();
|
|
333
|
+
|
|
334
|
+
// PSG clock cycles per audio sample
|
|
335
|
+
double cyclesPerSample = static_cast<double>(PSG_CLOCK) / sampleRate;
|
|
336
|
+
double toneStepsPerSample = cyclesPerSample / 8.0;
|
|
337
|
+
|
|
338
|
+
for (int i = 0; i < count; i++) {
|
|
339
|
+
phaseAccumulator_ += toneStepsPerSample;
|
|
340
|
+
|
|
341
|
+
// Advance PSG state and average output across all ticks
|
|
342
|
+
// This properly captures noise-tone gate interactions at clock resolution
|
|
343
|
+
float sampleAccum = 0.0f;
|
|
344
|
+
int ticks = 0;
|
|
345
|
+
while (phaseAccumulator_ >= 1.0) {
|
|
346
|
+
phaseAccumulator_ -= 1.0;
|
|
347
|
+
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
|
348
|
+
updateToneGenerator(ch);
|
|
349
|
+
}
|
|
350
|
+
updateNoiseGenerator();
|
|
351
|
+
updateEnvelopeGenerator();
|
|
352
|
+
sampleAccum += computeMixerOutput();
|
|
353
|
+
ticks++;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
float sample = (ticks > 0) ? sampleAccum / static_cast<float>(ticks) : computeMixerOutput();
|
|
357
|
+
|
|
358
|
+
buffer[i] = sample;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
void AY8910::generateSamples(float* buffer, int count, int sampleRate, uint64_t startCycle, uint64_t endCycle) {
|
|
363
|
+
// PSG clock cycles per audio sample
|
|
364
|
+
double cyclesPerSample = static_cast<double>(PSG_CLOCK) / sampleRate;
|
|
365
|
+
double toneStepsPerSample = cyclesPerSample / 8.0;
|
|
366
|
+
|
|
367
|
+
// Calculate CPU cycles per sample for timing
|
|
368
|
+
double cpuCyclesTotal = static_cast<double>(endCycle - startCycle);
|
|
369
|
+
double cpuCyclesPerSample = (count > 0) ? cpuCyclesTotal / count : 0;
|
|
370
|
+
|
|
371
|
+
// Index into pending writes
|
|
372
|
+
size_t writeIdx = 0;
|
|
373
|
+
|
|
374
|
+
for (int i = 0; i < count; i++) {
|
|
375
|
+
// Calculate the CPU cycle for this sample
|
|
376
|
+
uint64_t sampleCycle = startCycle + static_cast<uint64_t>(i * cpuCyclesPerSample);
|
|
377
|
+
|
|
378
|
+
// Apply any pending writes that should happen before this sample
|
|
379
|
+
while (writeIdx < pendingWrites_.size() && pendingWrites_[writeIdx].cycle <= sampleCycle) {
|
|
380
|
+
applyRegisterWrite(pendingWrites_[writeIdx].reg, pendingWrites_[writeIdx].value);
|
|
381
|
+
writeIdx++;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Advance PSG state and average output across all ticks
|
|
385
|
+
phaseAccumulator_ += toneStepsPerSample;
|
|
386
|
+
|
|
387
|
+
float sampleAccum = 0.0f;
|
|
388
|
+
int ticks = 0;
|
|
389
|
+
while (phaseAccumulator_ >= 1.0) {
|
|
390
|
+
phaseAccumulator_ -= 1.0;
|
|
391
|
+
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
|
392
|
+
updateToneGenerator(ch);
|
|
393
|
+
}
|
|
394
|
+
updateNoiseGenerator();
|
|
395
|
+
updateEnvelopeGenerator();
|
|
396
|
+
sampleAccum += computeMixerOutput();
|
|
397
|
+
ticks++;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
float sample = (ticks > 0) ? sampleAccum / static_cast<float>(ticks) : computeMixerOutput();
|
|
401
|
+
|
|
402
|
+
buffer[i] = sample;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Apply any remaining writes (for the end of the buffer)
|
|
406
|
+
while (writeIdx < pendingWrites_.size()) {
|
|
407
|
+
applyRegisterWrite(pendingWrites_[writeIdx].reg, pendingWrites_[writeIdx].value);
|
|
408
|
+
writeIdx++;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Clear processed writes
|
|
412
|
+
pendingWrites_.clear();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
float AY8910::generateSingleSample() {
|
|
416
|
+
// Precomputed constants for 48kHz sample rate
|
|
417
|
+
// toneStepsPerSample = PSG_CLOCK / (48000 * 8) = 1023000 / 384000 ≈ 2.6640625
|
|
418
|
+
static constexpr double TONE_STEPS = 1023000.0 / (48000.0 * 8.0);
|
|
419
|
+
|
|
420
|
+
// Advance PSG state and average output across all ticks
|
|
421
|
+
// Evaluating the mixer at each tick captures noise-tone gate interactions
|
|
422
|
+
// at the PSG clock resolution, preventing aliased modulation artifacts
|
|
423
|
+
phaseAccumulator_ += TONE_STEPS;
|
|
424
|
+
|
|
425
|
+
float sampleAccum = 0.0f;
|
|
426
|
+
int ticks = 0;
|
|
427
|
+
while (phaseAccumulator_ >= 1.0) {
|
|
428
|
+
phaseAccumulator_ -= 1.0;
|
|
429
|
+
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
|
430
|
+
updateToneGenerator(ch);
|
|
431
|
+
}
|
|
432
|
+
updateNoiseGenerator();
|
|
433
|
+
updateEnvelopeGenerator();
|
|
434
|
+
sampleAccum += computeMixerOutput();
|
|
435
|
+
ticks++;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return (ticks > 0) ? sampleAccum / static_cast<float>(ticks) : computeMixerOutput();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
void AY8910::generateChannelSamples(float* buffer, int count, int sampleRate, int channel) {
|
|
442
|
+
if (channel < 0 || channel >= NUM_CHANNELS) {
|
|
443
|
+
for (int i = 0; i < count; i++) {
|
|
444
|
+
buffer[i] = 0.0f;
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// PSG clock cycles per audio sample
|
|
450
|
+
double cyclesPerSample = static_cast<double>(PSG_CLOCK) / sampleRate;
|
|
451
|
+
double toneStepsPerSample = cyclesPerSample / 8.0;
|
|
452
|
+
|
|
453
|
+
for (int i = 0; i < count; i++) {
|
|
454
|
+
phaseAccumulator_ += toneStepsPerSample;
|
|
455
|
+
|
|
456
|
+
// Advance all generators (needed for accurate state)
|
|
457
|
+
while (phaseAccumulator_ >= 1.0) {
|
|
458
|
+
phaseAccumulator_ -= 1.0;
|
|
459
|
+
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
|
460
|
+
updateToneGenerator(ch);
|
|
461
|
+
}
|
|
462
|
+
updateNoiseGenerator();
|
|
463
|
+
updateEnvelopeGenerator();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Unipolar output for visualization (no DC removal - shows raw waveform)
|
|
467
|
+
uint8_t mixer = registers_[REG_MIXER];
|
|
468
|
+
uint8_t ampReg = registers_[REG_AMP_A + channel];
|
|
469
|
+
uint8_t volume = (ampReg & 0x10) ? envVolume_ : (ampReg & 0x0F);
|
|
470
|
+
if (volume == 0) {
|
|
471
|
+
buffer[i] = 0.0f;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
float level = volumeTable_[volume];
|
|
476
|
+
|
|
477
|
+
bool toneDisable = (mixer & (1 << channel)) != 0;
|
|
478
|
+
bool noiseDisable = (mixer & (1 << (channel + 3))) != 0;
|
|
479
|
+
bool toneOut = toneOutput_[channel] || toneDisable;
|
|
480
|
+
bool noiseOut = ((noiseShiftReg_ & 1) != 0) || noiseDisable;
|
|
481
|
+
|
|
482
|
+
buffer[i] = (toneOut && noiseOut) ? level : 0.0f;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
size_t AY8910::exportState(uint8_t* buffer) const {
|
|
487
|
+
size_t offset = 0;
|
|
488
|
+
|
|
489
|
+
// 16 registers
|
|
490
|
+
for (int i = 0; i < 16; i++) {
|
|
491
|
+
buffer[offset++] = registers_[i];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Current register address
|
|
495
|
+
buffer[offset++] = currentRegister_;
|
|
496
|
+
|
|
497
|
+
// Tone counters (3 x 4 bytes = 12 bytes)
|
|
498
|
+
for (int i = 0; i < 3; i++) {
|
|
499
|
+
buffer[offset++] = (toneCounters_[i] >> 0) & 0xFF;
|
|
500
|
+
buffer[offset++] = (toneCounters_[i] >> 8) & 0xFF;
|
|
501
|
+
buffer[offset++] = (toneCounters_[i] >> 16) & 0xFF;
|
|
502
|
+
buffer[offset++] = (toneCounters_[i] >> 24) & 0xFF;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Tone outputs (1 byte packed)
|
|
506
|
+
buffer[offset++] = (toneOutput_[0] ? 1 : 0) |
|
|
507
|
+
(toneOutput_[1] ? 2 : 0) |
|
|
508
|
+
(toneOutput_[2] ? 4 : 0);
|
|
509
|
+
|
|
510
|
+
// Noise state
|
|
511
|
+
buffer[offset++] = noiseToggle_ ? 1 : 0;
|
|
512
|
+
|
|
513
|
+
// Envelope state
|
|
514
|
+
buffer[offset++] = envVolume_;
|
|
515
|
+
|
|
516
|
+
// Additional state for proper audio continuity (added in state version 5)
|
|
517
|
+
// Noise counter (4 bytes)
|
|
518
|
+
buffer[offset++] = (noiseCounter_ >> 0) & 0xFF;
|
|
519
|
+
buffer[offset++] = (noiseCounter_ >> 8) & 0xFF;
|
|
520
|
+
buffer[offset++] = (noiseCounter_ >> 16) & 0xFF;
|
|
521
|
+
buffer[offset++] = (noiseCounter_ >> 24) & 0xFF;
|
|
522
|
+
|
|
523
|
+
// Noise shift register (4 bytes) - critical for noise pattern continuity
|
|
524
|
+
buffer[offset++] = (noiseShiftReg_ >> 0) & 0xFF;
|
|
525
|
+
buffer[offset++] = (noiseShiftReg_ >> 8) & 0xFF;
|
|
526
|
+
buffer[offset++] = (noiseShiftReg_ >> 16) & 0xFF;
|
|
527
|
+
buffer[offset++] = (noiseShiftReg_ >> 24) & 0xFF;
|
|
528
|
+
|
|
529
|
+
// Envelope counter (4 bytes)
|
|
530
|
+
buffer[offset++] = (envCounter_ >> 0) & 0xFF;
|
|
531
|
+
buffer[offset++] = (envCounter_ >> 8) & 0xFF;
|
|
532
|
+
buffer[offset++] = (envCounter_ >> 16) & 0xFF;
|
|
533
|
+
buffer[offset++] = (envCounter_ >> 24) & 0xFF;
|
|
534
|
+
|
|
535
|
+
// Envelope flags (1 byte packed)
|
|
536
|
+
buffer[offset++] = (envHolding_ ? 0x01 : 0) |
|
|
537
|
+
(envAttack_ ? 0x02 : 0);
|
|
538
|
+
|
|
539
|
+
// Pad to STATE_SIZE for consistent serialization
|
|
540
|
+
while (offset < STATE_SIZE) {
|
|
541
|
+
buffer[offset++] = 0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return offset; // Exactly STATE_SIZE bytes
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
void AY8910::importState(const uint8_t* buffer) {
|
|
548
|
+
size_t offset = 0;
|
|
549
|
+
|
|
550
|
+
// Clear any pending register writes from before state import
|
|
551
|
+
pendingWrites_.clear();
|
|
552
|
+
|
|
553
|
+
// 16 registers
|
|
554
|
+
for (int i = 0; i < 16; i++) {
|
|
555
|
+
registers_[i] = buffer[offset++];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Current register address
|
|
559
|
+
currentRegister_ = buffer[offset++];
|
|
560
|
+
|
|
561
|
+
// Tone counters
|
|
562
|
+
for (int i = 0; i < 3; i++) {
|
|
563
|
+
toneCounters_[i] = buffer[offset] |
|
|
564
|
+
(buffer[offset + 1] << 8) |
|
|
565
|
+
(buffer[offset + 2] << 16) |
|
|
566
|
+
(buffer[offset + 3] << 24);
|
|
567
|
+
offset += 4;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Tone outputs
|
|
571
|
+
uint8_t outputs = buffer[offset++];
|
|
572
|
+
toneOutput_[0] = (outputs & 1) != 0;
|
|
573
|
+
toneOutput_[1] = (outputs & 2) != 0;
|
|
574
|
+
toneOutput_[2] = (outputs & 4) != 0;
|
|
575
|
+
|
|
576
|
+
// Noise state
|
|
577
|
+
noiseToggle_ = buffer[offset++] != 0;
|
|
578
|
+
|
|
579
|
+
// Envelope state
|
|
580
|
+
envVolume_ = buffer[offset++];
|
|
581
|
+
|
|
582
|
+
// Additional state for proper audio continuity (added in state version 5)
|
|
583
|
+
// Noise counter (4 bytes)
|
|
584
|
+
noiseCounter_ = buffer[offset] |
|
|
585
|
+
(buffer[offset + 1] << 8) |
|
|
586
|
+
(buffer[offset + 2] << 16) |
|
|
587
|
+
(buffer[offset + 3] << 24);
|
|
588
|
+
offset += 4;
|
|
589
|
+
|
|
590
|
+
// Noise shift register (4 bytes)
|
|
591
|
+
noiseShiftReg_ = buffer[offset] |
|
|
592
|
+
(buffer[offset + 1] << 8) |
|
|
593
|
+
(buffer[offset + 2] << 16) |
|
|
594
|
+
(buffer[offset + 3] << 24);
|
|
595
|
+
offset += 4;
|
|
596
|
+
|
|
597
|
+
// Envelope counter (4 bytes)
|
|
598
|
+
envCounter_ = buffer[offset] |
|
|
599
|
+
(buffer[offset + 1] << 8) |
|
|
600
|
+
(buffer[offset + 2] << 16) |
|
|
601
|
+
(buffer[offset + 3] << 24);
|
|
602
|
+
offset += 4;
|
|
603
|
+
|
|
604
|
+
// Envelope flags (1 byte packed)
|
|
605
|
+
uint8_t envFlags = buffer[offset++];
|
|
606
|
+
envHolding_ = (envFlags & 0x01) != 0;
|
|
607
|
+
envAttack_ = (envFlags & 0x02) != 0;
|
|
608
|
+
|
|
609
|
+
// Restore envelope shape flags from register 13 (these are constant per shape)
|
|
610
|
+
uint8_t envShape = registers_[REG_ENV_SHAPE] & 0x0F;
|
|
611
|
+
envContinue_ = (envShape & 0x08) != 0;
|
|
612
|
+
envAlternate_ = (envShape & 0x02) != 0;
|
|
613
|
+
envHold_ = (envShape & 0x01) != 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
} // namespace a2e
|