OXYGENHUB

chore(): initial commit 03a3459e6372

Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-20 22:39 UTC
Signature
✔ signed by Andy McCall
Hash
03a3459e6372147b5d16d8410463208c38a7530ff5522230ddd2dddbb8f0829a
Depends on
nothing: a root change
Depended on by
8799a5e4565e bd65c0cc08c7 35bd422c9032 8e8d9cad1884 39d98c9da02a 12673a41ba17 0adc2e74f5cf 51099110490b 9a6dd8288e68 6b218d3faaaf ea9efff4e540 11debbbf5303
In bookmarks
main riscv-1
Edits
128 atoms, 137 tree operations, 128 files

Browse the tree at this change

added .clang-format +79 −0
1 +
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
2 +
3 +
BasedOnStyle: LLVM
4 +
AccessModifierOffset: -4
5 +
AlignAfterOpenBracket: AlwaysBreak
6 +
AlignConsecutiveAssignments: true
7 +
AlignConsecutiveDeclarations: true
8 +
AlignEscapedNewlinesLeft: true
9 +
AlignOperands: true
10 +
AlignTrailingComments: true
11 +
AllowAllParametersOfDeclarationOnNextLine: true
12 +
AllowShortBlocksOnASingleLine: false
13 +
AllowShortCaseLabelsOnASingleLine: true
14 +
AllowShortFunctionsOnASingleLine: false
15 +
AllowShortIfStatementsOnASingleLine: false
16 +
AllowShortLoopsOnASingleLine: false
17 +
AlwaysBreakAfterDefinitionReturnType: All
18 +
AlwaysBreakAfterReturnType: None
19 +
AlwaysBreakBeforeMultilineStrings: true
20 +
# AlwaysBreakTemplateDeclarations (bool)
21 +
# BinPackArguments (bool)
22 +
BinPackParameters: true
23 +
# BraceWrapping (BraceWrappingFlags)
24 +
# BreakAfterJavaFieldAnnotations (bool)
25 +
# BreakBeforeBinaryOperators (BinaryOperatorStyle)
26 +
BreakBeforeBraces: Linux
27 +
# BreakBeforeTernaryOperators (bool)
28 +
# BreakConstructorInitializersBeforeComma (bool)
29 +
# BreakStringLiterals (bool)
30 +
ColumnLimit: 0
31 +
# CommentPragmas (std::string)
32 +
# ConstructorInitializerAllOnOneLineOrOnePerLine (bool)
33 +
# ConstructorInitializerIndentWidth (unsigned)
34 +
# ContinuationIndentWidth (unsigned)
35 +
# Cpp11BracedListStyle (bool)
36 +
DerivePointerAlignment: false
37 +
# DisableFormat (bool)
38 +
# ExperimentalAutoDetectBinPacking (bool)
39 +
# ForEachMacros (std::vector<std::string>)
40 +
# IncludeCategories (std::vector<IncludeCategory>)
41 +
# IncludeIsMainRegex (std::string)
42 +
IndentCaseLabels: true
43 +
IndentWidth: 4
44 +
# IndentWrappedFunctionNames (bool)
45 +
# JavaScriptQuotes (JavaScriptQuoteStyle)
46 +
# KeepEmptyLinesAtTheStartOfBlocks (bool)
47 +
# Language (LanguageKind)
48 +
# MacroBlockBegin (std::string)
49 +
# MacroBlockEnd (std::string)
50 +
# MaxEmptyLinesToKeep (unsigned)
51 +
NamespaceIndentation: All
52 +
# ObjCBlockIndentWidth (unsigned)
53 +
# ObjCSpaceAfterProperty (bool)
54 +
# ObjCSpaceBeforeProtocolList (bool)
55 +
# PenaltyBreakBeforeFirstCallParameter (unsigned)
56 +
# PenaltyBreakComment (unsigned)
57 +
# PenaltyBreakFirstLessLess (unsigned)
58 +
# PenaltyBreakString (unsigned)
59 +
# PenaltyExcessCharacter (unsigned)
60 +
# PenaltyReturnTypeOnItsOwnLine: 1000
61 +
PointerAlignment: Right
62 +
# ReflowComments (bool)
63 +
SortIncludes: false
64 +
# SpaceAfterCStyleCast (bool)
65 +
# SpaceAfterTemplateKeyword (bool)
66 +
SpaceBeforeAssignmentOperators: true
67 +
SpaceBeforeParens: ControlStatements
68 +
SpaceInEmptyParentheses: false
69 +
# SpacesBeforeTrailingComments (unsigned)
70 +
# SpacesInAngles (bool)
71 +
SpacesInCStyleCastParentheses: false
72 +
# SpacesInContainerLiterals (bool)
73 +
SpacesInParentheses: false
74 +
SpacesInSquareBrackets: false
75 +
# Standard (LanguageStandard)
76 +
TabWidth: 4
77 +
UseTab: ForIndentation
78 +
79 +
IndentPPDirectives: AfterHash
added .editorconfig +24 −0
1 +
root = true
2 +
3 +
[*]
4 +
charset = utf-8
5 +
end_of_line = lf
6 +
indent_style = tab
7 +
insert_final_newline = true
8 +
tab_width = 4
9 +
trim_trailing_whitespace = true
10 +
11 +
[README]
12 +
indent_style = space
13 +
14 +
[*.yml]
15 +
indent_style = space
16 +
17 +
[*.py]
18 +
indent_style = space
19 +
20 +
[*.md]
21 +
indent_style = space
22 +
23 +
[{*.css,*.html,*.js}]
24 +
indent_style = space
added .oxyignore +66 −0
1 +
# Arcade reference material (ROM dumps, disassembly, sprite sheets) —
2 +
# local-only, never shipped. Generated headers derived from it ARE
3 +
# committed (same policy as the sibling pac-man-x16 project).
4 +
reference/
5 +
6 +
# Git 
7 +
.git
8 +
.github
9 +
.gitignore
10 +
.gh
11 +
.github-pandoc.css
12 +
.gitmodules
13 +
14 +
# Legacy build
15 +
.travis
16 +
.travis.yml
17 +
18 +
# The Flow image .flow/image/build.sh exports: about 2.3 GB.
19 +
/.flow/image/*.tar
20 +
21 +
# Build output
22 +
bin/
23 +
release/
24 +
build/
25 +
26 +
# Emulator scratch (memory.dump, storage volume side effects)
27 +
memory.dump
28 +
*.neo
29 +
!src/**/*.neo
30 +
31 +
# llvm-mos / C build artefacts
32 +
*.o
33 +
*.obj
34 +
*.elf
35 +
*.lst
36 +
*.map
37 +
38 +
# Editor / OS noise
39 +
.vscode/
40 +
.idea/
41 +
*.swp
42 +
.DS_Store
43 +
44 +
# Tiled session state
45 +
*.tiled-session
46 +
47 +
# Python
48 +
__pycache__/
49 +
*.pyc
50 +
.ruff_cache/
51 +
.pytest_cache/
52 +
.venv/
53 +
venv/
54 +
55 +
# build-generated
56 +
src/generated/
57 +
58 +
# Local cross-session coordination + audit scratch — shared out-of-band by
59 +
# absolute path, deliberately never committed (a stray `git add -A` swept the
60 +
# board into a PR once; this stops it recurring).
61 +
docs/calculator_codesign.md
62 +
agy_audit.md
63 +
reference/
64 +
65 +
# AI
66 +
.claude
added CODE_OF_CONDUCT.md +76 −0
1 +
# Contributor Covenant Code of Conduct
2 +
3 +
## Our Pledge
4 +
5 +
In the interest of fostering an open and welcoming environment, we as
6 +
contributors and maintainers pledge to making participation in our project and
7 +
our community a harassment-free experience for everyone, regardless of age, body
8 +
size, disability, ethnicity, sex characteristics, gender identity and expression,
9 +
level of experience, education, socio-economic status, nationality, personal
10 +
appearance, race, religion, or sexual identity and orientation.
11 +
12 +
## Our Standards
13 +
14 +
Examples of behavior that contributes to creating a positive environment
15 +
include:
16 +
17 +
* Using welcoming and inclusive language
18 +
* Being respectful of differing viewpoints and experiences
19 +
* Gracefully accepting constructive criticism
20 +
* Focusing on what is best for the community
21 +
* Showing empathy towards other community members
22 +
23 +
Examples of unacceptable behavior by participants include:
24 +
25 +
* The use of sexualized language or imagery and unwelcome sexual attention or
26 +
 advances
27 +
* Trolling, insulting/derogatory comments, and personal or political attacks
28 +
* Public or private harassment
29 +
* Publishing others' private information, such as a physical or electronic
30 +
 address, without explicit permission
31 +
* Other conduct which could reasonably be considered inappropriate in a
32 +
 professional setting
33 +
34 +
## Our Responsibilities
35 +
36 +
Project maintainers are responsible for clarifying the standards of acceptable
37 +
behavior and are expected to take appropriate and fair corrective action in
38 +
response to any instances of unacceptable behavior.
39 +
40 +
Project maintainers have the right and responsibility to remove, edit, or
41 +
reject comments, commits, code, wiki edits, issues, and other contributions
42 +
that are not aligned to this Code of Conduct, or to ban temporarily or
43 +
permanently any contributor for other behaviors that they deem inappropriate,
44 +
threatening, offensive, or harmful.
45 +
46 +
## Scope
47 +
48 +
This Code of Conduct applies both within project spaces and in public spaces
49 +
when an individual is representing the project or its community. Examples of
50 +
representing a project or community include using an official project e-mail
51 +
address, posting via an official social media account, or acting as an appointed
52 +
representative at an online or offline event. Representation of a project may be
53 +
further defined and clarified by project maintainers.
54 +
55 +
## Enforcement
56 +
57 +
Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 +
reported by contacting the project team at [email protected]. All
59 +
complaints will be reviewed and investigated and will result in a response that
60 +
is deemed necessary and appropriate to the circumstances. The project team is
61 +
obligated to maintain confidentiality with regard to the reporter of an incident.
62 +
Further details of specific enforcement policies may be posted separately.
63 +
64 +
Project maintainers who do not follow or enforce the Code of Conduct in good
65 +
faith may face temporary or permanent repercussions as determined by other
66 +
members of the project's leadership.
67 +
68 +
## Attribution
69 +
70 +
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 +
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 +
73 +
[homepage]: https://www.contributor-covenant.org
74 +
75 +
For answers to common questions about this code of conduct, see
76 +
https://www.contributor-covenant.org/faq
added LICENSE +9 −0
1 +
Copyright (c) 2019 Michael Steil
2 +
3 +
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 +
5 +
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 +
7 +
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 +
9 +
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
added Makefile +145 −0
1 +
##################################################################################################
2 +
#
3 +
# COMMANDER X16 EMULATOR MAKEFILE
4 +
#
5 +
##################################################################################################
6 +
7 +
ifeq ($(CROSS_COMPILE_WINDOWS),1)
8 +
	SDL2CONFIG?=$(WIN_SDL2)/bin/sdl2-config --prefix=$(WIN_SDL2)
9 +
else
10 +
	SDL2CONFIG=sdl2-config
11 +
endif
12 +
13 +
ifeq ($(CROSS_COMPILE_WINDOWS),1)
14 +
	TARGET_WIN32=yes
15 +
else ifeq ($(OS),Windows_NT)
16 +
	TARGET_WIN32=yes
17 +
endif
18 +
19 +
CFLAGS=-std=c11 -O3 -Wall -Werror -g $(shell $(SDL2CONFIG) --cflags) -Isrc/extern/include -Isrc/extern/retro-remote-debug-controller/core -pthread
20 +
CXXFLAGS=-std=c++17 -O3 -Wall -Werror -Isrc/extern/ymfm/src
21 +
LDFLAGS=$(shell $(SDL2CONFIG) --libs) -lm -lz -pthread
22 +
23 +
ifdef ADDL_INCLUDE
24 +
	CFLAGS+=-I$(ADDL_INCLUDE)
25 +
endif
26 +
27 +
X16_ODIR = build/x16emu
28 +
X16_SDIR = src
29 +
30 +
MAKECART_ODIR = build/makecart
31 +
MAKECART_SDIR = src
32 +
33 +
ifdef TRACE
34 +
	CFLAGS+=-D TRACE
35 +
endif
36 +
37 +
X16_OUTPUT=x16emu
38 +
MAKECART_OUTPUT=makecart
39 +
40 +
GIT_REV=$(shell git diff --quiet && /bin/echo -n $$(git rev-parse --short=8 HEAD || /bin/echo "00000000") || /bin/echo -n $$( /bin/echo -n $$(git rev-parse --short=7 HEAD || /bin/echo "0000000"); /bin/echo -n '+'))
41 +
42 +
CFLAGS+=-D GIT_REV='"$(GIT_REV)"'
43 +
44 +
ifeq ($(MAC_STATIC),1)
45 +
	LDFLAGS=$(LIBSDL_FILE) -lm -liconv -lz -Wl,-framework,CoreAudio -Wl,-framework,AudioToolbox -Wl,-framework,ForceFeedback -lobjc -Wl,-framework,CoreVideo -Wl,-framework,Cocoa -Wl,-framework,Carbon -Wl,-framework,IOKit -Wl,-weak_framework,QuartzCore -Wl,-weak_framework,Metal -Wl,-weak_framework,CoreHaptics -Wl,-weak_framework,GameController -pthread
46 +
	LDEMU=-ldl -Wl,-rpath,$(HOMEBREW_LIB)
47 +
else ifeq ($(CROSS_COMPILE_WINDOWS),1)
48 +
	LDFLAGS+=-L$(MINGW32)/lib
49 +
	# this enables printf() to show, but also forces a console window
50 +
	LDFLAGS+=-Wl,--subsystem,console
51 +
	LDFLAGS+=-static-libstdc++ -static-libgcc
52 +
ifeq ($(TARGET_CPU),x86)
53 +
	CC=i686-w64-mingw32-gcc
54 +
	CXX=i686-w64-mingw32-g++
55 +
else
56 +
	CC=x86_64-w64-mingw32-gcc
57 +
	CXX=x86_64-w64-mingw32-g++
58 +
endif
59 +
else # Not Mac, not Windows, probably Linux
60 +
	LDEMU=-ldl
61 +
endif
62 +
63 +
# build with link time optimization
64 +
ifndef NOLTO
65 +
	CFLAGS+=-flto
66 +
	LDFLAGS+=-flto
67 +
endif
68 +
69 +
ifdef TARGET_WIN32
70 +
	LDFLAGS+=-ldwmapi
71 +
endif
72 +
73 +
ifdef EMSCRIPTEN
74 +
	LDFLAGS+=--shell-file webassembly/x16emu-template.html --preload-file rom.bin -s TOTAL_MEMORY=32MB -s ASSERTIONS=1 -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1
75 +
	# To the Javascript runtime exported functions
76 +
	LDFLAGS+=-s EXPORTED_FUNCTIONS='["_j2c_reset", "_j2c_paste", "_j2c_start_audio", _main]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s USE_ZLIB=1 -s EXIT_RUNTIME=1
77 +
	CFLAGS+=-s USE_ZLIB=1
78 +
	X16_OUTPUT=x16emu.html
79 +
	MAKECART_OUTPUT=makecart.html
80 +
else
81 +
	LDFLAGS+=-pthread
82 +
endif
83 +
84 +
ifeq ($(FLUIDSYNTH),1)
85 +
	CFLAGS+=-DHAS_FLUIDSYNTH
86 +
endif
87 +
88 +
_X16_OBJS = cpu/fake6502.o memory.o disasm.o video.o i2c.o smc.o rtc.o via.o serial.o ieee.o vera_spi.o audio.o vera_pcm.o vera_psg.o sdcard.o main.o debugger.o javascript_interface.o joystick.o rendertext.o keyboard.o icon.o timing.o wav_recorder.o testbench.o files.o cartridge.o iso_8859_15.o ymglue.o midi.o netcard.o coverage.o
89 +
_X16_OBJS += extern/ymfm/src/ymfm_opm.o
90 +
# retro-remote-debug-controller: x16 backend + portable server core (submodule)
91 +
_X16_OBJS += control_backend.o extern/retro-remote-debug-controller/core/retro_control.o
92 +
93 +
ifdef TARGET_WIN32
94 +
	_X16_OBJS += video_win32.o
95 +
endif
96 +
97 +
X16_OBJS = $(patsubst %,$(X16_ODIR)/%,$(_X16_OBJS))
98 +
X16_DEPS := $(X16_OBJS:.o=.d)
99 +
100 +
_MAKECART_OBJS = makecart.o files.o cartridge.o makecart_javascript_interface.o
101 +
102 +
MAKECART_OBJS = $(patsubst %,$(X16_ODIR)/%,$(_MAKECART_OBJS))
103 +
MAKECART_DEPS := $(MAKECART_OBJS:.o=.d)
104 +
105 +
.PHONY: all clean wasm
106 +
all: x16emu makecart
107 +
108 +
x16emu: $(X16_OBJS)
109 +
	$(CXX) -o $(X16_OUTPUT) $(X16_OBJS) $(LDFLAGS) $(LDEMU)
110 +
111 +
$(X16_ODIR)/%.o: $(X16_SDIR)/%.c
112 +
	@mkdir -p $$(dirname $@)
113 +
	$(CC) $(CFLAGS) -c $< -MD -MT $@ -MF $(@:%o=%d) -o $@
114 +
115 +
$(X16_ODIR)/%.o: $(X16_SDIR)/%.cpp
116 +
	@mkdir -p $$(dirname $@)
117 +
	$(CXX) $(CXXFLAGS) -c $< -MD -MT $@ -MF $(@:%o=%d) -o $@
118 +
119 +
makecart: $(MAKECART_OBJS)
120 +
	$(CC) -o $(MAKECART_OUTPUT) $(MAKECART_OBJS) $(LDFLAGS)
121 +
122 +
$(MAKECART_ODIR)/%.o: $(MAKECART_SDIR)/%.c
123 +
	@mkdir -p $$(dirname $@)
124 +
	$(CC) $(CFLAGS) -c $< -MD -MT $@ -MF $(@:%o=%d) -o $@
125 +
126 +
cpu/tables.h cpu/mnemonics.h: cpu/buildtables.py cpu/6502.opcodes cpu/65c816.opcodes
127 +
	cd cpu && python buildtables.py
128 +
129 +
# Empty rules so that renames of header files do not trigger a failure to compile
130 +
$(X16_SDIR)/%.h:;
131 +
$(MAKECART_SDIR)/%.h:;
132 +
133 +
# WebAssembly/emscripten target
134 +
#
135 +
# See webassembly/WebAssembly.md
136 +
wasm:
137 +
	emmake make
138 +
139 +
clean:
140 +
	rm -rf $(X16_ODIR) $(MAKECART_ODIR) x16emu x16emu.exe x16emu.js x16emu.wasm x16emu.data x16emu.worker.js x16emu.html x16emu.html.mem makecart makecart.exe makecart.js makecart.wasm makecart.data makecart.worker.js makecart.html makecart.html.mem
141 +
142 +
ifeq ($(filter $(MAKECMDGOALS), clean),)
143 +
-include $(X16_DEPS)
144 +
-include $(MAKECART_DEPS)
145 +
endif
added README.md +610 −0
1 +
<p align="center">
2 +
  <img src="./.gh/logo.png" />
3 +
</p>
4 +
5 +
[![Build Status](https://github.com/x16community/x16-emulator/actions/workflows/build.yml/badge.svg)](https://github.com/x16community/x16-emulator/actions/workflows/build.yml)
6 +
[![Release](https://img.shields.io/github/v/release/x16community/x16-emulator)](https://github.com/x16community/x16-emulator/releases)
7 +
[![License: BSD-Clause](https://img.shields.io/github/license/x16community/x16-emulator)](./LICENSE)
8 +
[![Contributors](https://img.shields.io/github/contributors/x16community/x16-emulator.svg)](https://github.com/x16community/x16-emulator/graphs/contributors)
9 +
10 +
This is an emulator for the Commander X16 computer system. It only depends on SDL2 and should compile on all modern operating systems.
11 +
12 +
Features
13 +
--------
14 +
15 +
* CPU: 65C02 and 65C816 instruction sets, selected by command line switch
16 +
* VERA
17 +
	* Mostly cycle exact emulation
18 +
	* Supports almost all features:
19 +
		* composer
20 +
		* two layers
21 +
		* sprites
22 +
		* VSYNC, raster, sprite IRQ
23 +
* Sound
24 +
	* PCM
25 +
	* PSG
26 +
	* YM2151
27 +
* Real-Time-Clock
28 +
* NVRAM
29 +
* System Management Controller
30 +
* SD card: reading and writing (image file)
31 +
* VIA
32 +
	* ROM/RAM banking
33 +
	* keyboard
34 +
	* mouse
35 +
	* gamepads
36 +
37 +
Binaries & Compiling
38 +
--------------------
39 +
40 +
Binary releases for macOS, Windows and Linux are available on the [releases page][releases].
41 +
42 +
The emulator itself is dependent only on SDL2. However, to run the emulated system you will also need a compatible `rom.bin` ROM image. This will be
43 +
loaded from the directory containing the emulator binary, or you can use the `-rom .../path/to/rom.bin` option.
44 +
45 +
> __WARNING:__ Older versions of the ROM might not work in newer versions of the emulator, and vice versa.
46 +
47 +
You can build a ROM image yourself using the [build instructions][x16rom-build] in the [x16-rom] repo. The `rom.bin` included in the [_latest_ release][releases] of the emulator may also work with the HEAD of this repo, but this is not guaranteed.
48 +
49 +
### macOS Build
50 +
51 +
Install SDL2 using `brew install sdl2`.
52 +
53 +
### Linux Build
54 +
55 +
The SDL2 development package is available as a distribution package with most major versions of Linux:
56 +
- Red Hat: `yum install SDL2-devel`
57 +
- Debian: `apt-get install libsdl2-dev`
58 +
59 +
Type `make` to build the source. The output will be `x16emu` in the current directory. Remember you will also need a `rom.bin` as described above.
60 +
61 +
### WebAssembly Build
62 +
63 +
Steps for compiling WebAssembly/HTML5 can be found [here][webassembly].
64 +
65 +
### Windows Build
66 +
67 +
Currently macOS/Linux/MSYS2 is needed to build for Windows. Install mingw-w64 toolchain and mingw32 version of SDL.
68 +
Type the following command to build the source:
69 +
```
70 +
CROSS_COMPILE_WINDOWS=1 MINGW32=/usr/x86_64-w64-mingw32 WIN_SDL2=/usr/x86_64-w64-mingw32 make
71 +
```
72 +
Paths to those libraries can be changed to your installation directory if they aren't located there.
73 +
74 +
The output will be `x16emu.exe` in the current directory. Remember you will also need a `rom.bin` as described above and `SDL2.dll` in SDL2's binary folder.
75 +
76 +
77 +
Starting
78 +
--------
79 +
80 +
You can start `x16emu`/`x16emu.exe` either by double-clicking it, or from the command line. The latter allows you to specify additional arguments.
81 +
When starting `x16emu` without arguments, it will pick up the system ROM (`rom.bin`) from the executable's directory.
82 +
83 +
* `-prg <app.prg>[,<load_addr>]` lets you specify a `.prg` file that gets loaded after start. It is fetched from the host filesystem, even if an SD card is attached. The override load address is hex without a prefix.
84 +
* `-bas <app.txt>` lets you specify a BASIC program in ASCII format that automatically typed in (and tokenized).
85 +
* `-run` executes the application specified through `-prg` or `-bas` using `RUN`.
86 +
* `-scale {1|2|3|4}` scales video output to an integer multiple of 640x480
87 +
* `-quality {nearest|linear|best}` change image scaling algorithm quality
88 +
    * `nearest`: nearest pixel sampling
89 +
    * `linear`: linear filtering
90 +
    * `best`: (default) anisotropic filtering
91 +
* `-widescreen` Stretch output to 16:9 resolution to mimic display of a widescreen monitor.
92 +
* `-fullscreen` Start up in fullscreen mode instead of in a window.
93 +
* `-opacity (0.0,...,1.0)` Set the opacity value (0.0 for transparent, 1.0 for opaque) of the window. (default: 1.0)
94 +
* `-rtc` causes the real-time-clock set to the system's time and date.
95 +
* `-echo [{iso|raw}]` causes all KERNAL/BASIC output to be printed to the host's terminal. Enable this and use the BASIC command "LIST" to convert a BASIC program to ASCII (detokenize).
96 +
* `-rom <rom.bin>` Override KERNAL/BASIC/* ROM file.
97 +
* `-ram <ramsize>` specifies banked RAM size in KB (8, 16, 32, ..., 2048). The default is 512.
98 +
* `-cart <crtfile.crt>` loads a cartridge file. This requires a specially formatted cartridge file, as specified in the documentation.
99 +
* `-cartbin <romfile.bin>` loads a raw cartridge file. This will be loaded starting at ROM bank 32. All cart banks will be flagged as RAM.
100 +
* `-joy1`, `-joy2`, `-joy3`, `-joy4` enables binding a gamepad to that SNES controller port
101 +
* `-nvram` lets you specify a 64 byte file for the system's non-volatile RAM. If it does not exist, it will be created once the NVRAM is modified.
102 +
* `-keymap` tells the KERNAL to switch to a specific keyboard layout. Use it without an argument to view the supported layouts.
103 +
* `-noemucmdkeys`  Disable emulator command keys. `Ctrl+M`/`⇧⌘M` will always be intercepted by the emulator.
104 +
* `-capture` starts the emulator with the mouse/keyboard captured
105 +
* `-nokeyboardcapture` prevents the emulator from fully capturing the keyboard in capture mode, which allows OS-level keystrokes like Alt+Tab to work while in capture mode.
106 +
* `-sdcard` lets you specify an SD card image (partition table + FAT32) which will be presented as device 8 at boot.
107 +
* `-hostfsdev <unit>` specifies the device number to use for the HostFS device. If this argument is not used, and `-sdcard` is specified, HostFS is disabled. If `-sdcard` is not specified, the default is 8. If both `-sdcard` and `-hostfsdev 8` are specified, HostFS will take precedence, but both will be active. In this circumstance, if the HostFS device is changed away from unit 8 via a channel 15 command (e.g. `"S-9"`), the SD card device will then become visible on unit 8.
108 +
* `-fsroot <dir>` specifies a file system root for the HostFS interface. This lets you save and load files without an SD card image. (As of R42, this is the preferred method.) Default is the current working directory.
109 +
* `-startin <dir>` specify the host filesystem directory path that the emulated filesystem starts in. Default is the current working directory if it lies within the hierarchy of fsroot, otherwise it defaults to fsroot itself.
110 +
* `-serial` makes accesses to the host filesystem go through the Serial Bus [experimental].
111 +
* `-nohostieee` or `-nohostfs` disables IEEE API interception to access the host fs. IEEE API HostFS is normally enabled unless `-sdcard` or `-serial` is specified.
112 +
* `-warp` causes the emulator to run as fast as possible, possibly faster than a real X16.
113 +
* `-pastewarp` causes the emulator to enter warp mode during pasting (`Ctrl+V` or `⌘V`) and during loading via `-bas`.
114 +
* `-gif <filename>[,wait]` to record the screen into a GIF. See below for more info.
115 +
* `-wav <filename>[{,wait|,auto}]` to record audio into a WAV. See below for more info.
116 +
* `-log` enables one or more types of logging (e.g. `-log KS`):
117 +
	* `K`: keyboard (key-up and key-down events)
118 +
	* `S`: speed (CPU load, frame misses)
119 +
	* `V`: video I/O reads and writes
120 +
* `-debug [<address>]` enables the debugger. Optionally, set a breakpoint
121 +
* `-dump` configure system dump (e.g. `-dump CB`):
122 +
	* `C`: CPU registers (7 B: A,X,Y,SP,STATUS,PC)
123 +
	* `R`: RAM (40 KiB)
124 +
	* `B`: Banked RAM (2 MiB)
125 +
	* `V`: Video RAM and registers (128 KiB VRAM, 32 B composer registers, 512 B palette, 16 B layer0 registers, 16 B layer1 registers, 16 B sprite registers, 2 KiB sprite attributes)
126 +
* `-memorystats <filename.txt>` Saves memory read and write access statistics to the given file when emulator exits.
127 +
* `-testbench` Headless mode for unit testing with an external test runner
128 +
* `-sound <device>` can be used to specify the output sound device. If 'none', no audio is generated.
129 +
* `-abufs` can be used to specify the number of audio buffers (defaults to 8 when using the SD card, 32 when using HostFS). If you're experiencing stuttering in the audio, try increasing this number. This will result in additional audio latency though.
130 +
* `-via2` installs the second VIA chip expansion at $9F10.
131 +
* `-midline-effects` enables mid-scanline raster effects at the cost of vastly increased host CPU usage.
132 +
* `-mhz <integer>` sets the emulated CPU's speed. Range is from 1-40. This option is mainly for testing and benchmarking.
133 +
* `-enable-ym2151-irq` connects the YM2151's IRQ pin to the system's IRQ line with a modest increase in host CPU usage.
134 +
* `-wuninit` enables warnings on the console for reads of uninitialized memory.
135 +
* `-zeroram` fills RAM at startup with zeroes instead of the default of random data.
136 +
* `-version` prints additional version information of the emulator and ROM.
137 +
* `-c02` selects the 65C02 CPU (default).
138 +
* `-c816` selects the 65C816 CPU (experimental).
139 +
* `-rockwell` when used while running with the 65C02 CPU, suppresses the console warning emitted on the first occurence when executing a Rockwell instruction. These are the SMBx, RMBx, BBRx, and BBSx instructions. Since these instructions are not supported on the 65C816 processor, such a program using them would not run properly on the 65C816.
140 +
* `-longpwron` Simulate a long press of the power button at system power-on.
141 +
* When compiled with `#define TRACE`, `-trace` will enable an instruction trace on stdout.
142 +
143 +
Run `x16emu -h` to see all command line options.
144 +
145 +
Keyboard Layout
146 +
---------------
147 +
148 +
The X16 uses a PS/2 keyboard, and the ROM currently supports several different layouts. The following table shows their names, and what keys produce different characters than expected:
149 +
150 +
|Name  |Description 	       |Differences|
151 +
|------|------------------------|-------|
152 +
|en-us |US		       |[`] ⇒ [←], [~] ⇒ [π], [&#92;] ⇒ [£]|
153 +
|en-gb |United Kingdom	       |[`] ⇒ [←], [~] ⇒ [π]|
154 +
|de    |German		       |[§] ⇒ [£], [´] ⇒ [^], [^] ⇒ [←], [°] ⇒ [π]|
155 +
|nordic|Nordic                 |key left of [1] ⇒ [←],[π]|
156 +
|it    |Italian		       |[&#92;] ⇒ [←], [&vert;] ⇒ [π]|
157 +
|pl    |Polish (Programmers)   |[`] ⇒ [←], [~] ⇒ [π], [&#92;] ⇒ [£]|
158 +
|hu    |Hungarian	       |[&#92;] ⇒ [←], [&vert;] ⇒ [π], [§] ⇒ [£]|
159 +
|es    |Spanish		       |[&vert;] ⇒ π, &#92; ⇒ [←], Alt + [<] ⇒ [£]|
160 +
|fr    |French		       |[²] ⇒ [←], [§] ⇒ [£]|
161 +
|de-ch |Swiss German	       |[^] ⇒ [←], [°] ⇒ [π]|
162 +
|fr-be |Belgian French	       |[²] ⇒ [←], [³] ⇒ [π]|
163 +
|fi    |Finnish		       |[§] ⇒ [←], [½] ⇒ [π]|
164 +
|pt-br |Portuguese (Brazil ABNT)|[&#92;] ⇒ [←], [&vert;] ⇒ [π]|
165 +
166 +
Keys that produce international characters (like [ä] or [ç]) will not produce any character.
167 +
168 +
Since the host computer tells the Commander X16 via the emulator the *position* of keys that are pressed, you need to configure the layout for the X16 independently of the keyboard layout you have configured on the host.
169 +
170 +
**Use the `MENU` command to select a layout, or set the keyboard layout at startup using the `-keymap` command line argument.**
171 +
172 +
The following keys can be used for controlling games:
173 +
174 +
|Keyboard Key  | SNES Equivalent |
175 +
|--------------|-----------------|
176 +
|X or Ctrl     | A               |
177 +
|Z or Alt      | B               |
178 +
|S 	           | X               |
179 +
|A 	           | Y               |
180 +
|D 	           | L               |
181 +
|C 	           | R               |
182 +
|Shift         | SELECT          |
183 +
|Enter         | START           |
184 +
|Cursor Up     | UP              |
185 +
|Cursor Down   | DOWN            |
186 +
|Cursor Left   | LEFT            |
187 +
|Cursor Right  | RIGHT           |
188 +
189 +
190 +
Options for the WebEmulator
191 +
--------
192 +
193 +
The following options are available for the WebEmulator. With the exception of manifest, they all work exactly the same as in the normal emulator.
194 +
* `manifest`
195 +
* `ram`
196 +
* `cpu`
197 +
* `mhz`
198 +
* `keymap`
199 +
* `longpwron`
200 +
* `widescreen`
201 +
* `capture`
202 +
* `midlineeffects`
203 +
204 +
#### The Address Line
205 +
`manifest` tells the emulator what should be loaded as a startup program. If the file is a .bas or .prg, the emulator will load it and try to execute it. If the file is a .zip, the WebEmulator will get access to all the files inside that zip-file. When using a zip-file you may add a manifest file to provide additional information - See section below for more information on `manifest.json`
206 +
207 +
On the Commander X16 forums, a link to the webemulator could look something like this:
208 +
https://cx16forum.org/webemu/x16emu.html?manifest=/forum/download/file.php?id=1218&ram=2048&cpu=c816&mhz=10&keymap=da&widescreen&capture  
209 +
This will load the forum file with id 1218 into the emulator.  
210 +
Give the emulator 2MB of RAM  
211 +
Set the CPU type to 65C816  
212 +
Set the CPU speed at 10 MHz  
213 +
Set the keyboard layout to Danish  
214 +
Show the emulator in widescreen mode  
215 +
Capture the mouse and keyboard input 
216 +
The options `longpwron`, `widescreen`, `capture` & `midlineeffects` do not have any values, it is enough to have them on the address line to enable the feature.
217 +
218 +
#### The manifest.json File
219 +
If an application requires more than a single file to function, for example graphics or audio assets, it is necessary to package the needed files in a zip file. If there are more than one start file (BAS or PRG) the `manifest.json` file can be used to specify the default start file with `start_prg` or `start_bas` otherwise the WebEmulator will start the .prg or .bas it finds in the zip file.  
220 +
221 +
Here is an example of the optional `manifest.json` file  
222 +
	
223 +
	{
224 +
		"manifest_version": "1.0.0",
225 +
		"name": "My Program",
226 +
		"author": "John Smith",
227 +
		"app_version": "1.0.0",
228 +
		"license": "GPL 3",
229 +
		"ram": "2048",
230 +
		"cpu": "c816",
231 +
		"mhz": "10",
232 +
		"keymap": "da",
233 +
		"widescreen": true,
234 +
		"capture": true,
235 +
		"longpwron": false,
236 +
		"midlineeffects": false,
237 +
		"start_prg": "MYPROG.PRG",
238 +
		"resources": [
239 +
			"MYPROG.PRG",
240 +
			"FILE1.BIN",
241 +
			"FILE2.BIN"
242 +
		]
243 +
	}
244 +
245 +
If the resources section is present, only files specified will be made available to the WebEmulator.  
246 +
Options set in `manifest.json` will override options on the address line.
247 +
248 +
Functions while running
249 +
-----------------------
250 +
251 +
#### Windows and Linux
252 +
* `Ctrl` + `F` and `Ctrl` + `Return` will toggle full screen mode.
253 +
* `Ctrl` + `M` will toggle mouse capture mode.
254 +
* `Ctrl` + `P` will write a screenshot in PNG format to disk.
255 +
* `Ctrl` + `R` will reset the computer.
256 +
* `Ctrl` + `Backspace` will send an NMI to the computer (like RESTORE key).
257 +
* `Ctrl` + `S` will save a system dump (configurable with `-dump`) to disk.
258 +
* `Ctrl` + `V` will paste the clipboard by injecting key presses.
259 +
* `Ctrl` + `=` and `Ctrl` + `+` will toggle warp mode.
260 +
261 +
#### Mac OS
262 +
* `⌘F` and `⌘Return` will toggle full screen mode.
263 +
* `⇧⌘M` will toggle mouse capture mode.
264 +
* `⌘P` will write a screenshot in PNG format to disk.
265 +
* `⌘R` will reset the computer.
266 +
* `⌘Delete` aka `⌘Backspace` will send an NMI to the computer (like RESTORE key).
267 +
* `⌘S` will save a system dump (configurable with `-dump`) to disk.
268 +
* `⌘V` will paste the clipboard by injecting key presses.
269 +
* `⌘=` and `⇧⌘+` will toggle warp mode.
270 +
271 +
272 +
GIF Recording
273 +
-------------
274 +
275 +
With the argument `-gif`, followed by a filename, a screen recording will be saved into the given GIF file. Please exit the emulator before reading the GIF file.
276 +
277 +
If the option `,wait` is specified after the filename, it will start recording on `POKE $9FB5,2`. It will capture a single frame on `POKE $9FB5,1` and pause recording on `POKE $9FB5,0`. `PEEK($9FB5)` returns a 128 if recording is enabled but not active.
278 +
279 +
280 +
WAV Recording
281 +
-------------
282 +
283 +
With the argument `-wav`, followed by a filename, an audio recording will be saved into the given WAV file. Please exit the emulator before reading the WAV file.
284 +
285 +
If the option `,wait` is specified after the filename, it will start recording on `POKE $9FB6,1`. If the option `,auto` is specified after the filename, it will start recording on the first non-zero audio signal. It will pause recording on `POKE $9FB6,0`. `PEEK($9FB6)` returns a 1 if recording is enabled but not active.
286 +
287 +
288 +
Emulator I/O registers
289 +
-------------------
290 +
x16-emulator exposes registers in the range of, from `$9FB0`-`$9FBF`, which allows one to control or toggle various emulator features from within emulated code.
291 +
292 +
When writing machine code that uses these registers, good practice is to read `$9FBE` and `$9FBF` and check for their return values. If the emulator is present, those memory locations will return the ASCII/PETSCII characters "1" and "6" respectively (`$31` and `$36` hex).  After verifying that the code is running under the emulator, you can confidently use the features provided by these registers.
293 +
294 +
Several of the following registers are particularly useful for debugging. In particular, writing data to `$9FB9`, `$9FBA`, or `$9FBB` will output debug information to the console, terminal, or command prompt window from which you ran x16emu.
295 +
296 +
297 +
| Register | Read Behavior | Write Behavior |
298 +
|-|-|-|
299 +
| \$9FB0 | Returns debugger enabled flag | `0` disables, `1` enables the debugger, overriding the absence or presence of the `-debug` command line argument. |
300 +
| \$9FB1 | Returns video logging flag | `0` disables, `1` enables logging of VRAM accesses to the console |
301 +
| \$9FB2 | Returns keyboard logging flag | `0` disables, `1` enables logging of keyboard events to the console |
302 +
| \$9FB3 | Returns echo mode | `0` disables, `1` enables raw echo, `2` enables cooked (`\Xnn` for non-ASCII), and `3` enables ISO (w/ conversion to UTF-8). When on, characters sent via the `BSOUT` KERNAL call will also appear on the console. |
303 +
| \$9FB4 | Returns save-on-exit flag | `0` disables, `1` enables save-on-exit. When this option is set and the program counter reaches \$FFFF, the emulator outputs a dump of emulator state to `dump.bin` before exiting. |
304 +
| \$9FB5 | Returns GIF recorder state | `0` pauses, `1` captures a single frame, and `2` activates/resumes GIF recording. The path to the GIF file must have been passed to the `-gif` command line option in advance. |
305 +
| \$9FB6 | Returns WAV recorder state | `0` pauses, `1` enables WAV recording, and `2` sets up autostart. The path to the WAV file must have been passed to the `-wav` command line option in advance. |
306 +
| \$9FB7 | Returns emu command key flag | `0` allows, and `1` inhibits most emulator command keys. Setting this flag prevents the emulator from intercepting keystrokes such as Ctrl+V/⌘V or Ctrl+R/⌘R, allowing the Commander X16 application running inside to make use of them. |
307 +
| \$9FB8 | Latches the cpu clock counter and returns bits 0-7 | Resets the cpu clock counter to 0 |
308 +
| \$9FB9 | Returns bits 8-15 from the latched cpu clock counter value | Outputs `"User debug 1: $xx"` to the console with xx replaced by the value written. |
309 +
| \$9FBA | Returns bits 16-23 from the latched cpu clock counter value | Outputs `"User debug 2: $xx"` to the console with xx replaced by the value written. |
310 +
| \$9FBB | Returns bits 24-31 from the latched cpu clock counter value | Outputs the given character to the console. This is basically a STDOUT port for programs running in the emulator. Only printable characters are allowed. Non-printables are replaced with &#xfffd;.
311 +
| \$9FBC | - | - |
312 +
| \$9FBD | Returns the keymap index, based on the argument to the `-keymap` command line option | - |
313 +
| \$9FBE | Returns the value `$31`/ASCII "1", useful for emulator presence detection | - |
314 +
| \$9FBF | Returns the value `$36`/ASCII "6", useful for emulator presence detection | - |
315 +
316 +
317 +
BASIC and the Screen Editor
318 +
---------------------------
319 +
320 +
On startup, the X16 presents direct mode of BASIC V2. You can enter BASIC statements, or line numbers with BASIC statements and `RUN` the program, just like on Commodore computers.
321 +
322 +
* To stop execution of a BASIC program, hit the `RUN/STOP` key (`Pause`), or `Ctrl+C`.
323 +
* To insert characters, first insert spaces by pressing `Shift+Backspace` or `Insert`, then type over those spaces.
324 +
* To clear the screen, press `Shift+Home`.
325 +
* To send NMI, similar to `STOP+RESTORE` on the C64, use Ctrl+Backspace/⌘Delete. On real hardware this is done with `Ctrl+Alt+RESTORE` (`Ctrl+Alt+PrtScr`) or by pressing the NMI button.
326 +
327 +
328 +
SD Card Images
329 +
--------------
330 +
331 +
The command line argument `-sdcard` lets you attach an image file for the emulated SD card. Using an emulated SD card makes filesystem operations go through the X16's DOS implementation, so it supports all filesystem operations (including directory listing though `DOS"$` command channel commands using the `DOS` statement) and guarantees full compatibility with the real device.
332 +
333 +
Images must be greater than 32 MB in size and contain an MBR partition table and a FAT32 filesystem. The file `sdcard.img.zip` in this repository is an empty 100 MB image in this format.
334 +
335 +
On macOS, you can just double-click an image to mount it, or use the command line:
336 +
337 +
	# hdiutil attach sdcard.img
338 +
	/dev/disk2              FDisk_partition_scheme
339 +
	/dev/disk2s1            Windows_FAT_32                  /Volumes/X16 DISK
340 +
	# [do something with the filesystem]
341 +
	# hdiutil detach /dev/disk[n] # [n] = number of device as printed above
342 +
343 +
On Linux, you can use the command line:
344 +
345 +
	# sudo losetup -P /dev/loop21 disk.img
346 +
	# sudo mount /dev/loop21p1 /mnt # pick a location to mount it to, like /mnt
347 +
	# [do something with the filesystem]
348 +
	# sudo umount /mnt
349 +
	# sudo losetup -d /dev/loop21
350 +
351 +
On Windows, you can use the [OSFMount](https://www.osforensics.com/tools/mount-disk-images.html) tool. Windows VHD files can also be created using the built-in Disk Manager. Careful attention should be paid to the settings when creating and formatting the VHD:
352 +
353 +
 * The file must be at least 32MB and must be fixed size. Expanding VHDs are not supported.
354 +
 * Use an MBR partition tables. The Commander X16 does not recognize GPT partition tables.
355 +
 * You must format the VHD with FAT32. Other file formats are not supported.
356 +
357 +
 This is a trick, since Fixed-size VHD files contain the data first, with the metadata in a footer at the end. Since the emulator does not read or edit that medatada, it will only work with fixed-size files that are fully populated.
358 +
359 +
360 +
Host Filesystem Interface
361 +
-------------------------
362 +
363 +
If the system ROM contains any version of the KERNAL, and there is no SD card image attached, all accesses to the ("IEEE") Commodore Bus are intercepted by the emulator for device 8 (the default). So the BASIC statements will target the host computer's local filesystem:
364 +
365 +
      DOS"$"
366 +
      LOAD"FOO.PRG"
367 +
      LOAD"IMAGE.PRG",8,1
368 +
      SAVE"BAR.PRG"
369 +
      OPEN2,8,2,"FOO,S,R"
370 +
371 +
The emulator will interpret filenames relative to the directory it was started in. On macOS, when double-clicking the executable, this is the home directory. To specify a different path as the emulated root, you can use the `-fsroot` command line option.
372 +
373 +
To avoid compatibility problems between the PETSCII and ASCII encodings, you can
374 +
375 +
* use uppercase filenames on the host side, and unshifted filenames on the X16 side.
376 +
* use `Ctrl+O` to switch to the X16 to ISO mode for ASCII compatibility.
377 +
* use `Ctrl+N` to switch to the upper/lower character set for a workaround.
378 +
379 +
As of R42, the Host Filesystem interface (or HostFS) is the preferred method of accessing files. It does not require creating or managing an SDcard image, and it supports all of the CMDR-DOS commands. However, it is not cycle-accurate, since the emulator traps calls to DOS and performs the same actions in the host environment. If performance and hardware accuracy is required, you will want to perform final testing using an SD card image.
380 +
381 +
Dealing with BASIC Programs
382 +
---------------------------
383 +
384 +
BASIC programs are encoded in a tokenized form when saved. They are not simply ASCII files. If you want to edit BASIC programs on the host's text editor, you need to convert it to tokenized BASIC encoding from ASCII encoding before calling `LOAD` in the emulator.
385 +
386 +
* To convert the basic file from ASCII to tokenized BASIC encoding, reboot the machine and paste the ASCII text using `Ctrl + V` (Mac: `Cmd + V`) into the terminal. You can now run the program with `RUN`, or use the `SAVE` BASIC command to write the tokenized version to the host disk.  Below is an example.
387 +
  1. Copy ASCII text from host basic file "PRG.BAS"
388 +
  2. Paste into new terminal session
389 +
  3. `SAVE"ENCODED.BAS`
390 +
  4. Now you can restart the emulator and load the encoded basic file with `LOAD"ENCODED.BAS"`
391 +
  5. Run with `RUN"ENCODED.BAS"`
392 +
393 +
* To convert BASIC to ASCII, start x16emu with the `-echo` argument, `LOAD` the BASIC file, and type `LIST`. Now copy the ASCII version from the terminal.
394 +
395 +
396 +
Using the KERNAL/BASIC environment
397 +
----------------------------------
398 +
399 +
Please see the [KERNAL/BASIC documentation](https://github.com/X16Community/x16-docs/).
400 +
401 +
402 +
Debugger
403 +
--------
404 +
405 +
The debugger requires `-debug`.  To start the debugger, press the F12 key. Without `-debug`, the debugger is disabled and won't start.  If you wish to set an initial breakpoint you can also include the memory address, in hexadecimal, of the breakpoint after the `-debug` switch. For example `-debug 080d`.
406 +
407 +
There are 2 panels you can control. The code panel, the top left half, and the data panel, the bottom half of the screen. You can also edit the contents of the registers PC, A, B, C, D, K, DB, X, Y, and SP.
408 +
409 +
Greyed out numbers in the register display indicate values that are fixed at their given value due to the current processor state. This applies to
410 +
- the high bytes of X and Y if the index flags is set
411 +
- the flags M (memory) and I (index) if emulation mode is active
412 +
- the high byte of SP if emulation mode is active.
413 +
414 +
The debugger uses its own command line with the following syntax:
415 +
416 +
|Statement|Description|
417 +
|---------|----------------------------------------------------------------------------------------------------|
418 +
|d %x|Change the code panel to view disassembly starting from the address %x.|
419 +
|m %x|Change the data panel to view memory starting from the address %x.|
420 +
|v %x|Display VERA RAM (VRAM) starting from address %x.|
421 +
|b %s %d|Changes the current memory bank for disassembly and data. The %s param can be either 'ram' or 'rom', the %d is the memory bank to display (but see NOTE below!).|
422 +
|r %s %x|Changes the value in the specified register. Valid registers in the %s param are 'pc', 'a', 'b', 'c', 'd', 'k', 'dbr', 'x', 'y', and 'sp'. %x is the value to store in that register.|
423 +
424 +
NOTE. To disassemble or dump memory locations in banked RAM or ROM, prepend the bank number to the address; for example, "m 4a300" displays memory contents of BANK 4, starting at address $a300.  This also works for the 'd' command.
425 +
426 +
The debugger keys are similar to the Microsoft Debugger shortcut keys, and work as follows
427 +
428 +
| Key               | Description                                                                             |
429 +
|-------------------|-----------------------------------------------------------------------------------------|
430 +
| F1                | resets the disassembly position to the current PC                                       |
431 +
| F2                | resets the emulated CPU but not any of the hardware.                                    |
432 +
| F5                | close debugger window and return to Run mode, the emulator should run as normal.        |
433 +
| F9                | sets the breakpoint to the currently code position.                                     |
434 +
| F10               | steps 'over' routines - if the next instruction is JSR it will break on return.         |
435 +
| F11               | steps 'into' routines.                                                                  |
436 +
| F12               | is used to break back into the debugger. This does not happen if you do not have -debug |
437 +
| PAGE UP           | scrolls memory up by page.                                                              |
438 +
| PAGE DOWN         | scrolls memory down by page.                                                            |
439 +
| Shift + PAGE UP   | scrolls disassembly up by 16 bytes.                                                     |
440 +
| Shift + PAGE DOWN | scrolls disassembly down by 16 bytes.                                                   |
441 +
| UP                | scrolls memory up by row.                                                               |
442 +
| DOWN              | scrolls memory down by row.                                                             |
443 +
| Shift + UP        | scrolls disassembly up by one byte.                                                     |
444 +
| Shift + DOWN      | scrolls disassembly down by one byte.                                                   |
445 +
| TAB               | when stopped, or single stepping, hides the debug panel while pressed.                  |
446 +
447 +
When `-debug` is selected the STP instruction (opcode $DB) will break into the debugger automatically.
448 +
449 +
Keyboard routines only work when the emulator is running normally. Single stepping through keyboard code will not work at present.
450 +
451 +
CRT File Format
452 +
---------------
453 +
454 +
The Commander X16 will support cartridge ROMs, including auto-booting game cartridges. On the Gen-1 Developer board, the first slot will be used for cartridges. On the Gen-2 console machine, there is only one slot. ROM carts should work on both systems.
455 +
456 +
This CRT format is intended for the emulator, and it is not required or used by the hardware. You can, however, use the MakeCart tool to convert between a single CRT file and BIN files that can be used to program a ROM burner. Also, note that this is different from the CRT format used the VICE emualtor, so files are not interchangable.
457 +
458 +
Commander X16 cartridges will occupy the same address space as the Commander's KERNAL and BASIC ROMs. You can control the active bank by writing to address $0001 on the computer. Banks 0-31 are the built-in ROM banks, and banks 32-255 will select the cartridge ROMs.
459 +
460 +
### Header Layout
461 +
462 +
This is the cartridge header. The first 256 bytes are ASCII data and Human readable. The second 256 bytes are bank data; these are byte integers. Text fields are set to 16 or 32-byte boundaries for ease of formatting.
463 +
464 +
| Location | Length | Description                                                                                        |
465 +
|----------|--------|----------------------------------------------------------------------------------------------------|
466 +
| 00-15    | 16     | ASCII text: CX16 CARTRIDGE\r\n                                                                     |
467 +
| 16-31    | 16     | CRT format version. ASCII digits in format 01.02, space padded.                                    |
468 +
| 32-63    | 32     | Name. ASCII text.                                                                                  |
469 +
| 64-95    | 32     | Programmer/Developer. ASCII text.                                                                  |
470 +
| 96-127   | 32     | Copyright information. ASCII text.                                                                 |
471 +
| 128-191  | 32     | Program version. ASCII text.                                                                       |
472 +
| 192-255  | 64     | Empty.                                                                                             |
473 +
| 256-287  | 32     | Fill with zeros.                                                                                   |
474 +
| 288-511  | 224    | Bank Flags.                                                                                        |
475 +
|          |        | 00: Not Present. No data is present in the emulator or in the file.                                |
476 +
|          |        | 01: ROM: 16KB of ROM data. Data is write protected in emulator.                                    |
477 +
|          |        | 02: RAM: No data in file. Bank is read/write in emulator.                                          |
478 +
|          |        | 03: RAM: Data present: data is loaded from the file and discarded on shutdown. Useful for testing. |
479 +
|          |        | 04: NVRAM: No Data in file. Memory is writeable. Emulator saves data to NVRAM file.                |
480 +
|          |        | 05: NVRAM: Data present. Memory is writeable. Emulator saves data to NVRAM file.                   |
481 +
| 512-end  |        | Payload data.                                                                                      |
482 +
|          |        | 16384 bytes per bank for types 1, 3, and 5.                                                        |
483 +
|          |        | 0 bytes for types 0,2, and 4.                                                                      |
484 +
485 +
486 +
For NVRAM banks: on shutdown, the emulator will write out an NVRAM file that contains the data of all of the NVRAM banks. The next time this cartridge is started, the NVRAM file will be loaded into any NVRAM bank. This overwrites any data present in NVRAM banks in the CRT file.
487 +
488 +
For types 00, 02, and 04: The file does *not* contain data for these bank types. Instead, the file skips straight to the next bank with initialized data (01, 03, or 05).
489 +
490 +
For all "No Data" banks, the data in RAM is *undefined*. While the emulator currently initializes RAM to 0 bytes, the hardware will have random values. In addition, unpopulated addresses will be "open collector" and will have unpredicatable results.
491 +
492 +
### Vectors
493 +
494 +
X16 hardware, and thus the emulator, will only read 6502 vectors out of bank 0. This is done via the CPU's VPB pin being connected to the ROM bank latch reset pin. In the past specific vectors were recommended in cartridge ROMs, but this is no longer true. In cartridges, the addresses `$FFFA`-`$FFFF` are free to use for data.
495 +
496 +
497 +
<!-- For PDF formatting -->
498 +
<div class="page-break"></div>
499 +
500 +
## MakeCart Conversion Tool
501 +
502 +
A conversion tool to pack cartridge data into a CRT file, `makecart`, is included in this release.
503 +
504 +
`-cfg <filename.cfg>`
505 +
Use this file to pack the cartridge data. Config file is simply the command line switches, one per line.
506 +
507 +
`-desc "Name/Description"`
508 +
Set the description field of the cartridge file. Up to 32 bytes of ASCII text.
509 +
510 +
`-author "Author Information"`
511 +
Set the author information field of the cartridge file. Up to 32 bytes of ASCII text.
512 +
513 +
`-copyright "Copyright Information"`
514 +
Set the copyright information field of the cartridge file. Up to 32 bytes of ASCII text.
515 +
516 +
`-version "version"`
517 +
Set the version information field of the cartridge file. Up to 32 bytes of ASCII text.
518 +
519 +
`-fill <value>`
520 +
Set the fill value to use with any partially-filled banks of cartridge memory. Value can be defined in decimal, or in hexadecimal with a '$' or '0x' prefix. 8-bit values will be repeated every byte, 16-bit values every two bytes, and 32-bit values every 4 bytes.
521 +
522 +
`-rom_file <start_bank> [<filename.bin> [<filename.bin>] ... ]`
523 +
524 +
Define rom banks from the specified list of files. File data is tightly packed -- if a file does not end on a 16KB interval, the next file will be inserted immediately after it within the same bank. If the last file does not end on a 16KB interval, the remainder of the rom will be filled with the value set by '-fill'.
525 +
526 +
Valid bank numbers are 32 - 255.
527 +
528 +
`-ram <start_bank> [<end bank>]`
529 +
Define one or more banks of RAM. RAM banks are not included in the payload.
530 +
531 +
`-ram_file <start_bank> [<filename.bin> [<filename.bin>] ... ]`
532 +
Define one or more banks of initialized RAM. Note that Initialized RAM banks are not saved to the NVRAM file at shutdown.
533 +
534 +
`-nvram <start_bank> [<end_bank>]`
535 +
Define one or more uninitalized nvram banks.
536 +
537 +
`-nvram_value <start_bank> <end_bank>`
538 +
Define pre-initialized nvram banks with the value set by '-fill'. Repeated payload bytes will be written to the file.
539 +
540 +
`-nvram_file <start_bank> [<filename.bin> [<filename.bin>] ... ]`
541 +
542 +
Define pre-initialized nvram banks from the specified list of files. File data is tightly packed like with -rom. If the last file does not end on a 16KB interval, the remainder of the rom will be filled with the value set by '-fill'.
543 +
544 +
`-none <start_bank> [<end_bank>]`
545 +
Define one or more unpopulated banks of the cartridge. By default, all banks are unpopulated unless specified by a previous command-line option. These banks are not present in the payload and only popualte the bank header in the CRT file.
546 +
547 +
`-o <output.crt>`
548 +
Set the filename of the output cartridge file.
549 +
550 +
All options can be specified multiple times, and are applied  in-order from left to right. For -desc and -o, it is legal to specify them multiple times but only the right-most instances of each will have effect.
551 +
552 +
`-unpack <input.crt> [<rom_size>]`
553 +
Unpacks the binary data from the cartridge file into `<rom_size>` slices. (for use with an EPROM programmer.) The ouptut files will be the same filename as the input file, with _### appended. This will also create a .cfg file that can be used to re-pack the files into a new CRT if needed.
554 +
555 +
The config file is just a series of command-line switches, with one item per line. This example assumes ladder.bin uses 3 banks, for a total of 48K, and that each level map is 4KB in size.
556 +
557 +
```
558 +
-o ladder.crt
559 +
-name "Ladder"
560 +
-author "Yahoo Software"
561 +
-copyright "(c) 1982, 1983 Yahoo Software"
562 +
-version "1.30TP"
563 +
-rom_file 32 ladder.bin
564 +
-rom_file 35 level_01.bin level_02.bin level_03.bin level_04.bin
565 +
-nvram 37
566 +
-fill 0
567 +
```
568 +
569 +
This would create file with
570 +
571 +
* 512 byte header
572 +
* 5 ROM banks
573 +
  * 3 for the 48K ladder.bin
574 +
  * 1 for the four 4KB level files.
575 +
* 1 empty NVRAM bank
576 +
577 +
Since the NVRAM bank is not initialized, it is not included in the file. This makes the file a total of 66,048 bytes long. (512 bytes, plus four 16KB banks.)
578 +
579 +
580 +
Web Site
581 +
--------
582 +
583 +
[https://commanderx16.com](https://commanderx16.com)
584 +
585 +
Forum
586 +
-----
587 +
588 +
[https://cx16forum.com/forum](https://cx16forum.com/forum/)
589 +
590 +
591 +
License
592 +
-------
593 +
594 +
Copyright (c) 2019-2023 Michael Steil &lt;[email protected]&gt;, [www.pagetable.com](https://www.pagetable.com/), et al.
595 +
All rights reserved. License: 2-clause BSD
596 +
597 +
598 +
Release Notes
599 +
-------------
600 +
See [RELEASES](RELEASES.md#releases).
601 +
602 +
603 +
<!-------------------------------------------------------------------->
604 +
[releases]: https://github.com/X16Community/x16-emulator/releases
605 +
[webassembly]: http://github.com/X16Community/x16-emulator/blob/master/webassembly/WebAssembly.md
606 +
[x16rom-build]: https://github.com/X16Community/x16-rom#releases-and-building
607 +
[x16rom]: https://github.com/X16Community/x16-rom
608 +
609 +
<!-- For PDF formatting -->
610 +
<div class="page-break"></div>
added RELEASES.md +593 −0
1 +
2 +
<p align="center">
3 +
  <img src="./.gh/logo.png" />
4 +
</p>
5 +
6 +
## Releases
7 +
8 +
### Release R49 ("Pyrite")
9 +
10 +
* Features/Fixes
11 +
	* VERA: Add warnings when reading to write-only and when writing to read-only registers [irmen]
12 +
	* VERA: Make SPI busy time more true to hardware
13 +
	* VERA: Fix wrong behavior of 8bpp sprites when palette offset is nonzero
14 +
	* VERA PSG: Fix noise frequency being double that of hardware
15 +
	* SDCARD: add READ_MULTIPLE_BLOCK support [stople]
16 +
	* VIA: Fix for IFR flag handling [catharinejm]
17 +
	* Let emulator exit with Alt+F4 even if SDL does not handle it [JimmyDansbo]
18 +
	* Only free clipboard memory buffer when it comes from SDL's paste handler
19 +
	* Added serial MIDI emulation via FluidSynth
20 +
	* Exclude threading (needed for FluidSynth) in wasm build
21 +
	* Added `-gs` support, which implements a system with 24-bit addressable RAM and a 65C816 processor, in anticipation of the planned X16 GS with this feature. [Fulgen301]
22 +
	* Vastly improved performance of the HostFS's implementation of MACPTR
23 +
	* Added HostFS implementations of XMACPTR and XMCIOUT to mirror the CMDR-DOS implementations
24 +
	* 65C816 emulation fixes for MVN, MVP [Fulgen301, cnelson20]
25 +
	* Move from C99 to C11, some C syntax and header fixes [skeeto]
26 +
	* Additional web emulator options, including those to support CPU type and speed [JimmyDansbo]
27 +
	* Emulator debugger fixes, including support for GS, and pressing F9 a second time to clear the breakpoint
28 +
	* More changes in the [ROM](https://github.com/X16Community/x16-rom/tree/r49#release-49-pyrite).
29 +
30 +
### Release R48 ("Cadmium")
31 +
32 +
* Features/Fixes
33 +
	* Emulated VERA SPI clock has been updated to half of the VERA clock speed rather than the CPU clock.
34 +
	* Trace for ROM banks 13-15 (x16edit and Basload) is now supported [stefan-b-jakobsson]
35 +
	* Fixed debug reads from the stack and a few unbounded values in the debugger display
36 +
	* VERA PSG: for the saw and triangle waves, the inverse of the pulse width value is XORed with the value of the wave. This is the behavior of VERA 47.0.2 on hardware. [m00dawg]
37 +
	* Allow setting the amount of RAM in the web version of the emulator [JimmyDansbo]
38 +
	* Windows 11: Rounded corners should no longer cut off the lower left and lower right of the emulator window.
39 +
	* New memory statistics feature [irmen]
40 +
	* HostFS: Implement DOS "T"ell command
41 +
	* Debugger now includes elapsed cpu clocks since the last breakpoint or step
42 +
	* 65C816: Fixed stack relative indirect address calulation wrongly using the direct page [Fulgen301]
43 +
	* 65C816: high bytes of .X and .Y are now cleared when PLP or RTI set x=1 [cnelson20]
44 +
	* 65C816: PEI no longer does improper page wraparound, `[dp]` indirect modes now properly read the address. [Fulgen301]
45 +
	* More changes in the [ROM](https://github.com/X16Community/x16-rom/tree/r48#release-48-cadmium).
46 +
47 +
### Release 47 ("Roswell")
48 +
49 +
This is a major release with numerous bugfixes, as well as introducing 65C816 support.
50 +
51 +
* Features/Fixes.
52 +
	* Optionally emulate a 65C816 CPU, selectable via a new command line switch `-c816`. There is also a `-c02` command line switch to explicitly select 65C02 mode.  [Fulgen301]
53 +
	* In order to make room for the full 65C816 vector table, the R47 ROM relocates the "MIST" KERNAL signature from 0xFFF6 to 0xC008. Emulator has been updated to check for the signature in the newer location. [Fulgen301]
54 +
	* Made IEC device addressing more flexible by preventing the HostFS from consuming every device number [DragWx]
55 +
	* Improved timing calculations for HostFS operations to translate wall clock time to elapsed CPU ticks
56 +
	* If HostFS is in use, the default `-abufs` audio buffer count is 32, otherwise 8. This should reduce audio stuttering for games which load assets from disk while sound is playing.
57 +
	* HostFS can be changed to respond to a non-default device number. New command line option `-hostfsdev`. Using this option in conjunction with `-sdcard` allows both devices to appear on the system.
58 +
	* Matching the VERA hardware feature, the layer config T256 bit can be used as a palette switch in tile and bitmap modes. Most useful in 2bpp and 4bpp depths, setting this bit on the layer forces the resolved high bit of the palette entry index to 1. For instance, in 4bpp mode, a tile pixel with index 1, on a tile configured with palette offset 6 would normally resolve to palette entry 0x61 (or 97 decimal), having this layer bit set will cause this pixel to resolve to palette entry 0xE1 (or 225 decmial).
59 +
	* Copying VERA hardware behavior, the LINE IRQ can happen in the non-visible vertical blanking area.
60 +
	* HostFS: empty filename now returns an error as appropriate
61 +
	* IO2, the YM2151 range, is partially decoded on hardware. Emulator behavior now matches this so that the YM2151 is mirrored throughout the $9F42-$9F5F range.
62 +
	* New command line switch `-nokeyboardcapture` causes capture mode to operate without intercepting OS-level keystrokes such as Alt+Tab.
63 +
	* Improvements to `-wuninit` warnings to show the appropriate opcode and location of uninitialized accesses.
64 +
	* Recognize the INTL1 key code, which exists on some keyboard layouts such as pt-BR.
65 +
	* Update the 65C02 opcode table to properly handle multi-byte NOPs and update cycle counts.
66 +
	* `-trace` output has been updated and enhanced, showing banks for effective addresses. Add listings for new banks.
67 +
	* new `-longpwron` option to match SMC 47 long power button press behavior at power on.
68 +
	* VERA FX: fix address resolution bug with 4bpp unaligned cache filling.
69 +
	* VERA: DC_VSTOP was taking effect one scanline too late.
70 +
	* SMC: update activity light logic. Hardware was mistakenly documented as allowing 255 PWM intensity levels, while in fact it is binary on/off.
71 +
	* VERA PSG: update PSG volume table from VERA 47.0.0. Prior to this change, there were multiple PSG volume values at the low end that resulted in the same output level.
72 +
	* SDL2: Set the application name to "Commander X16 Emulator", which shows up in the audio mixer, and in the screensaver inhibit application list on Linux desktop environments. This should be a bit more informative than "My SDL application".
73 +
	* New `-pastewarp` command line switch that warps the emulator when receiving paste data either from the clipboard or from the `-bas` command line switch.
74 +
	* HostFS: improve path parsing with both CMD and unix path syntax, particularly with absolute path names.  DOS"MD//:TEST" now creates the directory in the FS root rather than erroneously in the current directory.
75 +
	* VERA: DC_HSCALE and DC_VSCALE values > 0x80 now render as they do on hardware.
76 +
	* SMC 47.0.0 fast PS/2 read support
77 +
	* Now with native build for Apple Silicon (built on the GitHub MacOS ARM runner)
78 +
	* More changes in the [ROM](https://github.com/X16Community/x16-rom/tree/r47#release-47-roswell).
79 +
80 +
81 +
### Release 46 ("Winnipeg")
82 +
83 +
This is mainly a bugfix release.
84 +
85 +
* Features/Fixes
86 +
	* A change in the ROM caused controllers to have their buttons offset due to how the emulated VIA responds to polling. The issue was fixed in the ROM and while it was not technically an emulator bug and no changes were made to the emulator's joystick routines, it gets a mention here.
87 +
	* HostFS: UNLSN was incorrectly setting the KERNAL status byte based on whether a file existed upon open. It has been changed to behave like CMDR-DOS. (discovered by [voidstar78])
88 +
	* HostFS: Opening a file in Modify mode now properly creates file if it doesn't yet exist (discovered by [m00dawg])
89 +
	* Audio: prevent `-sound none` from trying to use the uninitialized ymfm YM2151 library. The YM status register was also modified to always return 0 in this case. (discovered by [jestin])
90 +
	* WebAssembly: improve handling of zip files without `manifest.json` [Cyber-Ex]
91 +
	* GUI: If emulator launches in a window with the titlebar offscreen, move the window so that it is visible. (with help from [irmen])
92 +
	* GUI: Mouse movement now uses relative motion in capture mode, which makes the position of the host mouse irrelevant while in capture mode.
93 +
	* GUI: new `-capture` command line option to start emulator with mouse captured
94 +
	* Testbench: miscellaneous fixes [irmen]
95 +
	* More changes in the [ROM](https://github.com/X16Community/x16-rom/tree/r46#release-46-winnipeg).
96 +
97 +
### Release 45 ("Nuuk")
98 +
99 +
This is a minor release with respect to the emulator.  The bulk of the changes are in the [ROM](https://github.com/X16Community/x16-rom/tree/r45#release-45-nuuk).
100 +
101 +
* Features/Fixes
102 +
	* Revert VERA PSG amplitude resolution back to 6 bits. This was upped previously to match VERA firmware. It was subsequently reverted in VERA to make room for the FX feature. [akumanatt]
103 +
	* Intellimouse support added to the emulated SMC, partially implementing the new feature in hardware SMC firmware 45.1.0. [stefan-b-jakobsson]
104 +
		* Scroll wheel is supported (mouse device ID 3)
105 +
		* Not implemented: Extra buttons (mouse device ID 4)
106 +
	* New emulator debug register behaviors
107 +
		* Reading from \$9FB8-\$9FBB in this order returns the 32-bit CPU clock counter, snapshotted at the time \$9FB8 is read. Previously the clock counter would remain in motion and the upper counter bits could roll over unpredictably.
108 +
		* Writing to \$9FB8-\$9FBB has new behaviors:
109 +
			* \$9FB8: resets the cpu clock counter to 0.
110 +
			* \$9FB9: prints a debug message to the console `User debug 1: $xx`.
111 +
			* \$9FBA: prints a debug message to the console `User debug 2: $xx`.
112 +
			* \$9FBB: prints the UTF-8 representation of the ISO character to the console. This can be treated like a debug STDOUT.
113 +
		* Before using any of these emulator debug registers, it's recommended to test for emulator presence first.
114 +
			* Read from `$9FBE` and `$9FBF`. When running under the emulator, the returned values should be `$31` and `$36` respectively. If any other values are returned, you can usually assume to be running on real hardware. While the stock machine doesn't have any I/O devices that listen to the emulator I/O range, an add-on card could choose to use that same address space in the future for its own functions.
115 +
	* New MCIOUT (blockwise write) implementation for HostFS, mirroring the feature in the kernal for use on SD card.
116 +
	* New command key and capture behavior:
117 +
		* Ctrl+M/⇧⌘M is always processed by the emulator to toggle (mouse/keyboard) capture, regardless of the state of the "Disable Emulator Keys" flag.
118 +
		* Turning capture mode on disables all other emulator keys until capture mode is toggled off. While capture is on, the emulator also routes most OS shortcut key combos to the X16: for instance, Alt+Tab.
119 +
120 +
121 +
### Release 44 ("Milan")
122 +
123 +
This is the third release of x16-emulator by the X16Community team
124 +
125 +
* Features/Fixes
126 +
	* Many changes to HostFS, including
127 +
		* Fix regression for loading "`:*`" from HostFS while also using an SD card image
128 +
		* Fix `-prg` and `-sdcard` options working together, which did not properly handle the case after emulator reset
129 +
		* Proper wildcard behavior in dir filters and OPEN string
130 +
		* Fix filetype directory filter command parsing
131 +
		* Add `$=L` long mode directory listing to emulate the new feature in the ROM.
132 +
		* Speed up directory filetype filter
133 +
		* Add CMD/SD2IEC style directory navigation: `CD:←` to enter parent directory
134 +
		* Partial emulation of case-insensitivity in filenames on case-sensitive host filesystems.
135 +
			* This works in `OPEN` strings and directory names in commands (`CD:`, etc.) which do not contain a `/` character.
136 +
			* It also works on the last path segment in relative or absolute paths in directory names or `OPEN` strings. In other words, if given a path specification containing one or more `/` characters, it will do only a case-insensitive search on the part after the final `/`.
137 +
		* Proper translation between UTF-8 filenames and their ISO representations.
138 +
	* Implement VPB behavior to match hardware [akumanatt]
139 +
		* Fix BRK to have VPB behavior
140 +
	* Support for additional keycodes (NumLock, Menu) [stefan-b-jakobsson]
141 +
	* Fix debugger to set the correct bank for breakpoints [gaekwad]
142 +
	* New `-fullscreen` CLI option
143 +
	* Proper cleanup when the emulator exits when in full screen [irmen]
144 +
	* FX emulation, which mirrors the features in the FX enhancement to the VERA firmware. See the [VERA FX Reference](https://github.com/X16Community/x16-docs/blob/master/VERA%20FX%20Reference.md) for details.
145 +
	* Writing bit 6 and bit 7 together into VERA_AUDIO_CTRL now enables looping the PCM FIFO (and does not reset the FIFO). Any other write into VERA_AUDIO_CTRL disables looping.
146 +
	* New `-opacity` CLI option for window transparency [tstibor]
147 +
	* New support for screenshots (Ctrl+P/⌘P) [dressupgeekout]
148 +
	* Fix small memory leak caused by pasting into the emulator
149 +
	* Use relative mouse motion while in grabbed mode
150 +
	* Remove `-geos` CLI option [dressupgeekout]
151 +
	* New YM2151 audio core: remove old MAME core, replace with ymfm
152 +
		* Allows for IRQs from the YM, requires specifying `-enable-ym2151-irq` on the command line
153 +
	* Emulate hardware open bus behavior when reading from a device that doesn't exist in the `$9Fxx` space
154 +
	* Reset via I2C command: defer machine reset to the main loop, which allows the I2C write routine to return cleanly.
155 +
	* Fix 65C02 `BIT` immediate behavior [XarkLabs]
156 +
	* New NMI trigger emulator hotkey, emulates Ctrl+Alt+Restore on hardware (Ctrl+Backspace/⌘Delete) [XarkLabs]
157 +
	* Fix line artifact in application icon/logo
158 +
	* Grabbing the mouse with (Ctrl+M/⇧⌘M) now grabs the keyboard as well. It allows the emulator to receive keystrokes and key combinations which would otherwise be intercepted by the operating system.
159 +
	* Fix description of fill value in `makecart`
160 +
	* New features implemented in the [ROM](https://github.com/X16Community/x16-rom/tree/r44#release-44-milan)
161 +
* Build
162 +
	* Link-time optimization is now enabled by default
163 +
	* Portability enhancements [dressupgeekout]
164 +
	* Suppress clang warnings due to deprecated sprintf usage in ymfm lib [XarkLabs]
165 +
166 +
### Release 43 ("Stockholm")
167 +
168 +
This is the second release of x16-emulator by the X16Community team
169 +
170 +
* **BREAKING CHANGE**
171 +
	* The keyboard protocol between the emulated SMC and the KERNAL has changed, thus x16-emulator version R43 requires x16-rom version R43.
172 +
	* This change also affects how the custom keyboard handler vector works (keyhdl). For details, see [Chapter 2 of the Programmer's Reference Guide](https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-keyboard-keynum-code-handler)
173 +
	* **Your Keyboard will not work unless** you are running
174 +
		* R43 of both x16-rom and x16-emulator
175 +
* Features
176 +
	* Updates to support translation from SDL scancodes to new keynum encoding supported by KERNAL [stefan-b-jakobsson]
177 +
	* More granular support for RAM amount as argument to `-ram`
178 +
	* Minor HostFS bugfixes and enhancements, including tying the activity light to HostFS activity.
179 +
	* VERA updates: new support for 240p in NTSC/RGB modes. Chroma disable only works on NTSC.
180 +
	* Stepping the debugger now supports stepping over `WAI`
181 +
	* Debugger now shows the correct bank in the disassembly by default. [gaekwad]
182 +
	* Debugger breakpoints are now bank-specific [gaekwad]
183 +
	* Randomized RAM is now the default. New option: `-zeroram` [irmen]
184 +
	* Host's mouse cursor is now shown unless either the KERNAL mouse is enabled or the mouse cursor is captured (Ctrl+M/⇧⌘M).
185 +
	* Esc key is now Esc rather than STOP.  Pause key sends STOP.  (Ctrl+C is also recognized by the KERNAL as STOP)
186 +
	* SD card emulation now responds to CMD9
187 +
	* Emulated SMC can now assert NMI.
188 +
	* Add `-mhz` option to select a speed other than 8
189 +
	* When built with `TRACE`, the `-trace` output now shows the effective address for indirect and indexed opcodes and VERA data0/data1 reads and writes.
190 +
	* New comamnd line option `-midline-effects` that supports mid-line changes to the palette or tile/sprite data. R42 always had this behavior, which results in performance degradation for programs write to VERA heavily if the host CPU is not fast enough. This behavior is now disabled by default. `-midline-effects` restores this optional behavior.
191 +
	* New features implemented in the [ROM](https://github.com/X16Community/x16-rom/tree/r43#release-43-stockholm)
192 +
* Other
193 +
	* Release builds have link-time optimization enabled which seems to help performance.
194 +
	* Add git hash of build to `-version` string.
195 +
	* WebAssembly enhancements in the supporting html/js [Cyber-EX]
196 +
	* Fixed potential off-by one row with non-zero DC_VSTART.
197 +
	* Prevent laggy hostfs reads from causing the emulator to warp to catch up by translating the wall clock time to elapsed 6502 clocks. This effectively makes HostFS MACPTR behave like a DMA card, including the possibility that it prevents the CPU from executing instructions while interrupt sources may have been waiting for service.
198 +
	* Bugfix: Process multiple SDL events per frame. (Fixed choppy mouse movement if there were keystrokes in the keyboard buffer)
199 +
	* Audio resampling and ring buffer fixes [DragWx]
200 +
	* Build fixes on Mac
201 +
### Release 42 ("Cambridge")
202 +
203 +
This is the first release of x16-emulator by the X16Community team
204 +
205 +
* Features
206 +
	* Added testbench mode [stefan-b-jakobsson, indigodarkwolf]
207 +
	* Added `-noemucmdkeys` option [jestin]
208 +
	* New `FIFO_EMPTY` flag in `PCM_CTRL` to reflect new VERA feature [ZeroByteOrg]
209 +
	* Added `-widescreen` option to simulate stretched 640x480 output at a 16:9 aspect ratio [jestin]
210 +
	* New `SCANLINE` VERA register behavior to reflect updated VERA feature [mooinglemur]
211 +
	* Added `-randram` and `-wuninit` command line arguments to randomize RAM at boot, and to emit a console warning when uninitialized RAM is read, respectively. [stefan-b-jakobsson]
212 +
	* Allow specifying non-power-of-2 argument to `-ram`, in increments of 8k [JimmyDansbo]
213 +
	* Added `-via2` option to selectively enable a VIA at $9F10. [akumanatt]
214 +
	* Added ROM cart loading with `-cart` and `-cartbin` [indigodarkwolf]
215 +
	* New `makecart` utility for building `.crt` cartridge files [indigodarkwolf]
216 +
	* Compressed SD card image support [indigodarkwolf]
217 +
	* Mouse grab mode, press Ctrl+M (Mac: ⇧⌘M) to toggle. [mooinglemur]
218 +
	* New `-fsroot` and `-startin` options to specify the root of the emulated host fs, and the host directory to start in respectively. [mooinglemur]
219 +
	* Many, many new features implemented in the [ROM](https://github.com/X16Community/x16-rom/tree/r42#release-42-cambridge)
220 +
* Other
221 +
	* PS/2 devices now connected via SMC via I2C, I2C pins have moved to match hardware [stefan-b-jakobsson]
222 +
	* Recognize middle mouse button [ZeroByteOrg]
223 +
	* Synchronized keymaps with ROM [megagrump]
224 +
	* Build fixes [irmen]
225 +
	* Show dialog when a `STP` instruction is encountered with debug turned off [akumanatt]
226 +
	* Improved emulated behavior of `WAI` [LRFLEW]
227 +
	* Clear D flag on interrupt entry [LRFLEW]
228 +
	* Update BRK length in debugger [indigodarkwolf]
229 +
	* IRQ/NMI entry clock cycles are now accounted for [mooinglemur]
230 +
	* Add reason string to memory dump output [irmen]
231 +
	* Clear sprite line buffer when disabling sprite layer [jestin]
232 +
	* Improved audio balance between VERA and YM2151. Much improved mixing routines to reduce stutters and clicking. [akumanatt]
233 +
	* To match hardware, VERA ISR bits are set at VSYNC, LINE, and SPRCOL regardless of whether their respective IEN bits are set [mooinglemur]
234 +
	* Changes to match Proto 4, including moving VIA1 interrupt pin to IRQ [akumanatt]
235 +
	* VERA mid-frame raster effects more closely match the timing of real hardware [mooinglemur]
236 +
	* Enabled and built out CI/CD build workflows [maxgerhardt, indigodarkwolf, mooinglemur]
237 +
	* Many host fs enhancements, bringing host fs very close to feature parity with SD card images [davidgiven, ZeroByteOrg, mooinglemur]
238 +
	* Many documentation updates and fixes [veganaize, irmen, tomxp411]
239 +
240 +
### Release 41 ("Marrakech")
241 +
242 +
* allow apps to intercept Cmd/Win, Menu and Caps-Lock keys
243 +
* fixed `-prg` with `-sdcard`
244 +
* fixed loading from host filesystem (length reporting by `MACPTR` on EOI)
245 +
* macOS: support for older versions like Catalina (10.15)
246 +
247 +
### Release 40 ("Bonn")
248 +
249 +
* Features
250 +
	* improved VERA video timings [Natt Akuma]
251 +
	* added Host FS bridging using IEEE API
252 +
	* added Serial Bus emulation [experimental]
253 +
	* added WAV file recording [Stephen Horn]
254 +
	* possible to disable Ctrl/Cmd key interception ($9FB7) [mooinglemur] 
255 +
* Other
256 +
	* Fixed I2C (RTC, SMC)
257 +
	* Fixed RAM/ROM bank for PC when entering break [mjallison42]
258 +
	* LST support for -trace
259 +
260 +
## Release 39 ("Buenos Aires")
261 +
262 +
* Switch to Proto2 Hardware
263 +
	* banking through zp addresses 0 and 1
264 +
	* modified I/O layout
265 +
	* modified VIA GPIO layout
266 +
	* support for 4 controllers
267 +
	* I2C bus with SMC and RTC/NVRAM
268 +
* Features
269 +
	* implemented VIA timers [Natt Akuma]
270 +
	* added option to disable sound [Jimmy Dansbo]
271 +
	* added support for Delete, Insert, End, PgUp and PgDn keys [Stefan B Jakobsson]
272 +
	* debugger scroll up & down description [Matas Lesinskas]
273 +
	* added anti-aliasing to VERA PSG waveforms [TaleTN]
274 +
* Bugs
275 +
	* fixed sending only one mouse update per frame [Elektron72]
276 +
	* fixed VSYNC timing [Elektron72]
277 +
	* switched front and back porches [Elektron72]
278 +
	* fixed LOAD/SAVE hypercall so debugger doesn't break [Stephen Horn]
279 +
	* fixed YM2151 frequency from 4MHz ->3.579545MHz [Stephen Horn]
280 +
	* do not set compositor bypass hint for SDL Window [Stephen Horn]
281 +
	* reset timing after exiting debugger [Elektron72]
282 +
	* don't write nvram after every frame
283 +
	* fixed write outside of line buffer [Stephen Horn]
284 +
	* fixed BRA extra CPU cycle [LRFLEW]
285 +
	* fix: clear layer line once layer is disabled
286 +
	* fixed BBSx/BBRx timing [Natt Akuma]
287 +
* Other
288 +
	* misc speed optimizations [Stephen Horn]
289 +
290 +
## Release 38 ("Kyoto")
291 +
292 +
* CPU
293 +
	* added WAI, BBS, BBR, SMB, and RMB instructions [Stephen Horn]
294 +
* VERA
295 +
	* VERA speed optimizations [Stephen Horn]
296 +
	* fixed raster line interrupt [Stephen Horn]
297 +
	* added sprite collision interrupt [Stephen Horn]
298 +
	* fixed sprite wrapping [Stephen Horn]
299 +
	* added VERA dump, fill commands to debugger [Stephen Horn]
300 +
	* fixed VRAM memory dump [Stephen Horn]
301 +
* SD card
302 +
	* SD card write support
303 +
	* Ctrl+D/Cmd+D detaches/attaches SD card (for debugging)
304 +
	* improved/cleaned up SD card emulation [Frank van den Hoef]
305 +
	* SD card activity/error LED support
306 +
	* VERA-SPI: support Auto-TX mode
307 +
* misc
308 +
	* added warp mode (Ctrl+'+'/Cmd+'+' to toggle, or `-warp`)
309 +
	* added '-version' shell option [Alice Trillian Osako]
310 +
	* new app icon [Stephen Horn]
311 +
	* expose 32 bit cycle counter (up to 500 sec) in emulator I/O area
312 +
	* zero page register display in debugger [Mike Allison]
313 +
	* Various WebAssembly improvements and fixes [Sebastian Voges]
314 +
315 +
### Release 37 ("Geneva")
316 +
317 +
* VERA 0.9 register layout [Frank van den Hoef]
318 +
* audio [Frank van den Hoef]
319 +
    * VERA PCM and PSG audio support
320 +
    * YM2151 support is now enabled by default
321 +
    * added `-abufs` to specify number of audio buffers
322 +
* removed UART [Frank van den Hoef]
323 +
* added window icon [Nigel Stewart]
324 +
* fixed access to paths with non-ASCII characters on Windows [Serentty]
325 +
* SDL HiDPI hint to fix mouse scaling [Edward Kmett]
326 +
327 +
### Release 36 ("Berlin")
328 +
329 +
* added VERA UART emulation (`-uart-in`, `-uart-out`)
330 +
* correctly emulate missing SD card
331 +
* moved host filesystem interface from device 1 to device 8, only available if no SD card is attached
332 +
* require numeric argument for `-test` to auto-run test
333 +
* fixed JMP (a,x) for 65c02
334 +
* Fixed ESC as RUN/STOP [Ingo Hinterding]
335 +
336 +
### Release 35
337 +
338 +
* video optimization [Neil Forbes-Richardson]
339 +
* added `-geos` to launch GEOS on startup
340 +
* added `-test` to launch (graphics) unit test on startup
341 +
* debugger
342 +
	* switch viewed RAM/ROM bank with `numpad +` and `numpad -` [Kobrasadetin]
343 +
	* optimized character printing [Kobrasadetin]
344 +
* trace mode:
345 +
	* prepend ROM bank to address in trace
346 +
	* also prints 16 bit virtual regs (graph/GEOS)
347 +
* fixes
348 +
	* initialize memory to 0 [Kobrasadetin]
349 +
	* fixed SYS hex argument
350 +
	* disabled "buffer full, skipping" and SD card debug text, it was too noisy
351 +
352 +
### Release 34
353 +
354 +
* PS/2 mouse
355 +
* support for text mode with tiles other than 8x8 [Serentty]
356 +
* fix: programmatic echo mode control [Mikael O. Bonnier]
357 +
358 +
### Release 33
359 +
360 +
* significant performance optimizations
361 +
* VERA
362 +
	* enabled all 128 sprites
363 +
	* correct sprite zdepth
364 +
	* support for raster IRQs
365 +
* SDL controller support using `-joy1` and `-joy2` [John J Bliss]
366 +
* 65C02 BCD fixes [Norman B. Lancaster]
367 +
* feature parity with new LOAD/VLOAD features [John-Paul Gignac]
368 +
* default RAM and ROM banks are now 0, matching the hardware
369 +
* GIF recording can now be controlled from inside the machine [Randall Bohn]
370 +
* Debugging
371 +
	* Major enhancements to the debugger [kktos]
372 +
	* `-echo` will now encode non-printable characters like this: \X93 for CHR$(93), `-bas` as 	well as pasting accepts this convention again
373 +
	* `-echo raw` for the original behavior
374 +
	* `-echo iso` for correct character encoding in ISO mode
375 +
	* `-ram` to specify RAM size; now defaults to 512
376 +
377 +
### Release 32
378 +
379 +
* correct ROM banking
380 +
* VERA emulation optimizations [Stephen Horn]
381 +
* added `-dump` option to allow writing RAM, CPU state or VERA state to disk [Nils Hasenbanck]
382 +
* added `-quality` option to change scaling algorithm; now defaults to "best" [Maurizio Porrato]
383 +
* output of `-echo` can now be fed into UNIX pipes [Anonymous Maarten]
384 +
* relative speed of emulator is shown in the title if host can't keep up [Rien]
385 +
* fix: 6502 BCD arithmetic [Rien]
386 +
* fix: colors (white is now white) [Rien]
387 +
* fix: sprite flipping [jjbliss]
388 +
389 +
### Release 31
390 +
391 +
* VERA 0.8 register layout
392 +
* removed `-char` (character ROM is now part of `rom.bin`)
393 +
* GIF recording using `-gif` [Neil Forbes-Richardson]
394 +
* numpad support [Maurizio Porrato]
395 +
* fake support of VIA timers to work around BASIC RND(0)
396 +
* default ROM is taken from executable's directory [Michael Watters]
397 +
* emulator window has a title [Michael Watters]
398 +
* `-debug` allows specifying a breakpoint [Frank Buss]
399 +
* package contains the ROM symbols in `rom.txt`
400 +
* support for VERA SPI
401 +
402 +
### Release 30
403 +
404 +
Emulator:
405 +
* VERA can now generate VSYNC interrupts
406 +
* added `-keymap` for setting the keyboard layout
407 +
* added `-scale` for integer scaling of the window [Stephen Horn]
408 +
* added `-log` to enable various logging features (can also be enabled at runtime (POKE $9FB0+) [Randall Bohn])
409 +
* changed `-run` to be an option to `-prg` and `-bas`
410 +
* emulator detection: read $9FBE/$9FBF, must read 0x31 and 0x36
411 +
* fix: `-prg` and `-run` no longer corrupt BASIC programs.
412 +
* fix: `LOAD,1` into RAM bank [Stephen Horn]
413 +
* fix: 2bpp and 4bpp drawing [Stephen Horn]
414 +
* fix: 4bpp sprites [MonstersGoBoom]
415 +
* fix: build on Linux/ARM
416 +
417 +
### Release 29
418 +
419 +
* better keyboard support: if you pretend you have a US keyboard layout when typing, all keys should now be reachable [Paul Robson]
420 +
* `-debug` will enable the new debugger [Paul Robson]
421 +
* runs at the correct speed (was way too slow on most machines)
422 +
* keyboard shortcuts work on Windows/Linux: `Ctrl + F/R/S/V`
423 +
* `Ctrl + V` pastes the clipboard as keypresses
424 +
* `-bas file.txt` loads a BASIC program in ASCII encoding
425 +
* `-echo` prints all BASIC/KERNAL output to the terminal, use it with LIST to convert a BASIC program to ASCII
426 +
* `-run` acts like `-prg`, but also autostarts the program
427 +
* `JMP $FFFF` and `SYS 65535` exit the emulator and save memory into the host's storage
428 +
* the packages now contain the current version of the Programmer's Reference Guide (HTML)
429 +
* fix: on Windows, some file load/saves may be been truncated
430 +
431 +
### Release 28
432 +
433 +
* support for 65C02 opcodes [Paul Robson]
434 +
* keep aspect ratio when resizing window [Sebastian Voges]
435 +
* updated sprite logic to VERA 0.7 – **the layout of the sprite data registers has changed, you need to change your code!**
436 +
437 +
438 +
### Release 27
439 +
440 +
* Command line overhaul. Supports `-rom`, `-char`, `-sdcard` and `-prg`.
441 +
* ROM and char filename defaults, so x16emu can be started without arguments.
442 +
* Host Filesystem Interface supports `LOAD"$"`
443 +
* macOS and Windows packaging logic in Makefile
444 +
445 +
### Release 26
446 +
447 +
* better sprite support (clipping, palette offset, flipping)
448 +
* better border support
449 +
* KERNAL can set up interlaced NTSC mode with scaling and borders (compile time option)
450 +
451 +
### Release 25
452 +
453 +
* sdcard: fixed `LOAD,x,1` to load to the correct addressg
454 +
* sdcard: all temp data will be on bank #255; current bank will remain unchanged
455 +
* DOS: support for DOS commands ("UI", "I", "V", ...) and more status messages (e.g. 26,WRITE PROTECT ON,00,00)
456 +
* BASIC: `DOS` command. Without argument: print disk status; with "$" argument: show directory; with "8" or "9" argument: switch default drive; otherwise: send DOS command; also accessible through F7/F8
457 +
* Vera: cycle exact rendering, NTSC, interlacing, border
458 +
459 +
### Release 24
460 +
461 +
* SD card support
462 +
	* pass path to SD card image as third argument
463 +
	* access SD card as drive 8
464 +
	* the local PC/Mac disk is still drive 1
465 +
	* modulo debugging, this would work on a real X16 with the SD card (plus level shifters) hooked up to VIA#2PB as described in sdcard.c in the emulator surce
466 +
467 +
### Release 23
468 +
469 +
* Updated emulator and ROM to spec 0.6 – the ROM image should work on a real X16 with VERA 0.6 now.
470 +
471 +
### Release 22
472 +
473 +
SYS65375 (SWAPPER) now also clears the screen, avoid ing side effects.
474 +
475 +
### Release 21
476 +
477 +
* support for $ and % number prefixes in BASIC
478 +
* support for C128 KERNAL APIs LKUPLA, LKUPSA and CLOSE_ALL
479 +
480 +
### Release 20
481 +
482 +
* Toggle fullscreen using `Cmd + F` or `Cmd + return`
483 +
* new BASIC instructions and functions:
484 +
	* `MON`: enter monitor; no more SYS65280 required
485 +
	* `VPEEK(bank, address)`
486 +
	* `VPOKE bank, address, value`
487 +
example: `VPOKE4,0,VPEEK(4,0) OR 32` [for 256 color BASIC]
488 +
489 +
### Release 19
490 +
491 +
* fixed cursor trail bug
492 +
* fixed f7 key in PS/2 driver
493 +
* f keys are assigned with shortcuts now:
494 +
F1: LIST
495 +
F2: &lt;enter monitor&gt;
496 +
F3: RUN
497 +
F4: &lt;switch 40/80&gt;
498 +
F5: LOAD
499 +
F6: SAVE"
500 +
F7: DOS"$ &lt;doesn't work yet&gt;
501 +
F8: DOS &lt;doesn't work yet&gt;
502 +
503 +
### Release 18
504 +
505 +
* Fixed scrolling in 40x30 mode when there are double lines on the screen.
506 +
507 +
### Release 17
508 +
509 +
* video RAM support in the monitor (SYS65280)
510 +
* 40x30 screen support (SYS65375 to toggle)
511 +
512 +
### Release 16
513 +
514 +
* Integrated monitor, start with SYS65280
515 +
`rom.bin` is now 3*8 KB:
516 +
	* 0: BASIC (bank 0 at $C000)
517 +
	* 1: KERNAL ($E000)
518 +
	* 2: UTIL (bank 1 at $C000)
519 +
520 +
### Release 15
521 +
522 +
* correct text mode video RAM layout both in emulator and KERNAL
523 +
524 +
### Release 14
525 +
526 +
* KERNAL: fast scrolling
527 +
* KERNAL: upper/lower switching using CHR$($0E)/CHR$($8E)
528 +
* KERNAL: banking init
529 +
* KERNAL: new PS/2 driver
530 +
* Emulator: VERA updates (more modes, second data port)
531 +
* Emulator: RAM and ROM banks start out as all 1 bits
532 +
533 +
### Release 13
534 +
535 +
* Supports mode 7 (8bpp bitmap).
536 +
537 +
### Release 12
538 +
539 +
* Supports 8bpp tile mode (mode 4)
540 +
541 +
### Release 11
542 +
543 +
* The emulator and the KERNAL now speak the bit-level PS/2 protocol over VIA#2 PA0/PA1. The system behaves the same, but keyboard input in the ROM should work on a real device.
544 +
545 +
### Release 10
546 +
547 +
updated KERNAL with proper power-on message
548 +
549 +
### Release 9
550 +
551 +
* LOAD and SAVE commands are intercepted by the emulator, can be used to access local file system, like this:
552 +
553 +
      LOAD"TETRIS.PRG
554 +
      SAVE"TETRIS.PRG
555 +
556 +
* No device number is necessary. Loading absolute works like this:
557 +
558 +
      LOAD"FILE.PRG",1,1
559 +
560 +
### Release 8
561 +
562 +
* New optional override load address for PRG files:
563 +
564 +
      ./x64emu rom.bin chargen.bin basic.prg,0401
565 +
566 +
### Release 7
567 +
568 +
* Now with banking. `POKE40801,n` to switch the RAM bank at $A000. `POKE40800,n` to switch the ROM bank at $C000. The ROM file at the command line can be up to 72 KB now (layout: 0: bank 0, 1: KERNAL, 2: bank 1, 3: bank 2 etc.), and the RAM that `Cmd + S` saves is 2088KB ($0000-$9F00: regular RAM, $9F00-$9FFF: unused, $A000+: extra banks)
569 +
570 +
### Release 6
571 +
572 +
* Vera emulation now matches the complete spec dated 2019-07-06: correct video address space layout, palette format, redefinable character set
573 +
574 +
### Release 5
575 +
576 +
* BASIC now starts at $0401 (39679 BASIC BYTES FREE)
577 +
578 +
### Release 4
579 +
580 +
* `Cmd + S` now saves all of memory (linear 64 KB for now, including ROM) to `memory.bin`, `memory-1.bin`, `memory-2.bin`, etc. You can extract parts of it with Unix "dd", like: `dd if=memory.bin of=basic.bin bs=1 skip=2049 count=38655`
581 +
582 +
### Release 3
583 +
584 +
* Supports PRG file as third argument, which is injected after "READY.", so BASIC programs work as well.
585 +
586 +
### Release 2
587 +
588 +
* STOP key support
589 +
590 +
### Release 1
591 +
592 +
* 6502 core, fake PS/2 keyboard emulation (PS/2 data bytes appear at VIA#1 PB) and text mode Vera emulation
593 +
* KERNAL/BASIC modified for memory layout, missing VIC, Vera text mode and PS/2 keyboard
added build_rom_debug.sh +18 −0
1 +
#!/bin/bash
2 +
set -e
3 +
4 +
# build ROM
5 +
(cd ../x16-rom/; make clean all)
6 +
7 +
# copy ROM labels & LST
8 +
cp ../x16-rom/build/x16/rom_labels.h src
9 +
cp ../x16-rom/build/x16/rom_lst.h src
10 +
11 +
# update ROM symbols (for keyboard injection)
12 +
sh rom_symbols.sh
13 +
14 +
# always force re-build of main.c because of updated rom_labels.h
15 +
rm -f build/main.o
16 +
17 +
# build emulator with `-trace` support
18 +
TRACE=1 make
added github-pandoc.css +424 −0
1 +
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
2 +
3 +
/* ==========================================================================
4 +
   HTML5 display definitions
5 +
   ========================================================================== */
6 +
7 +
/**
8 +
 * Correct `block` display not defined in IE 8/9.
9 +
 */
10 +
11 +
article,
12 +
aside,
13 +
details,
14 +
figcaption,
15 +
figure,
16 +
footer,
17 +
header,
18 +
hgroup,
19 +
main,
20 +
nav,
21 +
section,
22 +
summary {
23 +
    display: block;
24 +
}
25 +
26 +
/**
27 +
 * Correct `inline-block` display not defined in IE 8/9.
28 +
 */
29 +
30 +
audio,
31 +
canvas,
32 +
video {
33 +
    display: inline-block;
34 +
}
35 +
36 +
/**
37 +
 * Prevent modern browsers from displaying `audio` without controls.
38 +
 * Remove excess height in iOS 5 devices.
39 +
 */
40 +
41 +
audio:not([controls]) {
42 +
    display: none;
43 +
    height: 0;
44 +
}
45 +
46 +
/**
47 +
 * Address `[hidden]` styling not present in IE 8/9.
48 +
 * Hide the `template` element in IE, Safari, and Firefox < 22.
49 +
 */
50 +
51 +
[hidden],
52 +
template {
53 +
    display: none;
54 +
}
55 +
56 +
/* ==========================================================================
57 +
   Base
58 +
   ========================================================================== */
59 +
60 +
/**
61 +
 * 1. Set default font family to sans-serif.
62 +
 * 2. Prevent iOS text size adjust after orientation change, without disabling
63 +
 *    user zoom.
64 +
 */
65 +
66 +
html {
67 +
    font-family: sans-serif; /* 1 */
68 +
    -ms-text-size-adjust: 100%; /* 2 */
69 +
    -webkit-text-size-adjust: 100%; /* 2 */
70 +
}
71 +
72 +
/**
73 +
 * Remove default margin.
74 +
 */
75 +
76 +
body {
77 +
    margin: 0;
78 +
}
79 +
80 +
/* ==========================================================================
81 +
   Links
82 +
   ========================================================================== */
83 +
84 +
/**
85 +
 * Remove the gray background color from active links in IE 10.
86 +
 */
87 +
88 +
a {
89 +
    background: transparent;
90 +
}
91 +
92 +
/**
93 +
 * Address `outline` inconsistency between Chrome and other browsers.
94 +
 */
95 +
96 +
a:focus {
97 +
    outline: thin dotted;
98 +
}
99 +
100 +
/**
101 +
 * Improve readability when focused and also mouse hovered in all browsers.
102 +
 */
103 +
104 +
a:active,
105 +
a:hover {
106 +
    outline: 0;
107 +
}
108 +
109 +
/* ==========================================================================
110 +
   Typography
111 +
   ========================================================================== */
112 +
113 +
/**
114 +
 * Address variable `h1` font-size and margin within `section` and `article`
115 +
 * contexts in Firefox 4+, Safari 5, and Chrome.
116 +
 */
117 +
118 +
h1 {
119 +
    font-size: 2em;
120 +
    margin: 0.67em 0;
121 +
}
122 +
123 +
/**
124 +
 * Address styling not present in IE 8/9, Safari 5, and Chrome.
125 +
 */
126 +
127 +
abbr[title] {
128 +
    border-bottom: 1px dotted;
129 +
}
130 +
131 +
/**
132 +
 * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
133 +
 */
134 +
135 +
b,
136 +
strong {
137 +
    font-weight: bold;
138 +
}
139 +
140 +
/**
141 +
 * Address styling not present in Safari 5 and Chrome.
142 +
 */
143 +
144 +
dfn {
145 +
    font-style: italic;
146 +
}
147 +
148 +
/**
149 +
 * Address differences between Firefox and other browsers.
150 +
 */
151 +
152 +
hr {
153 +
    -moz-box-sizing: content-box;
154 +
    box-sizing: content-box;
155 +
    height: 0;
156 +
}
157 +
158 +
/**
159 +
 * Address styling not present in IE 8/9.
160 +
 */
161 +
162 +
mark {
163 +
    background: #ff0;
164 +
    color: #000;
165 +
}
166 +
167 +
/**
168 +
 * Correct font family set oddly in Safari 5 and Chrome.
169 +
 */
170 +
171 +
code,
172 +
kbd,
173 +
pre,
174 +
samp {
175 +
    font-family: monospace, serif;
176 +
    font-size: 1em;
177 +
}
178 +
179 +
/**
180 +
 * Improve readability of pre-formatted text in all browsers.
181 +
 */
182 +
183 +
pre {
184 +
    white-space: pre-wrap;
185 +
}
186 +
187 +
/**
188 +
 * Set consistent quote types.
189 +
 */
190 +
191 +
q {
192 +
    quotes: "\201C" "\201D" "\2018" "\2019";
193 +
}
194 +
195 +
/**
196 +
 * Address inconsistent and variable font size in all browsers.
197 +
 */
198 +
199 +
small {
200 +
    font-size: 80%;
201 +
}
202 +
203 +
/**
204 +
 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
205 +
 */
206 +
207 +
sub,
208 +
sup {
209 +
    font-size: 75%;
210 +
    line-height: 0;
211 +
    position: relative;
212 +
    vertical-align: baseline;
213 +
}
214 +
215 +
sup {
216 +
    top: -0.5em;
217 +
}
218 +
219 +
sub {
220 +
    bottom: -0.25em;
221 +
}
222 +
223 +
/* ==========================================================================
224 +
   Embedded content
225 +
   ========================================================================== */
226 +
227 +
/**
228 +
 * Remove border when inside `a` element in IE 8/9.
229 +
 */
230 +
231 +
img {
232 +
    border: 0;
233 +
}
234 +
235 +
/**
236 +
 * Correct overflow displayed oddly in IE 9.
237 +
 */
238 +
239 +
svg:not(:root) {
240 +
    overflow: hidden;
241 +
}
242 +
243 +
/* ==========================================================================
244 +
   Figures
245 +
   ========================================================================== */
246 +
247 +
/**
248 +
 * Address margin not present in IE 8/9 and Safari 5.
249 +
 */
250 +
251 +
figure {
252 +
    margin: 0;
253 +
}
254 +
255 +
/* ==========================================================================
256 +
   Forms
257 +
   ========================================================================== */
258 +
259 +
/**
260 +
 * Define consistent border, margin, and padding.
261 +
 */
262 +
263 +
fieldset {
264 +
    border: 1px solid #c0c0c0;
265 +
    margin: 0 2px;
266 +
    padding: 0.35em 0.625em 0.75em;
267 +
}
268 +
269 +
/**
270 +
 * 1. Correct `color` not being inherited in IE 8/9.
271 +
 * 2. Remove padding so people aren't caught out if they zero out fieldsets.
272 +
 */
273 +
274 +
legend {
275 +
    border: 0; /* 1 */
276 +
    padding: 0; /* 2 */
277 +
}
278 +
279 +
/**
280 +
 * 1. Correct font family not being inherited in all browsers.
281 +
 * 2. Correct font size not being inherited in all browsers.
282 +
 * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
283 +
 */
284 +
285 +
button,
286 +
input,
287 +
select,
288 +
textarea {
289 +
    font-family: inherit; /* 1 */
290 +
    font-size: 100%; /* 2 */
291 +
    margin: 0; /* 3 */
292 +
}
293 +
294 +
/**
295 +
 * Address Firefox 4+ setting `line-height` on `input` using `!important` in
296 +
 * the UA stylesheet.
297 +
 */
298 +
299 +
button,
300 +
input {
301 +
    line-height: normal;
302 +
}
303 +
304 +
/**
305 +
 * Address inconsistent `text-transform` inheritance for `button` and `select`.
306 +
 * All other form control elements do not inherit `text-transform` values.
307 +
 * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
308 +
 * Correct `select` style inheritance in Firefox 4+ and Opera.
309 +
 */
310 +
311 +
button,
312 +
select {
313 +
    text-transform: none;
314 +
}
315 +
316 +
/**
317 +
 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
318 +
 *    and `video` controls.
319 +
 * 2. Correct inability to style clickable `input` types in iOS.
320 +
 * 3. Improve usability and consistency of cursor style between image-type
321 +
 *    `input` and others.
322 +
 */
323 +
324 +
button,
325 +
html input[type="button"], /* 1 */
326 +
input[type="reset"],
327 +
input[type="submit"] {
328 +
    -webkit-appearance: button; /* 2 */
329 +
    cursor: pointer; /* 3 */
330 +
}
331 +
332 +
/**
333 +
 * Re-set default cursor for disabled elements.
334 +
 */
335 +
336 +
button[disabled],
337 +
html input[disabled] {
338 +
    cursor: default;
339 +
}
340 +
341 +
/**
342 +
 * 1. Address box sizing set to `content-box` in IE 8/9/10.
343 +
 * 2. Remove excess padding in IE 8/9/10.
344 +
 */
345 +
346 +
input[type="checkbox"],
347 +
input[type="radio"] {
348 +
    box-sizing: border-box; /* 1 */
349 +
    padding: 0; /* 2 */
350 +
}
351 +
352 +
/**
353 +
 * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
354 +
 * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
355 +
 *    (include `-moz` to future-proof).
356 +
 */
357 +
358 +
input[type="search"] {
359 +
    -webkit-appearance: textfield; /* 1 */
360 +
    -moz-box-sizing: content-box;
361 +
    -webkit-box-sizing: content-box; /* 2 */
362 +
    box-sizing: content-box;
363 +
}
364 +
365 +
/**
366 +
 * Remove inner padding and search cancel button in Safari 5 and Chrome
367 +
 * on OS X.
368 +
 */
369 +
370 +
input[type="search"]::-webkit-search-cancel-button,
371 +
input[type="search"]::-webkit-search-decoration {
372 +
    -webkit-appearance: none;
373 +
}
374 +
375 +
/**
376 +
 * Remove inner padding and border in Firefox 4+.
377 +
 */
378 +
379 +
button::-moz-focus-inner,
380 +
input::-moz-focus-inner {
381 +
    border: 0;
382 +
    padding: 0;
383 +
}
384 +
385 +
/**
386 +
 * 1. Remove default vertical scrollbar in IE 8/9.
387 +
 * 2. Improve readability and alignment in all browsers.
388 +
 */
389 +
390 +
textarea {
391 +
    overflow: auto; /* 1 */
392 +
    vertical-align: top; /* 2 */
393 +
}
394 +
395 +
/* ==========================================================================
396 +
   Tables
397 +
   ========================================================================== */
398 +
399 +
/**
400 +
 * Remove most spacing between table cells.
401 +
 */
402 +
403 +
table {
404 +
    border-collapse: collapse;
405 +
    border-spacing: 0;
406 +
}
407 +
408 +
.go-top {
409 +
position: fixed;
410 +
bottom: 2em;
411 +
right: 2em;
412 +
text-decoration: none;
413 +
background-color: #E0E0E0;
414 +
font-size: 12px;
415 +
padding: 1em;
416 +
display: inline;
417 +
}
418 +
419 +
/* Github css */
420 +
421 +
html,body{        margin: auto;
422 +
    padding-right: 1em;
423 +
    padding-left: 1em;
424 +
    max-width: 44em; color:black;}*:not('#mkdbuttons'){margin:0;padding:0}body{font:13.34px helvetica,arial,freesans,clean,sans-serif;-webkit-font-smoothing:subpixel-antialiased;line-height:1.4;padding:3px;background:#fff;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px}p{margin:1em 0}a{color:#4183c4;text-decoration:none}body{background-color:#fff;padding:30px;margin:15px;font-size:14px;line-height:1.6}body>*:first-child{margin-top:0!important}body>*:last-child{margin-bottom:0!important}@media screen{body{box-shadow:0 0 0 1px #cacaca,0 0 0 4px #eee}}h1,h2,h3,h4,h5,h6{margin:20px 0 10px;padding:0;font-weight:bold;-webkit-font-smoothing:subpixel-antialiased;cursor:text}h1{font-size:28px;color:#000}h2{font-size:24px;border-bottom:1px solid #ccc;color:#000}h3{font-size:18px;color:#333}h4{font-size:16px;color:#333}h5{font-size:14px;color:#333}h6{color:#777;font-size:14px}p,blockquote,table,pre{margin:15px 0}ul{padding-left:30px}ol{padding-left:30px}ol li ul:first-of-type{margin-top:0}hr{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;border:0 none;color:#ccc;height:4px;padding:0}body>h2:first-child{margin-top:0;padding-top:0}body>h1:first-child{margin-top:0;padding-top:0}body>h1:first-child+h2{margin-top:0;padding-top:0}body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child{margin-top:0;padding-top:0}a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6{margin-top:0;padding-top:0}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p,ul li>:first-child,ol li>:first-child{margin-top:0}dl{padding:0}dl dt{font-size:14px;font-weight:bold;font-style:italic;padding:0;margin:15px 0 5px}dl dt:first-child{padding:0}dl dt>:first-child{margin-top:0}dl dt>:last-child{margin-bottom:0}dl dd{margin:0 0 15px;padding:0 15px}dl dd>:first-child{margin-top:0}dl dd>:last-child{margin-bottom:0}blockquote{border-left:4px solid #DDD;padding:0 15px;color:#777}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}table{border-collapse:collapse;border-spacing:0;font-size:100%;font:inherit}table th{font-weight:bold;border:1px solid #ccc;padding:6px 13px}table td{border:1px solid #ccc;padding:6px 13px}table tr{border-top:1px solid #ccc;background-color:#fff}table tr:nth-child(2n){background-color:#f8f8f8}img{max-width:100%}code,tt{margin:0 2px;padding:0 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px;font-family:Consolas,'Liberation Mono',Courier,monospace;font-size:12px;color:#333}pre>code{margin:0;padding:0;white-space:pre;border:0;background:transparent}.highlight pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre code,pre tt{background-color:transparent;border:0}.poetry pre{font-family:Georgia,Garamond,serif!important;font-style:italic;font-size:110%!important;line-height:1.6em;display:block;margin-left:1em}.poetry pre code{font-family:Georgia,Garamond,serif!important;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;white-space:pre-wrap}sup,sub,a.footnote{font-size:1.4ex;height:0;line-height:1;vertical-align:super;position:relative}sub{vertical-align:sub;top:-1px}@media print{body{background:#fff}img,pre,blockquote,table,figure{page-break-inside:avoid}body{background:#fff;border:0}code{background-color:#fff;color:#333!important;padding:0 .2em;border:1px solid #dedede}pre{background:#fff}pre code{background-color:white!important;overflow:visible}}@media screen{body.inverted{color:#eee!important;border-color:#555;box-shadow:none}.inverted body,.inverted hr .inverted p,.inverted td,.inverted li,.inverted h1,.inverted h2,.inverted h3,.inverted h4,.inverted h5,.inverted h6,.inverted th,.inverted .math,.inverted caption,.inverted dd,.inverted dt,.inverted blockquote{color:#eee!important;border-color:#555;box-shadow:none}.inverted td,.inverted th{background:#333}.inverted h2{border-color:#555}.inverted hr{border-color:#777;border-width:1px!important}::selection{background:rgba(157,193,200,0.5)}h1::selection{background-color:rgba(45,156,208,0.3)}h2::selection{background-color:rgba(90,182,224,0.3)}h3::selection,h4::selection,h5::selection,h6::selection,li::selection,ol::selection{background-color:rgba(133,201,232,0.3)}code::selection{background-color:rgba(0,0,0,0.7);color:#eee}code span::selection{background-color:rgba(0,0,0,0.7)!important;color:#eee!important}a::selection{background-color:rgba(255,230,102,0.2)}.inverted a::selection{background-color:rgba(255,230,102,0.6)}td::selection,th::selection,caption::selection{background-color:rgba(180,237,95,0.5)}.inverted{background:#0b2531;background:#252a2a}.inverted body{background:#252a2a}.inverted a{color:#acd1d5}}.highlight .c{color:#998;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .k,.highlight .o{font-weight:bold}.highlight .cm{color:#998;font-style:italic}.highlight .cp{color:#999;font-weight:bold}.highlight .c1{color:#998;font-style:italic}.highlight .cs{color:#999;font-weight:bold;font-style:italic}.highlight .gd{color:#000;background-color:#fdd}.highlight .gd .x{color:#000;background-color:#faa}.highlight .ge{font-style:italic}.highlight .gr{color:#a00}.highlight .gh{color:#999}.highlight .gi{color:#000;background-color:#dfd}.highlight .gi .x{color:#000;background-color:#afa}.highlight .go{color:#888}.highlight .gp{color:#555}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#a00}.highlight .kc,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr{font-weight:bold}.highlight .kt{color:#458;font-weight:bold}.highlight .m{color:#099}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .nb{color:#0086b3}.highlight .nc{color:#458;font-weight:bold}.highlight .no{color:#008080}.highlight .ni{color:#800080}.highlight .ne,.highlight .nf{color:#900;font-weight:bold}.highlight .nn{color:#555}.highlight .nt{color:#000080}.highlight .nv{color:#008080}.highlight .ow{font-weight:bold}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#099}.highlight .sb,.highlight .sc,.highlight .sd,.highlight .s2,.highlight .se,.highlight .sh,.highlight .si,.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .bp{color:#999}.highlight .vc,.highlight .vg,.highlight .vi{color:#008080}.highlight .il{color:#099}.highlight .gc{color:#999;background-color:#eaf2f5}.type-csharp .highlight .k,.type-csharp .highlight .kt{color:#00F}.type-csharp .highlight .nf{color:#000;font-weight:normal}.type-csharp .highlight .nc{color:#2b91af}.type-csharp .highlight .nn{color:#000}.type-csharp .highlight .s,.type-csharp .highlight .sc{color:#a31515}
added mdtohtml.lua +4 −0
1 +
function Link(el)
2 +
	el.target = string.gsub(el.target, "%.md", ".html")
3 +
	return el
4 +
end
added package/package.sh +97 −0
1 +
#
2 +
# Build x16 release packages
3 +
#
4 +
5 +
# Note: Intel Mac needs
6 +
#   sudo ln -s /usr/local /opt/homebrew
7 +
8 +
set -e 
9 +
10 +
##############################
11 +
export PRERELEASE_VERSION=41
12 +
#PRE=_rc3
13 +
##############################
14 +
15 +
16 +
PATCHES="`pwd`"
17 +
18 +
cd /tmp
19 +
20 +
rm -rf sdl2-win32
21 +
cp -prv ~/tmp/sdl2-win32 sdl2-win32
22 +
23 +
rm -rf x16-release
24 +
mkdir x16-release
25 +
cd x16-release
26 +
27 +
git clone https://github.com/commanderx16/x16-emulator
28 +
git clone https://github.com/commanderx16/x16-rom
29 +
git clone https://github.com/commanderx16/x16-docs
30 +
(cd x16-emulator; git reset --hard r$PRERELEASE_VERSION)
31 +
(cd x16-rom; git reset --hard r$PRERELEASE_VERSION)
32 +
(cd x16-docs; git reset --hard r$PRERELEASE_VERSION)
33 +
zip -r1m x16.zip x16-emulator x16-rom x16-docs
34 +
35 +
# Mac ARM
36 +
37 +
unzip x16.zip
38 +
(
39 +
        cd x16-emulator
40 +
        make package_mac
41 +
        mv x16emu_mac.zip ../x16emu_mac_arm.zip
42 +
)
43 +
rm -rf x16-emulator x16-rom x16-docs
44 +
45 +
# Mac Intel
46 +
47 +
scp x16.zip intel16.local:/tmp
48 +
ssh intel16.local "cd /tmp; rm -rf x16-emulator x16-rom x16-docs; unzip x16.zip; cd x16-emulator; PATH=/usr/local/bin:$PATH PRERELEASE_VERSION=$PRERELEASE_VERSION make package_mac"
49 +
scp intel16.local:/tmp/x16-emulator/x16emu_mac.zip x16emu_mac_intel.zip
50 +
51 +
# Mac Universal
52 +
(
53 +
        mkdir combine
54 +
        cd combine
55 +
        (mkdir arm; cd arm; unzip ../../x16emu_mac_arm.zip)
56 +
        (mkdir intel; cd intel; unzip ../../x16emu_mac_intel.zip)
57 +
        (
58 +
                cd arm
59 +
                mv x16emu x16emu.arm
60 +
                lipo x16emu.arm ../intel/x16emu -create -output x16emu
61 +
                rm x16emu.arm
62 +
                zip -r ../../x16emu_mac.zip *
63 +
        )
64 +
)
65 +
rm -rf combine
66 +
rm x16emu_mac_arm.zip
67 +
rm x16emu_mac_intel.zip
68 +
69 +
# Linux
70 +
71 +
unzip x16.zip
72 +
(
73 +
        cd x16-emulator
74 +
        make package_linux
75 +
        mv x16emu_linux.zip ..
76 +
)
77 +
rm -rf x16-emulator x16-rom x16-docs
78 +
79 +
# Windows
80 +
81 +
unzip x16.zip
82 +
(
83 +
        cd x16-emulator
84 +
        patch -p1 < $PATCHES/patch1.diff
85 +
        make package_win
86 +
        mv x16emu_win.zip ..
87 +
)
88 +
rm -rf x16-emulator x16-rom x16-docs
89 +
90 +
91 +
# rename
92 +
93 +
for i in x16emu_mac x16emu_linux x16emu_win; do
94 +
        mv $i.zip $i-r$PRERELEASE_VERSION$PRE.zip
95 +
done
96 +
97 +
open .
added package/patch1.diff +13 −0
1 +
diff --git a/Makefile b/Makefile
2 +
index cd36ed1..b4f23c3 100644
3 +
--- a/Makefile
4 +
+++ b/Makefile
5 +
@@ -11,7 +11,7 @@ endif
6 +
 # the Windows SDL2 path on macOS installed through
7 +
 # ./configure  --host=x86_64-w64-mingw32 --prefix=... && make && make install
8 +
 ifndef WIN_SDL2
9 +
-	WIN_SDL2=~/tmp/sdl2-win32
10 +
+	WIN_SDL2=/tmp/sdl2-win32
11 +
 endif
12 +
 
13 +
 ifeq ($(CROSS_COMPILE_WINDOWS),1)
added quickstart-linux.md +78 −0
1 +
Quick start guide for Linux users
2 +
=================================
3 +
4 +
Installing the emulator
5 +
-----------------------
6 +
7 +
### Getting the emulator
8 +
9 +
Obtain the X16 emulator by downloading the most recent version from the 'releases' page in Github:
10 +
https://github.com/X16Community/x16-emulator/releases
11 +
Choose the correct version for your CPU architecture.
12 +
13 +
14 +
#### Alternative builds or other platform
15 +
16 +
If for any reason you cannot run the official release of the emulator, there's a Snap package
17 +
of it that you could try instead, available at https://snapcraft.io/x16emu
18 +
19 +
It should avoid any shared library version incompatibilities that you could potentially run into with the
20 +
official version, and is available for a broad range of platforms. Details and instructions on how to install
21 +
this are on the linked page.
22 +
23 +
Ofcourse, you could also compile the emulator from source, but that is outside the scope of this guide.
24 +
25 +
26 +
27 +
### Installing the emulator and preparing for use
28 +
29 +
* Extract the .zip file containing the emulator into an appropriate location such as `~/x16/emulator/`.
30 +
For the rest of this guide we will assume this as the install location.
31 +
* This directory should now contain at least `x16emu` (the emulator itself) and `rom.bin` (the X16 system rom).
32 +
You can ignore all other files in the zip file for now.
33 +
* Make sure the emulator is executable: `chmod u+x ~/x16/emulator/x16emu`
34 +
35 +
36 +
### Create a place for the programs
37 +
38 +
Programs for the X16 will usually be stored on an SD memory card that the system sees as the main disk drive.
39 +
The emulator can use an SD card image file to replicate this, but we are not going to use this feature now,
40 +
because the emulator can also use a regular directory on your computer as the disk drive for the X16.
41 +
Notice that the X16 usually expects the file names to be in UPPERCASE.
42 +
43 +
* Create a `disk` directory that will be the X16's storage location: `mkdir ~/x16/disk`
44 +
45 +
46 +
### Convenient launching
47 +
48 +
The emulator has many options to tweak its behavior. To avoid having to retype these every time,
49 +
we'll be using a small shell script to launch the emulator.
50 +
51 +
* Create a launch script somewhere (for example `~/x16/startemu`) with the following:
52 +
    ```sh
53 +
    #!/usr/bin/env sh
54 +
    ~/x16/emulator/x16emu -fsroot ~/x16/disk -scale 2 $*
55 +
    ```
56 +
* make the script executable: `chmod u+x ~/x16/startemu`
57 +
58 +
59 +
Installing programs on the 'disk'
60 +
---------------------------------
61 +
The X16 is capable of using subdirectories. This can be used to avoid cluttering the disk contents
62 +
with files from different programs together in a single location.
63 +
Instead, create a directory for each program and the files it needs.
64 +
65 +
* assume we have a game called "kart" and it comes in a zip file that contains the game program,
66 +
  the music and the graphics files.
67 +
* create a directory for the game: `mkdir ~/x16/disk/KART`  Remember that the X16 usually needs
68 +
  directories and file names in UPPER CASE.
69 +
* extract the zip file into that directory.
70 +
71 +
72 +
Running programs on the X16
73 +
---------------------------
74 +
75 +
* start the emulator with the launch script
76 +
* on the X16, go to the directory containing the program: `DOS"CD:/DIRNAME"`
77 +
* load and run the desired program:  `LOAD"GAME"`, followed by `RUN`
78 +
added rom_symbols.sh +4 −0
1 +
#!/bin/sh
2 +
for i in ndx keyd; do
3 +
	echo "#define" `echo $i | tr '[:lower:]' '[:upper:]'` 0x`cat ../x16-rom/build/x16/kernal.sym ../x16-rom/build/x16/basic.sym | grep -w $i | head -n 1 | cut -d " " -f 2`;
4 +
done > src/rom_symbols.h
added sdcard.img.zip +0 −0

No line changes.

added src/audio.c +451 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2020 Frank van den Hoef
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include "audio.h"
6 +
#include "glue.h"
7 +
#include "vera_psg.h"
8 +
#include "vera_pcm.h"
9 +
#include "wav_recorder.h"
10 +
#include "ymglue.h"
11 +
#include "midi.h"
12 +
#include <stdint.h>
13 +
#include <stdio.h>
14 +
#include <string.h>
15 +
#include <stdlib.h>
16 +
17 +
/* RRDC (control_backend.c): tee each mixed stereo pair into the /audio ring. */
18 +
void x16_control_push_audio(int16_t l, int16_t r);
19 +
20 +
#ifdef __EMSCRIPTEN__
21 +
	#define SAMPLES_PER_BUFFER (1024)
22 +
	#define SAMP_POS_FRAC_BITS (22)
23 +
#else
24 +
	#define SAMPLES_PER_BUFFER (256)
25 +
	#define SAMP_POS_FRAC_BITS (24)
26 +
#endif
27 +
28 +
#define VERA_SAMP_CLKS_PER_CPU_CLK ((25000000ULL << SAMP_POS_FRAC_BITS) / 512 / MHZ / 1000000)
29 +
#define YM_SAMP_CLKS_PER_CPU_CLK ((3579545ULL << SAMP_POS_FRAC_BITS) / 64 / MHZ / 1000000)
30 +
#define FS_SAMP_CLKS_PER_CPU_CLK VERA_SAMP_CLKS_PER_CPU_CLK
31 +
#define SAMPLE_BYTES (2 * sizeof(int16_t))
32 +
#define SAMP_POS_MASK (SAMPLES_PER_BUFFER - 1)
33 +
#define SAMP_POS_MASK_FRAC (((uint32_t)SAMPLES_PER_BUFFER << SAMP_POS_FRAC_BITS) - 1)
34 +
35 +
// windowed sinc
36 +
static const int16_t filter[512] = {
37 +
	32767,32765,32761,32755,32746,32736,32723,32707,32690,32670,32649,32625,32598,32570,32539,32507,
38 +
	32472,32435,32395,32354,32310,32265,32217,32167,32115,32061,32004,31946,31885,31823,31758,31691,
39 +
	31623,31552,31479,31404,31327,31248,31168,31085,31000,30913,30825,30734,30642,30547,30451,30353,
40 +
	30253,30151,30048,29943,29835,29726,29616,29503,29389,29273,29156,29037,28916,28793,28669,28544,
41 +
	28416,28288,28157,28025,27892,27757,27621,27483,27344,27204,27062,26918,26774,26628,26481,26332,
42 +
	26182,26031,25879,25726,25571,25416,25259,25101,24942,24782,24621,24459,24296,24132,23967,23801,
43 +
	23634,23466,23298,23129,22959,22788,22616,22444,22271,22097,21923,21748,21572,21396,21219,21042,
44 +
	20864,20686,20507,20328,20148,19968,19788,19607,19426,19245,19063,18881,18699,18517,18334,18152,
45 +
	17969,17786,17603,17420,17237,17054,16871,16688,16505,16322,16139,15957,15774,15592,15409,15227,
46 +
	15046,14864,14683,14502,14321,14141,13961,13781,13602,13423,13245,13067,12890,12713,12536,12360,
47 +
	12185,12010,11836,11663,11490,11317,11146,10975,10804,10635,10466,10298,10131, 9964, 9799, 9634,
48 +
	 9470, 9306, 9144, 8983, 8822, 8662, 8504, 8346, 8189, 8033, 7878, 7724, 7571, 7419, 7268, 7118,
49 +
	 6969, 6822, 6675, 6529, 6385, 6241, 6099, 5958, 5818, 5679, 5541, 5405, 5269, 5135, 5002, 4870,
50 +
	 4739, 4610, 4482, 4355, 4229, 4104, 3981, 3859, 3738, 3619, 3500, 3383, 3268, 3153, 3040, 2928,
51 +
	 2817, 2708, 2600, 2493, 2388, 2284, 2181, 2079, 1979, 1880, 1783, 1686, 1591, 1498, 1405, 1314,
52 +
	 1225, 1136, 1049,  963,  879,  795,  714,  633,  554,  476,  399,  323,  249,  176,  105,   34,
53 +
	  -34, -102, -168, -234, -298, -361, -422, -482, -542, -599, -656, -712, -766, -819, -871, -922,
54 +
	 -971,-1020,-1067,-1113,-1158,-1202,-1244,-1286,-1326,-1366,-1404,-1441,-1477,-1512,-1546,-1579,
55 +
	-1611,-1642,-1671,-1700,-1728,-1755,-1781,-1806,-1830,-1852,-1874,-1896,-1916,-1935,-1953,-1971,
56 +
	-1987,-2003,-2018,-2032,-2045,-2058,-2069,-2080,-2090,-2099,-2108,-2116,-2123,-2129,-2134,-2139,
57 +
	-2143,-2147,-2150,-2152,-2153,-2154,-2154,-2154,-2153,-2151,-2149,-2146,-2143,-2139,-2135,-2130,
58 +
	-2124,-2118,-2112,-2105,-2098,-2090,-2082,-2073,-2064,-2054,-2045,-2034,-2024,-2012,-2001,-1989,
59 +
	-1977,-1965,-1952,-1939,-1926,-1912,-1898,-1884,-1870,-1855,-1840,-1825,-1810,-1794,-1778,-1762,
60 +
	-1746,-1730,-1714,-1697,-1680,-1663,-1646,-1629,-1612,-1595,-1577,-1560,-1542,-1525,-1507,-1489, 
61 +
	-1471,-1453,-1435,-1418,-1400,-1382,-1364,-1346,-1328,-1310,-1292,-1274,-1256,-1238,-1220,-1203,
62 +
	-1185,-1167,-1150,-1132,-1115,-1097,-1080,-1063,-1046,-1029,-1012, -995, -978, -962, -945, -929,
63 +
     -912, -896, -880, -864, -849, -833, -817, -802, -787, -772, -757, -742, -727, -713, -699, -684,
64 +
	 -670, -656, -643, -629, -616, -603, -589, -577, -564, -551, -539, -526, -514, -502, -491, -479,
65 +
	 -468, -456, -445, -434, -423, -413, -402, -392, -381, -371, -361, -352, -342, -333, -323, -314,
66 +
	 -305, -296, -288, -279, -270, -262, -254, -246, -238, -230, -222, -215, -207, -200, -193, -186,
67 +
	 -179, -172, -165, -158, -152, -145, -139, -133, -127, -120, -114, -108, -103,  -97,  -91,  -85,
68 +
	  -80,  -74,  -69,  -63,  -58,  -53,  -47,  -42,  -37,  -32,  -27,  -22,  -17,  -12,   -7,   -2
69 +
};
70 +
71 +
static SDL_AudioDeviceID audio_dev;
72 +
static int16_t * buffer;
73 +
static uint32_t buffer_size = 0;
74 +
static uint32_t rdidx = 0;
75 +
static uint32_t wridx = 0;
76 +
static uint32_t buffer_written = 0;
77 +
static uint32_t vera_samp_pos_rd = 0;
78 +
static uint32_t vera_samp_pos_wr = 0;
79 +
static uint32_t vera_samp_pos_hd = 0;
80 +
static uint32_t ym_samp_pos_rd = 0;
81 +
static uint32_t ym_samp_pos_wr = 0;
82 +
static uint32_t ym_samp_pos_hd = 0;
83 +
static uint32_t fs_samp_pos_rd = 0;
84 +
static uint32_t fs_samp_pos_wr = 0;
85 +
static uint32_t fs_samp_pos_hd = 0;
86 +
static uint32_t vera_samps_per_host_samps = 0;
87 +
static uint32_t ym_samps_per_host_samps = 0;
88 +
static uint32_t fs_samps_per_host_samps = 0;
89 +
static uint32_t limiter_amp = 0;
90 +
91 +
static int16_t psg_buf[2 * SAMPLES_PER_BUFFER];
92 +
static int16_t pcm_buf[2 * SAMPLES_PER_BUFFER];
93 +
static int16_t ym_buf[2 * SAMPLES_PER_BUFFER];
94 +
static int16_t fs_buf[2 * SAMPLES_PER_BUFFER];
95 +
96 +
uint32_t host_sample_rate = 0;
97 +
98 +
static void
99 +
audio_callback(void *userdata, Uint8 *stream, int len)
100 +
{
101 +
	if (audio_dev == 0) {
102 +
		return;
103 +
	}
104 +
105 +
	int expected = SAMPLES_PER_BUFFER * SAMPLE_BYTES;
106 +
	if (len != expected) {
107 +
		printf("Audio buffer size mismatch! (expected: %d, got: %d)\n", expected, len);
108 +
		return;
109 +
	}
110 +
111 +
	uint32_t spos = 0;
112 +
	if (rdidx > wridx) {
113 +
		uint32_t actual_len = SDL_min(len / SAMPLE_BYTES, (buffer_size - rdidx) / 2);
114 +
		if (actual_len > 0) {
115 +
			memcpy(&stream[spos], &buffer[rdidx], actual_len * SAMPLE_BYTES);
116 +
			spos += actual_len * SAMPLE_BYTES;
117 +
			len -= actual_len * SAMPLE_BYTES;
118 +
			rdidx = (rdidx + actual_len * 2) % buffer_size;
119 +
			buffer_written -= actual_len * 2;
120 +
		}
121 +
	}
122 +
	uint32_t actual_len = SDL_min(len / SAMPLE_BYTES, (wridx - rdidx) / 2);
123 +
	if (actual_len > 0) {
124 +
		memcpy(&stream[spos], &buffer[rdidx], actual_len * SAMPLE_BYTES);
125 +
		spos += actual_len * SAMPLE_BYTES;
126 +
		len -= actual_len * SAMPLE_BYTES;
127 +
		rdidx = (rdidx + actual_len * 2) % buffer_size;
128 +
		buffer_written -= actual_len * 2;
129 +
	}
130 +
	if (len > 0) memset(&stream[spos], 0, len);
131 +
}
132 +
133 +
void
134 +
audio_init(const char *dev_name, int num_audio_buffers)
135 +
{
136 +
	if (audio_dev > 0) {
137 +
		audio_close();
138 +
	}
139 +
140 +
	if (dev_name) {
141 +
		if (!strcmp("none", dev_name)) {
142 +
			return;
143 +
		}
144 +
	}
145 +
146 +
	// Set number of buffers
147 +
	int num_bufs = num_audio_buffers;
148 +
	if (num_bufs < 3) {
149 +
		num_bufs = 3;
150 +
	}
151 +
	if (num_bufs > 1024) {
152 +
		num_bufs = 1024;
153 +
	}
154 +
	buffer_size = SAMPLES_PER_BUFFER * num_bufs * 2;
155 +
156 +
	// Allocate audio buffer
157 +
	buffer = malloc(buffer_size * sizeof(int16_t));
158 +
	rdidx = 0;
159 +
	wridx = 0;
160 +
	buffer_written = 0;
161 +
162 +
	SDL_AudioSpec desired;
163 +
	SDL_AudioSpec obtained;
164 +
165 +
	// Setup SDL audio
166 +
	memset(&desired, 0, sizeof(desired));
167 +
	desired.freq     = AUDIO_SAMPLERATE;
168 +
	desired.format   = AUDIO_S16SYS;
169 +
	desired.samples  = SAMPLES_PER_BUFFER;
170 +
	desired.channels = 2;
171 +
	desired.callback = audio_callback;
172 +
173 +
	audio_dev = SDL_OpenAudioDevice(dev_name, 0, &desired, &obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
174 +
	if (audio_dev <= 0) {
175 +
		fprintf(stderr, "SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
176 +
		if (dev_name != NULL) {
177 +
			audio_usage();
178 +
		}
179 +
		exit(-1);
180 +
	}
181 +
	if (obtained.freq <= 0 || (AUDIO_SAMPLERATE / obtained.freq) > SAMPLES_PER_BUFFER) {
182 +
		fprintf(stderr, "Obtained sample rate is too low");
183 +
		audio_close();
184 +
		return;
185 +
	}
186 +
187 +
	// Init YM2151 emulation. 3.579545 MHz clock
188 +
	YM_Create(3579545);
189 +
	YM_init(3579545/64, 60);
190 +
191 +
	host_sample_rate = obtained.freq;
192 +
	vera_samps_per_host_samps = ((25000000ULL << SAMP_POS_FRAC_BITS) / 512 / host_sample_rate);
193 +
	ym_samps_per_host_samps = ((3579545ULL << SAMP_POS_FRAC_BITS) / 64 / host_sample_rate);
194 +
	fs_samps_per_host_samps = vera_samps_per_host_samps;
195 +
	vera_samp_pos_rd = 0;
196 +
	vera_samp_pos_wr = 0;
197 +
	vera_samp_pos_hd = 0;
198 +
	ym_samp_pos_rd = 0;
199 +
	ym_samp_pos_wr = 0;
200 +
	ym_samp_pos_hd = 0;
201 +
	fs_samp_pos_rd = 0;
202 +
	fs_samp_pos_wr = 0;
203 +
	fs_samp_pos_hd = 0;
204 +
	limiter_amp = (1 << 16);
205 +
206 +
	psg_buf[0] = psg_buf[1] = 0;
207 +
	pcm_buf[0] = pcm_buf[1] = 0;
208 +
	ym_buf[0] = ym_buf[1] = 0;
209 +
	fs_buf[0] = fs_buf[1] = 0;
210 +
211 +
	// Start playback
212 +
	SDL_PauseAudioDevice(audio_dev, 0);
213 +
}
214 +
215 +
void
216 +
audio_close(void)
217 +
{
218 +
	if (audio_dev == 0) {
219 +
		return;
220 +
	}
221 +
222 +
	SDL_CloseAudioDevice(audio_dev);
223 +
	audio_dev = 0;
224 +
225 +
	// Free audio buffers
226 +
	if (buffer != NULL) {
227 +
		free(buffer);
228 +
		buffer = NULL;
229 +
	}
230 +
}
231 +
232 +
void
233 +
audio_step(int cpu_clocks)
234 +
{
235 +
	// Accumulate how many samples each source have to render
236 +
	if (audio_dev == 0) {
237 +
		return;
238 +
	}
239 +
240 +
	while (cpu_clocks > 0) {
241 +
		// Only the source with the higest sample rate (YM2151) is needed for calculation
242 +
		uint32_t max_cpu_clks_ym = ((ym_samp_pos_rd - ym_samp_pos_hd - (1 << SAMP_POS_FRAC_BITS)) & SAMP_POS_MASK_FRAC) / YM_SAMP_CLKS_PER_CPU_CLK;
243 +
		uint32_t max_cpu_clks = SDL_min(cpu_clocks, max_cpu_clks_ym);
244 +
		vera_samp_pos_hd = (vera_samp_pos_hd + max_cpu_clks * VERA_SAMP_CLKS_PER_CPU_CLK) & SAMP_POS_MASK_FRAC;
245 +
		ym_samp_pos_hd = (ym_samp_pos_hd + max_cpu_clks * YM_SAMP_CLKS_PER_CPU_CLK) & SAMP_POS_MASK_FRAC;
246 +
		fs_samp_pos_hd = (fs_samp_pos_hd + max_cpu_clks * FS_SAMP_CLKS_PER_CPU_CLK) & SAMP_POS_MASK_FRAC;
247 +
		cpu_clocks -= max_cpu_clks;
248 +
		if (cpu_clocks > 0) audio_render();
249 +
	}
250 +
}
251 +
252 +
void
253 +
audio_render()
254 +
{
255 +
	// Render all audio sources until read and write positions catch up
256 +
	// This happens when there's a change to sound registers or one of the
257 +
	// sources' sample buffer head position is too far
258 +
	if (audio_dev == 0) {
259 +
		return;
260 +
	}
261 +
262 +
	uint32_t pos, len;
263 +
264 +
	pos = (vera_samp_pos_wr + 1) & SAMP_POS_MASK;
265 +
	len = ((vera_samp_pos_hd >> SAMP_POS_FRAC_BITS) - vera_samp_pos_wr) & SAMP_POS_MASK;
266 +
	vera_samp_pos_wr = vera_samp_pos_hd >> SAMP_POS_FRAC_BITS;
267 +
	if (pos + len > SAMPLES_PER_BUFFER) {
268 +
		psg_render(&psg_buf[pos * 2], SAMPLES_PER_BUFFER - pos);
269 +
		pcm_render(&pcm_buf[pos * 2], SAMPLES_PER_BUFFER - pos);
270 +
		len -= SAMPLES_PER_BUFFER - pos;
271 +
		pos = 0;
272 +
	}
273 +
	if (len > 0) {
274 +
		psg_render(&psg_buf[pos * 2], len);
275 +
		pcm_render(&pcm_buf[pos * 2], len);
276 +
	}
277 +
278 +
	pos = (ym_samp_pos_wr + 1) & SAMP_POS_MASK;
279 +
	len = ((ym_samp_pos_hd >> SAMP_POS_FRAC_BITS) - ym_samp_pos_wr) & SAMP_POS_MASK;
280 +
	ym_samp_pos_wr = ym_samp_pos_hd >> SAMP_POS_FRAC_BITS;
281 +
	if ((pos + len) > SAMPLES_PER_BUFFER) {
282 +
		YM_stream_update((uint16_t *)&ym_buf[pos * 2], SAMPLES_PER_BUFFER - pos);
283 +
		len -= SAMPLES_PER_BUFFER - pos;
284 +
		pos = 0;
285 +
	}
286 +
	if (len > 0) {
287 +
		YM_stream_update((uint16_t *)&ym_buf[pos * 2], len);
288 +
	}
289 +
290 +
	pos = (fs_samp_pos_wr + 1) & SAMP_POS_MASK;
291 +
	len = ((fs_samp_pos_hd >> SAMP_POS_FRAC_BITS) - fs_samp_pos_wr) & SAMP_POS_MASK;
292 +
	fs_samp_pos_wr = fs_samp_pos_hd >> SAMP_POS_FRAC_BITS;
293 +
	if ((pos + len) > SAMPLES_PER_BUFFER) {
294 +
		midi_synth_render(&fs_buf[pos * 2], SAMPLES_PER_BUFFER - pos);
295 +
		len -= SAMPLES_PER_BUFFER - pos;
296 +
		pos = 0;
297 +
	}
298 +
	if (len > 0) {
299 +
		midi_synth_render(&fs_buf[pos * 2], len);
300 +
	}
301 +
302 +
	uint32_t wridx_old = wridx;
303 +
	uint32_t len_vera = (vera_samp_pos_hd - vera_samp_pos_rd) & SAMP_POS_MASK_FRAC;
304 +
	uint32_t len_ym = (ym_samp_pos_hd - ym_samp_pos_rd) & SAMP_POS_MASK_FRAC;
305 +
	uint32_t len_fs = (fs_samp_pos_hd - fs_samp_pos_rd) & SAMP_POS_MASK_FRAC;
306 +
	if (len_vera < (4 << SAMP_POS_FRAC_BITS) || len_ym < (4 << SAMP_POS_FRAC_BITS) || len_fs < (4 << SAMP_POS_FRAC_BITS)) {
307 +
		// not enough samples yet, at least 4 are needed for the filter
308 +
		return;
309 +
	}
310 +
	len_vera = (len_vera - (4 << SAMP_POS_FRAC_BITS)) / vera_samps_per_host_samps;
311 +
	len_ym = (len_ym - (4 << SAMP_POS_FRAC_BITS)) / ym_samps_per_host_samps;
312 +
	len_fs = (len_fs - (4 << SAMP_POS_FRAC_BITS)) / fs_samps_per_host_samps;
313 +
	len = SDL_min(len_vera, len_ym);
314 +
	len = SDL_min(len, len_fs);
315 +
	SDL_LockAudioDevice(audio_dev);
316 +
	for (int i = 0; i < len; i++) {
317 +
		int32_t samp[8];
318 +
		int32_t filter_idx = 0;
319 +
		int32_t vera_out_l = 0;
320 +
		int32_t vera_out_r = 0;
321 +
		int32_t ym_out_l = 0;
322 +
		int32_t ym_out_r = 0;
323 +
		int32_t fs_out_l = 0;
324 +
		int32_t fs_out_r = 0;
325 +
		// Don't resample VERA outputs if the host sample rate is as desired
326 +
		if (host_sample_rate == AUDIO_SAMPLERATE) {
327 +
			pos = (vera_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2;
328 +
			vera_out_l = ((uint32_t)psg_buf[pos] + pcm_buf[pos]) << 14;
329 +
			vera_out_r = ((uint32_t)psg_buf[pos + 1] + pcm_buf[pos + 1]) << 14;
330 +
		} else {
331 +
			filter_idx = (vera_samp_pos_rd >> (SAMP_POS_FRAC_BITS - 8)) & 0xff;
332 +
			pos = (vera_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2;
333 +
			for (int j = 0; j < 8; j += 2) {
334 +
				samp[j] = (int32_t)psg_buf[pos] + pcm_buf[pos];
335 +
				samp[j + 1] = (int32_t)psg_buf[pos + 1] + pcm_buf[pos + 1];
336 +
				pos = (pos + 2) & (SAMP_POS_MASK * 2);
337 +
			}
338 +
			vera_out_l += samp[0] * filter[256 + filter_idx];
339 +
			vera_out_r += samp[1] * filter[256 + filter_idx];
340 +
			vera_out_l += samp[2] * filter[  0 + filter_idx];
341 +
			vera_out_r += samp[3] * filter[  0 + filter_idx];
342 +
			vera_out_l += samp[4] * filter[255 - filter_idx];
343 +
			vera_out_r += samp[5] * filter[255 - filter_idx];
344 +
			vera_out_l += samp[6] * filter[511 - filter_idx];
345 +
			vera_out_r += samp[7] * filter[511 - filter_idx];
346 +
		}
347 +
		filter_idx = (ym_samp_pos_rd >> (SAMP_POS_FRAC_BITS - 8)) & 0xff;
348 +
		pos = (ym_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2;
349 +
		for (int j = 0; j < 8; j += 2) {
350 +
			samp[j] = ym_buf[pos];
351 +
			samp[j + 1] = ym_buf[pos + 1];
352 +
			pos = (pos + 2) & (SAMP_POS_MASK * 2);
353 +
		}
354 +
		ym_out_l += samp[0] * filter[256 + filter_idx];
355 +
		ym_out_r += samp[1] * filter[256 + filter_idx];
356 +
		ym_out_l += samp[2] * filter[  0 + filter_idx];
357 +
		ym_out_r += samp[3] * filter[  0 + filter_idx];
358 +
		ym_out_l += samp[4] * filter[255 - filter_idx];
359 +
		ym_out_r += samp[5] * filter[255 - filter_idx];
360 +
		ym_out_l += samp[6] * filter[511 - filter_idx];
361 +
		ym_out_r += samp[7] * filter[511 - filter_idx];
362 +
		// Don't resample MIDI synth outputs if the host sample rate matches it
363 +
		if (host_sample_rate == AUDIO_SAMPLERATE) {
364 +
			pos = (fs_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2;
365 +
			fs_out_l = (uint32_t)fs_buf[pos] << 14;
366 +
			fs_out_r = (uint32_t)fs_buf[pos + 1] << 14;
367 +
		} else {
368 +
			filter_idx = (fs_samp_pos_rd >> (SAMP_POS_FRAC_BITS - 8)) & 0xff;
369 +
			pos = (fs_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2;
370 +
			for (int j = 0; j < 8; j += 2) {
371 +
				samp[j] = fs_buf[pos];
372 +
				samp[j + 1] = fs_buf[pos + 1];
373 +
				pos = (pos + 2) & (SAMP_POS_MASK * 2);
374 +
			}
375 +
			fs_out_l += samp[0] * filter[256 + filter_idx];
376 +
			fs_out_r += samp[1] * filter[256 + filter_idx];
377 +
			fs_out_l += samp[2] * filter[  0 + filter_idx];
378 +
			fs_out_r += samp[3] * filter[  0 + filter_idx];
379 +
			fs_out_l += samp[4] * filter[255 - filter_idx];
380 +
			fs_out_r += samp[5] * filter[255 - filter_idx];
381 +
			fs_out_l += samp[6] * filter[511 - filter_idx];
382 +
			fs_out_r += samp[7] * filter[511 - filter_idx];
383 +
		}
384 +
		// VERA+YM mixing is according to the Developer Board
385 +
		// Loudest single PSG channel is 1/8 times the max output
386 +
		// mix = (psg + pcm) * 2 + ym + fs * 4
387 +
		int32_t mix_l = (vera_out_l >> 13) + (ym_out_l >> 15) + (fs_out_l >> 12);
388 +
		int32_t mix_r = (vera_out_r >> 13) + (ym_out_r >> 15) + (fs_out_r >> 12);
389 +
		uint32_t amp = SDL_max(SDL_abs(mix_l), SDL_abs(mix_r));
390 +
		if (amp > 32767) {
391 +
			uint32_t limiter_amp_new = (32767 << 16) / amp;
392 +
			limiter_amp = SDL_min(limiter_amp_new, limiter_amp);
393 +
		}
394 +
		buffer[wridx++] = (int16_t)((mix_l * limiter_amp) >> 16);
395 +
		buffer[wridx++] = (int16_t)((mix_r * limiter_amp) >> 16);
396 +
		x16_control_push_audio(buffer[wridx - 2], buffer[wridx - 1]);  /* RRDC /audio tee */
397 +
		if (limiter_amp < (1 << 16)) limiter_amp++;
398 +
		vera_samp_pos_rd = (vera_samp_pos_rd + vera_samps_per_host_samps) & SAMP_POS_MASK_FRAC;
399 +
		ym_samp_pos_rd = (ym_samp_pos_rd + ym_samps_per_host_samps) & SAMP_POS_MASK_FRAC;
400 +
		fs_samp_pos_rd = (fs_samp_pos_rd + fs_samps_per_host_samps) & SAMP_POS_MASK_FRAC;
401 +
		if (wridx == buffer_size) {
402 +
			wav_recorder_process(&buffer[wridx_old], (buffer_size - wridx_old) / 2);
403 +
			wridx = 0;
404 +
			wridx_old = 0;
405 +
		}
406 +
	}
407 +
	if ((wridx - wridx_old) > 0) {
408 +
		wav_recorder_process(&buffer[wridx_old], (wridx - wridx_old) / 2);
409 +
	}
410 +
	buffer_written += len * 2;
411 +
	if (buffer_written > buffer_size) {
412 +
		// Prevent the buffer from overflowing by skipping the read pointer ahead.
413 +
		uint32_t buffer_skip_amount = (buffer_written / buffer_size) * SAMPLES_PER_BUFFER * 2;
414 +
		rdidx = (rdidx + buffer_skip_amount) % buffer_size;
415 +
		buffer_written -= buffer_skip_amount;
416 +
	}
417 +
	SDL_UnlockAudioDevice(audio_dev);
418 +
419 +
	// catch up all buffers if they are too far behind
420 +
	uint32_t skip = len_vera - len;
421 +
	if (skip > 1) {
422 +
		vera_samp_pos_rd = (vera_samp_pos_rd + vera_samps_per_host_samps) & SAMP_POS_MASK_FRAC;
423 +
	}
424 +
	skip = len_ym - len;
425 +
	if (skip > 1) {
426 +
		ym_samp_pos_rd = (ym_samp_pos_rd + ym_samps_per_host_samps) & SAMP_POS_MASK_FRAC;
427 +
	}
428 +
	skip = len_fs - len;
429 +
	if (skip > 1) {
430 +
		fs_samp_pos_rd = (fs_samp_pos_rd + fs_samps_per_host_samps) & SAMP_POS_MASK_FRAC;
431 +
	}
432 +
}
433 +
434 +
void
435 +
audio_usage(void)
436 +
{
437 +
	// SDL_GetAudioDeviceName doesn't work if audio isn't initialized.
438 +
	// Since argument parsing happens before initializing SDL, ensure the
439 +
	// audio subsystem is initialized before printing audio device names.
440 +
	SDL_InitSubSystem(SDL_INIT_AUDIO);
441 +
442 +
	// List all available sound devices
443 +
	printf("The following sound output devices are available:\n");
444 +
	const int sounds = SDL_GetNumAudioDevices(0);
445 +
	for (int i = 0; i < sounds; ++i) {
446 +
		printf("\t%s\n", SDL_GetAudioDeviceName(i, 0));
447 +
	}
448 +
449 +
	SDL_Quit();
450 +
	exit(1);
451 +
}
added src/audio.h +16 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2020 Frank van den Hoef
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#pragma once
6 +
7 +
#include <SDL.h>
8 +
9 +
#define AUDIO_SAMPLERATE (25000000 / 512)
10 +
11 +
void audio_init(const char *dev_name, int num_audio_buffers);
12 +
void audio_close(void);
13 +
void audio_step(int cpu_clocks);
14 +
void audio_render();
15 +
16 +
void audio_usage(void);
added src/cartridge.c +556 −0
1 +
#include "cartridge.h"
2 +
#include "files.h"
3 +
4 +
#include <ctype.h>
5 +
#include <string.h>
6 +
#include <stdlib.h>
7 +
#include <stdio.h>
8 +
9 +
struct x16cartridge_header Cartridge_info;
10 +
uint8_t *CART = NULL;
11 +
char *Cartridge_path = NULL;
12 +
char *Cartridge_nvram_path = NULL;
13 +
14 +
const char Cartridge_magic_number[CART_MAGIC_NUMBER_SIZE] = { 'C', 'X', '1', '6', ' ', 'C', 'A', 'R', 'T', 'R', 'I', 'D', 'G', 'E', '\r', '\n' };
15 +
const char Cartridge_current_version[CART_VERSION_SIZE]   = { '0', '1', '.', '0', '0', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '};
16 +
17 +
static void 
18 +
initialize_cart_bank(uint8_t *mem, bool randomize)
19 +
{
20 +
	if(randomize) {
21 +
		for(int i=0; i<CART_BANK_SIZE; ++i) {
22 +
			mem[i] = rand();
23 +
		}
24 +
	} else {
25 +
		memset(mem, 0, CART_BANK_SIZE);
26 +
	}
27 +
}
28 +
29 +
static int
30 +
x16_strnicmp(char const *s0, char const *s1, int len)
31 +
{
32 +
	for(int i=0; (i<len) && (*s0 && *s1); ++i) {
33 +
		if(tolower((unsigned char)*s0) != tolower((unsigned char)*s1)) {
34 +
			break;
35 +
		}
36 +
		++s0;
37 +
		++s1;
38 +
	}
39 +
40 +
	return *s0 - *s1;
41 +
}
42 +
43 +
bool 
44 +
cartridge_load(const char *path, bool randomize)
45 +
{
46 +
	const char *extension = file_find_extension(path, NULL);
47 +
	if(x16_strnicmp(extension, ".crt", 4) != 0) {
48 +
		printf("Path \"%s\" does not appear to be a cartridge (.crt) file.\n", path);
49 +
		return false;
50 +
	}
51 +
52 +
	const size_t path_len = strlen(path);
53 +
	Cartridge_path = malloc(path_len + 1);
54 +
	strcpy(Cartridge_path, path);
55 +
56 +
	Cartridge_nvram_path = malloc(path_len + 3);
57 +
	strcpy(Cartridge_nvram_path, Cartridge_path);
58 +
	strcpy(Cartridge_nvram_path + path_len - 3, "nvram");
59 +
60 +
	struct x16file *cart = x16open(Cartridge_path, "rb");
61 +
	struct x16file *nvram = x16open(Cartridge_nvram_path, "rb");
62 +
63 +
	bool ok = !!x16read(cart, (void *)(&Cartridge_info), sizeof(struct x16cartridge_header), 1);
64 +
	if(!ok) {
65 +
		printf("Could not load cartridge \"%s\": Could not read header.\n", path);\
66 +
		goto load_end;
67 +
	}
68 +
69 +
	if(memcmp(Cartridge_info.magic_number, Cartridge_magic_number, CART_MAGIC_NUMBER_SIZE) != 0) {
70 +
		printf("Could not load cartridge \"%s\": Not a cartridge file.\n", path);
71 +
		goto load_end;
72 +
	}
73 +
74 +
	if(strncmp(Cartridge_info.cart_version, Cartridge_current_version, CART_VERSION_SIZE) != 0) {
75 +
		printf("Could not load cartridge \"%s\": Unsupported version.\n", path);
76 +
		goto load_end;
77 +
	}
78 +
79 +
	if(CART != NULL) {
80 +
		free(CART);
81 +
	}
82 +
	CART = malloc(CART_MAX_SIZE);
83 +
84 +
	uint8_t *mem = CART;
85 +
	for(uint8_t t=0; ok && t<CART_MAX_BANKS; ++t) {
86 +
		switch(Cartridge_info.bank_info[t]) {
87 +
			case CART_BANK_NONE: /* skip */ break;
88 +
			case CART_BANK_ROM: ok = !!x16read(cart, mem, CART_BANK_SIZE, 1); break;
89 +
			case CART_BANK_UNINITIALIZED_RAM: initialize_cart_bank(mem, randomize); break;
90 +
			case CART_BANK_INITIALIZED_RAM: ok = !!x16read(cart, mem, CART_BANK_SIZE, 1); break;
91 +
			case CART_BANK_UNINITIALIZED_NVRAM: initialize_cart_bank(mem, randomize); break;
92 +
			case CART_BANK_INITIALIZED_NVRAM:
93 +
				if(nvram) {
94 +
					ok = !!x16read(nvram, mem, CART_BANK_SIZE, 1);
95 +
					x16seek(cart, CART_BANK_SIZE, XSEEK_CUR);
96 +
				} else {
97 +
					ok = !!x16read(cart, mem, CART_BANK_SIZE, 1);
98 +
				}
99 +
				break;
100 +
			default: printf("Warning: Unknown cartridge bank type at %d\n", t); break;
101 +
		}
102 +
		mem += CART_BANK_SIZE;
103 +
	}
104 +
105 +
load_end:
106 +
	if(!ok) {
107 +
		free(CART);
108 +
		free(Cartridge_path);
109 +
		free(Cartridge_nvram_path);
110 +
111 +
		CART = NULL;
112 +
		Cartridge_path = NULL;
113 +
		Cartridge_nvram_path = NULL;
114 +
	}
115 +
116 +
	x16close(cart);
117 +
	x16close(nvram);
118 +
119 +
	return ok;
120 +
}
121 +
122 +
void cartridge_unload()
123 +
{
124 +
	if(CART != NULL) {
125 +
		free(CART);
126 +
		CART = NULL;
127 +
	}
128 +
	if(Cartridge_path != NULL) {
129 +
		free(Cartridge_path);
130 +
		Cartridge_path = NULL;
131 +
	}
132 +
	if(Cartridge_nvram_path != NULL) {
133 +
		free(Cartridge_nvram_path);
134 +
		Cartridge_nvram_path = NULL;
135 +
	}
136 +
}
137 +
138 +
void 
139 +
cartridge_new()
140 +
{
141 +
	cartridge_unload();
142 +
143 +
	memset(&Cartridge_info, 0, sizeof(struct x16cartridge_header));
144 +
	memcpy(Cartridge_info.magic_number, Cartridge_magic_number, CART_MAGIC_NUMBER_SIZE);
145 +
	memcpy(Cartridge_info.cart_version, Cartridge_current_version, CART_VERSION_SIZE);
146 +
147 +
	CART = malloc(CART_MAX_SIZE);
148 +
}
149 +
150 +
void 
151 +
cartridge_set_desc(const char *name)
152 +
{
153 +
	if(name == NULL) {
154 +
		return;
155 +
	}
156 +
157 +
	int namelen = strlen(name);
158 +
	if(namelen > CART_DESCRIPTION_SIZE) {
159 +
		namelen = CART_DESCRIPTION_SIZE;
160 +
	}
161 +
162 +
	memset(Cartridge_info.description, 0x20, CART_DESCRIPTION_SIZE);
163 +
	memcpy(Cartridge_info.description, name, namelen);
164 +
}
165 +
166 +
void
167 +
cartridge_set_author(const char *name)
168 +
{
169 +
	if(name == NULL) {
170 +
		return;
171 +
	}
172 +
173 +
	int namelen = strlen(name);
174 +
	if(namelen > CART_AUTHOR_SIZE) {
175 +
		namelen = CART_AUTHOR_SIZE;
176 +
	}
177 +
178 +
	memset(Cartridge_info.author, 0x20, CART_AUTHOR_SIZE);
179 +
	memcpy(Cartridge_info.author, name, namelen);
180 +
}
181 +
182 +
void
183 +
cartridge_set_copyright(const char *name)
184 +
{
185 +
	if(name == NULL) {
186 +
		return;
187 +
	}
188 +
189 +
	int namelen = strlen(name);
190 +
	if(namelen > CART_COPYRIGHT_SIZE) {
191 +
		namelen = CART_COPYRIGHT_SIZE;
192 +
	}
193 +
194 +
	memset(Cartridge_info.copyright, 0x20, CART_COPYRIGHT_SIZE);
195 +
	memcpy(Cartridge_info.copyright, name, namelen);
196 +
197 +
}
198 +
199 +
void
200 +
cartridge_set_program_version(const char *name)
201 +
{
202 +
	if(name == NULL) {
203 +
		return;
204 +
	}
205 +
206 +
	int namelen = strlen(name);
207 +
	if(namelen > CART_PROGRAM_VERSION_SIZE) {
208 +
		namelen = CART_PROGRAM_VERSION_SIZE;
209 +
	}
210 +
211 +
	memset(Cartridge_info.prg_version, 0x20, CART_PROGRAM_VERSION_SIZE);
212 +
	memcpy(Cartridge_info.prg_version, name, namelen);
213 +
}
214 +
215 +
static char *
216 +
rtrim(char *str)
217 +
{
218 +
    char *c = str + strlen(str) - 1;
219 +
    while(isspace((unsigned char)*c)) {
220 +
		--c;
221 +
	}
222 +
    *(c + 1) = '\0';
223 +
	return str;
224 +
}
225 +
226 +
void 
227 +
cartridge_get_desc(char *buffer, size_t buffer_size)
228 +
{
229 +
	if(buffer == NULL) {
230 +
		return;
231 +
	}
232 +
233 +
	if(buffer_size > CART_DESCRIPTION_SIZE + 1) {
234 +
		buffer_size = CART_DESCRIPTION_SIZE + 1;
235 +
	}
236 +
237 +
	memcpy(buffer, Cartridge_info.description, buffer_size - 1);
238 +
	buffer[buffer_size - 1] = '\0';
239 +
240 +
	rtrim(buffer);
241 +
}
242 +
243 +
void
244 +
cartridge_get_author(char *buffer, size_t buffer_size)
245 +
{
246 +
	if(buffer == NULL) {
247 +
		return;
248 +
	}
249 +
250 +
	if(buffer_size > CART_AUTHOR_SIZE + 1) {
251 +
		buffer_size = CART_AUTHOR_SIZE + 1;
252 +
	}
253 +
254 +
	memcpy(buffer, Cartridge_info.author, buffer_size - 1);
255 +
	buffer[buffer_size - 1] = '\0';
256 +
257 +
	rtrim(buffer);
258 +
}
259 +
260 +
void
261 +
cartridge_get_copyright(char *buffer, size_t buffer_size)
262 +
{
263 +
	if(buffer == NULL) {
264 +
		return;
265 +
	}
266 +
267 +
	if(buffer_size > CART_COPYRIGHT_SIZE + 1) {
268 +
		buffer_size = CART_COPYRIGHT_SIZE + 1;
269 +
	}
270 +
271 +
	memcpy(buffer, Cartridge_info.copyright, buffer_size - 1);
272 +
	buffer[buffer_size - 1] = '\0';
273 +
274 +
	rtrim(buffer);
275 +
}
276 +
277 +
void
278 +
cartridge_get_program_version(char *buffer, size_t buffer_size)
279 +
{
280 +
	if(buffer == NULL) {
281 +
		return;
282 +
	}
283 +
284 +
	if(buffer_size > CART_PROGRAM_VERSION_SIZE + 1) {
285 +
		buffer_size = CART_PROGRAM_VERSION_SIZE + 1;
286 +
	}
287 +
288 +
	memcpy(buffer, Cartridge_info.prg_version, buffer_size - 1);
289 +
	buffer[buffer_size - 1] = '\0';
290 +
291 +
	rtrim(buffer);
292 +
}
293 +
294 +
bool 
295 +
cartridge_define_bank_range(uint8_t start_bank, uint8_t end_bank, uint8_t bank_type)
296 +
{
297 +
	if(start_bank < 32) {
298 +
		return false;
299 +
	}
300 +
301 +
	start_bank -= 32;
302 +
303 +
	if(start_bank >= CART_MAX_BANKS) {
304 +
		return false;
305 +
	}
306 +
307 +
	if(end_bank < 32) {
308 +
		return false;
309 +
	}
310 +
311 +
	end_bank -= 32;
312 +
313 +
	if(end_bank >= CART_MAX_BANKS) {
314 +
		return false;
315 +
	}
316 +
317 +
	if(start_bank > end_bank) {
318 +
		return false;
319 +
	}
320 +
321 +
	if(bank_type > CART_NUM_BANK_TYPES) {
322 +
		printf("Warning: Attempting to define unknown cartridge bank type %d\n", bank_type);
323 +
	}
324 +
325 +
	for(int i = start_bank; i <= end_bank; ++i) {
326 +
		Cartridge_info.bank_info[i] = bank_type;
327 +
	}
328 +
329 +
	return true;
330 +
}
331 +
332 +
bool
333 +
cartridge_import_files(char **bin_files, int num_files, uint8_t start_bank, uint8_t bank_type, uint8_t fill_value)
334 +
{
335 +
	if(start_bank < 32) {
336 +
		return false;
337 +
	}
338 +
339 +
	start_bank -= 32;
340 +
341 +
	if(start_bank >= CART_MAX_BANKS) {
342 +
		return false;
343 +
	}
344 +
345 +
	uint32_t start_address = ((uint32_t)start_bank) << 14;
346 +
	uint32_t available_size = CART_MAX_SIZE - start_address;
347 +
348 +
	for(int i=0; i<num_files; ++i) {
349 +
		if(available_size == 0) {
350 +
			return false;
351 +
		}
352 +
353 +
		struct x16file *bin = x16open(bin_files[i], "rb");
354 +
		if(bin == NULL) {
355 +
			return false;
356 +
		}
357 +
358 +
		const uint32_t data_read = (uint32_t)x16read(bin, CART + start_address, 1, available_size);
359 +
		available_size -= data_read;
360 +
		start_address += data_read;
361 +
362 +
		x16close(bin);
363 +
	}
364 +
365 +
	uint32_t fill_end = (start_address + CART_BANK_SIZE - 1) & (0xffUL << 14);
366 +
	if(fill_end > CART_MAX_SIZE) {
367 +
		fill_end = CART_MAX_SIZE;
368 +
	}
369 +
	memset(CART + start_address, fill_value, fill_end - start_address);
370 +
	
371 +
	uint8_t last_bank = (start_address - 1) >> 14;
372 +
	for(uint8_t i = start_bank; i <= last_bank; ++i) {
373 +
		Cartridge_info.bank_info[i] = bank_type;
374 +
	}
375 +
	return true;
376 +
}
377 +
378 +
bool
379 +
cartridge_fill(uint8_t start_bank, uint8_t end_bank, uint8_t bank_type, uint8_t fill_value)
380 +
{
381 +
	if(start_bank < 32) {
382 +
		return false;
383 +
	}
384 +
385 +
	start_bank -= 32;
386 +
387 +
	if(start_bank >= CART_MAX_BANKS) {
388 +
		return false;
389 +
	}
390 +
391 +
	if(end_bank < 32) {
392 +
		return false;
393 +
	}
394 +
395 +
	end_bank -= 32;
396 +
397 +
	if(end_bank >= CART_MAX_BANKS) {
398 +
		return false;
399 +
	}
400 +
401 +
	if(start_bank > end_bank) {
402 +
		return false;
403 +
	}
404 +
405 +
	uint32_t fill_start = ((uint32_t)start_bank) << 14;
406 +
	uint32_t fill_end = ((uint32_t)end_bank + 1) << 14;
407 +
	memset(CART + fill_start, fill_value, fill_end - fill_start);
408 +
409 +
	for(uint8_t i = start_bank; i <= end_bank; ++i) {
410 +
		Cartridge_info.bank_info[i] = bank_type;
411 +
	}
412 +
	return true;
413 +
}
414 +
415 +
bool 
416 +
cartridge_save(const char *path)
417 +
{
418 +
	bool compressed = file_is_compressed_type(path);
419 +
	const char *extension = file_find_extension(path, NULL);
420 +
	if(strncmp(extension, ".crt", 4) != 0) {
421 +
		printf("Path \"%s\" does not appear to be a cartridge (.crt) file.\n", path);
422 +
		return false;
423 +
	}
424 +
425 +
	struct x16file *cart = x16open(path, compressed ? "wb6" : "wb");
426 +
427 +
	uint64_t header_wrote = x16write(cart, (void*)(&Cartridge_info), sizeof(struct x16cartridge_header), 1);
428 +
	if(!header_wrote) {
429 +
		printf("Could not save cartridge \"%s\": Could not write header.\n", path);
430 +
		return false;
431 +
	}
432 +
433 +
	uint8_t *mem = CART;
434 +
	bool ok = true;
435 +
	for(uint8_t t=0; ok && t<CART_MAX_BANKS; ++t) {
436 +
		switch(Cartridge_info.bank_info[t]) {
437 +
			case CART_BANK_NONE: /* skip */ break;
438 +
			case CART_BANK_ROM: ok = !!x16write(cart, mem, CART_BANK_SIZE, 1); break;
439 +
			case CART_BANK_UNINITIALIZED_RAM: /* skip */ break;
440 +
			case CART_BANK_INITIALIZED_RAM: ok = !!x16write(cart, mem, CART_BANK_SIZE, 1); break;
441 +
			case CART_BANK_UNINITIALIZED_NVRAM: /* skip */ break;
442 +
			case CART_BANK_INITIALIZED_NVRAM: ok = !!x16write(cart, mem, CART_BANK_SIZE, 1); break;
443 +
			default: printf("Warning: Unknown cartridge bank type at %d\n", t); break;
444 +
		}
445 +
		mem += CART_BANK_SIZE;
446 +
	}
447 +
448 +
	if(!ok) {
449 +
		printf("Warning: Failed to save some cartridge data. The cartridge may be corrupt.\n");
450 +
	}
451 +
452 +
	x16close(cart);
453 +
454 +
	return ok;
455 +
}
456 +
457 +
bool
458 +
cartridge_save_nvram()
459 +
{
460 +
	if(Cartridge_nvram_path == NULL) {
461 +
		return false;
462 +
	}
463 +
464 +
	struct x16file *nvram = x16open(Cartridge_nvram_path, "wb");
465 +
466 +
	uint8_t *mem = CART;
467 +
	bool ok = true;
468 +
	for(uint8_t t=0; ok && t<CART_MAX_BANKS; ++t) {
469 +
		switch(Cartridge_info.bank_info[t]) {
470 +
			case CART_BANK_NONE: /* skip */ break;
471 +
			case CART_BANK_ROM: /* skip */ break;
472 +
			case CART_BANK_UNINITIALIZED_RAM: /* skip */ break;
473 +
			case CART_BANK_INITIALIZED_RAM: /* skip */ break;
474 +
			case CART_BANK_UNINITIALIZED_NVRAM: ok = !!x16write(nvram, mem, CART_BANK_SIZE, 1); break;
475 +
			case CART_BANK_INITIALIZED_NVRAM: ok = !!x16write(nvram, mem, CART_BANK_SIZE, 1); break;
476 +
			default: printf("Warning: Unknown cartridge bank type at %d\n", t); break;
477 +
		}
478 +
		mem += CART_BANK_SIZE;
479 +
	}
480 +
481 +
	if(!ok) {
482 +
		printf("Warning: Failed to save some nvram data. The nvram may be corrupt.\n");
483 +
	}
484 +
485 +
	x16close(nvram);
486 +
487 +
	return ok;
488 +
}
489 +
490 +
void 
491 +
cartridge_write(uint16_t address, uint8_t bank, uint8_t value)
492 +
{
493 +
	if(!CART) {
494 +
		return;
495 +
	}
496 +
497 +
	if(bank < 32) {
498 +
		return;
499 +
	}
500 +
501 +
	bank -= 32;
502 +
503 +
	if(bank >= CART_MAX_BANKS) {
504 +
		return;
505 +
	}
506 +
507 +
	switch(Cartridge_info.bank_info[bank]) {
508 +
		case CART_BANK_NONE: // fall-through
509 +
		case CART_BANK_ROM:
510 +
		default:
511 +
			break;
512 +
513 +
		case CART_BANK_UNINITIALIZED_RAM: // fall-through
514 +
		case CART_BANK_INITIALIZED_RAM: // fall-through
515 +
		case CART_BANK_UNINITIALIZED_NVRAM: // fall-through
516 +
		case CART_BANK_INITIALIZED_NVRAM:
517 +
			CART[((uint32_t)bank << 14) + address - 0xc000] = value;
518 +
			break;
519 +
	}
520 +
}
521 +
522 +
uint8_t 
523 +
cartridge_read(uint16_t address, uint8_t bank)
524 +
{
525 +
	if(!CART) {
526 +
		return 0;
527 +
	}
528 +
529 +
	if(bank < 32) {
530 +
		return 0;
531 +
	}
532 +
533 +
	bank -= 32;
534 +
535 +
	if(bank >= CART_MAX_BANKS) {
536 +
		return 0;
537 +
	}
538 +
539 +
	return CART[((uint32_t)bank << 14) + address - 0xc000];
540 +
}
541 +
542 +
uint8_t
543 +
cartridge_get_bank_type(uint8_t bank)
544 +
{
545 +
	if(bank < 32) {
546 +
		return CART_BANK_NONE;
547 +
	}
548 +
549 +
	bank -= 32;
550 +
551 +
	if(bank >= CART_MAX_BANKS) {
552 +
		return CART_BANK_NONE;
553 +
	}
554 +
555 +
	return Cartridge_info.bank_info[bank];
556 +
}
added src/cartridge.h +68 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2023 Stephen Horn
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#pragma once
6 +
7 +
#include <stddef.h>
8 +
#include <stdbool.h>
9 +
#include <stdint.h>
10 +
11 +
#define CART_MAX_BANKS 224
12 +
#define CART_BANK_SIZE 0x4000
13 +
#define CART_MAX_SIZE (CART_BANK_SIZE * CART_MAX_BANKS)
14 +
15 +
#define CART_MAGIC_NUMBER_SIZE 16
16 +
#define CART_VERSION_SIZE 16
17 +
#define CART_DESCRIPTION_SIZE 32
18 +
#define CART_AUTHOR_SIZE 32
19 +
#define CART_COPYRIGHT_SIZE 32
20 +
#define CART_PROGRAM_VERSION_SIZE 32
21 +
#define CART_RESERVED_SIZE (64 + 32)
22 +
23 +
#define CART_BANK_NONE 0
24 +
#define CART_BANK_ROM 1
25 +
#define CART_BANK_UNINITIALIZED_RAM 2
26 +
#define CART_BANK_INITIALIZED_RAM 3
27 +
#define CART_BANK_UNINITIALIZED_NVRAM 4
28 +
#define CART_BANK_INITIALIZED_NVRAM 5
29 +
#define CART_NUM_BANK_TYPES 5
30 +
31 +
struct x16cartridge_header
32 +
{
33 +
	char magic_number[CART_MAGIC_NUMBER_SIZE];
34 +
	char cart_version[CART_VERSION_SIZE];
35 +
	char description[CART_DESCRIPTION_SIZE];
36 +
	char author[CART_AUTHOR_SIZE];
37 +
	char copyright[CART_COPYRIGHT_SIZE];
38 +
	char prg_version[CART_PROGRAM_VERSION_SIZE];
39 +
	char reserved[CART_RESERVED_SIZE];
40 +
	uint8_t bank_info[CART_MAX_BANKS];
41 +
};
42 +
43 +
struct cartridge_import;
44 +
45 +
bool cartridge_load(const char *cartridge_file, bool randomize);
46 +
void cartridge_unload();
47 +
void cartridge_new();
48 +
49 +
void cartridge_set_desc(const char *name);
50 +
void cartridge_set_author(const char *name);
51 +
void cartridge_set_copyright(const char *name);
52 +
void cartridge_set_program_version(const char *name);
53 +
54 +
void cartridge_get_desc(char *buffer, size_t buffer_size);
55 +
void cartridge_get_author(char *buffer, size_t buffer_size);
56 +
void cartridge_get_copyright(char *buffer, size_t buffer_size);
57 +
void cartridge_get_program_version(char *buffer, size_t buffer_size);
58 +
59 +
bool cartridge_define_bank_range(uint8_t start_bank, uint8_t end_bank, uint8_t bank_type);
60 +
bool cartridge_import_files(char **bin_files, int num_files, uint8_t start_bank, uint8_t bank_type, uint8_t fill_value);
61 +
bool cartridge_fill(uint8_t start_bank, uint8_t end_bank, uint8_t bank_type, uint8_t fill_value);
62 +
63 +
bool cartridge_save(const char *cartridge_file);
64 +
bool cartridge_save_nvram();
65 +
66 +
uint8_t cartridge_read(uint16_t address, uint8_t bank);
67 +
void cartridge_write(uint16_t address, uint8_t bank, uint8_t value);
68 +
uint8_t cartridge_get_bank_type(uint8_t bank);
added src/control_backend.c +312 −0
1 +
/* control_backend.c — Commander X16 backend for the retro remote debug
2 +
 * controller (retro_control.h, from the retro-remote-debug-controller submodule
3 +
 * at extern/). Maps the portable read-only backend onto x16emu internals. */
4 +
5 +
#include "retro_control.h"
6 +
7 +
#include "glue.h"     /* struct regs regs; machine_reset() */
8 +
#include "memory.h"   /* debug_read6502, write6502, memory_get_ram_bank/rom_bank */
9 +
#include "video.h"    /* video_get_framebuffer, video_get_frame_count, video_space_read */
10 +
#include "keyboard.h" /* handle_keyboard (input injection) */
11 +
#include "i2c.h"      /* mouse_move / mouse_button_* / mouse_send_state (pointer) */
12 +
#include "audio.h"    /* AUDIO_SAMPLERATE */
13 +
#include "joystick.h" /* joystick_virtual_set / joystick_virtual_get (pads) */
14 +
15 +
#include <SDL.h>
16 +
#include <stdbool.h>
17 +
#include <stdio.h>
18 +
19 +
extern uint32_t host_sample_rate;   /* audio.c: the real (post-SDL) output rate */
20 +
21 +
/* Debug-read into out. bank<0 => current bank. $0000-$9FFF is low RAM
22 +
 * (bank-independent); $A000-$BFFF is banked RAM; $C000+ is ROM. */
23 +
static uint32_t
24 +
x16_read_mem(uint32_t addr, int32_t bank, uint32_t len, uint8_t *out, uint32_t cap)
25 +
{
26 +
	int16_t x16bank = (bank >= 0) ? (int16_t)bank : USE_CURRENT_X16_BANK;
27 +
	uint32_t n = 0;
28 +
	for (uint32_t i = 0; i < len && n < cap; i++) {
29 +
		uint16_t a = (uint16_t)(addr + i);
30 +
		out[n++] = debug_read6502(a, 0, x16bank);
31 +
	}
32 +
	return n;
33 +
}
34 +
35 +
/* Write (poke) len bytes at addr (0.3). Low RAM ($0000-$9EFF) goes through the
36 +
 * same bus a CPU write uses. Banked RAM ($A000-$BFFF) with an explicit bank= is
37 +
 * written into that bank's backing store directly, the mirror of what
38 +
 * x16_read_mem does through debug_read6502: write6502's bank argument is the
39 +
 * gen2 flat bank and is forced to 0 on gen1, so routing a banked poke through
40 +
 * it wrote whatever bank the CPU had mapped at the pause — the KERNAL's IRQ
41 +
 * handler maps bank 0, and a harness write for a tenant vanished into it.
42 +
 * Without bank= the poke still hits the current bank, as a CPU write would. */
43 +
static uint32_t
44 +
x16_write_mem(uint32_t addr, int32_t bank, uint32_t len, const uint8_t *in)
45 +
{
46 +
	for (uint32_t i = 0; i < len; i++) {
47 +
		uint16_t a = (uint16_t)(addr + i);
48 +
		if (bank >= 0 && a >= 0xa000 && a < 0xc000) {
49 +
			if ((uint32_t)bank < (uint32_t)num_ram_banks) {
50 +
				BRAM[((uint32_t)bank << 13) + a - 0xa000] = in[i];
51 +
			}
52 +
			continue;
53 +
		}
54 +
		write6502(a, 0, in[i]);
55 +
	}
56 +
	return len;
57 +
}
58 +
59 +
/* --- Audio drain (0.3): a lock-free ring teed from the mixer. The producer
60 +
 * (audio_render, via x16_control_push_audio) and the consumer (capture_audio)
61 +
 * both run on the emulator thread, so no locking is needed. --- */
62 +
#define X16_AUD_RING 65536              /* int16 samples (~0.67 s stereo @ 48.8 kHz) */
63 +
static int16_t  g_aud[X16_AUD_RING];
64 +
static uint32_t g_aud_head, g_aud_count, g_aud_dropped;
65 +
static int      g_aud_active;           /* start capturing only once the server is up */
66 +
67 +
/* Called from audio_render() for every mixed stereo pair. */
68 +
void
69 +
x16_control_push_audio(int16_t l, int16_t r)
70 +
{
71 +
	if (!g_aud_active) return;
72 +
	if (g_aud_count + 2 > X16_AUD_RING) { g_aud_dropped += 2; return; }
73 +
	g_aud[(g_aud_head + g_aud_count) % X16_AUD_RING] = l; g_aud_count++;
74 +
	g_aud[(g_aud_head + g_aud_count) % X16_AUD_RING] = r; g_aud_count++;
75 +
}
76 +
77 +
static uint32_t
78 +
x16_capture_audio(int16_t *out, uint32_t cap, int *rate, int *channels, uint32_t *dropped)
79 +
{
80 +
	uint32_t n = 0;
81 +
	if (rate)     *rate     = host_sample_rate ? (int)host_sample_rate : (int)AUDIO_SAMPLERATE;
82 +
	if (channels) *channels = 2;
83 +
	if (dropped)  *dropped  = g_aud_dropped;
84 +
	g_aud_dropped = 0;
85 +
	while (n < cap && g_aud_count) {
86 +
		out[n++] = g_aud[g_aud_head];
87 +
		g_aud_head = (g_aud_head + 1) % X16_AUD_RING;
88 +
		g_aud_count--;
89 +
	}
90 +
	return n;
91 +
}
92 +
93 +
static void
94 +
x16_get_regs_json(char *buf, size_t cap)
95 +
{
96 +
	snprintf(buf, cap,
97 +
		"{\"a\":%u,\"x\":%u,\"y\":%u,\"sp\":%u,\"pc\":%u,\"status\":%u,"
98 +
		"\"ram_bank\":%u,\"rom_bank\":%u}",
99 +
		(unsigned)regs.a, (unsigned)regs.xl, (unsigned)regs.yl,
100 +
		(unsigned)(regs.sp & 0xff), (unsigned)regs.pc, (unsigned)regs.status,
101 +
		(unsigned)memory_get_ram_bank(), (unsigned)memory_get_rom_bank());
102 +
}
103 +
104 +
static void
105 +
x16_get_framebuffer(retro_framebuffer_t *out)
106 +
{
107 +
	int w = 0, h = 0;
108 +
	out->pixels = video_get_framebuffer(&w, &h);
109 +
	out->width = w;
110 +
	out->height = h;
111 +
	out->fmt = RETRO_PIX_BGRA8888;   /* x16 framebuffer byte order */
112 +
}
113 +
114 +
static uint64_t
115 +
x16_get_frame_count(void)
116 +
{
117 +
	return video_get_frame_count();
118 +
}
119 +
120 +
/* Input injection (0.2). text => the char's key (letters use the lowercase
121 +
 * keycode); code => a raw SDL scancode. A tap presses then releases; hold with
122 +
 * down=1 across /step frames for a program that samples input each frame. */
123 +
static void
124 +
x16_key_event(SDL_Keycode sym, SDL_Scancode sc, int action)
125 +
{
126 +
	if (action == RETRO_KEY_DOWN) {
127 +
		handle_keyboard(true, sym, sc);
128 +
	} else if (action == RETRO_KEY_UP) {
129 +
		handle_keyboard(false, sym, sc);
130 +
	} else {                             /* RETRO_KEY_TAP */
131 +
		handle_keyboard(true, sym, sc);
132 +
		handle_keyboard(false, sym, sc);
133 +
	}
134 +
}
135 +
136 +
static int
137 +
x16_inject_key(int is_text, uint32_t value, int action)
138 +
{
139 +
	SDL_Keycode sym;
140 +
	SDL_Scancode sc;
141 +
	if (is_text) {
142 +
		int c = (int)value;
143 +
		if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';   /* keycodes are lowercase */
144 +
		sym = (SDL_Keycode)c;
145 +
		sc = SDL_GetScancodeFromKey(sym);
146 +
	} else {
147 +
		sc = (SDL_Scancode)value;                      /* raw SDL scancode */
148 +
		sym = SDL_GetKeyFromScancode(sc);
149 +
	}
150 +
	if (sc == SDL_SCANCODE_UNKNOWN) {
151 +
		return 0;
152 +
	}
153 +
	x16_key_event(sym, sc, action);
154 +
	return 1;
155 +
}
156 +
157 +
static void
158 +
x16_reset(void)
159 +
{
160 +
	machine_reset();
161 +
}
162 +
163 +
/* Pointer injection (0.4). The X16 mouse is a relative PS/2 device: the KERNAL
164 +
 * integrates the deltas into an absolute position and writes it to VERA sprite 0
165 +
 * (the pointer). So we inject through the same mouse_move/mouse_send_state path a
166 +
 * host mouse uses (device level, not driver state), and read the position back
167 +
 * out of sprite 0's attributes. Absolute is closed-loop: read sprite 0, inject
168 +
 * the delta to the target. The move lands once the KERNAL's next VSYNC consumes
169 +
 * the packet, so a /step must follow before it is visible — and the guest must
170 +
 * have the mouse enabled (KERNAL MOUSE / VERA sprite 0) for it to mean anything. */
171 +
#define VERA_SPRITE0_ATTR 0x1FC00U   /* sprite 0 attribute base in VERA VRAM */
172 +
173 +
static int32_t
174 +
x16_pointer_x(void)
175 +
{
176 +
	return (int32_t)video_space_read(VERA_SPRITE0_ATTR + 2)
177 +
	     | ((int32_t)(video_space_read(VERA_SPRITE0_ATTR + 3) & 3) << 8);
178 +
}
179 +
static int32_t
180 +
x16_pointer_y(void)
181 +
{
182 +
	return (int32_t)video_space_read(VERA_SPRITE0_ATTR + 4)
183 +
	     | ((int32_t)(video_space_read(VERA_SPRITE0_ATTR + 5) & 3) << 8);
184 +
}
185 +
186 +
static int
187 +
x16_set_pointer(int absolute, int32_t x, int32_t y, int buttons)
188 +
{
189 +
	if (absolute) {
190 +
		mouse_move((int)(x - x16_pointer_x()), (int)(y - x16_pointer_y()));
191 +
	} else {
192 +
		mouse_move((int)x, (int)y);            /* relative deltas, screen pixels */
193 +
	}
194 +
	if (buttons >= 0) {                        /* -1 => leave held buttons as-is */
195 +
		if (buttons & 1) mouse_button_down(0); else mouse_button_up(0); /* primary */
196 +
		if (buttons & 2) mouse_button_down(1); else mouse_button_up(1); /* secondary */
197 +
	}
198 +
	mouse_send_state();                        /* enqueue the PS/2 packet(s) */
199 +
	return 1;                                  /* the mouse device is always present */
200 +
}
201 +
202 +
static int
203 +
x16_get_pointer(int32_t *x, int32_t *y, int *buttons)
204 +
{
205 +
	*x = x16_pointer_x();
206 +
	*y = x16_pointer_y();
207 +
	*buttons = (int)(mouse_get_buttons() & 3);  /* bit0 primary, bit1 secondary */
208 +
	return 1;
209 +
}
210 +
211 +
/* --- Pads (contract 0.5) ---------------------------------------------
212 +
 *
213 +
 * RRDC fixes ONE canonical layout across every platform, so a test
214 +
 * written against one machine reads the same on another:
215 +
 *
216 +
 *   bit  0    1     2   3     4  5  6  7  8      9       10  11
217 +
 *        LEFT RIGHT UP  DOWN  A  B  X  Y  START  SELECT  L   R
218 +
 *
219 +
 * The X16's SNES pad disagrees on both counts, and its mask is ACTIVE
220 +
 * LOW. Converting here is exactly what the spec asks of a backend whose
221 +
 * hardware differs; the harness never learns about it.
222 +
 *
223 +
 * The X16 side is the SNES SERIAL order — the order bits leave the
224 +
 * shift register, which is what the KERNAL reassembles:
225 +
 *
226 +
 *   bit  0  1  2       3      4   5     6     7      8  9  10 11
227 +
 *        B  Y  SELECT  START  UP  DOWN  LEFT  RIGHT  A  X  L  R
228 +
 *
229 +
 * Read that off joystick.c's button_map[] with care: its comments name
230 +
 * SDL buttons, not SNES ones, and the two disagree where it matters.
231 +
 * SDL's A is the BOTTOM button, which is SNES B — so button_map's
232 +
 * `1 << 0 //SDL_CONTROLLER_BUTTON_A` is bit 0 = SNES **B**. Taking
233 +
 * those comments at face value swaps A<->B and X<->Y, which is exactly
234 +
 * the bug this table shipped with until it was checked against the
235 +
 * game's own joystick word rather than against a read-back. */
236 +
static const uint8_t pad_canon_to_x16[12] = {
237 +
	6,  /* LEFT   */ 7,  /* RIGHT  */ 4,  /* UP     */ 5,  /* DOWN   */
238 +
	8,  /* A      */ 0,  /* B      */ 9,  /* X      */ 1,  /* Y      */
239 +
	3,  /* START  */ 2,  /* SELECT */ 10, /* L      */ 11, /* R      */
240 +
};
241 +
242 +
static uint16_t
243 +
pad_canon_to_mask(int buttons)
244 +
{
245 +
	uint16_t mask = 0xffff;                  /* active low: nothing held */
246 +
	for (int b = 0; b < 12; ++b) {
247 +
		if (buttons & (1 << b)) {
248 +
			mask &= (uint16_t)~(1u << pad_canon_to_x16[b]);
249 +
		}
250 +
	}
251 +
	return mask;
252 +
}
253 +
254 +
static int
255 +
pad_mask_to_canon(uint16_t mask)
256 +
{
257 +
	int buttons = 0;
258 +
	for (int b = 0; b < 12; ++b) {
259 +
		if ((mask & (1u << pad_canon_to_x16[b])) == 0) {   /* 0 == held */
260 +
			buttons |= (1 << b);
261 +
		}
262 +
	}
263 +
	return buttons;
264 +
}
265 +
266 +
static int
267 +
x16_set_pad(int index, int buttons, int connected)
268 +
{
269 +
	/* buttons < 0 => presence-only change, leave the held mask alone. */
270 +
	return joystick_virtual_set(index, connected != 0, buttons >= 0,
271 +
	                            pad_canon_to_mask(buttons < 0 ? 0 : buttons))
272 +
	           ? 1
273 +
	           : 0;
274 +
}
275 +
276 +
static int
277 +
x16_get_pad(int index, int *buttons, int *connected)
278 +
{
279 +
	bool     present = false;
280 +
	uint16_t mask    = 0xffff;
281 +
282 +
	if (!joystick_virtual_get(index, &present, &mask)) {
283 +
		return 0;
284 +
	}
285 +
	*connected = present ? 1 : 0;
286 +
	*buttons   = pad_mask_to_canon(mask);
287 +
	return 1;
288 +
}
289 +
290 +
static const retro_control_backend_t x16_backend = {
291 +
	.platform        = "x16",
292 +
	.emulator        = "x16emu",
293 +
	.read_mem        = x16_read_mem,
294 +
	.get_regs_json   = x16_get_regs_json,
295 +
	.get_framebuffer = x16_get_framebuffer,
296 +
	.get_frame_count = x16_get_frame_count,
297 +
	.inject_key      = x16_inject_key,
298 +
	.reset           = x16_reset,
299 +
	.write_mem       = x16_write_mem,
300 +
	.capture_audio   = x16_capture_audio,
301 +
	.set_pointer     = x16_set_pointer,
302 +
	.get_pointer     = x16_get_pointer,
303 +
	.set_pad         = x16_set_pad,
304 +
	.get_pad         = x16_get_pad,
305 +
};
306 +
307 +
int
308 +
x16_control_start(int port)
309 +
{
310 +
	g_aud_active = 1;                 /* begin teeing mixer output into the ring */
311 +
	return retro_control_start(port, &x16_backend);
312 +
}
added src/coverage.c +101 −0
1 +
// Commander X16 Emulator
2 +
// Execution coverage: which addresses the CPU fetched an instruction from.
3 +
// See coverage.h for the file it writes.
4 +
5 +
#include <signal.h>
6 +
#include <stdio.h>
7 +
#include <stdlib.h>
8 +
#include <string.h>
9 +
#include "memory.h"
10 +
#include "coverage.h"
11 +
12 +
bool coverage_enabled = false;
13 +
volatile bool coverage_stop = false;
14 +
15 +
static const char *coverage_path;   // argv's, which lives as long as the program
16 +
static uint8_t *low;      // $0000-$9FFF
17 +
static uint8_t *ram;      // 256 banks of $A000-$BFFF
18 +
static uint8_t *rom;      // 256 banks of $C000-$FFFF
19 +
20 +
#define LOW_SIZE  0xa000
21 +
#define RAM_SIZE  0x2000
22 +
#define ROM_SIZE  0x4000
23 +
24 +
// SIGTERM ends the CPU loop rather than the process, so main's shutdown runs
25 +
// and the marks are written; a second SIGTERM, or SIGKILL, still ends it.
26 +
static void
27 +
coverage_sigterm(int sig)
28 +
{
29 +
	(void)sig;
30 +
	coverage_stop = true;
31 +
	signal(SIGTERM, SIG_DFL);
32 +
}
33 +
34 +
bool
35 +
coverage_init(const char *path)
36 +
{
37 +
	low = calloc(LOW_SIZE, 1);
38 +
	ram = calloc(256 * RAM_SIZE, 1);
39 +
	rom = calloc(256 * ROM_SIZE, 1);
40 +
	coverage_path = path;
41 +
	if (!low || !ram || !rom) {
42 +
		return false;
43 +
	}
44 +
	coverage_enabled = true;
45 +
	signal(SIGTERM, coverage_sigterm);
46 +
	return true;
47 +
}
48 +
49 +
void
50 +
coverage_mark(uint16_t pc)
51 +
{
52 +
	if (pc < 0xa000) {
53 +
		low[pc] = 1;
54 +
	} else if (pc < 0xc000) {
55 +
		ram[(memory_get_ram_bank() << 13) + (pc - 0xa000)] = 1;
56 +
	} else {
57 +
		rom[(memory_get_rom_bank() << 14) + (pc - 0xc000)] = 1;
58 +
	}
59 +
}
60 +
61 +
// One line per run of marked addresses in [0, size), named from base.
62 +
static void
63 +
write_runs(FILE *f, char kind, int bank, const uint8_t *marks, int size, int base)
64 +
{
65 +
	for (int i = 0; i < size; i++) {
66 +
		if (!marks[i]) {
67 +
			continue;
68 +
		}
69 +
		int start = i;
70 +
		while (i + 1 < size && marks[i + 1]) {
71 +
			i++;
72 +
		}
73 +
		if (bank < 0) {
74 +
			fprintf(f, "%c %04x-%04x\n", kind, base + start, base + i);
75 +
		} else {
76 +
			fprintf(f, "%c %02x:%04x-%04x\n", kind, bank, base + start, base + i);
77 +
		}
78 +
	}
79 +
}
80 +
81 +
void
82 +
coverage_write(void)
83 +
{
84 +
	if (!coverage_enabled) {
85 +
		return;
86 +
	}
87 +
	FILE *f = fopen(coverage_path, "w");
88 +
	if (!f) {
89 +
		fprintf(stderr, "Cannot write coverage to %s!\n", coverage_path);
90 +
		return;
91 +
	}
92 +
	fprintf(f, "x16emu-coverage 1\n");
93 +
	write_runs(f, 'm', -1, low, LOW_SIZE, 0);
94 +
	for (int bank = 0; bank < 256; bank++) {
95 +
		write_runs(f, 'r', bank, ram + (bank << 13), RAM_SIZE, 0xa000);
96 +
	}
97 +
	for (int bank = 0; bank < 256; bank++) {
98 +
		write_runs(f, 'k', bank, rom + (bank << 14), ROM_SIZE, 0xc000);
99 +
	}
100 +
	fclose(f);
101 +
}
added src/coverage.h +34 −0
1 +
// Commander X16 Emulator
2 +
// Execution coverage: which addresses the CPU fetched an instruction from.
3 +
4 +
/*
5 +
-coverage <file> marks every address the CPU runs an instruction from, keeping
6 +
the bank for the two banked windows, and writes the marks to <file> when the
7 +
emulator exits: normally, or on SIGTERM, which is how a test harness stops it.
8 +
A host tool maps them to source lines through the program's line tables.
9 +
10 +
The file is text, one run of consecutive marked addresses a line:
11 +
12 +
    x16emu-coverage 1
13 +
    m 0801-0843          low memory, $0000-$9FFF
14 +
    r 03:a000-a01f       a RAM bank in the $A000-$BFFF window
15 +
    k 20:c000-c00a       a ROM or cartridge bank in the $C000-$FFFF window
16 +
17 +
An address is marked once however often it runs: one byte of memory per
18 +
address, and no counting in the CPU loop.
19 +
*/
20 +
21 +
#ifndef COVERAGE_H
22 +
#define COVERAGE_H
23 +
24 +
#include <stdbool.h>
25 +
#include <stdint.h>
26 +
27 +
extern bool coverage_enabled;
28 +
extern volatile bool coverage_stop;
29 +
30 +
bool coverage_init(const char *path);
31 +
void coverage_mark(uint16_t pc);
32 +
void coverage_write(void);
33 +
34 +
#endif
added src/cpu/.editorconfig +1 −0
1 +
root = true
added src/cpu/6502.opcodes +193 −0
1 +
; *******************************************************************************************
2 +
; *******************************************************************************************
3 +
;
4 +
;		File:		6502.opcodes
5 +
;		Date:		3rd September 2019
6 +
;		Purpose:	Descriptor file, standard 6502 opcodes
7 +
;		Author:		Paul Robson ([email protected])
8 +
;
9 +
; *******************************************************************************************
10 +
; *******************************************************************************************
11 +
12 +
adc abso 4 $6d
13 +
adc absx 4 $7d
14 +
adc absy 4 $79
15 +
adc immm  2 $69
16 +
adc indx 6 $61
17 +
adc indy 5 $71
18 +
adc zp   3 $65
19 +
adc zpx  4 $75
20 +
21 +
and abso 4 $2d
22 +
and absx 4 $3d
23 +
and absy 4 $39
24 +
and immm  2 $29
25 +
and indx 6 $21
26 +
and indy 5 $31
27 +
and zp   3 $25
28 +
and zpx  4 $35
29 +
30 +
asl abso 6 $0e
31 +
asl absx 7 $1e
32 +
asl acc  2 $0a
33 +
asl zp   5 $06
34 +
asl zpx  6 $16
35 +
36 +
bcc rel  2 $90
37 +
bcs rel  2 $b0
38 +
beq rel  2 $f0
39 +
bmi rel  2 $30
40 +
bne rel  2 $d0
41 +
bpl rel  2 $10
42 +
bvc rel  2 $50
43 +
bvs rel  2 $70
44 +
45 +
brk imp8 7 $00
46 +
47 +
bit abso 4 $2c
48 +
bit zp   3 $24
49 +
50 +
clc imp  2 $18
51 +
cld imp  2 $d8
52 +
cli imp  2 $58
53 +
clv imp  2 $b8
54 +
55 +
cmp abso 4 $cd
56 +
cmp absx 4 $dd
57 +
cmp absy 4 $d9
58 +
cmp immm  2 $c9
59 +
cmp indx 6 $c1
60 +
cmp indy 5 $d1
61 +
cmp zp   3 $c5
62 +
cmp zpx  4 $d5
63 +
64 +
cpx abso 4 $ec
65 +
cpx immx  2 $e0
66 +
cpx zp   3 $e4
67 +
68 +
cpy abso 4 $cc
69 +
cpy immx  2 $c0
70 +
cpy zp   3 $c4
71 +
72 +
dec abso 6 $ce
73 +
dec absx 7 $de
74 +
dec zp   5 $c6
75 +
dec zpx  6 $d6
76 +
77 +
dex imp  2 $ca
78 +
dey imp  2 $88
79 +
80 +
eor abso 4 $4d
81 +
eor absx 4 $5d
82 +
eor absy 4 $59
83 +
eor immm  2 $49
84 +
eor indx 6 $41
85 +
eor indy 5 $51
86 +
eor zp   3 $45
87 +
eor zpx  4 $55
88 +
89 +
inc abso 6 $ee
90 +
inc absx 7 $fe
91 +
inc zp   5 $e6
92 +
inc zpx  6 $f6
93 +
94 +
inx imp  2 $e8
95 +
iny imp  2 $c8
96 +
97 +
jmp abso 3 $4c
98 +
jmp ind  5 $6c
99 +
jsr abso 6 $20
100 +
101 +
lda abso 4 $ad
102 +
lda absx 4 $bd
103 +
lda absy 4 $b9
104 +
lda immm  2 $a9
105 +
lda indx 6 $a1
106 +
lda indy 5 $b1
107 +
lda zp   3 $a5
108 +
lda zpx  4 $b5
109 +
110 +
ldx abso 4 $ae
111 +
ldx absy 4 $be
112 +
ldx immx  2 $a2
113 +
ldx zp   3 $a6
114 +
ldx zpy  4 $b6
115 +
ldy abso 4 $ac
116 +
ldy absx 4 $bc
117 +
118 +
ldy immx  2 $a0
119 +
ldy zp   3 $a4
120 +
ldy zpx  4 $b4
121 +
122 +
lsr abso 6 $4e
123 +
lsr absx 7 $5e
124 +
lsr acc  2 $4a
125 +
lsr zp   5 $46
126 +
lsr zpx  6 $56
127 +
128 +
ora abso 4 $0d
129 +
ora absx 4 $1d
130 +
ora absy 4 $19
131 +
ora immm  2 $09
132 +
ora indx 6 $01
133 +
ora indy 5 $11
134 +
ora zp   3 $05
135 +
ora zpx  4 $15
136 +
137 +
pha imp  3 $48
138 +
php imp  3 $08
139 +
pla imp  4 $68
140 +
plp imp  4 $28
141 +
142 +
rol abso 6 $2e
143 +
rol absx 7 $3e
144 +
rol acc  2 $2a
145 +
rol zp   5 $26
146 +
rol zpx  6 $36
147 +
148 +
ror abso 6 $6e
149 +
ror absx 7 $7e
150 +
ror acc  2 $6a
151 +
ror zp   5 $66
152 +
ror zpx  6 $76
153 +
154 +
rti imp  6 $40
155 +
rts imp  6 $60
156 +
157 +
sbc abso 4 $ed
158 +
sbc absx 4 $fd
159 +
sbc absy 4 $f9
160 +
sbc immm  2 $e9
161 +
sbc indx 6 $e1
162 +
sbc indy 5 $f1
163 +
sbc zp   3 $e5
164 +
sbc zpx  4 $f5
165 +
166 +
sec imp  2 $38
167 +
sed imp  2 $f8
168 +
sei imp  2 $78
169 +
170 +
sta abso 4 $8d
171 +
sta absx 5 $9d
172 +
sta absy 5 $99
173 +
sta indx 6 $81
174 +
sta indy 6 $91
175 +
sta zp   3 $85
176 +
sta zpx  4 $95
177 +
178 +
stx abso 4 $8e
179 +
stx zp   3 $86
180 +
stx zpy  4 $96
181 +
182 +
sty abso 4 $8c
183 +
sty zp   3 $84
184 +
sty zpx  4 $94
185 +
186 +
tax imp  2 $aa
187 +
tay imp  2 $a8
188 +
tsx imp  2 $ba
189 +
txa imp  2 $8a
190 +
txs imp  2 $9a
191 +
tya imp  2 $98
192 +
193 +
nop imp  2 $ea
added src/cpu/65c02.h +225 −0
1 +
// *******************************************************************************************
2 +
// *******************************************************************************************
3 +
//
4 +
//		File:		65C02.H
5 +
//		Date:		3rd September 2019
6 +
//		Purpose:	Additional functions for new 65C02 Opcodes.
7 +
//		Author:		Paul Robson ([email protected])
8 +
//
9 +
// *******************************************************************************************
10 +
// *******************************************************************************************
11 +
12 +
// *******************************************************************************************
13 +
//
14 +
//					Indirect without indexation.  (copied from indy)
15 +
//
16 +
// *******************************************************************************************
17 +
18 +
19 +
// *******************************************************************************************
20 +
//
21 +
//						(Absolute,Indexed) address mode for JMP
22 +
//
23 +
// *******************************************************************************************
24 +
25 +
static void ainx() { 		// absolute indexed branch
26 +
    uint16_t eahelp, eahelp2;
27 +
    eahelp = (uint16_t)read6502(regs.pc, regs.k) | (uint16_t)((uint16_t)read6502(regs.pc+1, regs.k) << 8);
28 +
    eahelp = (eahelp + regs.x) & 0xFFFF;
29 +
#if 0
30 +
    eahelp2 = (eahelp & 0xFF00) | ((eahelp + 1) & 0x00FF); //replicate 6502 page-boundary wraparound bug
31 +
#else
32 +
    eahelp2 = eahelp + 1; // the 65c02 doesn't have the bug
33 +
#endif
34 +
    ea = (uint16_t)read6502(eahelp, regs.k) | ((uint16_t)read6502(eahelp2, regs.k) << 8);
35 +
    regs.pc += 2;
36 +
}
37 +
38 +
// *******************************************************************************************
39 +
//
40 +
//								Store zero to memory.
41 +
//
42 +
// *******************************************************************************************
43 +
44 +
static void stz() {
45 +
    putvalue(0, memory_16bit());
46 +
}
47 +
48 +
// *******************************************************************************************
49 +
//
50 +
//								Unconditional Branch
51 +
//
52 +
// *******************************************************************************************
53 +
54 +
static void bra() {
55 +
    oldpc = regs.pc;
56 +
    regs.pc += reladdr;
57 +
    if (regs.e && (oldpc & 0xFF00) != (regs.pc & 0xFF00)) clockticks6502++; //check if jump crossed a page boundary
58 +
}
59 +
60 +
// *******************************************************************************************
61 +
//
62 +
//									Push/Pull X and Y
63 +
//
64 +
// *******************************************************************************************
65 +
66 +
static void phx() {
67 +
    penaltym = 1;
68 +
69 +
    if (index_16bit()) {
70 +
        push16(regs.x);
71 +
    } else {
72 +
        push8(regs.xl);
73 +
    }
74 +
}
75 +
76 +
static void plx() {
77 +
    penaltym = 1;
78 +
79 +
    if (index_16bit()) {
80 +
        regs.x = pull16();
81 +
        zerocalc(regs.x, 1);
82 +
        signcalc(regs.x, 1);
83 +
    } else {
84 +
        regs.xl = pull8();
85 +
86 +
        zerocalc(regs.xl, 0);
87 +
        signcalc(regs.xl, 0);
88 +
    }
89 +
}
90 +
91 +
static void phy() {
92 +
    penaltym = 1;
93 +
94 +
    if (index_16bit()) {
95 +
        push16(regs.y);
96 +
    } else {
97 +
        push8(regs.yl);
98 +
    }
99 +
}
100 +
101 +
static void ply() {
102 +
    penaltym = 1;
103 +
104 +
    if (index_16bit()) {
105 +
        regs.y = pull16();
106 +
        zerocalc(regs.y, 1);
107 +
        signcalc(regs.y, 1);
108 +
    } else {
109 +
        regs.yl = pull8();
110 +
111 +
        zerocalc(regs.yl, 0);
112 +
        signcalc(regs.yl, 0);
113 +
    }
114 +
}
115 +
116 +
// *******************************************************************************************
117 +
//
118 +
//								TRB & TSB - Test and Change bits
119 +
//
120 +
// *******************************************************************************************
121 +
122 +
static void tsb() {
123 +
    value = getvalue(memory_16bit()); 							// Read memory
124 +
    result = acc_for_mode() & value;                // calculate A & memory
125 +
    zerocalc(result, memory_16bit()); 								// Set Z flag from this.
126 +
    result = value | acc_for_mode(); 				// Write back value read, A bits are set.
127 +
    putvalue(result, memory_16bit());
128 +
}
129 +
130 +
static void trb() {
131 +
    value = getvalue(memory_16bit()); 							// Read memory
132 +
    result = acc_for_mode() & value;  			// calculate A & memory
133 +
    zerocalc(result, memory_16bit()); 								// Set Z flag from this.
134 +
    result = value & (memory_16bit() ? regs.c ^ 0xFFFF : regs.a ^ 0xFF); 		    	// Write back value read, A bits are clear.
135 +
    putvalue(result, memory_16bit());
136 +
}
137 +
138 +
// *******************************************************************************************
139 +
//
140 +
//                                   Stop (Invoke Debugger)
141 +
//
142 +
// *******************************************************************************************
143 +
144 +
static void dbg() {
145 +
    stop6502(regs.pc - 1, regs.k);
146 +
}
147 +
148 +
// *******************************************************************************************
149 +
//
150 +
//                                     Wait for interrupt
151 +
//
152 +
// *******************************************************************************************
153 +
154 +
static void wai() {
155 +
	waiting = 1;
156 +
}
157 +
158 +
// *******************************************************************************************
159 +
//
160 +
//                                     BBR and BBS
161 +
//
162 +
// *******************************************************************************************
163 +
static void bbr(uint16_t bitmask)
164 +
{
165 +
    if (warn_rockwell) rockwell_warning("BBR");
166 +
	if ((getvalue(0) & bitmask) == 0) {
167 +
		oldpc = regs.pc;
168 +
		regs.pc += reladdr;
169 +
		if ((oldpc & 0xFF00) != (regs.pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary
170 +
		else clockticks6502++;
171 +
	}
172 +
}
173 +
174 +
static void bbr0() { bbr(0x01); }
175 +
static void bbr1() { bbr(0x02); }
176 +
static void bbr2() { bbr(0x04); }
177 +
static void bbr3() { bbr(0x08); }
178 +
static void bbr4() { bbr(0x10); }
179 +
static void bbr5() { bbr(0x20); }
180 +
static void bbr6() { bbr(0x40); }
181 +
static void bbr7() { bbr(0x80); }
182 +
183 +
static void bbs(uint16_t bitmask)
184 +
{
185 +
    if (warn_rockwell) rockwell_warning("BBS");
186 +
	if ((getvalue(0) & bitmask) != 0) {
187 +
		oldpc = regs.pc;
188 +
		regs.pc += reladdr;
189 +
		if ((oldpc & 0xFF00) != (regs.pc & 0xFF00)) clockticks6502 += 2; //check if jump crossed a page boundary
190 +
		else clockticks6502++;
191 +
	}
192 +
}
193 +
194 +
static void bbs0() { bbs(0x01); }
195 +
static void bbs1() { bbs(0x02); }
196 +
static void bbs2() { bbs(0x04); }
197 +
static void bbs3() { bbs(0x08); }
198 +
static void bbs4() { bbs(0x10); }
199 +
static void bbs5() { bbs(0x20); }
200 +
static void bbs6() { bbs(0x40); }
201 +
static void bbs7() { bbs(0x80); }
202 +
203 +
// *******************************************************************************************
204 +
//
205 +
//                                     SMB and RMB
206 +
//
207 +
// *******************************************************************************************
208 +
209 +
static void smb0() { if (warn_rockwell) rockwell_warning("SMB0"); putvalue(getvalue(0) | 0x01, 0); }
210 +
static void smb1() { if (warn_rockwell) rockwell_warning("SMB1"); putvalue(getvalue(0) | 0x02, 0); }
211 +
static void smb2() { if (warn_rockwell) rockwell_warning("SMB2"); putvalue(getvalue(0) | 0x04, 0); }
212 +
static void smb3() { if (warn_rockwell) rockwell_warning("SMB3"); putvalue(getvalue(0) | 0x08, 0); }
213 +
static void smb4() { if (warn_rockwell) rockwell_warning("SMB4"); putvalue(getvalue(0) | 0x10, 0); }
214 +
static void smb5() { if (warn_rockwell) rockwell_warning("SMB5"); putvalue(getvalue(0) | 0x20, 0); }
215 +
static void smb6() { if (warn_rockwell) rockwell_warning("SMB6"); putvalue(getvalue(0) | 0x40, 0); }
216 +
static void smb7() { if (warn_rockwell) rockwell_warning("SMB7"); putvalue(getvalue(0) | 0x80, 0); }
217 +
218 +
static void rmb0() { if (warn_rockwell) rockwell_warning("RMB0"); putvalue(getvalue(0) & ~0x01, 0); }
219 +
static void rmb1() { if (warn_rockwell) rockwell_warning("RMB1"); putvalue(getvalue(0) & ~0x02, 0); }
220 +
static void rmb2() { if (warn_rockwell) rockwell_warning("RMB2"); putvalue(getvalue(0) & ~0x04, 0); }
221 +
static void rmb3() { if (warn_rockwell) rockwell_warning("RMB3"); putvalue(getvalue(0) & ~0x08, 0); }
222 +
static void rmb4() { if (warn_rockwell) rockwell_warning("RMB4"); putvalue(getvalue(0) & ~0x10, 0); }
223 +
static void rmb5() { if (warn_rockwell) rockwell_warning("RMB5"); putvalue(getvalue(0) & ~0x20, 0); }
224 +
static void rmb6() { if (warn_rockwell) rockwell_warning("RMB6"); putvalue(getvalue(0) & ~0x40, 0); }
225 +
static void rmb7() { if (warn_rockwell) rockwell_warning("RMB7"); putvalue(getvalue(0) & ~0x80, 0); }
added src/cpu/65c02.opcodes +101 −0
1 +
; *******************************************************************************************
2 +
; *******************************************************************************************
3 +
;
4 +
;		File:		65c02.opcodes
5 +
;		Date:		3rd September 2019
6 +
;		Purpose:	Descriptor file, extra 65C02 opcodes
7 +
;		Author:		Paul Robson ([email protected])
8 +
;
9 +
; *******************************************************************************************
10 +
; *******************************************************************************************
11 +
12 +
adc ind0 5 $72
13 +
and ind0 5 $32
14 +
cmp ind0 5 $d2
15 +
eor ind0 5 $52
16 +
lda ind0 5 $b2
17 +
ora ind0 5 $12
18 +
sbc ind0 5 $f2
19 +
sta ind0 5 $92
20 +
21 +
bit immm 2 $89
22 +
bit zpx 4 $34
23 +
bit absx 4 $3c
24 +
25 +
dec acc 2 $3a
26 +
inc acc 2 $1a
27 +
28 +
jmp ainx 6 $7c
29 +
30 +
bra rel 3 $80
31 +
32 +
phx imp 3 $da
33 +
phy imp 3 $5a
34 +
plx imp 4 $fa
35 +
ply imp 4 $7a
36 +
37 +
stz zp 3 $64
38 +
stz zpx 4 $74
39 +
stz abso 4 $9c
40 +
stz absx 5 $9e
41 +
42 +
trb zp 5 $14
43 +
trb abso 6 $1c
44 +
tsb zp 5 $04
45 +
tsb abso 6 $0c
46 +
47 +
bbr0 zprel 5 $0f
48 +
bbr1 zprel 5 $1f
49 +
bbr2 zprel 5 $2f
50 +
bbr3 zprel 5 $3f
51 +
bbr4 zprel 5 $4f
52 +
bbr5 zprel 5 $5f
53 +
bbr6 zprel 5 $6f
54 +
bbr7 zprel 5 $7f
55 +
56 +
bbs0 zprel 5 $8f
57 +
bbs1 zprel 5 $9f
58 +
bbs2 zprel 5 $af
59 +
bbs3 zprel 5 $bf
60 +
bbs4 zprel 5 $cf
61 +
bbs5 zprel 5 $df
62 +
bbs6 zprel 5 $ef
63 +
bbs7 zprel 5 $ff
64 +
65 +
rmb0 zp 5 $07
66 +
rmb1 zp 5 $17
67 +
rmb2 zp 5 $27
68 +
rmb3 zp 5 $37
69 +
rmb4 zp 5 $47
70 +
rmb5 zp 5 $57
71 +
rmb6 zp 5 $67
72 +
rmb7 zp 5 $77
73 +
74 +
smb0 zp 5 $87
75 +
smb1 zp 5 $97
76 +
smb2 zp 5 $a7
77 +
smb3 zp 5 $b7
78 +
smb4 zp 5 $c7
79 +
smb5 zp 5 $d7
80 +
smb6 zp 5 $e7
81 +
smb7 zp 5 $f7
82 +
83 +
wai imp 3 $cb
84 +
85 +
dbg imp 1 $DB
86 +
87 +
nop imp 8 $5c
88 +
nop imp 4 $dc
89 +
nop imp 4 $fc
90 +
91 +
nop imm8 2 $02
92 +
nop imm8 2 $22
93 +
nop imm8 2 $42
94 +
nop imm8 3 $44
95 +
nop imm8 4 $54
96 +
nop imm8 2 $62
97 +
nop imm8 2 $82
98 +
nop imm8 2 $c2
99 +
nop imm8 4 $d4
100 +
nop imm8 2 $e2
101 +
nop imm8 4 $f4
added src/cpu/65c816.opcodes +140 −0
1 +
; *******************************************************************************************
2 +
; *******************************************************************************************
3 +
;
4 +
;		File:		65C816.opcodes
5 +
;		Date:		10th Januay 2024
6 +
;		Purpose:	Descriptor file, standard 65C816 opcodes
7 +
;		Author:		Paul Robson ([email protected]), Fulgen
8 +
;
9 +
; *******************************************************************************************
10 +
; *******************************************************************************************
11 +
12 +
adc ind0 5 $72
13 +
and ind0 5 $32
14 +
cmp ind0 5 $d2
15 +
eor ind0 5 $52
16 +
lda ind0 5 $b2
17 +
ora ind0 5 $12
18 +
sbc ind0 5 $f2
19 +
sta ind0 5 $92
20 +
21 +
adc sr 4 $63
22 +
and sr 4 $23
23 +
cmp sr 4 $c3
24 +
eor sr 4 $43
25 +
lda sr 4 $a3
26 +
ora sr 4 $03
27 +
sbc sr 4 $e3
28 +
sta sr 4 $83
29 +
30 +
adc sridy 7 $73
31 +
and sridy 7 $33
32 +
cmp sridy 7 $d3
33 +
eor sridy 7 $53
34 +
lda sridy 7 $b3
35 +
ora sridy 7 $13
36 +
sbc sridy 7 $f3
37 +
sta sridy 7 $93
38 +
39 +
adc absl 5 $6f
40 +
and absl 5 $2f
41 +
cmp absl 5 $cf
42 +
eor absl 5 $4f
43 +
lda absl 5 $af
44 +
ora absl 5 $0f
45 +
sbc absl 5 $ef
46 +
sta absl 5 $8f
47 +
48 +
adc abslx 5 $7f
49 +
and abslx 5 $3f
50 +
cmp abslx 5 $df
51 +
eor abslx 5 $5f
52 +
lda abslx 5 $bf
53 +
ora abslx 5 $1f
54 +
sbc abslx 5 $ff
55 +
sta abslx 5 $9f
56 +
57 +
adc indl0 6 $67
58 +
and indl0 6 $27
59 +
cmp indl0 6 $c7
60 +
eor indl0 6 $47
61 +
lda indl0 6 $a7
62 +
ora indl0 6 $07
63 +
sbc indl0 6 $e7
64 +
sta indl0 6 $87
65 +
66 +
adc indly 6 $77
67 +
and indly 6 $37
68 +
cmp indly 6 $d7
69 +
eor indly 6 $57
70 +
lda indly 6 $b7
71 +
ora indly 6 $17
72 +
sbc indly 6 $f7
73 +
sta indly 6 $97
74 +
75 +
bit immm 2 $89
76 +
bit zpx 4 $34
77 +
bit absx 4 $3c
78 +
79 +
brl rel16 4 $82
80 +
81 +
cop imp8 7 $02
82 +
83 +
dec acc 2 $3a
84 +
inc acc 2 $1a
85 +
86 +
jmp ainx 6 $7c
87 +
jml absl 4 $5c
88 +
jml aindl 6 $dc
89 +
90 +
jsr ainx 8 $fc
91 +
jsl absl 8 $22
92 +
93 +
bra rel 3 $80
94 +
95 +
pea imm16 5 $f4
96 +
pei ind0p 6 $d4
97 +
per rel16 6 $62
98 +
phb imp 3 $8b
99 +
phd imp 4 $0b
100 +
phk imp 3 $4b
101 +
phx imp 3 $da
102 +
phy imp 3 $5a
103 +
plb imp 4 $ab
104 +
pld imp 5 $2b
105 +
plx imp 4 $fa
106 +
ply imp 4 $7a
107 +
108 +
rtl imp 6 $6b
109 +
110 +
stz zp 3 $64
111 +
stz zpx 4 $74
112 +
stz abso 4 $9c
113 +
stz absx 5 $9e
114 +
115 +
trb zp 5 $14
116 +
trb abso 6 $1c
117 +
tsb zp 5 $04
118 +
tsb abso 6 $0c
119 +
120 +
wai imp 3 $cb
121 +
122 +
dbg imp 1 $DB
123 +
124 +
mvn bmv 7 $54
125 +
mvp bmv 7 $44
126 +
127 +
rep imm8 3 $c2
128 +
sep imm8 3 $e2
129 +
130 +
tcd imp 2 $5b
131 +
tcs imp 2 $1b
132 +
tdc imp 2 $7b
133 +
tsc imp 2 $3b
134 +
txy imp 2 $9b
135 +
tyx imp 2 $bb
136 +
137 +
wdm imm8 2 $42
138 +
139 +
xba imp 3 $eb
140 +
xce imp 2 $fb
added src/cpu/README +46 −0
1 +
The original fake6502.c has been split up into subcomponents, rather than being one long file - 
2 +
support functions, 6502 instructions, address modes, data tables and 65c02 extensions.
3 +
4 +
The file tables.h is now created from 6502.opcodes and 65c02.opcodes which are lists of instructions, 
5 +
cycle times, address modes and opcodes.
6 +
7 +
The python script buildtables.py creates this.
8 +
9 +
Minor changes have been made to modes.h and instructions.h to correct for 65C02 behaviour. These
10 +
are documented in the files.
11 +
12 +
Breaking Changes
13 +
================
14 +
15 +
Note: these breaking changes are *only in the emulator*. The behaviour is correct for a 65C02, which is
16 +
almost but not the same as a 6502.
17 +
18 +
(1) 		BRK 		clears D after pushing on the stack. Probably unimportant as most routines in 6502
19 +
						code CLD to get round this issue anyway.
20 +
21 +
(2)			ADC/SBC		In Decimal Mode, these now set N and Z flags. They did not previously. It is 
22 +
						possible that code may rely on this to preserve flag status through an ALU
23 +
						operation but unlikely.
24 +
25 +
(3) 		JMP 		The absolute indirect mode JMP (xxxx) had a bug where JMP ($12FF) got its 
26 +
						target address from $12FF and $1200. This bug is corrected. 
27 +
						This could break code with oddly positioned jump tables. Even align any vector
28 +
						tables or memory vectors.
29 +
30 +
(4) 		(Misc)		Undocumented instructions now do not work. They don't work on a 65C02.
31 +
					
32 +
Also note that read-modify-write instructions all now do not do the double write. ASL xx would do
33 +
read xx ; write xx ; write shifted xx on a 6502. On a 65C02 it does not. However, the emulator did
34 +
not emulate this behaviour anyway, so it doesn't actually matter.
35 +
36 +
Paul Robson 4/Sep/19
37 +
38 +
Testing
39 +
=======
40 +
Fixed stz bug .... writing a ... duh !
41 +
42 +
43 +
44 +
45 +
46 +
added src/cpu/buildtables.py +220 −0
1 +
# *******************************************************************************************
2 +
# *******************************************************************************************
3 +
#
4 +
#		File:			buildtables.py
5 +
#		Date:			3rd September 2019
6 +
#		Purpose:		Creates files tables.h from the .opcodes descriptors
7 +
#						Creates disassembly include file.
8 +
#		Author:			Paul Robson ([email protected])
9 +
#		Formatted By: 	Jeries Abedrabbo ([email protected])
10 +
#
11 +
# *******************************************************************************************
12 +
# *******************************************************************************************
13 +
14 +
import re
15 +
16 +
############# CONSTANTS ##############
17 +
######################################
18 +
########### KEY CONSTANTS ############
19 +
ACTN_KEY_STR = "action"
20 +
CYCLES_KEY_STR = "cycles"
21 +
MODE_KEY_STR = "mode"
22 +
OPCODE_KEY_STR = "opcode"
23 +
24 +
######################################
25 +
########### REGEX CONSTANTS ##########
26 +
OPCODE_REGEX_STR = "^(?P<{actnCode}>\\w+)\\s+(?P<{addrMode}>\\w+)\\s+(?P<{mchnCycles}>\\d)\\s+\\$(?P<{opCode}>[0-9a-fA-F]+)$".format(
27 +
    actnCode=ACTN_KEY_STR,
28 +
    addrMode=MODE_KEY_STR,
29 +
    mchnCycles=CYCLES_KEY_STR,
30 +
    opCode=OPCODE_KEY_STR
31 +
)
32 +
33 +
#####################################
34 +
########## HEADER CONSTANTS #########
35 +
ADDR_MODE_HEADER_C02 = "static void (*addrtable_c02[256])() = {"
36 +
ACTN_CODE_HEADER_C02 = "static void (*optable_c02[256])() = {"
37 +
MCHN_CYCLES_HEADER_C02 = "static const uint32_t ticktable_c02[256] = {"
38 +
MNEMONICS_DISASSEM_HEADER_C02 = "static const char *mnemonics_c02[256] = {"
39 +
ADDR_MODE_HEADER_C816 = "static void (*addrtable_c816[256])() = {"
40 +
ACTN_CODE_HEADER_C816 = "static void (*optable_c816[256])() = {"
41 +
MCHN_CYCLES_HEADER_C816 = "static const uint32_t ticktable_c816[256] = {"
42 +
MNEMONICS_DISASSEM_HEADER_C816 = "static const char *mnemonics_c816[256] = {"
43 +
TABLE_MAP = "/*{0:8}|  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |{0:5}*/\n"
44 +
45 +
#####################################
46 +
######### OPCODE CONSTANTS ##########
47 +
EMPTY_NOP_ACTN = "nop"
48 +
EMPTY_NOP_CYCLS = "1"
49 +
EMPTY_NOP_MOD = "imp"
50 +
OPCODE_ROW_LEN = 16
51 +
TOTAL_NUMBER_OPCODES = 2 ** 8
52 +
53 +
#####################################
54 +
############# FILENAMES #############
55 +
TABLES_HEADER_FNAME = "tables.h"
56 +
MNEMONICS_DISASSEM_HEADER_FNAME = "mnemonics.h"
57 +
OPCODES_6502_FNAME = "6502.opcodes"
58 +
OPCODES_65c02_FNAME = "65c02.opcodes"
59 +
OPCODES_65c816_FNAME = "65c816.opcodes"
60 +
61 +
62 +
#####################################
63 +
#####################################
64 +
65 +
66 +
#######################################################################################################################
67 +
####################################  Load in a source file with opcode descriptors  ##################################
68 +
#######################################################################################################################
69 +
def loadSource(srcFile, opcodesList):
70 +
    # Read file line by line and omit committed
71 +
    fileLinesObject = open(srcFile).readlines()
72 +
    strippedOpcodeLines = [opcodeLine.strip() for opcodeLine in fileLinesObject if
73 +
                           opcodeLine.strip() != "" and not opcodeLine.startswith(";")]
74 +
75 +
    # Import Opcodes
76 +
    for opcodeLine in strippedOpcodeLines:
77 +
        matchLine = re.match(OPCODE_REGEX_STR, opcodeLine)
78 +
        assert matchLine is not None, "Format {}".format(opcodeLine)
79 +
80 +
        opcode = int(matchLine.group(OPCODE_KEY_STR), 16)
81 +
        assert opcodesList[opcode] is None, "Duplicate {0:02x}".format(opcode)
82 +
83 +
        opcodesList[opcode] = {
84 +
            MODE_KEY_STR: matchLine.group(MODE_KEY_STR),
85 +
            ACTN_KEY_STR: matchLine.group(ACTN_KEY_STR),
86 +
            CYCLES_KEY_STR: matchLine.group(CYCLES_KEY_STR),
87 +
            OPCODE_KEY_STR: opcode
88 +
        }
89 +
90 +
91 +
#######################################################################################################################
92 +
#############################################  Fill unused slots with NOPs  ###########################################
93 +
#######################################################################################################################
94 +
def fillNop(opcodesList):
95 +
    for i in range(0, TOTAL_NUMBER_OPCODES):
96 +
        if opcodesList[i] is None:
97 +
            opcodesList[i] = {
98 +
                MODE_KEY_STR: EMPTY_NOP_MOD,
99 +
                ACTN_KEY_STR: EMPTY_NOP_ACTN,
100 +
                CYCLES_KEY_STR: EMPTY_NOP_CYCLS,
101 +
                OPCODE_KEY_STR: i
102 +
            }
103 +
104 +
105 +
#######################################################################################################################
106 +
###################################################  Output a table  ##################################################
107 +
#######################################################################################################################
108 +
def generateTable(hFileName, header, element, opcodesList):
109 +
    spacing = 5
110 +
    hFileName.write("{}{}{}".format("\n", header, "\n"))
111 +
    hFileName.write(TABLE_MAP.format("", ""))
112 +
    for row in range(0, OPCODE_ROW_LEN):
113 +
        elements = ",".join(
114 +
            ["{0:>9}".format(opcodesList[opcodeItem][element])[-spacing:] for opcodeItem in
115 +
             range(row * OPCODE_ROW_LEN, OPCODE_ROW_LEN * (row + 1))])
116 +
117 +
        hFileName.write("/* {0:X} */ {3}  {1}{2} /* {0:X} */\n".format(
118 +
            row,
119 +
            elements,
120 +
            " " if row == OPCODE_ROW_LEN - 1 else ",",
121 +
            " " if element == ACTN_KEY_STR else "")
122 +
        )
123 +
    hFileName.write("};\n")
124 +
125 +
126 +
127 +
#######################################################################################################################
128 +
###################################################  Output a list   ##################################################
129 +
#######################################################################################################################
130 +
def generateList(hFileName, header, elements):
131 +
    hFileName.write("{}{}{}".format("\n", header, "\n"))
132 +
    for row in range(0, OPCODE_ROW_LEN):
133 +
        hFileName.write("\t// ${0:X}X\n".format(row))
134 +
135 +
        for op in range(0, OPCODE_ROW_LEN):
136 +
            hFileName.write("\t/* ${0:X}{1:X} */ \"{2}\"{3}".format(row, op, elements[row * OPCODE_ROW_LEN + op], "" if row == OPCODE_ROW_LEN-1 and op == OPCODE_ROW_LEN-1 else ",\n"))
137 +
138 +
        hFileName.write("{}".format("" if row == OPCODE_ROW_LEN-1 else "\n"))
139 +
    hFileName.write("};\n")
140 +
141 +
142 +
143 +
#######################################################################################################################
144 +
########################################  Convert opcode structure to mnemonic  #######################################
145 +
#######################################################################################################################
146 +
def convertMnemonic(opInfo):
147 +
    modeStr = {
148 +
        "imp": "",
149 +
        "imp8": "#$%02x",
150 +
        "imm8": "#$%02x",
151 +
        "immm": "#$%%0%hhux",
152 +
        "immx": "#$%%0%hhux",
153 +
        "imm16": "#$%04x",
154 +
        "zp": "$%02x",
155 +
        "rel": "$%02x",
156 +
        "rel16": "$%04x",
157 +
        "zprel": "$%02x, $%04x",
158 +
        "zpx": "$%02x,x",
159 +
        "zpy": "$%02x,y",
160 +
        "abso": "$%04x",
161 +
        "absx": "$%04x,x",
162 +
        "absy": "$%04x,y",
163 +
        "absl": "$%06x",
164 +
        "abslx": "$%06x,x",
165 +
        "aindl": "[$%04x]",
166 +
        "ainx": "($%04x,x)",
167 +
        "indy": "($%02x),y",
168 +
        "indly": "[$%02x],y",
169 +
        "indx": "($%02x,x)",
170 +
        "ind": "($%04x)",
171 +
        "ind0": "($%02x)",
172 +
        "ind0p": "($%02x)",
173 +
        "indl0": "[$%02x]",
174 +
        "acc": "a",
175 +
        "sr": "$%02x,S",
176 +
        "sridy": "($%02x,S),y",
177 +
        "bmv": "$%02x,$%02x",
178 +
    }
179 +
    return "{} {}".format(opInfo[ACTN_KEY_STR], modeStr[opInfo[MODE_KEY_STR]])
180 +
181 +
182 +
#######################################################################################################################
183 +
##################################################  Load in opcodes  ##################################################
184 +
#######################################################################################################################
185 +
if __name__ == '__main__':
186 +
    opcodesList_c02 = [None] * TOTAL_NUMBER_OPCODES
187 +
188 +
    # Load 6502 opcodes list
189 +
    loadSource(OPCODES_6502_FNAME, opcodesList_c02)
190 +
    # Load 65C02 opcodes list
191 +
    loadSource(OPCODES_65c02_FNAME, opcodesList_c02)
192 +
193 +
    opcodesList_c816 = [None] * TOTAL_NUMBER_OPCODES
194 +
195 +
    # Load 6502 opcodes list
196 +
    loadSource(OPCODES_6502_FNAME, opcodesList_c816)
197 +
    # Load 65C816 opcodes list
198 +
    loadSource(OPCODES_65c816_FNAME, opcodesList_c816)
199 +
200 +
    # Fill opcodes list with NOP instructions
201 +
    fillNop(opcodesList_c02)
202 +
    fillNop(opcodesList_c816)
203 +
204 +
    # Create "TABLES_HEADER_FNAME" header file
205 +
    with open(TABLES_HEADER_FNAME, "w") as output_h_file:
206 +
        output_h_file.write("/* Generated by buildtables.py */\n")
207 +
        generateTable(output_h_file, ADDR_MODE_HEADER_C02, MODE_KEY_STR, opcodesList_c02)
208 +
        generateTable(output_h_file, ADDR_MODE_HEADER_C816, MODE_KEY_STR, opcodesList_c816)
209 +
        generateTable(output_h_file, ACTN_CODE_HEADER_C02, ACTN_KEY_STR, opcodesList_c02)
210 +
        generateTable(output_h_file, ACTN_CODE_HEADER_C816, ACTN_KEY_STR, opcodesList_c816)
211 +
        generateTable(output_h_file, MCHN_CYCLES_HEADER_C02, CYCLES_KEY_STR, opcodesList_c02)
212 +
        generateTable(output_h_file, MCHN_CYCLES_HEADER_C816, CYCLES_KEY_STR, opcodesList_c816)
213 +
214 +
    # Create disassembly "MNEMONICS_DISASSEM_HEADER_FNAME" header file.
215 +
    mnemonics_c02 = [convertMnemonic(opcodesList_c02[x]) for x in range(0, TOTAL_NUMBER_OPCODES)]
216 +
    mnemonics_c816 = [convertMnemonic(opcodesList_c816[x]) for x in range(0, TOTAL_NUMBER_OPCODES)]
217 +
    with open(MNEMONICS_DISASSEM_HEADER_FNAME, "w") as output_h_file:
218 +
        output_h_file.write("/* Generated by buildtables.py */\n")
219 +
        generateList(output_h_file, MNEMONICS_DISASSEM_HEADER_C02, mnemonics_c02)
220 +
        generateList(output_h_file, MNEMONICS_DISASSEM_HEADER_C816, mnemonics_c816)
added src/cpu/extract.py +47 −0
1 +
# *******************************************************************************************
2 +
# *******************************************************************************************
3 +
#
4 +
#		File:		extract.py
5 +
#		Date:		3rd September 2019
6 +
#		Purpose:	Extract opcode descriptions from tables.h.org
7 +
#		Author:		Paul Robson ([email protected])
8 +
#
9 +
# *******************************************************************************************
10 +
# *******************************************************************************************
11 +
12 +
import re
13 +
#
14 +
#							Get one element from one table.
15 +
#
16 +
def extract(opc,n):
17 +
	line = opc[n >> 4]
18 +
	m = re.match("^\\/\\*\\s+[0-9A-Fa-f]\\s+\\*\\/(.*)\\s*\\/\\*\\s*[0-9A-Fa-f]\\s*\\*\\/$",line)
19 +
	assert m is not None,line
20 +
	return m.group(1).replace(" ","").split(",")[n % 16]
21 +
22 +
#
23 +
#						Convert a tables.h type file to a list of opcodes
24 +
#
25 +
def convert(src,tgt):
26 +
	src = [x.strip() for x in open(src).readlines()]
27 +
	#
28 +
	#		Pull out data.
29 +
	#
30 +
	eac = src[3:3+16]
31 +
	opcodes = src[23:23+16]
32 +
	cycles = src[43:43+16]
33 +
	h = open(tgt,"w")
34 +
	for n in range(0,256):
35 +
		action = extract(opcodes,n)
36 +
		if action != "nop":
37 +
			amode = extract(eac,n)
38 +
			icycles = extract(cycles,n)
39 +
			h.write("{1:3} {2:4} {3} ${0:02x}\n".format(n,action,amode,icycles))
40 +
	h.close()
41 +
#
42 +
#		Stop it being overwritten accidentally. Should be no reason to regenerate 6502.opcodes
43 +
#		The pair is so you can compare the two sets of opcodes. (dis-include the 65c02.opcodes
44 +
#		extensions first.)
45 +
#
46 +
#convert("tables.h.org","6502.opcodes")
47 +
#convert("tables.h","/tmp/6502.opcodes")	
added src/cpu/fake6502.c +308 −0
1 +
/* Fake6502 CPU emulator core v1.1 *******************
2 +
 * (c)2011 Mike Chambers ([email protected])       *
3 +
 *****************************************************
4 +
 * v1.1 - Small bugfix in BIT opcode, but it was the *
5 +
 *        difference between a few games in my NES   *
6 +
 *        emulator working and being broken!         *
7 +
 *        I went through the rest carefully again    *
8 +
 *        after fixing it just to make sure I didn't *
9 +
 *        have any other typos! (Dec. 17, 2011)      *
10 +
 *                                                   *
11 +
 * v1.0 - First release (Nov. 24, 2011)              *
12 +
 *****************************************************
13 +
 * LICENSE: This source code is released into the    *
14 +
 * public domain, but if you use it please do give   *
15 +
 * credit. I put a lot of effort into writing this!  *
16 +
 *                                                   *
17 +
 *****************************************************
18 +
 * Fake6502 is a MOS Technology 6502 CPU emulation   *
19 +
 * engine in C. It was written as part of a Nintendo *
20 +
 * Entertainment System emulator I've been writing.  *
21 +
 *                                                   *
22 +
 * If you do discover an error in timing accuracy,   *
23 +
 * or operation in general please e-mail me at the   *
24 +
 * address above so that I can fix it. Thank you!    *
25 +
 *                                                   *
26 +
 *****************************************************
27 +
 * Usage:                                            *
28 +
 *                                                   *
29 +
 * Fake6502 requires you to provide two external     *
30 +
 * functions:                                        *
31 +
 *                                                   *
32 +
 * uint8_t read6502(uint16_t address, uint8_t bank)  *
33 +
 * void write6502(uint16_t address, uint8_t bank,    *
34 +
 * uint8_t value)                                    *
35 +
 *                                                   *
36 +
 * You may optionally pass Fake6502 the pointer to a *
37 +
 * function which you want to be called after every  *
38 +
 * emulated instruction. This function should be a   *
39 +
 * void with no parameters expected to be passed to  *
40 +
 * it.                                               *
41 +
 *                                                   *
42 +
 * This can be very useful. For example, in a NES    *
43 +
 * emulator, you check the number of clock ticks     *
44 +
 * that have passed so you can know when to handle   *
45 +
 * APU events.                                       *
46 +
 *                                                   *
47 +
 * To pass Fake6502 this pointer, use the            *
48 +
 * hookexternal(void *funcptr) function provided.    *
49 +
 *                                                   *
50 +
 * To disable the hook later, pass NULL to it.       *
51 +
 *****************************************************
52 +
 * Useful functions in this emulator:                *
53 +
 *                                                   *
54 +
 * void reset6502(bool c816)                         *
55 +
 *   - Call this once before you begin execution.    *
56 +
 *                                                   *
57 +
 * void exec6502(uint32_t tickcount)                 *
58 +
 *   - Execute 6502 code up to the next specified    *
59 +
 *     count of clock ticks.                         *
60 +
 *                                                   *
61 +
 * void step6502()                                   *
62 +
 *   - Execute a single instrution.                  *
63 +
 *                                                   *
64 +
 * void irq6502()                                    *
65 +
 *   - Trigger a hardware IRQ in the 6502 core.      *
66 +
 *                                                   *
67 +
 * void nmi6502()                                    *
68 +
 *   - Trigger an NMI in the 6502 core.              *
69 +
 *                                                   *
70 +
 * void hookexternal(void *funcptr)                  *
71 +
 *   - Pass a pointer to a void function taking no   *
72 +
 *     parameters. This will cause Fake6502 to call  *
73 +
 *     that function once after each emulated        *
74 +
 *     instruction.                                  *
75 +
 *                                                   *
76 +
 *****************************************************
77 +
 * Useful variables in this emulator:                *
78 +
 *                                                   *
79 +
 * uint32_t clockticks6502                           *
80 +
 *   - A running total of the emulated cycle count.  *
81 +
 *                                                   *
82 +
 * uint32_t instructions                             *
83 +
 *   - A running total of the total emulated         *
84 +
 *     instruction count. This is not related to     *
85 +
 *     clock cycle timing.                           *
86 +
 *                                                   *
87 +
 *****************************************************/
88 +
89 +
#include "registers.h"
90 +
91 +
#include <stdio.h>
92 +
#include <stdint.h>
93 +
#include <stdbool.h>
94 +
95 +
// 6502 / 65816 registers
96 +
97 +
struct regs regs;
98 +
99 +
//helper variables
100 +
uint32_t instructions = 0; //keep track of total instructions executed
101 +
uint32_t clockticks6502 = 0, clockgoal6502 = 0;
102 +
uint16_t opcode_addr, oldpc, reladdr, value;
103 +
uint32_t ea;
104 +
uint32_t result;
105 +
uint8_t opcode, oldstatus;
106 +
107 +
bool warn_rockwell = true;
108 +
109 +
uint8_t penaltyop, penaltyaddr;
110 +
uint8_t penaltym = 0;
111 +
uint8_t penaltye = 0;
112 +
uint8_t penaltyn = 0;
113 +
uint8_t penaltyx = 0;
114 +
uint8_t penaltyd = 0;
115 +
uint8_t waiting = 0;
116 +
117 +
//externally supplied functions
118 +
extern uint8_t read6502(uint16_t address, uint8_t bank);
119 +
extern void write6502(uint16_t address, uint8_t bank, uint8_t value);
120 +
extern void stop6502(uint16_t address, uint8_t bank);
121 +
extern void vp6502();
122 +
extern uint8_t memory_get_ram_bank();
123 +
extern uint8_t memory_get_rom_bank();
124 +
125 +
static void (*addrtable_c02[256])();
126 +
static void (*addrtable_c816[256])();
127 +
static void (*optable_c02[256])();
128 +
static void (*optable_c816[256])();
129 +
130 +
static const uint32_t ticktable_c02[256];
131 +
static const uint32_t ticktable_c816[256];
132 +
133 +
static void (**addrtable)();
134 +
static void (**optable)();
135 +
static const uint32_t *ticktable;
136 +
137 +
#include "support.h"
138 +
#include "modes.h"
139 +
140 +
void rockwell_warning(const char *instruction) {
141 +
    uint8_t pc_bank;
142 +
143 +
    if (opcode_addr < 0xa000) {
144 +
        pc_bank = 0;
145 +
    } else if (opcode_addr < 0xc000) {
146 +
        pc_bank = memory_get_ram_bank();
147 +
    } else {
148 +
        pc_bank = memory_get_rom_bank();
149 +
    }
150 +
151 +
    printf("Warning: encountered Rockwell instruction %s at $%02x:%04x.\n", instruction, pc_bank, opcode_addr);
152 +
    printf("\tFuture Commander X16 hardware may ship with a 65C816 CPU,\n");
153 +
    printf("\twhich does not support these instructions.\n");
154 +
    printf("\tThis will be the only warning given for Rockwell\n");
155 +
    printf("\tinstructions until the emulator is relaunched.\n");
156 +
    printf("\tPass -rockwell to the command line to suppress this warning.\n\n");
157 +
158 +
    warn_rockwell = false;
159 +
}
160 +
161 +
static uint8_t wrappedBankByte(uint32_t addr) {
162 +
    if (addrtable[opcode] == zp || addrtable[opcode] == zpx || addrtable[opcode] == zpy) {
163 +
        return 0;
164 +
    }
165 +
    else {
166 +
        return bank_byte(addr+1);
167 +
    }
168 +
}
169 +
170 +
static uint16_t getvalue(bool use16Bit) {
171 +
    if (addrtable[opcode] == acc) {
172 +
        return use16Bit ? regs.c : (uint16_t)regs.a;
173 +
    } else if (use16Bit) {
174 +
        return ((uint16_t)read6502(ea, bank_byte(ea)) | ((uint16_t)read6502(ea+1, wrappedBankByte(ea)) << 8));
175 +
    }
176 +
    return read6502(ea, bank_byte(ea));
177 +
}
178 +
179 +
__attribute__((unused)) static uint16_t getvalue16() {
180 +
    return((uint16_t)read6502(ea, bank_byte(ea)) | ((uint16_t)read6502(ea+1, wrappedBankByte(ea)) << 8));
181 +
}
182 +
183 +
static void putvalue(uint16_t saveval, bool use16Bit) {
184 +
    if (addrtable[opcode] == acc) {
185 +
        if (use16Bit) {
186 +
            regs.c = saveval;
187 +
        } else {
188 +
            regs.a = (uint8_t)(saveval & 0x00FF);
189 +
        }
190 +
    } else if (use16Bit) {
191 +
        write6502(ea, bank_byte(ea), (saveval & 0x00FF));
192 +
        write6502(ea + 1, wrappedBankByte(ea), saveval >> 8);
193 +
    } else {
194 +
        write6502(ea, bank_byte(ea), (saveval & 0x00FF));
195 +
    }
196 +
}
197 +
198 +
#include "instructions.h"
199 +
#include "65c02.h"
200 +
#include "tables.h"
201 +
202 +
void nmi6502() {
203 +
    interrupt6502(INT_NMI);
204 +
    waiting = 0;
205 +
}
206 +
207 +
void irq6502() {
208 +
    if (!(regs.status & FLAG_INTERRUPT)) {
209 +
        interrupt6502(INT_IRQ);
210 +
    }
211 +
    waiting = 0;
212 +
}
213 +
214 +
uint8_t callexternal = 0;
215 +
void (*loopexternal)();
216 +
217 +
void exec6502(uint32_t tickcount) {
218 +
	if (waiting) {
219 +
		clockticks6502 += tickcount;
220 +
		clockgoal6502 = clockticks6502;
221 +
		return;
222 +
    }
223 +
224 +
    opcode_addr = regs.pc;
225 +
226 +
    clockgoal6502 += tickcount;
227 +
228 +
    while (clockticks6502 < clockgoal6502) {
229 +
        opcode = read6502(regs.pc++, regs.k);
230 +
231 +
        if (regs.e) {
232 +
            regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
233 +
        }
234 +
235 +
        penaltyop = 0;
236 +
        penaltyaddr = 0;
237 +
        penaltym = 0;
238 +
        penaltye = 0;
239 +
        penaltyn = 0;
240 +
        penaltyx = 0;
241 +
242 +
        (*addrtable[opcode])();
243 +
        (*optable[opcode])();
244 +
        clockticks6502 += ticktable[opcode];
245 +
246 +
        if (!regs.e && penaltyop && penaltyaddr) clockticks6502++;
247 +
        if (memory_16bit()) clockticks6502 += penaltym;
248 +
        if (index_16bit()) clockticks6502 += penaltyx;
249 +
        if (penaltyn && !regs.e) clockticks6502++;
250 +
        if (penaltye && regs.e) clockticks6502++;
251 +
252 +
        instructions++;
253 +
254 +
        if (callexternal) (*loopexternal)();
255 +
    }
256 +
}
257 +
258 +
void step6502() {
259 +
	if (waiting) {
260 +
		++clockticks6502;
261 +
		clockgoal6502 = clockticks6502;
262 +
		return;
263 +
	}
264 +
265 +
    opcode_addr = regs.pc;
266 +
267 +
    opcode = read6502(regs.pc++, regs.k);
268 +
269 +
    if (regs.e) {
270 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
271 +
    }
272 +
273 +
    penaltyop = 0;
274 +
    penaltyaddr = 0;
275 +
    penaltym = 0;
276 +
    penaltye = 0;
277 +
    penaltyn = 0;
278 +
    penaltyx = 0;
279 +
280 +
    (*addrtable[opcode])();
281 +
    (*optable[opcode])();
282 +
    clockticks6502 += ticktable[opcode];
283 +
284 +
    if (penaltyop && penaltyaddr) clockticks6502++;
285 +
    if (memory_16bit()) clockticks6502 += penaltym;
286 +
    if (index_16bit()) clockticks6502 += penaltyx;
287 +
    if (penaltyn && !regs.e) clockticks6502++;
288 +
    if (penaltye && regs.e) clockticks6502++;
289 +
    if (penaltyd) clockticks6502 ++;
290 +
    clockgoal6502 = clockticks6502;
291 +
292 +
    instructions++;
293 +
294 +
    if (callexternal) (*loopexternal)();
295 +
}
296 +
297 +
void hookexternal(void *funcptr) {
298 +
    if (funcptr != (void *)NULL) {
299 +
        loopexternal = funcptr;
300 +
        callexternal = 1;
301 +
    } else callexternal = 0;
302 +
}
303 +
304 +
//  Fixes from http://6502.org/tutorials/65c02opcodes.html
305 +
//
306 +
//  65C02 Cycle Count differences.
307 +
//        ADC/SBC work differently in decimal mode.
308 +
//        The wraparound fixes may not be required.
added src/cpu/fake6502.h +19 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _FAKE6502_H_
6 +
#define _FAKE6502_H_
7 +
8 +
#include <stdint.h>
9 +
10 +
extern void reset6502(bool c816);
11 +
extern void step6502();
12 +
extern void exec6502(uint32_t tickcount);
13 +
extern void irq6502();
14 +
extern void nmi6502();
15 +
extern uint32_t clockticks6502;
16 +
extern uint8_t waiting;
17 +
extern bool warn_rockwell;
18 +
19 +
#endif
added src/cpu/instructions.h +883 −0
1 +
/*
2 +
3 +
						Extracted from original single fake6502.c file
4 +
5 +
*/
6 +
//
7 +
//          65C02 changes.
8 +
//
9 +
//          BRK                 now clears D
10 +
//          ADC/SBC             set N and Z in decimal mode. They also set V
11 +
//
12 +
//
13 +
//
14 +
//          instruction handler functions
15 +
//
16 +
static void adc() {
17 +
    penaltyop = 1;
18 +
    if (regs.status & FLAG_DECIMAL) {
19 +
        uint16_t tmp, tmp2;
20 +
        uint32_t tmpov;
21 +
        if (memory_16bit()) {
22 +
            uint16_t tmp3;
23 +
            uint32_t tmp4;
24 +
            value = getvalue(1);
25 +
            tmp = (regs.c & 0x000F) + (value & 0x000F) + (uint16_t)(regs.status & FLAG_CARRY);
26 +
            tmp2 = (regs.c & 0x00F0) + (value & 0x00F0);
27 +
            tmp3 = (regs.c & 0x0F00) + (value & 0x0F00);
28 +
            tmp4 = ((uint32_t)regs.c & 0xF000) + (value & 0xF000);
29 +
            if (tmp > 0x0009) {
30 +
                tmp2 += 0x0010;
31 +
                tmp += 0x0006;
32 +
            }
33 +
            if (tmp2 > 0x0090) {
34 +
                tmp3 += 0x0100;
35 +
                tmp2 += 0x0060;
36 +
            }
37 +
            if (tmp3 > 0x0900) {
38 +
                tmp4 += 0x1000;
39 +
                tmp3 += 0x0600;
40 +
            }
41 +
            tmpov = tmp4;
42 +
            if (tmp4 > 0x9000) {
43 +
                tmp4 += 0x6000;
44 +
            }
45 +
            if (tmp4 & 0xFFFF0000) {
46 +
                setcarry();
47 +
            } else {
48 +
                clearcarry();
49 +
            }
50 +
            result = (tmp & 0x000F) | (tmp2 & 0x00F0) | (tmp3 & 0x0F00) | (tmp4 & 0xF000);
51 +
            uint16_t ovresult = (tmp & 0x000F) | (tmp2 & 0x00F0) | (tmp3 & 0x0F00) | (tmpov & 0xF000);
52 +
            overflowcalc16(ovresult, regs.c, value);
53 +
        } else {
54 +
            value = getvalue(0);
55 +
            tmp = ((uint16_t)regs.a & 0x0F) + (value & 0x0F) + (uint16_t)(regs.status & FLAG_CARRY);
56 +
            tmp2 = ((uint16_t)regs.a & 0xF0) + (value & 0xF0);
57 +
            if (tmp > 0x09) {
58 +
                tmp2 += 0x10;
59 +
                tmp += 0x06;
60 +
            }
61 +
            tmpov = tmp2;
62 +
            if (tmp2 > 0x90) {
63 +
                tmp2 += 0x60;
64 +
            }
65 +
            if (tmp2 & 0xFF00) {
66 +
                setcarry();
67 +
            } else {
68 +
                clearcarry();
69 +
            }
70 +
            result = (tmp & 0x0F) | (tmp2 & 0xF0);
71 +
            uint8_t ovresult = (tmp & 0x0F) | (tmpov & 0xF0);
72 +
            overflowcalc8((uint16_t)ovresult, (uint16_t)regs.a, value);
73 +
        }
74 +
        clockticks6502 += (uint32_t)(!regs.is65c816);
75 +
    } else {
76 +
        if (memory_16bit()) {
77 +
            value = getvalue(1);
78 +
            result = regs.c + value + (uint16_t) (regs.status & FLAG_CARRY);
79 +
            overflowcalc16(result, regs.c, value);
80 +
            carrycalc(result, 1);
81 +
        } else {
82 +
            value = getvalue(0);
83 +
            result = (uint16_t)regs.a + value + (uint16_t) (regs.status & FLAG_CARRY);
84 +
            overflowcalc8(result, (uint16_t)regs.a, value);
85 +
            carrycalc(result, 0);
86 +
        }
87 +
    }
88 +
89 +
    zerocalc(result, memory_16bit());
90 +
    signcalc(result, memory_16bit());
91 +
92 +
    saveaccum(result);
93 +
}
94 +
95 +
static void and() {
96 +
    penaltyop = 1;
97 +
    value = getvalue(memory_16bit());
98 +
    result = acc_for_mode() & value;
99 +
100 +
    zerocalc(result, memory_16bit());
101 +
    signcalc(result, memory_16bit());
102 +
103 +
    saveaccum(result);
104 +
}
105 +
106 +
static void asl() {
107 +
    value = getvalue(memory_16bit());
108 +
    result = value << 1;
109 +
110 +
    carrycalc(result, memory_16bit());
111 +
    zerocalc(result, memory_16bit());
112 +
    signcalc(result, memory_16bit());
113 +
114 +
    putvalue(result, memory_16bit());
115 +
}
116 +
117 +
static void _do_branch(int condition) {
118 +
    if (condition) {
119 +
        oldpc = regs.pc;
120 +
        regs.pc += reladdr;
121 +
        clockticks6502++;
122 +
        if ((oldpc & 0xFF00) != (regs.pc & 0xFF00)) //check if jump crossed a page boundary
123 +
            penaltye = 1;
124 +
    }
125 +
}
126 +
127 +
static void bcc() {
128 +
    _do_branch((regs.status & FLAG_CARRY) == 0);
129 +
}
130 +
131 +
static void bcs() {
132 +
    _do_branch((regs.status & FLAG_CARRY) == FLAG_CARRY);
133 +
}
134 +
135 +
static void beq() {
136 +
    _do_branch((regs.status & FLAG_ZERO) == FLAG_ZERO);
137 +
}
138 +
139 +
static void bit() {
140 +
    value = getvalue(memory_16bit());
141 +
    result = acc_for_mode() & value;
142 +
143 +
    zerocalc(result, memory_16bit());
144 +
    // Xark - BUGFIX: 65C02 BIT #$xx only affects Z  See: http://6502.org/tutorials/65c02opcodes.html#2
145 +
    if (opcode != 0x89)
146 +
    {
147 +
        regs.status = (regs.status & 0x3F) | (uint8_t)(value & 0xC0);
148 +
    }
149 +
}
150 +
151 +
static void bmi() {
152 +
    _do_branch((regs.status & FLAG_SIGN) == FLAG_SIGN);
153 +
}
154 +
155 +
static void bne() {
156 +
    _do_branch((regs.status & FLAG_ZERO) == 0);
157 +
}
158 +
159 +
static void bpl() {
160 +
    _do_branch((regs.status & FLAG_SIGN) == 0);
161 +
}
162 +
163 +
static void brk() {
164 +
    penaltyn = 1;
165 +
    regs.pc++;
166 +
167 +
    interrupt6502(INT_BRK);
168 +
}
169 +
170 +
static void brl() {
171 +
    regs.pc += reladdr;
172 +
}
173 +
174 +
static void bvc() {
175 +
    _do_branch((regs.status & FLAG_OVERFLOW) == 0);
176 +
}
177 +
178 +
static void bvs() {
179 +
    _do_branch((regs.status & FLAG_OVERFLOW) == FLAG_OVERFLOW);
180 +
}
181 +
182 +
static void clc() {
183 +
    clearcarry();
184 +
}
185 +
186 +
static void cld() {
187 +
    cleardecimal();
188 +
}
189 +
190 +
static void cli() {
191 +
    clearinterrupt();
192 +
}
193 +
194 +
static void clv() {
195 +
    clearoverflow();
196 +
}
197 +
198 +
static void cmp() {
199 +
    penaltyop = 1;
200 +
    value = getvalue(memory_16bit());
201 +
202 +
    if (memory_16bit()) {
203 +
        result = regs.c - value;
204 +
        if (regs.c >= value) setcarry();
205 +
        else clearcarry();
206 +
        if (regs.c == value) setzero();
207 +
        else clearzero();
208 +
    } else {
209 +
        result = (uint16_t)regs.a - value;
210 +
        if (regs.a >= (uint8_t)(value & 0x00FF)) setcarry();
211 +
        else clearcarry();
212 +
        if (regs.a == (uint8_t)(value & 0x00FF)) setzero();
213 +
        else clearzero();
214 +
    }
215 +
216 +
    signcalc(result, memory_16bit());
217 +
}
218 +
219 +
static void cop() {
220 +
    penaltyn = 1;
221 +
    regs.pc++;
222 +
223 +
    interrupt6502(INT_COP);
224 +
}
225 +
226 +
static void cpx() {
227 +
    value = getvalue(index_16bit());
228 +
229 +
    if (index_16bit()) {
230 +
        result = regs.x - value;
231 +
        if(regs.x >= value) setcarry();
232 +
        else clearcarry();
233 +
        if (regs.x == value) setzero();
234 +
        else clearzero();
235 +
    } else {
236 +
        result = (uint16_t)regs.xl - value;
237 +
        if (regs.xl >= (uint8_t)(value & 0x00FF)) setcarry();
238 +
        else clearcarry();
239 +
        if (regs.xl == (uint8_t)(value & 0x00FF)) setzero();
240 +
        else clearzero();
241 +
    }
242 +
    signcalc(result, index_16bit());
243 +
}
244 +
245 +
static void cpy() {
246 +
    value = getvalue(index_16bit());
247 +
248 +
    if (index_16bit()) {
249 +
        result = regs.y - value;
250 +
        if(regs.y >= value) setcarry();
251 +
        else clearcarry();
252 +
        if (regs.y == value) setzero();
253 +
        else clearzero();
254 +
    } else {
255 +
        result = (uint16_t)regs.yl - value;
256 +
        if (regs.yl >= (uint8_t)(value & 0x00FF)) setcarry();
257 +
        else clearcarry();
258 +
        if (regs.yl == (uint8_t)(value & 0x00FF)) setzero();
259 +
        else clearzero();
260 +
    }
261 +
    signcalc(result, index_16bit());
262 +
}
263 +
264 +
static void dec() {
265 +
    value = getvalue(memory_16bit());
266 +
    result = value - 1;
267 +
268 +
    zerocalc(result, memory_16bit());
269 +
    signcalc(result, memory_16bit());
270 +
271 +
    putvalue(result, memory_16bit());
272 +
}
273 +
274 +
static void dex() {
275 +
    if (index_16bit()) {
276 +
        regs.x--;
277 +
        zerocalc(regs.x, 1);
278 +
        signcalc(regs.x, 1);
279 +
    } else {
280 +
        regs.xl--;
281 +
        zerocalc(regs.xl, 0);
282 +
        signcalc(regs.xl, 0);
283 +
    }
284 +
}
285 +
286 +
static void dey() {
287 +
    if (index_16bit()) {
288 +
        regs.y--;
289 +
        zerocalc(regs.y, 1);
290 +
        signcalc(regs.y, 1);
291 +
    } else {
292 +
        regs.yl--;
293 +
        zerocalc(regs.yl, 0);
294 +
        signcalc(regs.yl, 0);
295 +
    }
296 +
}
297 +
298 +
static void eor() {
299 +
    penaltyop = 1;
300 +
    value = getvalue(memory_16bit());
301 +
    result = acc_for_mode() ^ value;
302 +
303 +
    zerocalc(result, memory_16bit());
304 +
    signcalc(result, memory_16bit());
305 +
306 +
    saveaccum(result);
307 +
}
308 +
309 +
static void inc() {
310 +
    value = getvalue(memory_16bit());
311 +
    result = value + 1;
312 +
313 +
    zerocalc(result, memory_16bit());
314 +
    signcalc(result, memory_16bit());
315 +
316 +
    putvalue(result, memory_16bit());
317 +
}
318 +
319 +
static void inx() {
320 +
    if (index_16bit()) {
321 +
        regs.x++;
322 +
        zerocalc(regs.x, 1);
323 +
        signcalc(regs.x, 1);
324 +
    } else {
325 +
        regs.xl++;
326 +
        zerocalc(regs.xl, 0);
327 +
        signcalc(regs.xl, 0);
328 +
    }
329 +
}
330 +
331 +
static void iny() {
332 +
    if (index_16bit()) {
333 +
        regs.y++;
334 +
        zerocalc(regs.y, 1);
335 +
        signcalc(regs.y, 1);
336 +
    } else {
337 +
        regs.yl++;
338 +
        zerocalc(regs.yl, 0);
339 +
        signcalc(regs.yl, 0);
340 +
    }
341 +
}
342 +
343 +
static void jml() {
344 +
    regs.pc = ea & 0xFFFF;
345 +
    regs.k = ea >> 16;
346 +
}
347 +
348 +
static void jmp() {
349 +
    regs.pc = ea;
350 +
}
351 +
352 +
static void jsr() {
353 +
    push16(regs.pc - 1);
354 +
    regs.pc = ea;
355 +
}
356 +
357 +
static void jsl() {
358 +
    push8(regs.k);
359 +
    push16(regs.pc - 1);
360 +
    regs.pc = ea & 0xFFFF;
361 +
    regs.k = ea >> 16;
362 +
}
363 +
364 +
static void lda() {
365 +
    penaltyop = 1;
366 +
    penaltym = 1;
367 +
368 +
    if (memory_16bit()) {
369 +
        regs.c = getvalue(1);
370 +
        zerocalc(regs.c, 1);
371 +
        signcalc(regs.c, 1);
372 +
    } else {
373 +
        value = getvalue(0);
374 +
        regs.a = (uint8_t)(value & 0x00FF);
375 +
        zerocalc(regs.a, 0);
376 +
        signcalc(regs.a, 0);
377 +
    }
378 +
}
379 +
380 +
static void ldx() {
381 +
    penaltyop = 1;
382 +
    penaltyx = 1;
383 +
384 +
    if (index_16bit()) {
385 +
        regs.x = getvalue(1);
386 +
        zerocalc(regs.x, 1);
387 +
        signcalc(regs.x, 1);
388 +
    } else {
389 +
        value = getvalue(0);
390 +
        regs.xl = (uint8_t)(value & 0x00FF);
391 +
        zerocalc(regs.xl, 0);
392 +
        signcalc(regs.xl, 0);
393 +
    }
394 +
}
395 +
396 +
static void ldy() {
397 +
    penaltyop = 1;
398 +
    penaltyx = 1;
399 +
400 +
    if (index_16bit()) {
401 +
        regs.y = getvalue(1);
402 +
        zerocalc(regs.y, 1);
403 +
        signcalc(regs.y, 1);
404 +
    } else {
405 +
        regs.yl = (uint8_t)(getvalue(0) & 0x00FF);
406 +
        zerocalc(regs.yl, 0);
407 +
        signcalc(regs.yl, 0);
408 +
    }
409 +
}
410 +
411 +
static void lsr() {
412 +
    value = getvalue(memory_16bit());
413 +
    result = value >> 1;
414 +
415 +
    if (value & 1) setcarry();
416 +
        else clearcarry();
417 +
    zerocalc(result, memory_16bit());
418 +
    signcalc(result, memory_16bit());
419 +
420 +
    putvalue(result, memory_16bit());
421 +
}
422 +
423 +
static void nop() {
424 +
    switch (opcode) {
425 +
        case 0x1C:
426 +
        case 0x3C:
427 +
        case 0x5C:
428 +
        case 0x7C:
429 +
        case 0xDC:
430 +
        case 0xFC:
431 +
            penaltyop = 1;
432 +
            break;
433 +
    }
434 +
}
435 +
436 +
static void ora() {
437 +
    penaltyop = 1;
438 +
    value = getvalue(memory_16bit());
439 +
    result = acc_for_mode() | value;
440 +
441 +
    zerocalc(result, memory_16bit());
442 +
    signcalc(result, memory_16bit());
443 +
444 +
    saveaccum(result);
445 +
}
446 +
447 +
static void pea() {
448 +
    push16(getvalue(1));
449 +
}
450 +
451 +
static void pei() {
452 +
    push16(ea);
453 +
}
454 +
455 +
static void per() {
456 +
    push16(regs.pc + reladdr);
457 +
}
458 +
459 +
static void pha() {
460 +
    if (memory_16bit()) {
461 +
        push16(regs.c);
462 +
    } else {
463 +
        push8(regs.a);
464 +
    }
465 +
}
466 +
467 +
static void phb() {
468 +
    push8(regs.db);
469 +
}
470 +
471 +
static void phd() {
472 +
    push16(regs.dp);
473 +
}
474 +
475 +
static void phk() {
476 +
    push8(regs.k);
477 +
}
478 +
479 +
static void php() {
480 +
    push8(regs.e ? regs.status | FLAG_BREAK : regs.status);
481 +
}
482 +
483 +
static void pla() {
484 +
    if (memory_16bit()) {
485 +
        regs.c = pull16();
486 +
        zerocalc(regs.c, 1);
487 +
        signcalc(regs.c, 1);
488 +
    } else {
489 +
        regs.a = pull8();
490 +
        zerocalc(regs.a, 0);
491 +
        signcalc(regs.a, 0);
492 +
    }
493 +
}
494 +
495 +
static void plb() {
496 +
    regs.db = pull8();
497 +
    zerocalc(regs.db, 0);
498 +
    signcalc(regs.db, 0);
499 +
}
500 +
501 +
static void pld() {
502 +
    regs.dp = pull16();
503 +
}
504 +
505 +
static void plp() {
506 +
    regs.status = pull8();
507 +
    if (regs.e) {
508 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
509 +
    } else if (regs.status & FLAG_INDEX_WIDTH) {
510 +
        regs.xh = 0;
511 +
        regs.yh = 0;
512 +
    }
513 +
}
514 +
515 +
static void rep() {
516 +
    value = getvalue(0);
517 +
    regs.status &= ~(value & 0xFF);
518 +
519 +
    if (regs.e) {
520 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
521 +
    }
522 +
}
523 +
524 +
static void rol() {
525 +
    value = getvalue(memory_16bit());
526 +
    result = (value << 1) | (regs.status & FLAG_CARRY);
527 +
528 +
    carrycalc(result, memory_16bit());
529 +
    zerocalc(result, memory_16bit());
530 +
    signcalc(result, memory_16bit());
531 +
532 +
    putvalue(result, memory_16bit());
533 +
}
534 +
535 +
static void ror() {
536 +
    value = getvalue(memory_16bit());
537 +
    result = (value >> 1) | ((regs.status & FLAG_CARRY) << (memory_16bit() ? 15 : 7));
538 +
539 +
    if (value & 1) setcarry();
540 +
        else clearcarry();
541 +
    zerocalc(result, memory_16bit());
542 +
    signcalc(result, memory_16bit());
543 +
544 +
    putvalue(result, memory_16bit());
545 +
}
546 +
547 +
static void rti() {
548 +
    regs.status = pull8();
549 +
    value = pull16();
550 +
    regs.pc = value;
551 +
552 +
    if (regs.e) {
553 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
554 +
    } else {
555 +
        if (regs.status & FLAG_INDEX_WIDTH) {
556 +
            regs.xh = 0;
557 +
            regs.yh = 0;
558 +
        }
559 +
        regs.k = pull8();
560 +
    }
561 +
}
562 +
563 +
static void rtl() {
564 +
    value = pull16();
565 +
    regs.pc = value + 1;
566 +
    regs.k = pull8();
567 +
}
568 +
569 +
static void rts() {
570 +
    value = pull16();
571 +
    regs.pc = value + 1;
572 +
}
573 +
574 +
static void sbc() {
575 +
    penaltyop = 1;
576 +
577 +
    if (regs.status & FLAG_DECIMAL) {
578 +
        uint16_t tmp, tmp2;
579 +
        uint32_t tmpc;
580 +
        if (memory_16bit()) {
581 +
            uint16_t tmp3;
582 +
            uint32_t tmp4;
583 +
            value = getvalue(1);
584 +
            tmp = (regs.c & 0x000F) - (value & 0x000F) + (regs.status & FLAG_CARRY) - 1;
585 +
            tmp2 = (regs.c & 0x00F0) - (value & 0x00F0);
586 +
            tmp3 = (regs.c & 0x0F00) - (value & 0x0F00);
587 +
            tmp4 = (regs.c & 0xF000) - (value & 0xF000);
588 +
589 +
            if (tmp & 0xFFF0) {
590 +
                tmp2 -= 0x0010;
591 +
                tmp -= 0x0006;
592 +
            }
593 +
594 +
            if (tmp2 & 0xFF00) {
595 +
                tmp3 -= 0x0100;
596 +
                tmp2 -= 0x0060;
597 +
            }
598 +
599 +
            if (tmp3 & 0xF000) {
600 +
                tmp4 -= 0x1000;
601 +
                tmp3 -= 0x0600;
602 +
            }
603 +
604 +
            tmpc = tmp4;
605 +
            if (tmp4 >= 0x0000A000) {
606 +
                tmp4 -= 0x6000;
607 +
            }
608 +
609 +
            result = (tmp & 0x000F) | (tmp2 & 0x00F0) | (tmp3 & 0x0F00) | (tmp4 & 0xF000);
610 +
            uint16_t c_result = (tmp & 0x000F) | (tmp2 & 0x00F0) | (tmp3 & 0x0F00) | (tmpc & 0xF000);
611 +
612 +
            if (c_result <= regs.c) {
613 +
                setcarry();
614 +
            } else {
615 +
                clearcarry();
616 +
            }
617 +
            uint16_t ovresult = regs.c + (value ^ 0xFFFF) + (regs.status & FLAG_CARRY);
618 +
            overflowcalc16(ovresult, regs.c, value ^ 0xFFFF);
619 +
        } else {
620 +
            value = getvalue(0);
621 +
            tmp = ((uint16_t)regs.a & 0x0F) - (value & 0x0F) + (regs.status & FLAG_CARRY) - 1;
622 +
            tmp2 = ((uint16_t)regs.a & 0xF0) - (value & 0xF0);
623 +
624 +
            if (tmp & 0xFFF0) {
625 +
                tmp2 -= 0x10;
626 +
                tmp -= 0x06;
627 +
            }
628 +
629 +
            tmpc = tmp2;
630 +
            if (tmp2 & 0xFF00) {
631 +
                tmp2 -= 0x60;
632 +
            }
633 +
634 +
            result = (tmp & 0x0F) | (tmp2 & 0xF0);
635 +
            uint16_t c_result = (tmp & 0x0F) | (tmpc & 0xF0);
636 +
637 +
            if (c_result <= (uint16_t)regs.a) {
638 +
                setcarry();
639 +
            } else {
640 +
                clearcarry();
641 +
            }
642 +
            uint8_t ovresult = regs.a + (value ^ 0xFF) + (regs.status & FLAG_CARRY);
643 +
            overflowcalc8((uint16_t)ovresult, (uint16_t)regs.a, value ^ 0xFF);
644 +
        }
645 +
646 +
        clockticks6502 += (uint32_t)(!regs.is65c816);
647 +
    } else {
648 +
        if (memory_16bit()) {
649 +
            value = getvalue(1) ^ 0xFFFF;
650 +
            result = regs.c + value + (regs.status & FLAG_CARRY);
651 +
            overflowcalc16(result, regs.c, value);
652 +
        } else {
653 +
            value = getvalue(0) ^ 0x00FF;
654 +
            result = (uint16_t)regs.a + value + (uint16_t)(regs.status & FLAG_CARRY);
655 +
            overflowcalc8(result, (uint16_t)regs.a, value);
656 +
        }
657 +
658 +
        carrycalc(result, memory_16bit());
659 +
    }
660 +
661 +
    zerocalc(result, memory_16bit());
662 +
    signcalc(result, memory_16bit());
663 +
664 +
    saveaccum(result);
665 +
}
666 +
667 +
static void sec() {
668 +
    setcarry();
669 +
}
670 +
671 +
static void sed() {
672 +
    setdecimal();
673 +
}
674 +
675 +
static void sei() {
676 +
    setinterrupt();
677 +
}
678 +
679 +
static void sep() {
680 +
    regs.status |= getvalue(0) & 0xFF;
681 +
    if (regs.e) {
682 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
683 +
    }
684 +
    if (regs.status & FLAG_INDEX_WIDTH) {
685 +
        regs.xh = 0;
686 +
        regs.yh = 0;
687 +
    }
688 +
}
689 +
690 +
static void sta() {
691 +
    putvalue(acc_for_mode(), memory_16bit());
692 +
}
693 +
694 +
static void stx() {
695 +
    putvalue(index_16bit() ? regs.x : regs.xl, index_16bit());
696 +
}
697 +
698 +
static void sty() {
699 +
    putvalue(index_16bit() ? regs.y : regs.yl, index_16bit());
700 +
}
701 +
702 +
static void tax() {
703 +
    if (index_16bit()) {
704 +
        regs.x = regs.c; // 16 bits transferred, no matter the state of m
705 +
        zerocalc(regs.x, 1);
706 +
        signcalc(regs.x, 1);
707 +
    } else {
708 +
        regs.xl = (uint8_t)(regs.a & 0x00FF);
709 +
        zerocalc(regs.xl, 0);
710 +
        signcalc(regs.xl, 0);
711 +
    }
712 +
}
713 +
714 +
static void tay() {
715 +
    if (index_16bit()) {
716 +
        regs.y = regs.c; // 16 bits transferred, no matter the state of m
717 +
        zerocalc(regs.y, 1);
718 +
        signcalc(regs.y, 1);
719 +
    } else {
720 +
        regs.yl = (uint8_t)(regs.a & 0x00FF);
721 +
        zerocalc(regs.yl, 0);
722 +
        signcalc(regs.yl, 0);
723 +
    }
724 +
}
725 +
726 +
static void tcd() {
727 +
    regs.dp = regs.c;
728 +
    zerocalc(regs.dp, 1);
729 +
    signcalc(regs.dp, 1);
730 +
}
731 +
732 +
static void tdc() {
733 +
    regs.c = regs.dp;
734 +
    zerocalc(regs.c, 1);
735 +
    signcalc(regs.c, 1);
736 +
}
737 +
738 +
static void tsx() {
739 +
    if (index_16bit()) {
740 +
        regs.x = regs.sp; // 16 bits transferred, no matter the state of m
741 +
        zerocalc(regs.x, 1);
742 +
        signcalc(regs.x, 1);
743 +
    } else {
744 +
        regs.xl = (uint8_t)(regs.sp & 0x00FF);
745 +
        regs.xh = 0;
746 +
        zerocalc(regs.xl, 0);
747 +
        signcalc(regs.xl, 0);
748 +
    }
749 +
}
750 +
751 +
static void txa() {
752 +
    if (memory_16bit()) {
753 +
        if (index_16bit()) {
754 +
            regs.c = regs.x;
755 +
            zerocalc(regs.c, 1);
756 +
            signcalc(regs.c, 1);
757 +
        } else {
758 +
            regs.a = regs.xl;
759 +
            regs.b = 0;
760 +
            zerocalc(regs.a, 0);
761 +
            signcalc(regs.a, 0);
762 +
        }
763 +
    } else {
764 +
        regs.a = regs.xl;
765 +
        zerocalc(regs.a, 0);
766 +
        signcalc(regs.a, 0);
767 +
    }
768 +
}
769 +
770 +
static void txs() {
771 +
    if (regs.e) {
772 +
        regs.sp = 0x100 | regs.xl;
773 +
    } else {
774 +
        regs.sp = regs.x;
775 +
    }
776 +
}
777 +
778 +
static void txy() {
779 +
    if (index_16bit()) {
780 +
        regs.y = regs.x;
781 +
        zerocalc(regs.y, 1);
782 +
        signcalc(regs.y, 1);
783 +
    } else {
784 +
        regs.yl = regs.xl;
785 +
        zerocalc(regs.yl, 0);
786 +
        signcalc(regs.yl, 0);
787 +
    }
788 +
}
789 +
790 +
static void tya() {
791 +
    if (memory_16bit()) {
792 +
        if (index_16bit()) {
793 +
            regs.c = regs.y;
794 +
            zerocalc(regs.c, 1);
795 +
            signcalc(regs.c, 1);
796 +
        } else {
797 +
            regs.a = regs.yl;
798 +
            regs.b = 0;
799 +
            zerocalc(regs.a, 0);
800 +
            signcalc(regs.a, 0);
801 +
        }
802 +
    } else {
803 +
        regs.a = regs.yl;
804 +
        zerocalc(regs.a, 0);
805 +
        signcalc(regs.a, 0);
806 +
    }
807 +
}
808 +
809 +
static void tyx() {
810 +
    if (index_16bit()) {
811 +
        regs.x = regs.y;
812 +
        zerocalc(regs.x, 1);
813 +
        signcalc(regs.x, 1);
814 +
    } else {
815 +
        regs.xl = regs.yl;
816 +
        zerocalc(regs.xl, 0);
817 +
        signcalc(regs.xl, 0);
818 +
    }
819 +
}
820 +
821 +
static void tcs() {
822 +
    regs.sp = regs.c;
823 +
}
824 +
825 +
static void tsc() {
826 +
    regs.c = regs.sp;
827 +
    zerocalc(regs.c, 1);
828 +
    signcalc(regs.c, 1);
829 +
}
830 +
831 +
static void mvn() {
832 +
    uint8_t sourceBank = ea >> 8;
833 +
    uint8_t destBank = ea;
834 +
    regs.db = destBank;
835 +
836 +
    if (index_16bit()) {
837 +
        write6502(regs.y++, destBank, read6502(regs.x++, sourceBank));
838 +
    } else {
839 +
        write6502(regs.yl++, destBank, read6502(regs.xl++, sourceBank));
840 +
    }
841 +
    if (--regs.c != 0xFFFF) {
842 +
        regs.pc -= 3;
843 +
    }
844 +
}
845 +
846 +
static void mvp() {
847 +
    uint8_t sourceBank = ea >> 8;
848 +
    uint8_t destBank = ea;
849 +
    regs.db = ea;
850 +
851 +
    if (index_16bit()) {
852 +
        write6502(regs.y--, destBank, read6502(regs.x--, sourceBank));
853 +
    } else {
854 +
        write6502(regs.yl--, destBank, read6502(regs.xl--, sourceBank));
855 +
    }
856 +
    if (--regs.c != 0xFFFF) {
857 +
        regs.pc -= 3;
858 +
    }
859 +
}
860 +
861 +
static void wdm() {
862 +
}
863 +
864 +
static void xba() {
865 +
    uint8_t tmp = regs.b;
866 +
    regs.b = regs.a;
867 +
    regs.a = tmp;
868 +
    zerocalc(regs.a, 0);
869 +
    signcalc(regs.a, 0);
870 +
}
871 +
872 +
static void xce() {
873 +
    uint8_t carry = regs.status & FLAG_CARRY;
874 +
    regs.status = (regs.status & ~FLAG_CARRY) | (regs.e ? FLAG_CARRY : 0);
875 +
    regs.e = carry != 0;
876 +
877 +
    if (regs.e) {
878 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
879 +
        regs.sp = 0x0100 | (regs.sp & 0x00FF);
880 +
        regs.xh = 0x00;
881 +
        regs.yh = 0x00;
882 +
    }
883 +
}
added src/cpu/mnemonics.h +579 −0
1 +
/* Generated by buildtables.py */
2 +
3 +
static const char *mnemonics_c02[256] = {
4 +
	// $0X
5 +
	/* $00 */ "brk #$%02x",
6 +
	/* $01 */ "ora ($%02x,x)",
7 +
	/* $02 */ "nop #$%02x",
8 +
	/* $03 */ "nop ",
9 +
	/* $04 */ "tsb $%02x",
10 +
	/* $05 */ "ora $%02x",
11 +
	/* $06 */ "asl $%02x",
12 +
	/* $07 */ "rmb0 $%02x",
13 +
	/* $08 */ "php ",
14 +
	/* $09 */ "ora #$%%0%hhux",
15 +
	/* $0A */ "asl a",
16 +
	/* $0B */ "nop ",
17 +
	/* $0C */ "tsb $%04x",
18 +
	/* $0D */ "ora $%04x",
19 +
	/* $0E */ "asl $%04x",
20 +
	/* $0F */ "bbr0 $%02x, $%04x",
21 +
22 +
	// $1X
23 +
	/* $10 */ "bpl $%02x",
24 +
	/* $11 */ "ora ($%02x),y",
25 +
	/* $12 */ "ora ($%02x)",
26 +
	/* $13 */ "nop ",
27 +
	/* $14 */ "trb $%02x",
28 +
	/* $15 */ "ora $%02x,x",
29 +
	/* $16 */ "asl $%02x,x",
30 +
	/* $17 */ "rmb1 $%02x",
31 +
	/* $18 */ "clc ",
32 +
	/* $19 */ "ora $%04x,y",
33 +
	/* $1A */ "inc a",
34 +
	/* $1B */ "nop ",
35 +
	/* $1C */ "trb $%04x",
36 +
	/* $1D */ "ora $%04x,x",
37 +
	/* $1E */ "asl $%04x,x",
38 +
	/* $1F */ "bbr1 $%02x, $%04x",
39 +
40 +
	// $2X
41 +
	/* $20 */ "jsr $%04x",
42 +
	/* $21 */ "and ($%02x,x)",
43 +
	/* $22 */ "nop #$%02x",
44 +
	/* $23 */ "nop ",
45 +
	/* $24 */ "bit $%02x",
46 +
	/* $25 */ "and $%02x",
47 +
	/* $26 */ "rol $%02x",
48 +
	/* $27 */ "rmb2 $%02x",
49 +
	/* $28 */ "plp ",
50 +
	/* $29 */ "and #$%%0%hhux",
51 +
	/* $2A */ "rol a",
52 +
	/* $2B */ "nop ",
53 +
	/* $2C */ "bit $%04x",
54 +
	/* $2D */ "and $%04x",
55 +
	/* $2E */ "rol $%04x",
56 +
	/* $2F */ "bbr2 $%02x, $%04x",
57 +
58 +
	// $3X
59 +
	/* $30 */ "bmi $%02x",
60 +
	/* $31 */ "and ($%02x),y",
61 +
	/* $32 */ "and ($%02x)",
62 +
	/* $33 */ "nop ",
63 +
	/* $34 */ "bit $%02x,x",
64 +
	/* $35 */ "and $%02x,x",
65 +
	/* $36 */ "rol $%02x,x",
66 +
	/* $37 */ "rmb3 $%02x",
67 +
	/* $38 */ "sec ",
68 +
	/* $39 */ "and $%04x,y",
69 +
	/* $3A */ "dec a",
70 +
	/* $3B */ "nop ",
71 +
	/* $3C */ "bit $%04x,x",
72 +
	/* $3D */ "and $%04x,x",
73 +
	/* $3E */ "rol $%04x,x",
74 +
	/* $3F */ "bbr3 $%02x, $%04x",
75 +
76 +
	// $4X
77 +
	/* $40 */ "rti ",
78 +
	/* $41 */ "eor ($%02x,x)",
79 +
	/* $42 */ "nop #$%02x",
80 +
	/* $43 */ "nop ",
81 +
	/* $44 */ "nop #$%02x",
82 +
	/* $45 */ "eor $%02x",
83 +
	/* $46 */ "lsr $%02x",
84 +
	/* $47 */ "rmb4 $%02x",
85 +
	/* $48 */ "pha ",
86 +
	/* $49 */ "eor #$%%0%hhux",
87 +
	/* $4A */ "lsr a",
88 +
	/* $4B */ "nop ",
89 +
	/* $4C */ "jmp $%04x",
90 +
	/* $4D */ "eor $%04x",
91 +
	/* $4E */ "lsr $%04x",
92 +
	/* $4F */ "bbr4 $%02x, $%04x",
93 +
94 +
	// $5X
95 +
	/* $50 */ "bvc $%02x",
96 +
	/* $51 */ "eor ($%02x),y",
97 +
	/* $52 */ "eor ($%02x)",
98 +
	/* $53 */ "nop ",
99 +
	/* $54 */ "nop #$%02x",
100 +
	/* $55 */ "eor $%02x,x",
101 +
	/* $56 */ "lsr $%02x,x",
102 +
	/* $57 */ "rmb5 $%02x",
103 +
	/* $58 */ "cli ",
104 +
	/* $59 */ "eor $%04x,y",
105 +
	/* $5A */ "phy ",
106 +
	/* $5B */ "nop ",
107 +
	/* $5C */ "nop ",
108 +
	/* $5D */ "eor $%04x,x",
109 +
	/* $5E */ "lsr $%04x,x",
110 +
	/* $5F */ "bbr5 $%02x, $%04x",
111 +
112 +
	// $6X
113 +
	/* $60 */ "rts ",
114 +
	/* $61 */ "adc ($%02x,x)",
115 +
	/* $62 */ "nop #$%02x",
116 +
	/* $63 */ "nop ",
117 +
	/* $64 */ "stz $%02x",
118 +
	/* $65 */ "adc $%02x",
119 +
	/* $66 */ "ror $%02x",
120 +
	/* $67 */ "rmb6 $%02x",
121 +
	/* $68 */ "pla ",
122 +
	/* $69 */ "adc #$%%0%hhux",
123 +
	/* $6A */ "ror a",
124 +
	/* $6B */ "nop ",
125 +
	/* $6C */ "jmp ($%04x)",
126 +
	/* $6D */ "adc $%04x",
127 +
	/* $6E */ "ror $%04x",
128 +
	/* $6F */ "bbr6 $%02x, $%04x",
129 +
130 +
	// $7X
131 +
	/* $70 */ "bvs $%02x",
132 +
	/* $71 */ "adc ($%02x),y",
133 +
	/* $72 */ "adc ($%02x)",
134 +
	/* $73 */ "nop ",
135 +
	/* $74 */ "stz $%02x,x",
136 +
	/* $75 */ "adc $%02x,x",
137 +
	/* $76 */ "ror $%02x,x",
138 +
	/* $77 */ "rmb7 $%02x",
139 +
	/* $78 */ "sei ",
140 +
	/* $79 */ "adc $%04x,y",
141 +
	/* $7A */ "ply ",
142 +
	/* $7B */ "nop ",
143 +
	/* $7C */ "jmp ($%04x,x)",
144 +
	/* $7D */ "adc $%04x,x",
145 +
	/* $7E */ "ror $%04x,x",
146 +
	/* $7F */ "bbr7 $%02x, $%04x",
147 +
148 +
	// $8X
149 +
	/* $80 */ "bra $%02x",
150 +
	/* $81 */ "sta ($%02x,x)",
151 +
	/* $82 */ "nop #$%02x",
152 +
	/* $83 */ "nop ",
153 +
	/* $84 */ "sty $%02x",
154 +
	/* $85 */ "sta $%02x",
155 +
	/* $86 */ "stx $%02x",
156 +
	/* $87 */ "smb0 $%02x",
157 +
	/* $88 */ "dey ",
158 +
	/* $89 */ "bit #$%%0%hhux",
159 +
	/* $8A */ "txa ",
160 +
	/* $8B */ "nop ",
161 +
	/* $8C */ "sty $%04x",
162 +
	/* $8D */ "sta $%04x",
163 +
	/* $8E */ "stx $%04x",
164 +
	/* $8F */ "bbs0 $%02x, $%04x",
165 +
166 +
	// $9X
167 +
	/* $90 */ "bcc $%02x",
168 +
	/* $91 */ "sta ($%02x),y",
169 +
	/* $92 */ "sta ($%02x)",
170 +
	/* $93 */ "nop ",
171 +
	/* $94 */ "sty $%02x,x",
172 +
	/* $95 */ "sta $%02x,x",
173 +
	/* $96 */ "stx $%02x,y",
174 +
	/* $97 */ "smb1 $%02x",
175 +
	/* $98 */ "tya ",
176 +
	/* $99 */ "sta $%04x,y",
177 +
	/* $9A */ "txs ",
178 +
	/* $9B */ "nop ",
179 +
	/* $9C */ "stz $%04x",
180 +
	/* $9D */ "sta $%04x,x",
181 +
	/* $9E */ "stz $%04x,x",
182 +
	/* $9F */ "bbs1 $%02x, $%04x",
183 +
184 +
	// $AX
185 +
	/* $A0 */ "ldy #$%%0%hhux",
186 +
	/* $A1 */ "lda ($%02x,x)",
187 +
	/* $A2 */ "ldx #$%%0%hhux",
188 +
	/* $A3 */ "nop ",
189 +
	/* $A4 */ "ldy $%02x",
190 +
	/* $A5 */ "lda $%02x",
191 +
	/* $A6 */ "ldx $%02x",
192 +
	/* $A7 */ "smb2 $%02x",
193 +
	/* $A8 */ "tay ",
194 +
	/* $A9 */ "lda #$%%0%hhux",
195 +
	/* $AA */ "tax ",
196 +
	/* $AB */ "nop ",
197 +
	/* $AC */ "ldy $%04x",
198 +
	/* $AD */ "lda $%04x",
199 +
	/* $AE */ "ldx $%04x",
200 +
	/* $AF */ "bbs2 $%02x, $%04x",
201 +
202 +
	// $BX
203 +
	/* $B0 */ "bcs $%02x",
204 +
	/* $B1 */ "lda ($%02x),y",
205 +
	/* $B2 */ "lda ($%02x)",
206 +
	/* $B3 */ "nop ",
207 +
	/* $B4 */ "ldy $%02x,x",
208 +
	/* $B5 */ "lda $%02x,x",
209 +
	/* $B6 */ "ldx $%02x,y",
210 +
	/* $B7 */ "smb3 $%02x",
211 +
	/* $B8 */ "clv ",
212 +
	/* $B9 */ "lda $%04x,y",
213 +
	/* $BA */ "tsx ",
214 +
	/* $BB */ "nop ",
215 +
	/* $BC */ "ldy $%04x,x",
216 +
	/* $BD */ "lda $%04x,x",
217 +
	/* $BE */ "ldx $%04x,y",
218 +
	/* $BF */ "bbs3 $%02x, $%04x",
219 +
220 +
	// $CX
221 +
	/* $C0 */ "cpy #$%%0%hhux",
222 +
	/* $C1 */ "cmp ($%02x,x)",
223 +
	/* $C2 */ "nop #$%02x",
224 +
	/* $C3 */ "nop ",
225 +
	/* $C4 */ "cpy $%02x",
226 +
	/* $C5 */ "cmp $%02x",
227 +
	/* $C6 */ "dec $%02x",
228 +
	/* $C7 */ "smb4 $%02x",
229 +
	/* $C8 */ "iny ",
230 +
	/* $C9 */ "cmp #$%%0%hhux",
231 +
	/* $CA */ "dex ",
232 +
	/* $CB */ "wai ",
233 +
	/* $CC */ "cpy $%04x",
234 +
	/* $CD */ "cmp $%04x",
235 +
	/* $CE */ "dec $%04x",
236 +
	/* $CF */ "bbs4 $%02x, $%04x",
237 +
238 +
	// $DX
239 +
	/* $D0 */ "bne $%02x",
240 +
	/* $D1 */ "cmp ($%02x),y",
241 +
	/* $D2 */ "cmp ($%02x)",
242 +
	/* $D3 */ "nop ",
243 +
	/* $D4 */ "nop #$%02x",
244 +
	/* $D5 */ "cmp $%02x,x",
245 +
	/* $D6 */ "dec $%02x,x",
246 +
	/* $D7 */ "smb5 $%02x",
247 +
	/* $D8 */ "cld ",
248 +
	/* $D9 */ "cmp $%04x,y",
249 +
	/* $DA */ "phx ",
250 +
	/* $DB */ "dbg ",
251 +
	/* $DC */ "nop ",
252 +
	/* $DD */ "cmp $%04x,x",
253 +
	/* $DE */ "dec $%04x,x",
254 +
	/* $DF */ "bbs5 $%02x, $%04x",
255 +
256 +
	// $EX
257 +
	/* $E0 */ "cpx #$%%0%hhux",
258 +
	/* $E1 */ "sbc ($%02x,x)",
259 +
	/* $E2 */ "nop #$%02x",
260 +
	/* $E3 */ "nop ",
261 +
	/* $E4 */ "cpx $%02x",
262 +
	/* $E5 */ "sbc $%02x",
263 +
	/* $E6 */ "inc $%02x",
264 +
	/* $E7 */ "smb6 $%02x",
265 +
	/* $E8 */ "inx ",
266 +
	/* $E9 */ "sbc #$%%0%hhux",
267 +
	/* $EA */ "nop ",
268 +
	/* $EB */ "nop ",
269 +
	/* $EC */ "cpx $%04x",
270 +
	/* $ED */ "sbc $%04x",
271 +
	/* $EE */ "inc $%04x",
272 +
	/* $EF */ "bbs6 $%02x, $%04x",
273 +
274 +
	// $FX
275 +
	/* $F0 */ "beq $%02x",
276 +
	/* $F1 */ "sbc ($%02x),y",
277 +
	/* $F2 */ "sbc ($%02x)",
278 +
	/* $F3 */ "nop ",
279 +
	/* $F4 */ "nop #$%02x",
280 +
	/* $F5 */ "sbc $%02x,x",
281 +
	/* $F6 */ "inc $%02x,x",
282 +
	/* $F7 */ "smb7 $%02x",
283 +
	/* $F8 */ "sed ",
284 +
	/* $F9 */ "sbc $%04x,y",
285 +
	/* $FA */ "plx ",
286 +
	/* $FB */ "nop ",
287 +
	/* $FC */ "nop ",
288 +
	/* $FD */ "sbc $%04x,x",
289 +
	/* $FE */ "inc $%04x,x",
290 +
	/* $FF */ "bbs7 $%02x, $%04x"};
291 +
292 +
static const char *mnemonics_c816[256] = {
293 +
	// $0X
294 +
	/* $00 */ "brk #$%02x",
295 +
	/* $01 */ "ora ($%02x,x)",
296 +
	/* $02 */ "cop #$%02x",
297 +
	/* $03 */ "ora $%02x,S",
298 +
	/* $04 */ "tsb $%02x",
299 +
	/* $05 */ "ora $%02x",
300 +
	/* $06 */ "asl $%02x",
301 +
	/* $07 */ "ora [$%02x]",
302 +
	/* $08 */ "php ",
303 +
	/* $09 */ "ora #$%%0%hhux",
304 +
	/* $0A */ "asl a",
305 +
	/* $0B */ "phd ",
306 +
	/* $0C */ "tsb $%04x",
307 +
	/* $0D */ "ora $%04x",
308 +
	/* $0E */ "asl $%04x",
309 +
	/* $0F */ "ora $%06x",
310 +
311 +
	// $1X
312 +
	/* $10 */ "bpl $%02x",
313 +
	/* $11 */ "ora ($%02x),y",
314 +
	/* $12 */ "ora ($%02x)",
315 +
	/* $13 */ "ora ($%02x,S),y",
316 +
	/* $14 */ "trb $%02x",
317 +
	/* $15 */ "ora $%02x,x",
318 +
	/* $16 */ "asl $%02x,x",
319 +
	/* $17 */ "ora [$%02x],y",
320 +
	/* $18 */ "clc ",
321 +
	/* $19 */ "ora $%04x,y",
322 +
	/* $1A */ "inc a",
323 +
	/* $1B */ "tcs ",
324 +
	/* $1C */ "trb $%04x",
325 +
	/* $1D */ "ora $%04x,x",
326 +
	/* $1E */ "asl $%04x,x",
327 +
	/* $1F */ "ora $%06x,x",
328 +
329 +
	// $2X
330 +
	/* $20 */ "jsr $%04x",
331 +
	/* $21 */ "and ($%02x,x)",
332 +
	/* $22 */ "jsl $%06x",
333 +
	/* $23 */ "and $%02x,S",
334 +
	/* $24 */ "bit $%02x",
335 +
	/* $25 */ "and $%02x",
336 +
	/* $26 */ "rol $%02x",
337 +
	/* $27 */ "and [$%02x]",
338 +
	/* $28 */ "plp ",
339 +
	/* $29 */ "and #$%%0%hhux",
340 +
	/* $2A */ "rol a",
341 +
	/* $2B */ "pld ",
342 +
	/* $2C */ "bit $%04x",
343 +
	/* $2D */ "and $%04x",
344 +
	/* $2E */ "rol $%04x",
345 +
	/* $2F */ "and $%06x",
346 +
347 +
	// $3X
348 +
	/* $30 */ "bmi $%02x",
349 +
	/* $31 */ "and ($%02x),y",
350 +
	/* $32 */ "and ($%02x)",
351 +
	/* $33 */ "and ($%02x,S),y",
352 +
	/* $34 */ "bit $%02x,x",
353 +
	/* $35 */ "and $%02x,x",
354 +
	/* $36 */ "rol $%02x,x",
355 +
	/* $37 */ "and [$%02x],y",
356 +
	/* $38 */ "sec ",
357 +
	/* $39 */ "and $%04x,y",
358 +
	/* $3A */ "dec a",
359 +
	/* $3B */ "tsc ",
360 +
	/* $3C */ "bit $%04x,x",
361 +
	/* $3D */ "and $%04x,x",
362 +
	/* $3E */ "rol $%04x,x",
363 +
	/* $3F */ "and $%06x,x",
364 +
365 +
	// $4X
366 +
	/* $40 */ "rti ",
367 +
	/* $41 */ "eor ($%02x,x)",
368 +
	/* $42 */ "wdm #$%02x",
369 +
	/* $43 */ "eor $%02x,S",
370 +
	/* $44 */ "mvp $%02x,$%02x",
371 +
	/* $45 */ "eor $%02x",
372 +
	/* $46 */ "lsr $%02x",
373 +
	/* $47 */ "eor [$%02x]",
374 +
	/* $48 */ "pha ",
375 +
	/* $49 */ "eor #$%%0%hhux",
376 +
	/* $4A */ "lsr a",
377 +
	/* $4B */ "phk ",
378 +
	/* $4C */ "jmp $%04x",
379 +
	/* $4D */ "eor $%04x",
380 +
	/* $4E */ "lsr $%04x",
381 +
	/* $4F */ "eor $%06x",
382 +
383 +
	// $5X
384 +
	/* $50 */ "bvc $%02x",
385 +
	/* $51 */ "eor ($%02x),y",
386 +
	/* $52 */ "eor ($%02x)",
387 +
	/* $53 */ "eor ($%02x,S),y",
388 +
	/* $54 */ "mvn $%02x,$%02x",
389 +
	/* $55 */ "eor $%02x,x",
390 +
	/* $56 */ "lsr $%02x,x",
391 +
	/* $57 */ "eor [$%02x],y",
392 +
	/* $58 */ "cli ",
393 +
	/* $59 */ "eor $%04x,y",
394 +
	/* $5A */ "phy ",
395 +
	/* $5B */ "tcd ",
396 +
	/* $5C */ "jml $%06x",
397 +
	/* $5D */ "eor $%04x,x",
398 +
	/* $5E */ "lsr $%04x,x",
399 +
	/* $5F */ "eor $%06x,x",
400 +
401 +
	// $6X
402 +
	/* $60 */ "rts ",
403 +
	/* $61 */ "adc ($%02x,x)",
404 +
	/* $62 */ "per $%04x",
405 +
	/* $63 */ "adc $%02x,S",
406 +
	/* $64 */ "stz $%02x",
407 +
	/* $65 */ "adc $%02x",
408 +
	/* $66 */ "ror $%02x",
409 +
	/* $67 */ "adc [$%02x]",
410 +
	/* $68 */ "pla ",
411 +
	/* $69 */ "adc #$%%0%hhux",
412 +
	/* $6A */ "ror a",
413 +
	/* $6B */ "rtl ",
414 +
	/* $6C */ "jmp ($%04x)",
415 +
	/* $6D */ "adc $%04x",
416 +
	/* $6E */ "ror $%04x",
417 +
	/* $6F */ "adc $%06x",
418 +
419 +
	// $7X
420 +
	/* $70 */ "bvs $%02x",
421 +
	/* $71 */ "adc ($%02x),y",
422 +
	/* $72 */ "adc ($%02x)",
423 +
	/* $73 */ "adc ($%02x,S),y",
424 +
	/* $74 */ "stz $%02x,x",
425 +
	/* $75 */ "adc $%02x,x",
426 +
	/* $76 */ "ror $%02x,x",
427 +
	/* $77 */ "adc [$%02x],y",
428 +
	/* $78 */ "sei ",
429 +
	/* $79 */ "adc $%04x,y",
430 +
	/* $7A */ "ply ",
431 +
	/* $7B */ "tdc ",
432 +
	/* $7C */ "jmp ($%04x,x)",
433 +
	/* $7D */ "adc $%04x,x",
434 +
	/* $7E */ "ror $%04x,x",
435 +
	/* $7F */ "adc $%06x,x",
436 +
437 +
	// $8X
438 +
	/* $80 */ "bra $%02x",
439 +
	/* $81 */ "sta ($%02x,x)",
440 +
	/* $82 */ "brl $%04x",
441 +
	/* $83 */ "sta $%02x,S",
442 +
	/* $84 */ "sty $%02x",
443 +
	/* $85 */ "sta $%02x",
444 +
	/* $86 */ "stx $%02x",
445 +
	/* $87 */ "sta [$%02x]",
446 +
	/* $88 */ "dey ",
447 +
	/* $89 */ "bit #$%%0%hhux",
448 +
	/* $8A */ "txa ",
449 +
	/* $8B */ "phb ",
450 +
	/* $8C */ "sty $%04x",
451 +
	/* $8D */ "sta $%04x",
452 +
	/* $8E */ "stx $%04x",
453 +
	/* $8F */ "sta $%06x",
454 +
455 +
	// $9X
456 +
	/* $90 */ "bcc $%02x",
457 +
	/* $91 */ "sta ($%02x),y",
458 +
	/* $92 */ "sta ($%02x)",
459 +
	/* $93 */ "sta ($%02x,S),y",
460 +
	/* $94 */ "sty $%02x,x",
461 +
	/* $95 */ "sta $%02x,x",
462 +
	/* $96 */ "stx $%02x,y",
463 +
	/* $97 */ "sta [$%02x],y",
464 +
	/* $98 */ "tya ",
465 +
	/* $99 */ "sta $%04x,y",
466 +
	/* $9A */ "txs ",
467 +
	/* $9B */ "txy ",
468 +
	/* $9C */ "stz $%04x",
469 +
	/* $9D */ "sta $%04x,x",
470 +
	/* $9E */ "stz $%04x,x",
471 +
	/* $9F */ "sta $%06x,x",
472 +
473 +
	// $AX
474 +
	/* $A0 */ "ldy #$%%0%hhux",
475 +
	/* $A1 */ "lda ($%02x,x)",
476 +
	/* $A2 */ "ldx #$%%0%hhux",
477 +
	/* $A3 */ "lda $%02x,S",
478 +
	/* $A4 */ "ldy $%02x",
479 +
	/* $A5 */ "lda $%02x",
480 +
	/* $A6 */ "ldx $%02x",
481 +
	/* $A7 */ "lda [$%02x]",
482 +
	/* $A8 */ "tay ",
483 +
	/* $A9 */ "lda #$%%0%hhux",
484 +
	/* $AA */ "tax ",
485 +
	/* $AB */ "plb ",
486 +
	/* $AC */ "ldy $%04x",
487 +
	/* $AD */ "lda $%04x",
488 +
	/* $AE */ "ldx $%04x",
489 +
	/* $AF */ "lda $%06x",
490 +
491 +
	// $BX
492 +
	/* $B0 */ "bcs $%02x",
493 +
	/* $B1 */ "lda ($%02x),y",
494 +
	/* $B2 */ "lda ($%02x)",
495 +
	/* $B3 */ "lda ($%02x,S),y",
496 +
	/* $B4 */ "ldy $%02x,x",
497 +
	/* $B5 */ "lda $%02x,x",
498 +
	/* $B6 */ "ldx $%02x,y",
499 +
	/* $B7 */ "lda [$%02x],y",
500 +
	/* $B8 */ "clv ",
501 +
	/* $B9 */ "lda $%04x,y",
502 +
	/* $BA */ "tsx ",
503 +
	/* $BB */ "tyx ",
504 +
	/* $BC */ "ldy $%04x,x",
505 +
	/* $BD */ "lda $%04x,x",
506 +
	/* $BE */ "ldx $%04x,y",
507 +
	/* $BF */ "lda $%06x,x",
508 +
509 +
	// $CX
510 +
	/* $C0 */ "cpy #$%%0%hhux",
511 +
	/* $C1 */ "cmp ($%02x,x)",
512 +
	/* $C2 */ "rep #$%02x",
513 +
	/* $C3 */ "cmp $%02x,S",
514 +
	/* $C4 */ "cpy $%02x",
515 +
	/* $C5 */ "cmp $%02x",
516 +
	/* $C6 */ "dec $%02x",
517 +
	/* $C7 */ "cmp [$%02x]",
518 +
	/* $C8 */ "iny ",
519 +
	/* $C9 */ "cmp #$%%0%hhux",
520 +
	/* $CA */ "dex ",
521 +
	/* $CB */ "wai ",
522 +
	/* $CC */ "cpy $%04x",
523 +
	/* $CD */ "cmp $%04x",
524 +
	/* $CE */ "dec $%04x",
525 +
	/* $CF */ "cmp $%06x",
526 +
527 +
	// $DX
528 +
	/* $D0 */ "bne $%02x",
529 +
	/* $D1 */ "cmp ($%02x),y",
530 +
	/* $D2 */ "cmp ($%02x)",
531 +
	/* $D3 */ "cmp ($%02x,S),y",
532 +
	/* $D4 */ "pei ($%02x)",
533 +
	/* $D5 */ "cmp $%02x,x",
534 +
	/* $D6 */ "dec $%02x,x",
535 +
	/* $D7 */ "cmp [$%02x],y",
536 +
	/* $D8 */ "cld ",
537 +
	/* $D9 */ "cmp $%04x,y",
538 +
	/* $DA */ "phx ",
539 +
	/* $DB */ "dbg ",
540 +
	/* $DC */ "jml [$%04x]",
541 +
	/* $DD */ "cmp $%04x,x",
542 +
	/* $DE */ "dec $%04x,x",
543 +
	/* $DF */ "cmp $%06x,x",
544 +
545 +
	// $EX
546 +
	/* $E0 */ "cpx #$%%0%hhux",
547 +
	/* $E1 */ "sbc ($%02x,x)",
548 +
	/* $E2 */ "sep #$%02x",
549 +
	/* $E3 */ "sbc $%02x,S",
550 +
	/* $E4 */ "cpx $%02x",
551 +
	/* $E5 */ "sbc $%02x",
552 +
	/* $E6 */ "inc $%02x",
553 +
	/* $E7 */ "sbc [$%02x]",
554 +
	/* $E8 */ "inx ",
555 +
	/* $E9 */ "sbc #$%%0%hhux",
556 +
	/* $EA */ "nop ",
557 +
	/* $EB */ "xba ",
558 +
	/* $EC */ "cpx $%04x",
559 +
	/* $ED */ "sbc $%04x",
560 +
	/* $EE */ "inc $%04x",
561 +
	/* $EF */ "sbc $%06x",
562 +
563 +
	// $FX
564 +
	/* $F0 */ "beq $%02x",
565 +
	/* $F1 */ "sbc ($%02x),y",
566 +
	/* $F2 */ "sbc ($%02x)",
567 +
	/* $F3 */ "sbc ($%02x,S),y",
568 +
	/* $F4 */ "pea #$%04x",
569 +
	/* $F5 */ "sbc $%02x,x",
570 +
	/* $F6 */ "inc $%02x,x",
571 +
	/* $F7 */ "sbc [$%02x],y",
572 +
	/* $F8 */ "sed ",
573 +
	/* $F9 */ "sbc $%04x,y",
574 +
	/* $FA */ "plx ",
575 +
	/* $FB */ "xce ",
576 +
	/* $FC */ "jsr ($%04x,x)",
577 +
	/* $FD */ "sbc $%04x,x",
578 +
	/* $FE */ "inc $%04x,x",
579 +
	/* $FF */ "sbc $%06x,x"};
added src/cpu/modes.h +243 −0
1 +
/*
2 +
3 +
						Extracted from original single fake6502.c file
4 +
5 +
*/
6 +
7 +
//
8 +
//          65C02 changes.
9 +
//
10 +
//          ind         absolute indirect
11 +
//
12 +
//                      A 6502 has a bug whereby if you jmp ($12FF) it reads the address from
13 +
//                      $12FF and $1200. This has been fixed in the 65C02.
14 +
//
15 +
static void imp() { //implied
16 +
}
17 +
18 +
static void imp8() { // brk / cop
19 +
}
20 +
21 +
static void acc() { //accumulator
22 +
}
23 +
24 +
static void imm8() { //immediate, 8bit
25 +
    ea = regs.pc++;
26 +
}
27 +
28 +
static void immm() { //immediate, 16bit if M = 0
29 +
    ea = regs.pc++;
30 +
31 +
    if (memory_16bit()) {
32 +
        regs.pc++;
33 +
    }
34 +
}
35 +
36 +
static void immx() { //immediate, 16bit if M = 0
37 +
    ea = regs.pc++;
38 +
39 +
    if (index_16bit()) {
40 +
        regs.pc++;
41 +
    }
42 +
}
43 +
44 +
static void imm16() {
45 +
    ea = regs.pc;
46 +
    regs.pc += 2;
47 +
}
48 +
49 +
static void _zp_with_offset(uint16_t offset) {
50 +
    uint16_t imm_value = (uint16_t) read6502((uint16_t)regs.pc++, regs.k);
51 +
52 +
    if (regs.dp & 0x00FF) {
53 +
        penaltyd = 1;
54 +
    }
55 +
56 +
    ea = direct_page_add(imm_value + offset);
57 +
}
58 +
59 +
static void _zp_long_with_offset(uint16_t offset) {
60 +
    uint16_t eahelp;
61 +
    eahelp = (uint16_t)read6502(regs.pc++, regs.k);
62 +
63 +
    ea = (uint16_t)read6502(regs.dp + eahelp, regs.k) | ((uint16_t)read6502(regs.dp + eahelp + 1, regs.k) << 8) | ((uint16_t)read6502(regs.dp + eahelp + 2, regs.k) << 16);
64 +
    ea = mask_long_addr(ea + offset);
65 +
66 +
    if (regs.dp & 0x00FF) {
67 +
        penaltyd = 1;
68 +
    }
69 +
}
70 +
71 +
static void zp() { //zero-page
72 +
    _zp_with_offset(0);
73 +
}
74 +
75 +
static void zpx() { //zero-page,X
76 +
    _zp_with_offset(regs.x);
77 +
}
78 +
79 +
static void zpy() { //zero-page,Y
80 +
    _zp_with_offset(regs.y);
81 +
}
82 +
83 +
static void rel() { //relative for branch ops (8-bit immediate value, sign-extended)
84 +
    reladdr = (uint16_t)read6502(regs.pc++, regs.k);
85 +
    if (reladdr & 0x80) reladdr |= 0xFF00;
86 +
}
87 +
88 +
static void rel16() { //relative for PER and BRL (16-bit immediate value)
89 +
    reladdr = (uint16_t)read6502(regs.pc, regs.k) | ((uint16_t)read6502(regs.pc+1, regs.k) << 8);
90 +
    regs.pc += 2;
91 +
}
92 +
93 +
static void abso() { //absolute
94 +
    ea = addr_with_db((uint16_t) read6502(regs.pc, regs.k) | ((uint16_t)read6502(regs.pc+1, regs.k) << 8));
95 +
    regs.pc += 2;
96 +
}
97 +
98 +
static void absl() { // absolute long
99 +
    ea = (uint32_t) read6502(regs.pc, regs.k) | ((uint32_t)read6502(regs.pc+1, regs.k) << 8) | ((uint32_t)read6502(regs.pc+2, regs.k) << 16);
100 +
    regs.pc += 3;
101 +
}
102 +
103 +
static void absx() { //absolute,X
104 +
    uint16_t startpage;
105 +
    ea = addr_with_db((uint16_t)read6502(regs.pc, regs.k) | ((uint16_t)read6502(regs.pc+1, regs.k) << 8));
106 +
    startpage = ea & 0xFF00;
107 +
    ea = mask_long_addr(ea + regs.x);
108 +
109 +
    if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes
110 +
        penaltyaddr = 1;
111 +
    }
112 +
113 +
    regs.pc += 2;
114 +
}
115 +
116 +
static void abslx() { // absolute long, X
117 +
    uint16_t startpage;
118 +
    ea = (uint32_t)read6502(regs.pc, regs.k) | ((uint32_t)read6502(regs.pc+1, regs.k) << 8) | ((uint32_t)read6502(regs.pc+2, regs.k) << 16);
119 +
    startpage = ea & 0xFF00;
120 +
    ea = mask_long_addr(ea + regs.x);
121 +
122 +
    if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes
123 +
        penaltyaddr = 1;
124 +
    }
125 +
126 +
    regs.pc += 3;
127 +
}
128 +
129 +
static void absy() { //absolute,Y
130 +
    uint16_t startpage;
131 +
    ea = addr_with_db((uint16_t)read6502(regs.pc, regs.k) | ((uint16_t)read6502(regs.pc+1, regs.k) << 8));
132 +
    startpage = ea & 0xFF00;
133 +
    ea = mask_long_addr(ea + regs.y);
134 +
135 +
    if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes
136 +
        penaltyaddr = 1;
137 +
    }
138 +
139 +
    regs.pc += 2;
140 +
}
141 +
142 +
static void ind() { //indirect - used for jmp, which assumes the pointer is in bank 0!
143 +
    uint16_t eahelp, eahelp2;
144 +
    eahelp = (uint16_t)read6502(regs.pc, regs.k) | (uint16_t)((uint16_t)read6502(regs.pc+1, regs.k) << 8);
145 +
    //
146 +
    //      The 6502 page boundary wraparound bug does not occur on a 65C02.
147 +
    //
148 +
    //eahelp2 = (eahelp & 0xFF00) | ((eahelp + 1) & 0x00FF); //replicate 6502 page-boundary wraparound bug
149 +
    eahelp2 = (eahelp+1) & 0xFFFF;
150 +
    ea = (uint16_t)read6502(eahelp, 0) | ((uint16_t)read6502(eahelp2, 0) << 8);
151 +
    regs.pc += 2;
152 +
}
153 +
154 +
static void aindl() { // [addr]    uint16_t eahelp, eahelp2;
155 +
    uint16_t eahelp, eahelp2;
156 +
    eahelp = (uint16_t)read6502(regs.pc, regs.k) | (uint16_t)((uint16_t)read6502(regs.pc+1, regs.k) << 8);
157 +
    eahelp2 = (eahelp+1) & 0xFFFF;
158 +
    ea = (uint32_t)read6502(eahelp, 0) | ((uint32_t)read6502(eahelp2, 0) << 8) | ((uint16_t)read6502((eahelp2 + 1) & 0xFFFF, 0) << 16);
159 +
    regs.pc += 2;
160 +
}
161 +
162 +
static void ind0() { // (zp)
163 +
    uint16_t eahelp;
164 +
    eahelp = (uint16_t)read6502(regs.pc++, regs.k);
165 +
    ea = (uint16_t)read6502(direct_page_add(eahelp), 0) | ((uint16_t)read6502(direct_page_add(eahelp + 1), 0) << 8);
166 +
167 +
    if (regs.dp & 0x00FF) {
168 +
        penaltyd = 1;
169 +
    }
170 +
}
171 +
172 +
static void indl0() { // [dp]
173 +
    _zp_long_with_offset(0);
174 +
}
175 +
176 +
static void indx() { // (indirect,X)
177 +
    uint16_t eahelp;
178 +
    eahelp = (uint16_t)read6502(regs.pc++, regs.k) + regs.x;
179 +
    ea = (uint16_t)read6502(direct_page_add(eahelp), 0) | ((uint16_t)read6502(direct_page_add(eahelp + 1), 0) << 8);
180 +
181 +
    if (regs.dp & 0x00FF) {
182 +
        penaltyd = 1;
183 +
    }
184 +
}
185 +
186 +
static void indy() { // (indirect),Y
187 +
    uint16_t eahelp, startpage;
188 +
    eahelp = (uint16_t)read6502(regs.pc++, regs.k);
189 +
    ea = (uint16_t)read6502(direct_page_add(eahelp), 0) | ((uint16_t)read6502(direct_page_add(eahelp + 1), 0) << 8);
190 +
    startpage = ea & 0xFF00;
191 +
    ea += regs.y;
192 +
193 +
    if (regs.dp & 0x00FF) {
194 +
        penaltyd = 1;
195 +
    }
196 +
197 +
    if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes
198 +
        penaltyaddr = 1;
199 +
    }
200 +
}
201 +
202 +
static void indly() { // [dp],Y
203 +
    _zp_long_with_offset(regs.y);
204 +
}
205 +
206 +
static void ind0p() { // (zp) used by PEI, which doesn't do wraparound calculations
207 +
    uint16_t eahelp;
208 +
    eahelp = (uint16_t)read6502(regs.pc++, regs.k);
209 +
    ea = (uint16_t)read6502(regs.dp + eahelp, 0) | ((uint16_t)read6502(regs.dp + eahelp + 1, 0) << 8);
210 +
211 +
    if (regs.dp & 0x00FF) {
212 +
        penaltyd = 1;
213 +
    }
214 +
}
215 +
216 +
static void zprel() { // zero-page, relative for branch ops (8-bit immediatel value, sign-extended) - only used for the 65C02's Rockwell extensions
217 +
	ea = (uint16_t)read6502(regs.pc, 0);
218 +
	reladdr = (uint16_t)read6502(regs.pc+1, 0);
219 +
	if (reladdr & 0x80) reladdr |= 0xFF00;
220 +
221 +
	regs.pc += 2;
222 +
}
223 +
224 +
static void sr() { // absolute,S
225 +
    ea = regs.sp + (uint16_t)read6502(regs.pc++, regs.k);
226 +
}
227 +
228 +
static void sridy() { // (indirect,S),Y
229 +
    uint16_t eahelp, startpage;
230 +
    eahelp = regs.sp + (uint16_t)read6502(regs.pc++, regs.k);
231 +
    ea = (uint16_t)read6502(eahelp, 0) | ((uint16_t)read6502(eahelp + 1, 0) << 8);
232 +
    startpage = ea & 0xFF00;
233 +
    ea += (uint16_t)regs.y;
234 +
235 +
    if (startpage != (ea & 0xFF00)) { //one cycle penlty for page-crossing on some opcodes
236 +
        penaltyaddr = 1;
237 +
    }
238 +
}
239 +
240 +
static void bmv() { // block move
241 +
    uint8_t dest = read6502(regs.pc++, regs.k);
242 +
    ea = (read6502(regs.pc++, regs.k) << 8) | dest;
243 +
}
added src/cpu/registers.h +47 −0
1 +
2 +
#ifndef _REGISTERS_H_
3 +
#define _REGISTERS_H_
4 +
5 +
#include <stdint.h>
6 +
#include <stdbool.h>
7 +
#include "../endian.h"
8 +
9 +
#define FLAG_CARRY     0x01
10 +
#define FLAG_ZERO      0x02
11 +
#define FLAG_INTERRUPT 0x04
12 +
#define FLAG_DECIMAL   0x08
13 +
#define FLAG_INDEX_WIDTH 0x10
14 +
#define FLAG_MEMORY_WIDTH  0x20
15 +
#define FLAG_OVERFLOW  0x40
16 +
#define FLAG_SIGN      0x80
17 +
18 +
// For 65C02
19 +
#define FLAG_BREAK FLAG_INDEX_WIDTH
20 +
#define FLAG_CONSTANT FLAG_MEMORY_WIDTH
21 +
22 +
//6502 CPU registers
23 +
24 +
struct regs
25 +
{
26 +
    LOW_HIGH_UNION(c, a, b);
27 +
    LOW_HIGH_UNION(x, xl, xh);
28 +
    LOW_HIGH_UNION(y, yl, yh);
29 +
30 +
    uint16_t dp;
31 +
    uint16_t sp;
32 +
33 +
    uint8_t db;
34 +
    uint16_t pc;
35 +
    uint8_t k;
36 +
37 +
    uint8_t status;
38 +
    uint8_t e;
39 +
40 +
    bool is65c816;
41 +
};
42 +
43 +
void increment_wrap_at_page_boundary(uint16_t *value);
44 +
void decrement_wrap_at_page_boundary(uint16_t *value);
45 +
uint16_t direct_page_add(uint16_t offset);
46 +
47 +
#endif
added src/cpu/support.h +208 −0
1 +
/*
2 +
3 +
						Extracted from original single fake6502.c file
4 +
5 +
*/
6 +
7 +
8 +
//flag modifier macros
9 +
#define setcarry() regs.status |= FLAG_CARRY
10 +
#define clearcarry() regs.status &= (~FLAG_CARRY)
11 +
#define setzero() regs.status |= FLAG_ZERO
12 +
#define clearzero() regs.status &= (~FLAG_ZERO)
13 +
#define setinterrupt() regs.status |= FLAG_INTERRUPT
14 +
#define clearinterrupt() regs.status &= (~FLAG_INTERRUPT)
15 +
#define setdecimal() regs.status |= FLAG_DECIMAL
16 +
#define cleardecimal() regs.status &= (~FLAG_DECIMAL)
17 +
#define setoverflow() regs.status |= FLAG_OVERFLOW
18 +
#define clearoverflow() regs.status &= (~FLAG_OVERFLOW)
19 +
#define setsign() regs.status |= FLAG_SIGN
20 +
#define clearsign() regs.status &= (~FLAG_SIGN)
21 +
22 +
23 +
//flag calculation macros
24 +
#define zerocalc(n, use16Bit) {\
25 +
    if (use16Bit) \
26 +
        if ((n) & 0xFFFF) clearzero();\
27 +
            else setzero();\
28 +
    else if ((n) & 0x00FF) clearzero();\
29 +
        else setzero();\
30 +
}
31 +
32 +
#define signcalc(n, use16Bit) {\
33 +
    if (use16Bit) \
34 +
        if ((n) & 0x8000) setsign();\
35 +
            else clearsign();\
36 +
    else if ((n) & 0x0080) setsign();\
37 +
        else clearsign();\
38 +
}
39 +
40 +
#define carrycalc(n, use16Bit) {\
41 +
    if (use16Bit) \
42 +
        if ((n) & 0x10000) setcarry();\
43 +
            else clearcarry();\
44 +
    else if ((n) & 0x0100) setcarry();\
45 +
        else clearcarry();\
46 +
}
47 +
48 +
#define overflowcalc8(n, m, o) { /* n = result, m = accumulator, o = memory */ \
49 +
    if (((n) ^ (m)) & ((n) ^ (o)) & 0x80) setoverflow();\
50 +
        else clearoverflow();\
51 +
}
52 +
53 +
#define overflowcalc16(n, m, o) { /* n = result, m = accumulator, o = memory */ \
54 +
    if (((n) ^ (m)) & ((n) ^ (o)) & 0x8000) setoverflow();\
55 +
        else clearoverflow();\
56 +
}
57 +
58 +
#define index_16bit() (regs.is65c816 && !(regs.status & FLAG_INDEX_WIDTH))
59 +
#define memory_16bit() (regs.is65c816 && !(regs.status & FLAG_MEMORY_WIDTH))
60 +
#define acc_for_mode() (memory_16bit() ? regs.c : ((uint16_t) regs.a))
61 +
62 +
63 +
#define saveaccum(n) (memory_16bit() ? (regs.c = (n)) : (regs.a = (uint8_t)((n) & 0x00FF)))
64 +
65 +
#define mask_long_addr(addr) ((addr) & 0xFFFFFF)
66 +
#define as_bank_byte(b) ((uint32_t) (((uint8_t) (b)) << 16))
67 +
#define bank_byte(b) ((uint8_t) ((b) >> 16))
68 +
69 +
//a few general functions used by various other functions
70 +
71 +
uint16_t add_wrap_at_page_boundary(uint16_t value, uint8_t add) {
72 +
    if (regs.e) {
73 +
        return (value & 0xFF00) | ((uint16_t) (((uint8_t) (value & 0x00FF)) + add) & 0x00FF);
74 +
    } else {
75 +
        return value + add;
76 +
    }
77 +
}
78 +
79 +
uint16_t subtract_wrap_at_page_boundary(uint16_t value, uint8_t subtract) {
80 +
    if (regs.e) {
81 +
        return (value & 0xFF00) | ((uint16_t) (((uint8_t) (value & 0x00FF)) - subtract) & 0x00FF);
82 +
    } else {
83 +
        return value - subtract;
84 +
    }
85 +
}
86 +
87 +
void increment_wrap_at_page_boundary(uint16_t *value) {
88 +
    if (regs.e) {
89 +
        *value = (*value & 0xFF00) | ((uint16_t) (((uint8_t) (*value & 0x00FF)) + 1) & 0x00FF);
90 +
    } else {
91 +
        (*value)++;
92 +
    }
93 +
}
94 +
95 +
void decrement_wrap_at_page_boundary(uint16_t *value) {
96 +
    if (regs.e) {
97 +
        *value = (*value & 0xFF00) | ((uint16_t) (((uint8_t) (*value & 0x00FF)) - 1) & 0x00FF);
98 +
    } else {
99 +
        (*value)--;
100 +
    }
101 +
}
102 +
103 +
uint16_t direct_page_add(uint16_t offset) {
104 +
    if (regs.e && (regs.dp & 0x00FF) == 0) {
105 +
        return (regs.dp & 0xFF00) | ((uint16_t) ((uint8_t) (regs.dp & 0x00FF)) + (offset & 0xFF));
106 +
    } else {
107 +
        return regs.dp + offset;
108 +
    }
109 +
}
110 +
111 +
#define incsp() increment_wrap_at_page_boundary(&regs.sp)
112 +
#define decsp() decrement_wrap_at_page_boundary(&regs.sp)
113 +
114 +
void push16(uint16_t pushval) {
115 +
    write6502(regs.sp, 0, (pushval >> 8) & 0xFF);
116 +
    decsp();
117 +
    write6502(regs.sp, 0, pushval & 0xFF);
118 +
    decsp();
119 +
}
120 +
121 +
void push8(uint8_t pushval) {
122 +
    write6502(regs.sp, 0, pushval);
123 +
    decsp();
124 +
}
125 +
126 +
uint16_t pull16() {
127 +
    incsp();
128 +
    uint16_t temp16 = read6502(regs.sp, 0);
129 +
    incsp();
130 +
    temp16 |= (uint16_t) read6502(regs.sp, 0) << 8;
131 +
    return temp16;
132 +
}
133 +
134 +
uint8_t pull8() {
135 +
    incsp();
136 +
    uint8_t value = read6502(regs.sp, 0);
137 +
    return value;
138 +
}
139 +
140 +
void reset6502(bool c816) {
141 +
    regs.pc = (uint16_t)read6502(0xFFFC, 0) | ((uint16_t)read6502(0xFFFD, 0) << 8);
142 +
    regs.c = 0;
143 +
    regs.x = 0;
144 +
    regs.y = 0;
145 +
    regs.dp = 0;
146 +
    regs.sp = 0x1FD;
147 +
    regs.e = 1;
148 +
    regs.k = 0;
149 +
    regs.db = 0;
150 +
    if (c816) {
151 +
        regs.status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
152 +
        regs.is65c816 = true;
153 +
        ticktable = ticktable_c816;
154 +
        optable = optable_c816;
155 +
        addrtable = addrtable_c816;
156 +
    } else {
157 +
        regs.status |= FLAG_CONSTANT;
158 +
        regs.is65c816 = false;
159 +
        ticktable = ticktable_c02;
160 +
        optable = optable_c02;
161 +
        addrtable = addrtable_c02;
162 +
    }
163 +
    setinterrupt();
164 +
    cleardecimal();
165 +
    waiting = 0;
166 +
}
167 +
168 +
enum InterruptType {
169 +
    INT_COP = 0x4,
170 +
    INT_BRK = 0x6,
171 +
    INT_NMI = 0xA,
172 +
    INT_IRQ = 0xE
173 +
};
174 +
175 +
void interrupt6502(enum InterruptType vector) {
176 +
    if (!regs.e) {
177 +
        push8(regs.k);
178 +
    }
179 +
180 +
    regs.k = 0; // also in emulated mode
181 +
182 +
    push16(regs.pc);
183 +
184 +
    if (regs.e) {
185 +
        if (vector == INT_BRK) {
186 +
            push8(regs.status | FLAG_BREAK);
187 +
            vector = INT_IRQ;
188 +
        } else {
189 +
            push8(regs.status & ~FLAG_BREAK);
190 +
        }
191 +
    } else {
192 +
        push8(regs.status);
193 +
    }
194 +
195 +
    setinterrupt();
196 +
    cleardecimal();
197 +
    vp6502();
198 +
199 +
    uint16_t vector_address = (regs.e ? 0xFFF0 : 0xFFE0) + (uint8_t) vector;
200 +
    regs.pc = (uint16_t) read6502(vector_address, 0) | ((uint16_t) read6502(vector_address + 1, 0) << 8);
201 +
202 +
    clockticks6502 += 7; // consumed by CPU to process interrupt
203 +
}
204 +
205 +
static uint32_t addr_with_db(uint16_t addr) {
206 +
    return mask_long_addr(as_bank_byte(regs.db) | addr);
207 +
}
208 +
added src/cpu/tables.h +121 −0
1 +
/* Generated by buildtables.py */
2 +
3 +
static void (*addrtable_c02[256])() = {
4 +
/*        |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |     */
5 +
/* 0 */    imp8, indx, imm8,  imp,   zp,   zp,   zp,   zp,  imp, immm,  acc,  imp, abso, abso, abso,zprel, /* 0 */
6 +
/* 1 */     rel, indy, ind0,  imp,   zp,  zpx,  zpx,   zp,  imp, absy,  acc,  imp, abso, absx, absx,zprel, /* 1 */
7 +
/* 2 */    abso, indx, imm8,  imp,   zp,   zp,   zp,   zp,  imp, immm,  acc,  imp, abso, abso, abso,zprel, /* 2 */
8 +
/* 3 */     rel, indy, ind0,  imp,  zpx,  zpx,  zpx,   zp,  imp, absy,  acc,  imp, absx, absx, absx,zprel, /* 3 */
9 +
/* 4 */     imp, indx, imm8,  imp, imm8,   zp,   zp,   zp,  imp, immm,  acc,  imp, abso, abso, abso,zprel, /* 4 */
10 +
/* 5 */     rel, indy, ind0,  imp, imm8,  zpx,  zpx,   zp,  imp, absy,  imp,  imp,  imp, absx, absx,zprel, /* 5 */
11 +
/* 6 */     imp, indx, imm8,  imp,   zp,   zp,   zp,   zp,  imp, immm,  acc,  imp,  ind, abso, abso,zprel, /* 6 */
12 +
/* 7 */     rel, indy, ind0,  imp,  zpx,  zpx,  zpx,   zp,  imp, absy,  imp,  imp, ainx, absx, absx,zprel, /* 7 */
13 +
/* 8 */     rel, indx, imm8,  imp,   zp,   zp,   zp,   zp,  imp, immm,  imp,  imp, abso, abso, abso,zprel, /* 8 */
14 +
/* 9 */     rel, indy, ind0,  imp,  zpx,  zpx,  zpy,   zp,  imp, absy,  imp,  imp, abso, absx, absx,zprel, /* 9 */
15 +
/* A */    immx, indx, immx,  imp,   zp,   zp,   zp,   zp,  imp, immm,  imp,  imp, abso, abso, abso,zprel, /* A */
16 +
/* B */     rel, indy, ind0,  imp,  zpx,  zpx,  zpy,   zp,  imp, absy,  imp,  imp, absx, absx, absy,zprel, /* B */
17 +
/* C */    immx, indx, imm8,  imp,   zp,   zp,   zp,   zp,  imp, immm,  imp,  imp, abso, abso, abso,zprel, /* C */
18 +
/* D */     rel, indy, ind0,  imp, imm8,  zpx,  zpx,   zp,  imp, absy,  imp,  imp,  imp, absx, absx,zprel, /* D */
19 +
/* E */    immx, indx, imm8,  imp,   zp,   zp,   zp,   zp,  imp, immm,  imp,  imp, abso, abso, abso,zprel, /* E */
20 +
/* F */     rel, indy, ind0,  imp, imm8,  zpx,  zpx,   zp,  imp, absy,  imp,  imp,  imp, absx, absx,zprel  /* F */
21 +
};
22 +
23 +
static void (*addrtable_c816[256])() = {
24 +
/*        |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |     */
25 +
/* 0 */    imp8, indx, imp8,   sr,   zp,   zp,   zp,indl0,  imp, immm,  acc,  imp, abso, abso, abso, absl, /* 0 */
26 +
/* 1 */     rel, indy, ind0,sridy,   zp,  zpx,  zpx,indly,  imp, absy,  acc,  imp, abso, absx, absx,abslx, /* 1 */
27 +
/* 2 */    abso, indx, absl,   sr,   zp,   zp,   zp,indl0,  imp, immm,  acc,  imp, abso, abso, abso, absl, /* 2 */
28 +
/* 3 */     rel, indy, ind0,sridy,  zpx,  zpx,  zpx,indly,  imp, absy,  acc,  imp, absx, absx, absx,abslx, /* 3 */
29 +
/* 4 */     imp, indx, imm8,   sr,  bmv,   zp,   zp,indl0,  imp, immm,  acc,  imp, abso, abso, abso, absl, /* 4 */
30 +
/* 5 */     rel, indy, ind0,sridy,  bmv,  zpx,  zpx,indly,  imp, absy,  imp,  imp, absl, absx, absx,abslx, /* 5 */
31 +
/* 6 */     imp, indx,rel16,   sr,   zp,   zp,   zp,indl0,  imp, immm,  acc,  imp,  ind, abso, abso, absl, /* 6 */
32 +
/* 7 */     rel, indy, ind0,sridy,  zpx,  zpx,  zpx,indly,  imp, absy,  imp,  imp, ainx, absx, absx,abslx, /* 7 */
33 +
/* 8 */     rel, indx,rel16,   sr,   zp,   zp,   zp,indl0,  imp, immm,  imp,  imp, abso, abso, abso, absl, /* 8 */
34 +
/* 9 */     rel, indy, ind0,sridy,  zpx,  zpx,  zpy,indly,  imp, absy,  imp,  imp, abso, absx, absx,abslx, /* 9 */
35 +
/* A */    immx, indx, immx,   sr,   zp,   zp,   zp,indl0,  imp, immm,  imp,  imp, abso, abso, abso, absl, /* A */
36 +
/* B */     rel, indy, ind0,sridy,  zpx,  zpx,  zpy,indly,  imp, absy,  imp,  imp, absx, absx, absy,abslx, /* B */
37 +
/* C */    immx, indx, imm8,   sr,   zp,   zp,   zp,indl0,  imp, immm,  imp,  imp, abso, abso, abso, absl, /* C */
38 +
/* D */     rel, indy, ind0,sridy,ind0p,  zpx,  zpx,indly,  imp, absy,  imp,  imp,aindl, absx, absx,abslx, /* D */
39 +
/* E */    immx, indx, imm8,   sr,   zp,   zp,   zp,indl0,  imp, immm,  imp,  imp, abso, abso, abso, absl, /* E */
40 +
/* F */     rel, indy, ind0,sridy,imm16,  zpx,  zpx,indly,  imp, absy,  imp,  imp, ainx, absx, absx,abslx  /* F */
41 +
};
42 +
43 +
static void (*optable_c02[256])() = {
44 +
/*        |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |     */
45 +
/* 0 */      brk,  ora,  nop,  nop,  tsb,  ora,  asl, rmb0,  php,  ora,  asl,  nop,  tsb,  ora,  asl, bbr0, /* 0 */
46 +
/* 1 */      bpl,  ora,  ora,  nop,  trb,  ora,  asl, rmb1,  clc,  ora,  inc,  nop,  trb,  ora,  asl, bbr1, /* 1 */
47 +
/* 2 */      jsr,  and,  nop,  nop,  bit,  and,  rol, rmb2,  plp,  and,  rol,  nop,  bit,  and,  rol, bbr2, /* 2 */
48 +
/* 3 */      bmi,  and,  and,  nop,  bit,  and,  rol, rmb3,  sec,  and,  dec,  nop,  bit,  and,  rol, bbr3, /* 3 */
49 +
/* 4 */      rti,  eor,  nop,  nop,  nop,  eor,  lsr, rmb4,  pha,  eor,  lsr,  nop,  jmp,  eor,  lsr, bbr4, /* 4 */
50 +
/* 5 */      bvc,  eor,  eor,  nop,  nop,  eor,  lsr, rmb5,  cli,  eor,  phy,  nop,  nop,  eor,  lsr, bbr5, /* 5 */
51 +
/* 6 */      rts,  adc,  nop,  nop,  stz,  adc,  ror, rmb6,  pla,  adc,  ror,  nop,  jmp,  adc,  ror, bbr6, /* 6 */
52 +
/* 7 */      bvs,  adc,  adc,  nop,  stz,  adc,  ror, rmb7,  sei,  adc,  ply,  nop,  jmp,  adc,  ror, bbr7, /* 7 */
53 +
/* 8 */      bra,  sta,  nop,  nop,  sty,  sta,  stx, smb0,  dey,  bit,  txa,  nop,  sty,  sta,  stx, bbs0, /* 8 */
54 +
/* 9 */      bcc,  sta,  sta,  nop,  sty,  sta,  stx, smb1,  tya,  sta,  txs,  nop,  stz,  sta,  stz, bbs1, /* 9 */
55 +
/* A */      ldy,  lda,  ldx,  nop,  ldy,  lda,  ldx, smb2,  tay,  lda,  tax,  nop,  ldy,  lda,  ldx, bbs2, /* A */
56 +
/* B */      bcs,  lda,  lda,  nop,  ldy,  lda,  ldx, smb3,  clv,  lda,  tsx,  nop,  ldy,  lda,  ldx, bbs3, /* B */
57 +
/* C */      cpy,  cmp,  nop,  nop,  cpy,  cmp,  dec, smb4,  iny,  cmp,  dex,  wai,  cpy,  cmp,  dec, bbs4, /* C */
58 +
/* D */      bne,  cmp,  cmp,  nop,  nop,  cmp,  dec, smb5,  cld,  cmp,  phx,  dbg,  nop,  cmp,  dec, bbs5, /* D */
59 +
/* E */      cpx,  sbc,  nop,  nop,  cpx,  sbc,  inc, smb6,  inx,  sbc,  nop,  nop,  cpx,  sbc,  inc, bbs6, /* E */
60 +
/* F */      beq,  sbc,  sbc,  nop,  nop,  sbc,  inc, smb7,  sed,  sbc,  plx,  nop,  nop,  sbc,  inc, bbs7  /* F */
61 +
};
62 +
63 +
static void (*optable_c816[256])() = {
64 +
/*        |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |     */
65 +
/* 0 */      brk,  ora,  cop,  ora,  tsb,  ora,  asl,  ora,  php,  ora,  asl,  phd,  tsb,  ora,  asl,  ora, /* 0 */
66 +
/* 1 */      bpl,  ora,  ora,  ora,  trb,  ora,  asl,  ora,  clc,  ora,  inc,  tcs,  trb,  ora,  asl,  ora, /* 1 */
67 +
/* 2 */      jsr,  and,  jsl,  and,  bit,  and,  rol,  and,  plp,  and,  rol,  pld,  bit,  and,  rol,  and, /* 2 */
68 +
/* 3 */      bmi,  and,  and,  and,  bit,  and,  rol,  and,  sec,  and,  dec,  tsc,  bit,  and,  rol,  and, /* 3 */
69 +
/* 4 */      rti,  eor,  wdm,  eor,  mvp,  eor,  lsr,  eor,  pha,  eor,  lsr,  phk,  jmp,  eor,  lsr,  eor, /* 4 */
70 +
/* 5 */      bvc,  eor,  eor,  eor,  mvn,  eor,  lsr,  eor,  cli,  eor,  phy,  tcd,  jml,  eor,  lsr,  eor, /* 5 */
71 +
/* 6 */      rts,  adc,  per,  adc,  stz,  adc,  ror,  adc,  pla,  adc,  ror,  rtl,  jmp,  adc,  ror,  adc, /* 6 */
72 +
/* 7 */      bvs,  adc,  adc,  adc,  stz,  adc,  ror,  adc,  sei,  adc,  ply,  tdc,  jmp,  adc,  ror,  adc, /* 7 */
73 +
/* 8 */      bra,  sta,  brl,  sta,  sty,  sta,  stx,  sta,  dey,  bit,  txa,  phb,  sty,  sta,  stx,  sta, /* 8 */
74 +
/* 9 */      bcc,  sta,  sta,  sta,  sty,  sta,  stx,  sta,  tya,  sta,  txs,  txy,  stz,  sta,  stz,  sta, /* 9 */
75 +
/* A */      ldy,  lda,  ldx,  lda,  ldy,  lda,  ldx,  lda,  tay,  lda,  tax,  plb,  ldy,  lda,  ldx,  lda, /* A */
76 +
/* B */      bcs,  lda,  lda,  lda,  ldy,  lda,  ldx,  lda,  clv,  lda,  tsx,  tyx,  ldy,  lda,  ldx,  lda, /* B */
77 +
/* C */      cpy,  cmp,  rep,  cmp,  cpy,  cmp,  dec,  cmp,  iny,  cmp,  dex,  wai,  cpy,  cmp,  dec,  cmp, /* C */
78 +
/* D */      bne,  cmp,  cmp,  cmp,  pei,  cmp,  dec,  cmp,  cld,  cmp,  phx,  dbg,  jml,  cmp,  dec,  cmp, /* D */
79 +
/* E */      cpx,  sbc,  sep,  sbc,  cpx,  sbc,  inc,  sbc,  inx,  sbc,  nop,  xba,  cpx,  sbc,  inc,  sbc, /* E */
80 +
/* F */      beq,  sbc,  sbc,  sbc,  pea,  sbc,  inc,  sbc,  sed,  sbc,  plx,  xce,  jsr,  sbc,  inc,  sbc  /* F */
81 +
};
82 +
83 +
static const uint32_t ticktable_c02[256] = {
84 +
/*        |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |     */
85 +
/* 0 */       7,    6,    2,    1,    5,    3,    5,    5,    3,    2,    2,    1,    6,    4,    6,    5, /* 0 */
86 +
/* 1 */       2,    5,    5,    1,    5,    4,    6,    5,    2,    4,    2,    1,    6,    4,    7,    5, /* 1 */
87 +
/* 2 */       6,    6,    2,    1,    3,    3,    5,    5,    4,    2,    2,    1,    4,    4,    6,    5, /* 2 */
88 +
/* 3 */       2,    5,    5,    1,    4,    4,    6,    5,    2,    4,    2,    1,    4,    4,    7,    5, /* 3 */
89 +
/* 4 */       6,    6,    2,    1,    3,    3,    5,    5,    3,    2,    2,    1,    3,    4,    6,    5, /* 4 */
90 +
/* 5 */       2,    5,    5,    1,    4,    4,    6,    5,    2,    4,    3,    1,    8,    4,    7,    5, /* 5 */
91 +
/* 6 */       6,    6,    2,    1,    3,    3,    5,    5,    4,    2,    2,    1,    5,    4,    6,    5, /* 6 */
92 +
/* 7 */       2,    5,    5,    1,    4,    4,    6,    5,    2,    4,    4,    1,    6,    4,    7,    5, /* 7 */
93 +
/* 8 */       3,    6,    2,    1,    3,    3,    3,    5,    2,    2,    2,    1,    4,    4,    4,    5, /* 8 */
94 +
/* 9 */       2,    6,    5,    1,    4,    4,    4,    5,    2,    5,    2,    1,    4,    5,    5,    5, /* 9 */
95 +
/* A */       2,    6,    2,    1,    3,    3,    3,    5,    2,    2,    2,    1,    4,    4,    4,    5, /* A */
96 +
/* B */       2,    5,    5,    1,    4,    4,    4,    5,    2,    4,    2,    1,    4,    4,    4,    5, /* B */
97 +
/* C */       2,    6,    2,    1,    3,    3,    5,    5,    2,    2,    2,    3,    4,    4,    6,    5, /* C */
98 +
/* D */       2,    5,    5,    1,    4,    4,    6,    5,    2,    4,    3,    1,    4,    4,    7,    5, /* D */
99 +
/* E */       2,    6,    2,    1,    3,    3,    5,    5,    2,    2,    2,    1,    4,    4,    6,    5, /* E */
100 +
/* F */       2,    5,    5,    1,    4,    4,    6,    5,    2,    4,    4,    1,    4,    4,    7,    5  /* F */
101 +
};
102 +
103 +
static const uint32_t ticktable_c816[256] = {
104 +
/*        |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  A  |  B  |  C  |  D  |  E  |  F  |     */
105 +
/* 0 */       7,    6,    7,    4,    5,    3,    5,    6,    3,    2,    2,    4,    6,    4,    6,    5, /* 0 */
106 +
/* 1 */       2,    5,    5,    7,    5,    4,    6,    6,    2,    4,    2,    2,    6,    4,    7,    5, /* 1 */
107 +
/* 2 */       6,    6,    8,    4,    3,    3,    5,    6,    4,    2,    2,    5,    4,    4,    6,    5, /* 2 */
108 +
/* 3 */       2,    5,    5,    7,    4,    4,    6,    6,    2,    4,    2,    2,    4,    4,    7,    5, /* 3 */
109 +
/* 4 */       6,    6,    2,    4,    7,    3,    5,    6,    3,    2,    2,    3,    3,    4,    6,    5, /* 4 */
110 +
/* 5 */       2,    5,    5,    7,    7,    4,    6,    6,    2,    4,    3,    2,    4,    4,    7,    5, /* 5 */
111 +
/* 6 */       6,    6,    6,    4,    3,    3,    5,    6,    4,    2,    2,    6,    5,    4,    6,    5, /* 6 */
112 +
/* 7 */       2,    5,    5,    7,    4,    4,    6,    6,    2,    4,    4,    2,    6,    4,    7,    5, /* 7 */
113 +
/* 8 */       3,    6,    4,    4,    3,    3,    3,    6,    2,    2,    2,    3,    4,    4,    4,    5, /* 8 */
114 +
/* 9 */       2,    6,    5,    7,    4,    4,    4,    6,    2,    5,    2,    2,    4,    5,    5,    5, /* 9 */
115 +
/* A */       2,    6,    2,    4,    3,    3,    3,    6,    2,    2,    2,    4,    4,    4,    4,    5, /* A */
116 +
/* B */       2,    5,    5,    7,    4,    4,    4,    6,    2,    4,    2,    2,    4,    4,    4,    5, /* B */
117 +
/* C */       2,    6,    3,    4,    3,    3,    5,    6,    2,    2,    2,    3,    4,    4,    6,    5, /* C */
118 +
/* D */       2,    5,    5,    7,    6,    4,    6,    6,    2,    4,    3,    1,    6,    4,    7,    5, /* D */
119 +
/* E */       2,    6,    3,    4,    3,    3,    5,    6,    2,    2,    2,    3,    4,    4,    6,    5, /* E */
120 +
/* F */       2,    5,    5,    7,    5,    4,    6,    6,    2,    4,    4,    2,    8,    4,    7,    5  /* F */
121 +
};
added src/debugger.c +1054 −0
1 +
2 +
// *******************************************************************************************
3 +
// *******************************************************************************************
4 +
//
5 +
//		File:		debugger.c
6 +
//		Date:		5th September 2019
7 +
//		Purpose:	Debugger code
8 +
//		Author:		Paul Robson ([email protected])
9 +
//
10 +
// *******************************************************************************************
11 +
// *******************************************************************************************
12 +
13 +
#include <stdlib.h>
14 +
#include <stdio.h>
15 +
#include <string.h>
16 +
#include <inttypes.h>
17 +
#include <stdbool.h>
18 +
#include <SDL.h>
19 +
#include "glue.h"
20 +
#include "timing.h"
21 +
#include "disasm.h"
22 +
#include "memory.h"
23 +
#include "video.h"
24 +
#include "cpu/fake6502.h"
25 +
#include "debugger.h"
26 +
#include "rendertext.h"
27 +
28 +
static void DEBUGHandleKeyEvent(SDL_Keycode key,int isShift);
29 +
30 +
static void DEBUGNumber(int x,int y,int n,int w, SDL_Color colour);
31 +
static void DEBUGNumberDec(int x, int y, int n, int w, SDL_Color colour);
32 +
static void DEBUGAddress(int x, int y, int x16Bank, int addr, uint8_t bank, SDL_Color colour);
33 +
static void DEBUGVAddress(int x, int y, int addr, SDL_Color colour);
34 +
35 +
static void DEBUGRenderData(int y,uint32_t data);
36 +
static int DEBUGRenderZeroPageRegisters(int y);
37 +
static void DEBUGRenderVERAState(int y);
38 +
static int DEBUGRenderRegisters(void);
39 +
static void DEBUGRenderVRAM(int y, int data);
40 +
static void DEBUGRenderCode(int lines,int initialPC);
41 +
static void DEBUGRenderStack(int bytesCount);
42 +
static void DEBUGRenderCmdLine(int x, int width, int height);
43 +
static bool DEBUGEditCmdLine(SDL_Keycode key);
44 +
static void DEBUGBuildCmdLine(char *text);
45 +
static void DEBUGExecCmd();
46 +
47 +
// *******************************************************************************************
48 +
//
49 +
//		This is the minimum-interference flag. It's designed so that when
50 +
//		its non-zero DEBUGRenderDisplay() is called.
51 +
//
52 +
//			if (showDebugOnRender != 0) {
53 +
//				DEBUGRenderDisplay(SCREEN_WIDTH,SCREEN_HEIGHT,renderer);
54 +
//				SDL_RenderPresent(renderer);
55 +
//				return true;
56 +
//			}
57 +
//
58 +
//		before the SDL_RenderPresent call in video_update() in video.c
59 +
//
60 +
//		This controls what is happening. It is at the top of the main loop in main.c
61 +
//
62 +
//			if (isDebuggerEnabled != 0) {
63 +
//				int dbgCmd = DEBUGGetCurrentStatus();
64 +
//				if (dbgCmd > 0) continue;
65 +
//				if (dbgCmd < 0) break;
66 +
//			}
67 +
//
68 +
//		Both video.c and main.c require debugger.h to be included.
69 +
//
70 +
//		isDebuggerEnabled should be a flag set as a command line switch - without it
71 +
//		it will run unchanged. It should not be necessary to test the render code
72 +
//		because showDebugOnRender is statically initialised to zero and will only
73 +
//		change if DEBUGGetCurrentStatus() is called.
74 +
//
75 +
// *******************************************************************************************
76 +
77 +
//
78 +
//				0-9A-F sets the program address, with shift sets the data address.
79 +
//
80 +
#define DBGKEY_HOME     SDLK_F1                         // F1 is "Goto PC"
81 +
#define DBGKEY_RESET    SDLK_F2                         // F2 resets the 6502
82 +
#define DBGKEY_RUN      SDLK_F5                         // F5 is run.
83 +
#define DBGKEY_SETBRK   SDLK_F9                         // F9 sets breakpoint
84 +
#define DBGKEY_STEP     SDLK_F11                        // F11 is step into.
85 +
#define DBGKEY_STEPOVER SDLK_F10                        // F10 is step over.
86 +
#define DBGKEY_BANK_NEXT	SDLK_KP_PLUS
87 +
#define DBGKEY_BANK_PREV	SDLK_KP_MINUS
88 +
89 +
#define DBGSCANKEY_BRK  SDL_SCANCODE_F12                // F12 is break into running code.
90 +
#define DBGSCANKEY_SHOW SDL_SCANCODE_TAB                // Show screen key.
91 +
                                                        // *** MUST BE SCAN CODES ***
92 +
93 +
#define DBGMAX_ZERO_PAGE_REGISTERS 16
94 +
95 +
#define DDUMP_RAM	0
96 +
#define DDUMP_VERA	1
97 +
98 +
enum DBG_CMD { CMD_DUMP_MEM='m', CMD_DUMP_VERA='v', CMD_DISASM='d', CMD_SET_BANK='b', CMD_SET_REGISTER='r', CMD_FILL_MEMORY='f' };
99 +
100 +
// RGB colours
101 +
const SDL_Color col_bkgnd= {0, 0, 0, 255};
102 +
const SDL_Color col_label= {0, 255, 0, 255};
103 +
const SDL_Color col_data= {0, 255, 255, 255};
104 +
const SDL_Color col_highlight= {255, 255, 0, 255};
105 +
const SDL_Color col_cmdLine= {255, 255, 255, 255};
106 +
107 +
const SDL_Color col_directpage= {192, 224, 255, 255};
108 +
109 +
const SDL_Color col_vram_tilemap = {0, 255, 255, 255};
110 +
const SDL_Color col_vram_tiledata = {0, 255, 0, 255};
111 +
const SDL_Color col_vram_special  = {255, 92, 92, 255};
112 +
const SDL_Color col_vram_other  = {128, 128, 128, 255};
113 +
114 +
int showDebugOnRender = 0;                            // Used to trigger rendering in video.c
115 +
int showFullDisplay = 0;                              // If non-zero show the whole thing.
116 +
int currentPC = -1;                                   // Current disassembly PC value (16-bit).
117 +
uint8_t currentPCBank = 0;                            // Current disassembly PC bank (like regs.k) value.
118 +
int currentData = 0;                                  // Current data display address (RAM or VRAM).
119 +
int currentPCX16Bank = -1;                            // Current disassembly X16 RAM/ROM bank, -1 unless $A000-$FFFF, .K = 0
120 +
int currentX16Bank = -1;                              // Current data display (memory dump) X16 RAM/ROM bank
121 +
int currentMode = DMODE_RUN;                          // Start running.
122 +
uint32_t debugCPUClocks = 0;
123 +
124 +
int dumpmode          = DDUMP_RAM;
125 +
126 +
struct breakpoint breakPoint = { -1, 0, -1 };         // User Break
127 +
struct breakpoint stepBreakPoint = { -1, 0, -1 };     // Single step break.
128 +
char cmdLine[64]= "";                                 // command line buffer
129 +
int currentPosInLine= 0;                              // cursor position in the buffer (NOT USED _YET_)
130 +
int currentLineLen= 0;                                // command line buffer length
131 +
132 +
int    oldRegisters[DBGMAX_ZERO_PAGE_REGISTERS];      // Old ZP Register values, for change detection
133 +
char * oldRegChange[DBGMAX_ZERO_PAGE_REGISTERS];      // Change notification flags for output
134 +
int    oldRegisterTicks = 0;                          // Last PC when change notification was run
135 +
136 +
//
137 +
//		This flag controls
138 +
//
139 +
140 +
SDL_Renderer *dbgRenderer;                            // Renderer passed in.
141 +
142 +
static inline int getCurrentBank(int pc, uint8_t bank) {
143 +
	int x16Bank = -1;
144 +
	if (pc >= 0xA000 && bank == 0) {
145 +
		x16Bank = pc < 0xC000 ? memory_get_ram_bank() : memory_get_rom_bank();
146 +
	}
147 +
	return x16Bank;
148 +
}
149 +
150 +
// *******************************************************************************************
151 +
//
152 +
//      	This determines if we have hit a breakpoint, both in pc and bank
153 +
//
154 +
// *******************************************************************************************
155 +
156 +
static inline bool hitBreakpoint(int pc, uint8_t bank, struct breakpoint bp) {
157 +
	if ((pc == bp.pc) && bank == bp.bank && getCurrentBank(pc, bank) == bp.x16Bank) {
158 +
		return true;
159 +
	}
160 +
	return false;
161 +
}
162 +
163 +
// *******************************************************************************************
164 +
//
165 +
//			This is used to determine who is in control. If it returns zero then
166 +
//			everything runs normally till the next call.
167 +
//			If it returns +ve, then it will update the video, and loop round.
168 +
//			If it returns -ve, then exit.
169 +
//
170 +
// *******************************************************************************************
171 +
172 +
int  DEBUGGetCurrentStatus(void) {
173 +
174 +
	SDL_Event event;
175 +
	if (currentPC < 0) currentPC = regs.pc;                                      // Initialise current PC displayed.
176 +
177 +
	if (currentMode == DMODE_STEP) {                                // Single step before
178 +
		if (currentPC != regs.pc || currentPCBank != regs.k || currentPCX16Bank != getCurrentBank(regs.pc, regs.k)) { // Ensure that the PC moved
179 +
			currentPC = regs.pc;                                    // Update current PC
180 +
			currentPCBank = regs.k;
181 +
			currentPCX16Bank = getCurrentBank(regs.pc, regs.k);     // Update the bank if we are in upper memory.
182 +
			currentMode = DMODE_STOP;                               // So now stop, as we've done it.
183 +
		}
184 +
	}
185 +
186 +
	if ((currentMode != DMODE_STOP) && (hitBreakpoint(regs.pc, regs.k, breakPoint) || hitBreakpoint(regs.pc, regs.k, stepBreakPoint))) {       // Hit a breakpoint.
187 +
		currentPC = regs.pc;                                    // Update current PC
188 +
		currentPCBank = regs.k;
189 +
		currentPCX16Bank = getCurrentBank(regs.pc, regs.k);     // Update the bank if we are in upper memory.
190 +
		currentMode = DMODE_STOP;                               // So now stop, as we've done it.
191 +
		stepBreakPoint.pc = -1;                                 // Clear step breakpoint.
192 +
		stepBreakPoint.bank = 0;
193 +
		stepBreakPoint.x16Bank = -1;
194 +
	}
195 +
196 +
	if (SDL_GetKeyboardState(NULL)[DBGSCANKEY_BRK]) {            // Stop on break pressed.
197 +
		currentMode = DMODE_STOP;
198 +
		currentPC = regs.pc;                                     // Set the PC to what it is.
199 +
		currentPCBank = regs.k;
200 +
		currentPCX16Bank = getCurrentBank(regs.pc, regs.k);      // Update the bank if we are in upper memory.
201 +
	}
202 +
203 +
	if (currentPCX16Bank<0 && currentPC >= 0xA000 && currentPCBank == 0) {
204 +
		currentPCX16Bank = currentPC < 0xC000 ? memory_get_ram_bank() : memory_get_rom_bank();
205 +
	}
206 +
207 +
	if (currentMode != DMODE_RUN) {                                     // Not running, we own the keyboard.
208 +
		showFullDisplay =                                               // Check showing screen.
209 +
					SDL_GetKeyboardState(NULL)[DBGSCANKEY_SHOW];
210 +
		while (SDL_PollEvent(&event)) {                                 // We now poll events here.
211 +
			switch(event.type) {
212 +
				case SDL_QUIT:                                  // Time for exit
213 +
					return -1;
214 +
215 +
				case SDL_KEYDOWN:                               // Handle key presses.
216 +
					DEBUGHandleKeyEvent(event.key.keysym.sym, event.key.keysym.mod & (KMOD_LSHIFT|KMOD_RSHIFT));
217 +
					break;
218 +
219 +
				case SDL_TEXTINPUT:
220 +
					DEBUGBuildCmdLine(event.text.text);
221 +
					break;
222 +
			}
223 +
		}
224 +
	}
225 +
226 +
	showDebugOnRender = (currentMode != DMODE_RUN);                         // Do we draw it - only in RUN mode.
227 +
	if (currentMode == DMODE_STOP) {                                        // We're in charge.
228 +
		video_update();
229 +
		SDL_Delay(10);
230 +
		return 1;
231 +
	}
232 +
233 +
	return 0;                                                               // Run wild, run free.
234 +
}
235 +
236 +
// *******************************************************************************************
237 +
//
238 +
//								Setup fonts and co
239 +
//
240 +
// *******************************************************************************************
241 +
void DEBUGInitUI(SDL_Renderer *pRenderer) {
242 +
		DEBUGInitChars(pRenderer);
243 +
		dbgRenderer = pRenderer;				// Save renderer.
244 +
}
245 +
246 +
// *******************************************************************************************
247 +
//
248 +
//								Setup fonts and co
249 +
//
250 +
// *******************************************************************************************
251 +
void DEBUGFreeUI() {
252 +
}
253 +
254 +
// *******************************************************************************************
255 +
//
256 +
//								Set a new breakpoint address. -1 to disable.
257 +
//
258 +
// *******************************************************************************************
259 +
260 +
void DEBUGSetBreakPoint(struct breakpoint newBreakPoint) {
261 +
	breakPoint = newBreakPoint;
262 +
}
263 +
264 +
// *******************************************************************************************
265 +
//
266 +
//								Break into debugger from code.
267 +
//
268 +
// *******************************************************************************************
269 +
270 +
void DEBUGBreakToDebugger(void) {
271 +
	currentMode = DMODE_STOP;
272 +
	currentPC = regs.pc;
273 +
	currentPCBank = regs.k;
274 +
	currentPCX16Bank = getCurrentBank(regs.pc, regs.k);
275 +
}
276 +
277 +
// *******************************************************************************************
278 +
//
279 +
//									Handle keyboard state.
280 +
//
281 +
// *******************************************************************************************
282 +
283 +
static void DEBUGHandleKeyEvent(SDL_Keycode key, int isShift) {
284 +
	int opcode;
285 +
286 +
	switch(key) {
287 +
288 +
		case DBGKEY_STEP:      // Single step (F11 by default)
289 +
			currentMode = DMODE_STEP;  // Runs once, then switches back.
290 +
			currentPC = regs.pc;
291 +
			currentPCBank = regs.k;
292 +
			currentPCX16Bank = getCurrentBank(regs.pc, regs.k);
293 +
			debugCPUClocks = clockticks6502;
294 +
			break;
295 +
296 +
		case DBGKEY_STEPOVER:  // Step over (F10 by default)
297 +
			opcode = debug_read6502(regs.pc, regs.k, currentPCX16Bank); // What opcode is it ?
298 +
			if (opcode == 0x20 || opcode == 0xFC || opcode == 0x22) {   // Is it JSR or JSL ?
299 +
				stepBreakPoint.pc = regs.pc + 3 + (opcode == 0x22);  // Then break 3 / 4 on.
300 +
				stepBreakPoint.bank = regs.k;
301 +
				stepBreakPoint.x16Bank = getCurrentBank(regs.pc, regs.k);
302 +
				currentMode = DMODE_RUN;                // And run.
303 +
				debugCPUClocks = clockticks6502;
304 +
				timing_init();
305 +
			} else {
306 +
				currentMode = DMODE_STEP;               // Otherwise single step.
307 +
				currentPC = regs.pc;
308 +
				currentPCBank = regs.k;
309 +
				currentPCX16Bank = getCurrentBank(regs.pc, regs.k);
310 +
				debugCPUClocks = clockticks6502;
311 +
			}
312 +
			break;
313 +
314 +
		case DBGKEY_RUN:                                // F5 Runs until Break.
315 +
			currentMode = DMODE_RUN;
316 +
			debugCPUClocks = clockticks6502;
317 +
			timing_init();
318 +
			break;
319 +
320 +
		case DBGKEY_SETBRK:                             // F9 Set breakpoint to displayed.
321 +
			if (breakPoint.pc == currentPC && breakPoint.bank == currentPCBank && breakPoint.x16Bank == currentPCX16Bank) {
322 +
				// Clear (unset) breakpoint by pressing DBGKEY_SETBRK twice
323 +
				breakPoint.pc = -1;
324 +
				breakPoint.bank = 0;
325 +
				breakPoint.x16Bank = -1;
326 +
			} else {
327 +
				breakPoint.pc = currentPC;
328 +
				breakPoint.bank = currentPCBank;
329 +
				breakPoint.x16Bank = currentPCX16Bank;
330 +
			}
331 +
			break;
332 +
333 +
		case DBGKEY_HOME:                               // F1 sets the display PC to the actual one.
334 +
			currentPC = regs.pc;
335 +
			currentPCBank = regs.k;
336 +
			currentPCX16Bank = getCurrentBank(regs.pc, regs.k);
337 +
			break;
338 +
339 +
		case DBGKEY_RESET:                              // F2 reset the 6502
340 +
			reset6502(regs.is65c816);
341 +
			currentPC = regs.pc;
342 +
			currentPCBank = regs.k;
343 +
			currentPCX16Bank = -1;
344 +
			break;
345 +
346 +
		case DBGKEY_BANK_NEXT:
347 +
			currentX16Bank += 1;
348 +
			break;
349 +
350 +
		case DBGKEY_BANK_PREV:
351 +
			currentX16Bank -= 1;
352 +
			break;
353 +
354 +
		case SDLK_PAGEDOWN:
355 +
			if (isShift) {
356 +
				currentPC = (currentPC + 0x10) & 0xFFFF;
357 +
				if (currentPCBank == 0 && currentPC < 0xA000) {
358 +
					currentPCX16Bank = -1;
359 +
				}
360 +
			} else {
361 +
				if (dumpmode == DDUMP_RAM) {
362 +
					currentData = (currentData + 0x100) & (is_gen2 ? 0xFFFFFF : 0xFFFF);
363 +
				} else {
364 +
					currentData = (currentData + 0x200) & 0x1FFFF;
365 +
				}
366 +
			}
367 +
			break;
368 +
369 +
		case SDLK_PAGEUP:
370 +
			if (isShift) {
371 +
				currentPC = (currentPC - 0x10) & 0xFFFF;
372 +
				if (currentPCBank == 0 && currentPC < 0xA000) {
373 +
					currentPCX16Bank = -1;
374 +
				}
375 +
			} else {
376 +
				if (dumpmode == DDUMP_RAM) {
377 +
					currentData = (currentData - 0x100) & (is_gen2 ? 0xFFFFFF : 0xFFFF);
378 +
				} else {
379 +
					currentData = (currentData - 0x200) & 0x1FFFF;
380 +
				}
381 +
			}
382 +
			break;
383 +
384 +
		case SDLK_DOWN:
385 +
			if (isShift) {
386 +
				currentPC = (currentPC + 1) & 0xFFFF;
387 +
				if (currentPCBank == 0 && currentPC < 0xA000) {
388 +
					currentPCX16Bank = -1;
389 +
				}
390 +
			} else {
391 +
				if (dumpmode == DDUMP_RAM) {
392 +
					currentData = (currentData + 0x08) & (is_gen2 ? 0xFFFFFF : 0xFFFF);
393 +
				} else {
394 +
					currentData = (currentData + 0x10) & 0x1FFFF;
395 +
				}
396 +
			}
397 +
			break;
398 +
399 +
		case SDLK_UP:
400 +
			if (isShift) {
401 +
				currentPC = (currentPC - 1) & 0xFFFF;
402 +
				if (currentPCBank == 0 && currentPC < 0xA000) {
403 +
					currentPCX16Bank = -1;
404 +
				}
405 +
			} else {
406 +
				if (dumpmode == DDUMP_RAM) {
407 +
					currentData = (currentData - 0x08) & (is_gen2 ? 0xFFFFFF : 0xFFFF);
408 +
				} else {
409 +
					currentData = (currentData - 0x10) & 0x1FFFF;
410 +
				}
411 +
			}
412 +
			break;
413 +
414 +
		default:
415 +
			if(DEBUGEditCmdLine(key)) {
416 +
				// printf("cmd line: %s\n", cmdLine);
417 +
				DEBUGExecCmd();
418 +
			}
419 +
			break;
420 +
	}
421 +
422 +
}
423 +
424 +
static void DEBUGBuildCmdLine(char *text) {
425 +
	uint8_t *ptr = (uint8_t *)text;
426 +
	while(currentLineLen < sizeof(cmdLine)-1 && *ptr != 0) {
427 +
		if (*ptr < 0x80) { // We only care about characters in ASCII range.
428 +
		                   // UTF-8 encodings will always have the high bit
429 +
		                   // set for any byte which is part of a multibyte
430 +
		                   // character, so we need not parse it whatsoever.
431 +
			uint8_t key = *ptr;
432 +
433 +
			if (key >= 0x20 && key <= 0x7D) {
434 +
				if (key >= 'A' && key <= 'Z') {
435 +
					key += 0x20; // case-fold to lower
436 +
				}
437 +
				cmdLine[currentPosInLine++]= key;
438 +
				if(currentPosInLine > currentLineLen) {
439 +
					currentLineLen++;
440 +
					cmdLine[currentLineLen]= 0;
441 +
				}
442 +
			}
443 +
		}
444 +
		ptr++;
445 +
	}
446 +
}
447 +
448 +
static bool DEBUGEditCmdLine(SDL_Keycode key) {
449 +
	// right now, let's have a rudimentary input: only backspace to delete last char
450 +
	// later, I want a real input line with delete, backspace, left and right cursor
451 +
	// devs like their comfort ;)
452 +
	if(key == SDLK_BACKSPACE) {
453 +
		currentPosInLine--;
454 +
		if(currentPosInLine<0) {
455 +
			currentPosInLine= 0;
456 +
		}
457 +
		currentLineLen--;
458 +
		if(currentLineLen<0) {
459 +
			currentLineLen= 0;
460 +
		}
461 +
		cmdLine[currentLineLen]= 0;
462 +
	}
463 +
	return (key == SDLK_RETURN) || (key == SDLK_KP_ENTER);
464 +
}
465 +
466 +
static void DEBUGExecCmd() {
467 +
	int number, bnumber, addr, size, incr;
468 +
	char reg[10];
469 +
	char cmd;
470 +
	char *line= ltrim(cmdLine);
471 +
472 +
	cmd= *line;
473 +
	if(*line) {
474 +
		line++;
475 +
	}
476 +
	// printf("cmd:%c line: '%s'\n", cmd, line);
477 +
478 +
	switch (cmd) {
479 +
		case CMD_DUMP_MEM:
480 +
			if (sscanf(line, "%x:%x", &bnumber, &number) == 2) {
481 +
				currentX16Bank = bnumber & 0xFF;
482 +
			} else if (sscanf(line, "%x", &number) == 1) {
483 +
				if (!is_gen2) {
484 +
					currentX16Bank = (number >> 16) & 0xFF;
485 +
				}
486 +
			} else {
487 +
				break;
488 +
			}
489 +
			addr = number & (is_gen2 ? 0xFFFFFF : 0xFFFF);
490 +
			currentData = addr;
491 +
			dumpmode    = DDUMP_RAM;
492 +
			break;
493 +
494 +
		case CMD_DUMP_VERA:
495 +
			if (sscanf(line, "%x", &number) == 1) {
496 +
				addr = number & 0x1FFFF;
497 +
				currentData = addr;
498 +
				dumpmode    = DDUMP_VERA;
499 +
			}
500 +
			break;
501 +
502 +
		case CMD_FILL_MEMORY:
503 +
			size = 1;
504 +
			incr = 1;
505 +
			if (sscanf(line, "%x %x %d %d", &addr, &number, &size, &incr) >= 2) {
506 +
				if (dumpmode == DDUMP_RAM) {
507 +
					addr &= 0xFFFFFF;
508 +
					do {
509 +
						if (addr >= 0xC000 && addr < 0x10000) {
510 +
							// Nop.
511 +
						} else if (addr >= 0xA000 && addr < 0xC000) {
512 +
							BRAM[(currentX16Bank << 13) + addr - 0xA000] = number;
513 +
						} else if ((addr >> 16) < num_banks) {
514 +
							RAM[addr] = number;
515 +
						}
516 +
						if (incr) {
517 +
							addr += incr;
518 +
						} else {
519 +
							++addr;
520 +
						}
521 +
						addr &= 0xFFFFFF;
522 +
						--size;
523 +
					} while (size > 0);
524 +
				} else {
525 +
					addr &= 0x1FFFF;
526 +
					do {
527 +
						video_space_write(addr, number);
528 +
						if (incr) {
529 +
							addr += incr;
530 +
						} else {
531 +
							++addr;
532 +
						}
533 +
						addr &= 0x1FFFF;
534 +
						--size;
535 +
					} while (size > 0);
536 +
				}
537 +
			}
538 +
			break;
539 +
540 +
		case CMD_DISASM:
541 +
			if (sscanf(line, "%x:%x", &bnumber, &number) == 2) {
542 +
				currentPCX16Bank = (number >= 0xA000 && number <= 0xFFFF) ? bnumber & 0xFF : -1;
543 +
			} else if (sscanf(line, "%x", &number) == 1) {
544 +
				if (!is_gen2) {
545 +
					currentPCX16Bank = (number & 0xFFFF) >= 0xA000 ? (number >> 16) & 0xFF : -1;
546 +
				} else if (number < 0x00A000 || number >= 0x010000) { // treat input as 24-bit (K+PC)
547 +
					currentPCX16Bank = -1; // if outside of the X16 banked range, set to undef (-1)
548 +
				}
549 +
			} else {
550 +
				break;
551 +
			}
552 +
553 +
			addr = number & (is_gen2 ? 0xFFFFFF : 0xFFFF);
554 +
555 +
			currentPC = addr & 0xFFFF;
556 +
			if (is_gen2) {
557 +
				currentPCBank = (addr >> 16) & 0xFF;
558 +
			}
559 +
			break;
560 +
561 +
		case CMD_SET_BANK:
562 +
			if (sscanf(line, "%s %d", reg, &number) != 2) {
563 +
				break;
564 +
			}
565 +
566 +
			if (!strcmp(reg, "rom")) {
567 +
				memory_set_rom_bank(number & 0x00FF);
568 +
			} else if (!strcmp(reg, "ram")) {
569 +
				memory_set_ram_bank(number & 0x00FF);
570 +
			}
571 +
			break;
572 +
573 +
		case CMD_SET_REGISTER:
574 +
			if (sscanf(line, "%s %x", reg, &number) != 2) {
575 +
				break;
576 +
			}
577 +
578 +
			if(!strcmp(reg, "pc")) {
579 +
				regs.pc= number & 0xFFFF;
580 +
				waiting = 0;
581 +
			}
582 +
			if(!strcmp(reg, "a")) {
583 +
				regs.a= number & 0x00FF;
584 +
			}
585 +
			if(!strcmp(reg, "b")) {
586 +
				regs.b= number & 0x00FF;
587 +
			}
588 +
			if(!strcmp(reg, "c")) {
589 +
				regs.c= number & 0xFFFF;
590 +
			}
591 +
			if(!strcmp(reg, "d")) {
592 +
				regs.dp= number & 0xFFFF;
593 +
			}
594 +
			if(!strcmp(reg, "k")) {
595 +
				regs.k= number & 0x00FF;
596 +
			}
597 +
			if(!strcmp(reg, "dbr")) {
598 +
				regs.db= number & 0x00FF;
599 +
			}
600 +
			if(!strcmp(reg, "x")) {
601 +
				if (regs.e) {
602 +
					regs.xl= number & 0x00FF;
603 +
				} else {
604 +
					regs.x= number & 0xFFFF;
605 +
				}
606 +
			}
607 +
			if(!strcmp(reg, "y")) {
608 +
				if (regs.e) {
609 +
					regs.yl= number & 0x00FF;
610 +
				} else {
611 +
					regs.y= number & 0xFFFF;
612 +
				}
613 +
			}
614 +
			if(!strcmp(reg, "sp")) {
615 +
				if (regs.e) {
616 +
					regs.sp= 0x100 | (number & 0x00FF);
617 +
				} else {
618 +
					regs.sp= number & 0xFFFF;
619 +
				}
620 +
			}
621 +
			break;
622 +
623 +
		default:
624 +
			break;
625 +
	}
626 +
627 +
	currentPosInLine= currentLineLen= *cmdLine= 0;
628 +
}
629 +
630 +
// *******************************************************************************************
631 +
//
632 +
//							Render the emulator debugger display.
633 +
//
634 +
// *******************************************************************************************
635 +
636 +
void DEBUGRenderDisplay(int width, int height) {
637 +
	if (showFullDisplay) return;								// Not rendering debug.
638 +
639 +
	SDL_Rect rc;
640 +
	rc.w = DBG_WIDTH * 6 * CHAR_SCALE;							// Erase background, set up rect
641 +
	rc.h = height;
642 +
	xPos = width-rc.w;yPos = 0; 								// Position screen
643 +
	rc.x = xPos;rc.y = yPos; 									// Set rectangle and black out.
644 +
	SDL_SetRenderDrawColor(dbgRenderer,0,0,0,SDL_ALPHA_OPAQUE);
645 +
	SDL_RenderFillRect(dbgRenderer,&rc);
646 +
647 +
	int register_lines = DEBUGRenderRegisters();							// Draw register name and values.
648 +
	DEBUGRenderCode(register_lines, currentPC);							// Render 6502 disassembly.
649 +
	if (dumpmode == DDUMP_RAM) {
650 +
		DEBUGRenderData(register_lines + 1, currentData);
651 +
		int zp_lines = DEBUGRenderZeroPageRegisters(register_lines + 1);
652 +
		DEBUGRenderVERAState(zp_lines + 1);
653 +
	} else {
654 +
		DEBUGRenderVRAM(register_lines + 1, currentData);
655 +
	}
656 +
	DEBUGRenderStack(register_lines);
657 +
658 +
	DEBUGRenderCmdLine(xPos, rc.w, height);
659 +
}
660 +
661 +
// *******************************************************************************************
662 +
//
663 +
//									 Render command Line
664 +
//
665 +
// *******************************************************************************************
666 +
667 +
static void DEBUGRenderCmdLine(int x, int width, int height) {
668 +
	char buffer[sizeof(cmdLine)+1];
669 +
670 +
	SDL_SetRenderDrawColor(dbgRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
671 +
	SDL_RenderDrawLine(dbgRenderer, x, height-12, x+width, height-12);
672 +
673 +
	sprintf(buffer, ">%s", cmdLine);
674 +
	DEBUGString(dbgRenderer, 0, DBG_HEIGHT-1, buffer, col_cmdLine);
675 +
	DEBUGString(dbgRenderer, currentPosInLine+1, DBG_HEIGHT-1, "_", col_cmdLine); // crude cursor
676 +
}
677 +
678 +
// *******************************************************************************************
679 +
//
680 +
//									 Render Zero Page Registers
681 +
//
682 +
// *******************************************************************************************
683 +
684 +
static int DEBUGRenderZeroPageRegisters(int y) {
685 +
#define LAST_R 15
686 +
	unsigned char reg = 0;
687 +
	int y_start = y;
688 +
	char lbl[6];
689 +
	while (reg < DBGMAX_ZERO_PAGE_REGISTERS) {
690 +
		if (((y-y_start) % 5) != 0) {           // Break registers into groups of 5, easier to locate
691 +
			if (reg <= LAST_R)
692 +
				sprintf(lbl, "R%d", reg);
693 +
			else
694 +
				sprintf(lbl, "x%d", reg);
695 +
696 +
			DEBUGString(dbgRenderer, DBG_ZP_REG, y, lbl, col_label);
697 +
698 +
			int reg_addr = 2 + reg * 2;
699 +
			int n = debug_read6502(direct_page_add(reg_addr+1), 0, USE_CURRENT_X16_BANK)*256+debug_read6502(direct_page_add(reg_addr), 0, USE_CURRENT_X16_BANK);
700 +
701 +
			DEBUGNumber(DBG_ZP_REG+5, y, n, 4, col_data);
702 +
703 +
			if (oldRegChange[reg] != NULL)
704 +
				DEBUGString(dbgRenderer, DBG_ZP_REG+9, y, oldRegChange[reg], col_data);
705 +
706 +
			if (oldRegisterTicks != clockticks6502) {   // change detection only when the emulated CPU changes
707 +
				oldRegChange[reg] = n != oldRegisters[reg] ? "*" : " ";
708 +
				oldRegisters[reg]=n;
709 +
			}
710 +
			reg++;
711 +
		}
712 +
		y++;
713 +
	}
714 +
715 +
	if (oldRegisterTicks != clockticks6502) {
716 +
		oldRegisterTicks = clockticks6502;
717 +
	}
718 +
719 +
	return y;
720 +
}
721 +
722 +
// *******************************************************************************************
723 +
//
724 +
//									 Render Data Area
725 +
//
726 +
// *******************************************************************************************
727 +
728 +
static void DEBUGRenderData(int y,uint32_t data) {
729 +
	while (y < DBG_HEIGHT-2) {									// To bottom of screen
730 +
		DEBUGAddress(DBG_MEMX, y, (uint8_t)currentX16Bank, data & 0xFFFF, data >> 16, col_label);	// Show label.
731 +
732 +
		for (int i = 0;i < 8;i++) {
733 +
			bool isDP = (data >> 16) == 0 && ((data+i - regs.dp) & 0xffff) < 256;
734 +
			int byte = debug_read6502((data+i) & 0xFFFF, data >> 16, currentX16Bank);
735 +
			DEBUGNumber(DBG_MEMX+8+i*3,y,byte,2, isDP ? col_directpage : col_data);
736 +
			DEBUGWrite(dbgRenderer, DBG_MEMX+33+i,y,byte, isDP ? col_directpage : col_data);
737 +
		}
738 +
		y++;
739 +
		data = (data + 8) & 0xFFFFFF;
740 +
	}
741 +
}
742 +
743 +
static void DEBUGRenderVRAM(int y, int data) {
744 +
	while (y < DBG_HEIGHT - 2) {                                                   // To bottom of screen
745 +
		DEBUGVAddress(DBG_MEMX, y, data & 0x1FFFF, col_label); // Show label.
746 +
747 +
		for (int i = 0; i < 16; i++) {
748 +
			int addr = (data + i) & 0x1FFFF;
749 +
			int byte = video_space_read(addr);
750 +
751 +
			if (video_is_tilemap_address(addr)) {
752 +
				DEBUGNumber(DBG_MEMX + 6 + i * 3, y, byte, 2, col_vram_tilemap);
753 +
			} else if (video_is_tiledata_address(addr)) {
754 +
				DEBUGNumber(DBG_MEMX + 6 + i * 3, y, byte, 2, col_vram_tiledata);
755 +
			} else if (video_is_special_address(addr)) {
756 +
				DEBUGNumber(DBG_MEMX + 6 + i * 3, y, byte, 2, col_vram_special);
757 +
			} else {
758 +
				DEBUGNumber(DBG_MEMX + 6 + i * 3, y, byte, 2, col_vram_other);
759 +
			}
760 +
		}
761 +
		y++;
762 +
		data += 16;
763 +
	}
764 +
}
765 +
766 +
// *******************************************************************************************
767 +
//
768 +
//									 Render Disassembly
769 +
//
770 +
// *******************************************************************************************
771 +
772 +
static void DEBUGRenderCode(int lines, int initialPC) {
773 +
	char buffer[32];
774 +
	uint8_t implied_status = regs.status;
775 +
	uint8_t implied_e = regs.e;
776 +
	uint8_t opcode, operand, carry;
777 +
	int implied_x16_bank = currentPCX16Bank;
778 +
779 +
	for (int y = 0; y < lines; y++) { 							// Each line
780 +
		DEBUGAddress(DBG_ASMX, y, implied_x16_bank, initialPC, currentPCBank, col_label);
781 +
		int32_t eff_addr;
782 +
783 +
		// Attempt to display the disassembly correctly more often
784 +
		// if the code logic is reasonably straightforward with respect
785 +
		// to status flags changing in the immediate instruction list
786 +
787 +
		// This doesn't predict status flags changes except by the few
788 +
		// opcodes below. The PLP instruction, for instance, could easily
789 +
		// render disassembly following it invalid, but this would have
790 +
		// still been true without the added logic, anyway.
791 +
792 +
		if (regs.is65c816) {
793 +
			opcode = debug_read6502(initialPC, currentPCBank, currentPCX16Bank);
794 +
			switch (opcode) {
795 +
				case 0x81: // CLC
796 +
					implied_status &= ~FLAG_CARRY;
797 +
					;;
798 +
				case 0x83: // SEC
799 +
					implied_status |= FLAG_CARRY;
800 +
					;;
801 +
				case 0xC2: // REP
802 +
					operand = debug_read6502((initialPC+1) & 0xffff, currentPCBank, currentPCX16Bank);
803 +
					implied_status = ~operand & implied_status;
804 +
					;;
805 +
				case 0xE2: // SEP
806 +
					operand = debug_read6502((initialPC+1) & 0xffff, currentPCBank, currentPCX16Bank);
807 +
					implied_status = operand | implied_status;
808 +
					;;
809 +
				case 0xFB: // XCE
810 +
					carry = implied_status & FLAG_CARRY;
811 +
					implied_status = (implied_status & ~FLAG_CARRY) | (implied_e ? FLAG_CARRY : 0);
812 +
					implied_e = carry != 0;
813 +
					;;
814 +
				default:
815 +
					;;
816 +
			}
817 +
			if (implied_e) implied_status |= FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH;
818 +
819 +
		}
820 +
		int size = disasm(initialPC, currentPCBank, RAM, buffer, sizeof(buffer), implied_x16_bank, implied_status, &eff_addr);	// Disassemble code
821 +
		// Output assembly highlighting PC
822 +
		DEBUGString(dbgRenderer, DBG_ASMX+8, y, buffer, initialPC == regs.pc ? col_highlight : col_data);
823 +
		// Populate effective address
824 +
		if (initialPC == regs.pc) {
825 +
			if (eff_addr < 0) {
826 +
				DEBUGString(dbgRenderer, DBG_DATX, lines-1, "----", col_data);
827 +
			} else {
828 +
				DEBUGNumber(DBG_DATX, lines-1, eff_addr, 4, col_data);
829 +
			}
830 +
		}
831 +
		initialPC = (initialPC + size) & 0xFFFF;										// Forward to next
832 +
		if (currentPCBank == 0 && initialPC >= 0xA000 && implied_x16_bank == -1) {
833 +
			implied_x16_bank = memory_get_ram_bank();
834 +
		}
835 +
	}
836 +
}
837 +
838 +
// *******************************************************************************************
839 +
//
840 +
//									Render Register Display
841 +
//
842 +
// *******************************************************************************************
843 +
844 +
static char *labels_c816[] = { "NVMXDIZCE","","","A","B","C","X","Y","K","DB","","PC","DP","SP","BKA","BKO","","BRK","EFF", NULL };
845 +
static char *labels_c02[] = { "NV-BDIZC","","","A","X","Y","","PC","SP","BKA","BKO","","BRK","EFF", NULL };
846 +
847 +
static void DEBUGNumberHighByteCondition(int x, int y, int n, bool condition, SDL_Color ifTrue, SDL_Color ifFalse) {
848 +
	if (condition) {
849 +
		DEBUGNumber(x, y, n >> 8, 2, ifTrue);
850 +
		DEBUGNumber(x + 2, y, n & 0xFF, 2, ifFalse);
851 +
	} else {
852 +
		DEBUGNumber(x, y, n, 4, ifFalse);
853 +
	}
854 +
}
855 +
856 +
static int DEBUGRenderRegisters(void) {
857 +
	int n = 0,yc = 0;
858 +
	if (regs.is65c816) {
859 +
		while (labels_c816[n] != NULL) {								// Labels
860 +
			DEBUGString(dbgRenderer, DBG_LBLX,n,labels_c816[n], col_label);n++;
861 +
		}
862 +
		yc++;
863 +
		DEBUGNumber(DBG_LBLX, yc, (regs.status >> 7) & 1, 1, col_data);
864 +
		DEBUGNumber(DBG_LBLX+1, yc, (regs.status >> 6) & 1, 1, col_data);
865 +
		DEBUGNumber(DBG_LBLX+2, yc, (regs.status >> 5) & 1, 1, regs.e ? col_vram_other : col_data);
866 +
		DEBUGNumber(DBG_LBLX+3, yc, (regs.status >> 4) & 1, 1, regs.e ? col_vram_other : col_data);
867 +
		DEBUGNumber(DBG_LBLX+4, yc, (regs.status >> 3) & 1, 1, col_data);
868 +
		DEBUGNumber(DBG_LBLX+5, yc, (regs.status >> 2) & 1, 1, col_data);
869 +
		DEBUGNumber(DBG_LBLX+6, yc, (regs.status >> 1) & 1, 1, col_data);
870 +
		DEBUGNumber(DBG_LBLX+7, yc, (regs.status >> 0) & 1, 1, col_data);
871 +
		DEBUGNumber(DBG_LBLX+8, yc, regs.e, 1, col_data);
872 +
		yc+= 2;
873 +
874 +
		DEBUGNumber(DBG_DATX, yc++, regs.a, 2, col_data);
875 +
		DEBUGNumber(DBG_DATX, yc++, regs.b, 2, col_data);
876 +
		DEBUGNumber(DBG_DATX, yc++, regs.c, 4, col_data);
877 +
		DEBUGNumberHighByteCondition(DBG_DATX, yc++, regs.x, (regs.status >> 4) & 1, col_vram_other, col_data);
878 +
		DEBUGNumberHighByteCondition(DBG_DATX, yc++, regs.y, (regs.status >> 4) & 1, col_vram_other, col_data);
879 +
		DEBUGNumber(DBG_DATX, yc++, regs.k, 2, col_data);
880 +
		DEBUGNumber(DBG_DATX, yc++, regs.db, 2, col_data);
881 +
		yc++;
882 +
883 +
		DEBUGNumber(DBG_DATX, yc++, regs.pc, 4, col_data);
884 +
		DEBUGNumber(DBG_DATX, yc++, regs.dp, 4, col_data);
885 +
		DEBUGNumberHighByteCondition(DBG_DATX, yc++, regs.sp, regs.e, col_vram_other, col_data);
886 +
		DEBUGNumber(DBG_DATX, yc++, memory_get_ram_bank(), 2, col_data);
887 +
		DEBUGNumber(DBG_DATX, yc++, memory_get_rom_bank(), 2, col_data);
888 +
		yc++;
889 +
	} else {
890 +
		while (labels_c02[n] != NULL) {									// Labels
891 +
			DEBUGString(dbgRenderer, DBG_LBLX,n,labels_c02[n], col_label);n++;
892 +
		}
893 +
		yc++;
894 +
		DEBUGNumber(DBG_LBLX, yc, (regs.status >> 7) & 1, 1, col_data);
895 +
		DEBUGNumber(DBG_LBLX+1, yc, (regs.status >> 6) & 1, 1, col_data);
896 +
		DEBUGNumber(DBG_LBLX+3, yc, (regs.status >> 4) & 1, 1, col_data);
897 +
		DEBUGNumber(DBG_LBLX+4, yc, (regs.status >> 3) & 1, 1, col_data);
898 +
		DEBUGNumber(DBG_LBLX+5, yc, (regs.status >> 2) & 1, 1, col_data);
899 +
		DEBUGNumber(DBG_LBLX+6, yc, (regs.status >> 1) & 1, 1, col_data);
900 +
		DEBUGNumber(DBG_LBLX+7, yc, (regs.status >> 0) & 1, 1, col_data);
901 +
		yc+= 2;
902 +
903 +
		DEBUGNumber(DBG_DATX, yc++, regs.a, 2, col_data);
904 +
		DEBUGNumber(DBG_DATX, yc++, regs.xl, 2, col_data);
905 +
		DEBUGNumber(DBG_DATX, yc++, regs.yl, 2, col_data);
906 +
		yc++;
907 +
908 +
		DEBUGNumber(DBG_DATX, yc++, regs.pc, 4, col_data);
909 +
		DEBUGNumber(DBG_DATX, yc++, regs.sp|0x100, 4, col_data);
910 +
		DEBUGNumber(DBG_DATX, yc++, memory_get_ram_bank(), 2, col_data);
911 +
		DEBUGNumber(DBG_DATX, yc++, memory_get_rom_bank(), 2, col_data);
912 +
		yc++;
913 +
914 +
	}
915 +
916 +
	if (breakPoint.pc < 0) {
917 +
		DEBUGString(dbgRenderer, DBG_DATX, yc++, "--:----", col_data);
918 +
	} else if (breakPoint.x16Bank < 0) {
919 +
		if (is_gen2) {
920 +
			DEBUGNumber(DBG_DATX, yc, breakPoint.bank, 2, col_data);
921 +
			DEBUGNumber(DBG_DATX+3, yc++, breakPoint.pc, 4, col_data);
922 +
		} else {
923 +
			DEBUGString(dbgRenderer, DBG_DATX, yc, "--:", col_data);
924 +
			DEBUGNumber(DBG_DATX+3, yc++, (uint16_t)breakPoint.pc, 4, col_data);
925 +
		}
926 +
	} else {
927 +
		DEBUGNumber(DBG_DATX, yc, breakPoint.x16Bank, 2, col_data);
928 +
		DEBUGString(dbgRenderer, DBG_DATX+2, yc, ":", col_data);
929 +
		DEBUGNumber(DBG_DATX+3, yc++, breakPoint.pc, 4, col_data);
930 +
	}
931 +
	yc++;
932 +
933 +
	return n; 													// Number of code display lines
934 +
}
935 +
936 +
937 +
static char *vera_labels[] = { "ADDR0", "ADDR1", "DATA0","DATA1", "CTRL", "VIDEO", "HSCLE", "VSCLE", "FXCTL", "FXMUL", "CACHE", "ACCUM", "", "CLOCKS ELAPSED", NULL };
938 +
939 +
static void DEBUGRenderVERAState(int y) {
940 +
	int n=0;
941 +
	int yc=y;
942 +
	while (vera_labels[n] != NULL) { // Labels
943 +
		DEBUGString(dbgRenderer, DBG_VERA_REGX, yc, vera_labels[n], col_label);n++;yc++;
944 +
	}
945 +
946 +
	yc=y;
947 +
948 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_address(0), 5, col_data);
949 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_address(1), 5, col_data);
950 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_read(3, true), 2, col_data);
951 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_read(4, true), 2, col_data);
952 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_read(5, true), 2, col_data);
953 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_dc_value(0), 2, col_data);
954 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_dc_value(1), 2, col_data);
955 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_dc_value(2), 2, col_data);
956 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_dc_value(8), 2, col_data);
957 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_dc_value(11), 2, col_data);
958 +
	DEBUGNumber(DBG_VERA_REGX+6, yc, video_get_dc_value(24), 2, col_data);
959 +
	DEBUGNumber(DBG_VERA_REGX+8, yc, video_get_dc_value(25), 2, col_data);
960 +
	DEBUGNumber(DBG_VERA_REGX+10, yc, video_get_dc_value(26), 2, col_data);
961 +
	DEBUGNumber(DBG_VERA_REGX+12, yc++, video_get_dc_value(27), 2, col_data);
962 +
	DEBUGNumber(DBG_VERA_REGX+6, yc++, video_get_fx_accum(), 8, col_data);
963 +
964 +
	yc+=2;
965 +
	DEBUGNumberDec(DBG_VERA_REGX, yc++, clockticks6502 - debugCPUClocks, 14, col_data);
966 +
}
967 +
968 +
// *******************************************************************************************
969 +
//
970 +
//									Render Top of Stack
971 +
//
972 +
// *******************************************************************************************
973 +
974 +
static void DEBUGRenderStack(int bytesCount) {
975 +
	uint16_t sp = regs.sp;
976 +
	increment_wrap_at_page_boundary(&sp);
977 +
978 +
	int y= 0;
979 +
	while (y < bytesCount) {
980 +
		DEBUGNumber(DBG_STCK,y, sp,4, col_label);
981 +
		int byte = debug_read6502(sp, 0, USE_CURRENT_X16_BANK);
982 +
		DEBUGNumber(DBG_STCK+5,y,byte,2, col_data);
983 +
		DEBUGWrite(dbgRenderer, DBG_STCK+9,y,byte, col_data);
984 +
		y++;
985 +
		increment_wrap_at_page_boundary(&sp);
986 +
	}
987 +
}
988 +
989 +
// *******************************************************************************************
990 +
//
991 +
//									Write Hex Constant
992 +
//
993 +
// *******************************************************************************************
994 +
995 +
static void DEBUGNumber(int x, int y, int n, int w, SDL_Color colour) {
996 +
	char fmtString[8],buffer[16];
997 +
	snprintf(fmtString, sizeof(fmtString), "%%0%dX", w);
998 +
	snprintf(buffer, sizeof(buffer), fmtString, n);
999 +
	DEBUGString(dbgRenderer, x, y, buffer, colour);
1000 +
}
1001 +
1002 +
// *******************************************************************************************
1003 +
//
1004 +
//					Write Decimal Constant with thousands separator
1005 +
//
1006 +
// *******************************************************************************************
1007 +
1008 +
static void DEBUGNumberDec(int x, int y, int n, int w, SDL_Color colour) {
1009 +
	char buf1[32], buf2[32];
1010 +
	int i,j;
1011 +
	snprintf(buf1, sizeof(buf1), "%d", n);
1012 +
	buf2[sizeof(buf2)-1] = 0; // null terminate string
1013 +
	int count = 0;
1014 +
	for (i=strlen(buf1) - 1, j=sizeof(buf2) - 1; i >= 0 && j > 1; i--) {
1015 +
		buf2[--j] = buf1[i];
1016 +
		count++;
1017 +
1018 +
		if (count == 3) {
1019 +
			buf2[--j] = ' ';
1020 +
			count = 0;
1021 +
		}
1022 +
	}
1023 +
1024 +
	if (buf2[j] == ' ') j++;
1025 +
	DEBUGString(dbgRenderer, x+(w-strlen(buf2+j)), y, buf2+j, colour);
1026 +
}
1027 +
1028 +
// *******************************************************************************************
1029 +
//
1030 +
//									Write Bank:Address
1031 +
//
1032 +
// *******************************************************************************************
1033 +
static void DEBUGAddress(int x, int y, int x16Bank, int addr, uint8_t bank, SDL_Color colour) {
1034 +
	char buffer[4];
1035 +
1036 +
	if(addr >= 0xA000 && bank == 0) {
1037 +
		snprintf(buffer, sizeof(buffer), "%.2X:", x16Bank);
1038 +
	} else if (is_gen2) {
1039 +
		snprintf(buffer, sizeof(buffer), "%.2X ", bank);
1040 +
	} else {
1041 +
		strcpy(buffer, "--:");
1042 +
	}
1043 +
1044 +
	DEBUGString(dbgRenderer, x, y, buffer, colour);
1045 +
1046 +
	DEBUGNumber(x+3, y, addr, 4, colour);
1047 +
1048 +
}
1049 +
1050 +
static void
1051 +
DEBUGVAddress(int x, int y, int addr, SDL_Color colour)
1052 +
{
1053 +
	DEBUGNumber(x, y, addr, 5, colour);
1054 +
}
added src/debugger.h +47 −0
1 +
// *******************************************************************************************
2 +
// *******************************************************************************************
3 +
//
4 +
//		File:		debugger.h
5 +
//		Date:		5th September 2019
6 +
//		Purpose:	Debugger header
7 +
//		Author:		Paul Robson ([email protected])
8 +
//
9 +
// *******************************************************************************************
10 +
// *******************************************************************************************
11 +
12 +
#ifndef _DEBUGGER_H
13 +
#define _DEBUGGER_H
14 +
15 +
#include <SDL.h>
16 +
17 +
extern int showDebugOnRender;
18 +
19 +
struct breakpoint {
20 +
	int pc;
21 +
	uint8_t bank;
22 +
	int x16Bank;
23 +
};
24 +
25 +
void DEBUGRenderDisplay(int width,int height);
26 +
void DEBUGBreakToDebugger(void);
27 +
int  DEBUGGetCurrentStatus(void);
28 +
void DEBUGSetBreakPoint(struct breakpoint newBreakPoint);
29 +
void DEBUGInitUI(SDL_Renderer *pRenderer);
30 +
void DEBUGFreeUI();
31 +
32 +
#define DBG_WIDTH 		(60)									// Char cells across
33 +
#define DBG_HEIGHT 		(60)
34 +
35 +
#define DBG_ASMX 		(1)										// Disassembly starts here
36 +
#define DBG_LBLX 		(26) 									// Debug labels start here
37 +
#define DBG_DATX		(30)									// Debug data starts here.
38 +
#define DBG_STCK		(40)									// Debug stack starts here.
39 +
#define DBG_MEMX 		(1)										// Memory Display starts here
40 +
#define DBG_ZP_REG     (45)                             // Zero page registers start here
41 +
#define DBG_VERA_REGX   (45)                             // VERA registers start here
42 +
43 +
#define DMODE_STOP 		(0)										// Debugger is waiting for action.
44 +
#define DMODE_STEP 		(1)										// Debugger is doing a single step
45 +
#define DMODE_RUN 		(2)										// Debugger is running normally.
46 +
47 +
#endif
added src/disasm.c +411 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdlib.h>
6 +
#include <stdio.h>
7 +
#include <inttypes.h>
8 +
#include <string.h>
9 +
#include "memory.h"
10 +
#include "glue.h"
11 +
12 +
#include "cpu/mnemonics.h"				// Automatically generated mnemonic table.
13 +
14 +
// *******************************************************************************************
15 +
//
16 +
//		Disassemble a single 65C02/65C816 instruction into buffer. Returns the length of the
17 +
//		instruction in total in bytes.
18 +
//
19 +
// *******************************************************************************************
20 +
21 +
int disasm(uint16_t pc, uint8_t bank, uint8_t *RAM, char *line, unsigned int max_line, int16_t x16Bank, uint8_t implied_status, int32_t *eff_addr) {
22 +
	uint8_t opcode = debug_read6502(pc, bank, x16Bank);
23 +
	const char *mnemonic = regs.is65c816 ? mnemonics_c816[opcode] : mnemonics_c02[opcode];
24 +
25 +
	*eff_addr = -1;
26 +
27 +
	int isZprel = 0;
28 +
	int isRel16 = 0;
29 +
	int isYrel = 0;
30 +
	int isXrel = 0;
31 +
	int isIndirect = 0;
32 +
	int isBlockMove = 0;
33 +
	int isImmediate = 0;
34 +
	int isStackRel = 0;
35 +
36 +
	if (regs.is65c816) {
37 +
		// Immediate opcodes (65C816)
38 +
		switch (opcode) {
39 +
			case 0xC2: // rep #imm8
40 +
			case 0xE2: // sep #imm8
41 +
				isImmediate = 1;
42 +
				;;
43 +
			default:
44 +
				;;
45 +
		}
46 +
47 +
		// X relative opcodes (65C816)
48 +
		switch (opcode) {
49 +
			case 0x1F: // ora long,x
50 +
			case 0x3F: // and long,x
51 +
			case 0x5F: // eor long,x
52 +
			case 0x7F: // adc long,x
53 +
			case 0x9F: // sta long,x
54 +
			case 0xBF: // lda long,x
55 +
			case 0xDF: // cmp long,x
56 +
			case 0xFC: // jsr (abs,x)
57 +
			case 0xFF: // sbc long,x
58 +
				isXrel = 1;
59 +
				;;
60 +
			default:
61 +
				;;
62 +
		}
63 +
64 +
		// PER and BRL
65 +
		switch (opcode) {
66 +
			case 0x62: // per rel16
67 +
			case 0x82: // brl rel16
68 +
				isRel16 = 1;
69 +
			default:
70 +
				;;
71 +
		}
72 +
73 +
		// Y relative opcodes (65C816)
74 +
		switch (opcode) {
75 +
			case 0x13: // ora (zp,S),Y
76 +
			case 0x17: // ora [zp],y
77 +
			case 0x33: // and (zp,S),Y
78 +
			case 0x37: // and [zp],y
79 +
			case 0x53: // eor (zp,S),Y
80 +
			case 0x57: // eor [zp],y
81 +
			case 0x73: // adc (zp,S),Y
82 +
			case 0x77: // adc [zp],y
83 +
			case 0x93: // sta (zp,S),Y
84 +
			case 0x97: // sta [zp],y
85 +
			case 0xB3: // lda (zp,S),Y
86 +
			case 0xB7: // lda [zp],y
87 +
			case 0xD3: // cmp (zp,S),Y
88 +
			case 0xD7: // cmp [zp],y
89 +
			case 0xF3: // sbc (zp,S),Y
90 +
			case 0xF7: // sbc [zp],y
91 +
				isYrel = 1;
92 +
				;;
93 +
			default:
94 +
				;;
95 +
		}
96 +
97 +
		// indirect opcodes (65C816)
98 +
		switch (opcode) {
99 +
			case 0x07: // ora [zp]
100 +
			case 0x13: // ora (zp,S),Y
101 +
			case 0x17: // ora [zp],y
102 +
			case 0x27: // and [zp]
103 +
			case 0x33: // and (zp,S),Y
104 +
			case 0x37: // and [zp],y
105 +
			case 0x47: // eor [zp]
106 +
			case 0x53: // eor (zp,S),Y
107 +
			case 0x57: // eor [zp],y
108 +
			case 0x67: // adc [zp]
109 +
			case 0x73: // adc (zp,S),Y
110 +
			case 0x77: // adc [zp],y
111 +
			case 0x87: // sta [zp]
112 +
			case 0x93: // sta (zp,S),Y
113 +
			case 0x97: // sta [zp],y
114 +
			case 0xA7: // lda [zp]
115 +
			case 0xB3: // lda (zp,S),Y
116 +
			case 0xB7: // lda [zp],y
117 +
			case 0xC7: // cmp [zp]
118 +
			case 0xD3: // cmp (zp,S),Y
119 +
			case 0xD4: // pei (zp)
120 +
			case 0xD7: // cmp [zp],y
121 +
			case 0xDC: // jmp [abs]
122 +
			case 0xE7: // sbc [zp]
123 +
			case 0xF3: // sbc (zp,S),Y
124 +
			case 0xF7: // sbc [zp],y
125 +
			case 0xFC: // jsr (abs,x)
126 +
				isIndirect = 1;
127 +
				;;
128 +
			default:
129 +
				;;
130 +
		}
131 +
132 +
		// stack-relative opcodes (65C816)
133 +
		isStackRel = (opcode & 0x0F) == 0x03;
134 +
135 +
		// block move (MVN and MVP)
136 +
		isBlockMove = opcode == 0x44 || opcode == 0x54;
137 +
138 +
	} else {
139 +
		// BBRx and BBSx
140 +
		isZprel = (opcode & 0x0F) == 0x0F;
141 +
142 +
	}
143 +
144 +
	//
145 +
	//		The following cases are common to both 65C816 and 65C02
146 +
	//
147 +
148 +
	//
149 +
	//      X relative opcodes (including indirect/indexed)
150 +
	//
151 +
	switch (opcode) {
152 +
		case 0x01: // ora (zp,x)
153 +
		case 0x15: // ora zp,x
154 +
		case 0x16: // asl zp,x
155 +
		case 0x1D: // ora abs,x
156 +
		case 0x1E: // asl abs,x
157 +
		case 0x21: // and (zp,x)
158 +
		case 0x34: // bit zp,x
159 +
		case 0x35: // and zp,x
160 +
		case 0x36: // rol zp,x
161 +
		case 0x3C: // bit abs,x
162 +
		case 0x3D: // rol abs,x
163 +
		case 0x41: // eor (zp,x)
164 +
		case 0x55: // eor zp,x
165 +
		case 0x56: // lsr zp,x
166 +
		case 0x5d: // eor abs,x
167 +
		case 0x5e: // lsr abs,x
168 +
		case 0x61: // adc (zp,x)
169 +
		case 0x74: // stz zp,x
170 +
		case 0x75: // adc zp,x
171 +
		case 0x76: // ror zp,x
172 +
		case 0x7C: // jmp (abs,x)
173 +
		case 0x7D: // adc abs,x
174 +
		case 0x7e: // ror abs,x
175 +
		case 0x81: // sta (zp,x)
176 +
		case 0x94: // sty zp,x
177 +
		case 0x95: // sta zp,x
178 +
		case 0x9D: // sta abs,x
179 +
		case 0x9E: // stz abs,x
180 +
		case 0xA1: // lda (zp,x)
181 +
		case 0xB4: // ldy zp,x
182 +
		case 0xB5: // lda zp,x
183 +
		case 0xBC: // ldy abs,x
184 +
		case 0xBD: // lda abs,x
185 +
		case 0xC1: // cmp (zp,x)
186 +
		case 0xD5: // cmp zp,x
187 +
		case 0xD6: // dec zp,x
188 +
		case 0xDD: // cmp abs,x
189 +
		case 0xDE: // dec abs,x
190 +
		case 0xD1: // sbc (zp,x)
191 +
		case 0xF5: // sbc zp,x
192 +
		case 0xF6: // inc zp,x
193 +
		case 0xFC: // jsr (abs,x)
194 +
		case 0xFD: // sbc abs,x
195 +
		case 0xFE: // inc abs,x
196 +
			isXrel = 1;
197 +
			;;
198 +
		default:
199 +
			;;
200 +
	}
201 +
202 +
	//
203 +
	//		Test for branches, relative address. These are BRA ($80) and
204 +
	//		$10,$30,$50,$70,$90,$B0,$D0,$F0.
205 +
	//
206 +
	int isRel8 = (opcode == 0x80) || ((opcode & 0x1F) == 0x10);
207 +
208 +
	// is opcode immediate?
209 +
	switch (opcode) {
210 +
		case 0x09: // ora #imm
211 +
		case 0x29: // and #imm
212 +
		case 0x49: // eor #imm
213 +
		case 0x69: // adc #imm
214 +
		case 0x89: // bit #imm
215 +
		case 0xA0: // ldy #imm
216 +
		case 0xA2: // ldx #imm
217 +
		case 0xA9: // lda #imm
218 +
		case 0xC0: // cpy #imm
219 +
		case 0xC9: // cmp #imm
220 +
		case 0xE0: // cpx #imm
221 +
		case 0xE9: // sbc #imm
222 +
			isImmediate = 1;
223 +
			;;
224 +
		default:
225 +
			;;
226 +
	}
227 +
228 +
	//      Y relative opcodes (including indirect/indexed)
229 +
	switch (opcode) {
230 +
		case 0x11: // ora (zp),y
231 +
		case 0x19: // ora abs,y
232 +
		case 0x31: // and (zp),y
233 +
		case 0x39: // and abs,y
234 +
		case 0x51: // eor (zp),y
235 +
		case 0x59: // eor abs,y
236 +
		case 0x71: // adc (zp),y
237 +
		case 0x79: // adc abs,y
238 +
		case 0x91: // sta (zp),y
239 +
		case 0x96: // stx zp,y
240 +
		case 0x99: // sta abs,y
241 +
		case 0xB1: // lda (zp),y
242 +
		case 0xB6: // ldx zp,y
243 +
		case 0xB9: // lda abs,y
244 +
		case 0xBE: // ldx abs,y
245 +
		case 0xD1: // cmp (zp),y
246 +
		case 0xD9: // cmp abs,y
247 +
		case 0xF1: // sbc (zp),y
248 +
		case 0xF9: // sbc abs,y
249 +
			isYrel = 1;
250 +
			;;
251 +
		default:
252 +
			;;
253 +
	}
254 +
255 +
	//	All indirect opcodes
256 +
	
257 +
	switch (opcode) {
258 +
		case 0x01: // ora (zp,x)
259 +
		case 0x11: // ora (zp),y
260 +
		case 0x12: // ora (zp)
261 +
		case 0x21: // and (zp,x)
262 +
		case 0x31: // and (zp),y
263 +
		case 0x32: // and (zp)
264 +
		case 0x41: // eor (zp,x)
265 +
		case 0x51: // eor (zp),y
266 +
		case 0x52: // eor (zp)
267 +
		case 0x61: // adc (zp,x)
268 +
		case 0x6C: // jmp (abs)
269 +
		case 0x71: // adc (zp),y
270 +
		case 0x72: // adc (zp)
271 +
		case 0x7C: // jmp (abs,x)
272 +
		case 0x81: // sta (zp,x)
273 +
		case 0x91: // sta (zp),y
274 +
		case 0x92: // sta (zp)
275 +
		case 0xA1: // lda (zp,x)
276 +
		case 0xB1: // lda (zp),y
277 +
		case 0xB2: // lda (zp)
278 +
		case 0xC1: // cmp (zp,x)
279 +
		case 0xD1: // cmp (zp),y
280 +
		case 0xD2: // cmp (zp)
281 +
		case 0xE1: // sbc (zp,x)
282 +
		case 0xF1: // sbc (zp),y
283 +
		case 0xF2: // sbc (zp)
284 +
			isIndirect = 1;
285 +
			;;
286 +
		default:
287 +
			;;
288 +
	}
289 +
290 +
	// BRK and COP (works with 65C02 as well since it's a nop #imm8)
291 +
	int isBrkOrCop = opcode == 0x00 || opcode == 0x02;
292 +
293 +
	int length = 1;
294 +
295 +
	char *where = strstr(mnemonic, "%%0%hhux");
296 +
	if (where) {
297 +
		int isImmediateIndex = (opcode == 0xa0) || (opcode == 0xa2) || (opcode == 0xc0) || (opcode == 0xe0);
298 +
		int len = snprintf(line, max_line, mnemonic, (implied_status & (isImmediateIndex ? FLAG_INDEX_WIDTH : FLAG_MEMORY_WIDTH)) ? 2 : 4);
299 +
		if (len == -1) {
300 +
			return 0;
301 +
		}
302 +
303 +
		mnemonic = malloc(len + 1);
304 +
		memcpy((char *) mnemonic, line, len + 1);
305 +
	}
306 +
	else {
307 +
		strncpy(line,mnemonic,max_line);
308 +
	}
309 +
310 +
	if (isBlockMove) {
311 +
		snprintf(line, max_line, mnemonic, debug_read6502(pc + 2, bank, x16Bank), debug_read6502(pc + 1, bank, x16Bank));
312 +
		length = 3;
313 +
		if (regs.c != 0xFFFF) *eff_addr = regs.y; // We can have only one effective address, so we're choosing the destination
314 +
	}
315 +
	else if (isZprel) {
316 +
		snprintf(line, max_line, mnemonic, debug_read6502(pc + 1, bank, x16Bank), pc + 3 + (int8_t)debug_read6502(pc + 2, bank, x16Bank));
317 +
		length = 3;
318 +
	}
319 +
	else if (isRel16) {
320 +
		snprintf(line, max_line, mnemonic, (pc + 3 + (int16_t)(debug_read6502(pc + 1, bank, x16Bank) | (debug_read6502(pc + 2, bank, x16Bank) << 8))) & 0xffff);
321 +
		length = 3;
322 +
	}
323 +
	else if (isBrkOrCop) {
324 +
		snprintf(line, max_line, mnemonic, debug_read6502(pc + 1, bank, x16Bank));
325 +
		length = 2;
326 +
	} else {
327 +
		if (strstr(line, "%02x")) {
328 +
			length = 2;
329 +
			if (isRel8) {
330 +
				snprintf(line, max_line, mnemonic, pc + 2 + (int8_t)debug_read6502(pc + 1, bank, x16Bank));
331 +
			} else {
332 +
				snprintf(line, max_line, mnemonic, debug_read6502(pc + 1, bank, x16Bank));
333 +
				if (isStackRel) {
334 +
					uint16_t ptr = regs.sp + debug_read6502(pc + 1, bank, x16Bank);
335 +
					uint8_t ind_bank = ptr < 0xc000 ? memory_get_ram_bank() : memory_get_rom_bank();
336 +
					if (isIndirect && isYrel) {
337 +
						*eff_addr = (debug_read6502(ptr, bank, ind_bank) | (debug_read6502(ptr + 1, bank, ind_bank) << 8)) + regs.y;
338 +
					} else {
339 +
						*eff_addr = ptr;
340 +
					}
341 +
				} else if (isIndirect) {
342 +
					uint16_t ptr = debug_read6502(pc + 1, bank, x16Bank);
343 +
					if (isXrel)
344 +
						ptr += regs.x;
345 +
					*eff_addr = debug_read6502(ptr, bank, x16Bank) | (debug_read6502(ptr + 1, bank, x16Bank) << 8);
346 +
					if (isYrel)
347 +
						*eff_addr += regs.y;
348 +
				} else if (!isImmediate) {
349 +
					*eff_addr = debug_read6502(pc + 1, bank, x16Bank);
350 +
					if (isXrel)
351 +
						*eff_addr += regs.x;
352 +
					if (isYrel)
353 +
						*eff_addr += regs.y;
354 +
				}
355 +
			}
356 +
		}
357 +
		if (strstr(line, "%04x")) {
358 +
			length = 3;
359 +
			snprintf(line, max_line, mnemonic, debug_read6502(pc + 1, bank, x16Bank) | debug_read6502(pc + 2, bank, x16Bank) << 8);
360 +
			if (isIndirect) {
361 +
				uint16_t ptr = debug_read6502(pc + 1, bank, x16Bank) | (debug_read6502(pc + 2, bank, x16Bank) << 8);
362 +
				if (isXrel)
363 +
					ptr += regs.x;
364 +
				uint8_t ind_bank = ptr < 0xc000 ? memory_get_ram_bank() : memory_get_rom_bank();
365 +
				*eff_addr = debug_read6502(ptr, bank, ind_bank) | (debug_read6502(ptr + 1, bank, ind_bank) << 8);
366 +
				if (isYrel)
367 +
					*eff_addr += regs.y;
368 +
			} else if (!isImmediate) {
369 +
				*eff_addr = debug_read6502(pc + 1, bank, x16Bank) | (debug_read6502(pc + 2, bank, x16Bank) << 8);
370 +
				if (isXrel)
371 +
					*eff_addr += regs.x;
372 +
				if (isYrel)
373 +
					*eff_addr += regs.y;
374 +
			}
375 +
		}
376 +
		if (strstr(line, "%06x")) {
377 +
			length = 4;
378 +
			snprintf(line, max_line, mnemonic, debug_read6502(pc + 1, bank, x16Bank) | debug_read6502(pc + 2, bank, x16Bank) << 8 | debug_read6502(pc + 3, bank, x16Bank) << 16);
379 +
			if (isIndirect) {
380 +
				uint16_t ptr = debug_read6502(pc + 1, bank, x16Bank) | (debug_read6502(pc + 2, bank, x16Bank) << 8);
381 +
				if (isXrel)
382 +
					ptr += regs.x;
383 +
				uint8_t ind_bank = ptr < 0xc000 ? memory_get_ram_bank() : memory_get_rom_bank();
384 +
				*eff_addr = debug_read6502(ptr, bank, ind_bank) | (debug_read6502(ptr + 1, bank, ind_bank) << 8);
385 +
				if (isYrel)
386 +
					*eff_addr += regs.y;
387 +
			} else {
388 +
				*eff_addr = debug_read6502(pc + 1, bank, x16Bank) | (debug_read6502(pc + 2, bank, x16Bank) << 8);
389 +
				if (isXrel)
390 +
					*eff_addr += regs.x;
391 +
				if (isYrel)
392 +
					*eff_addr += regs.y;
393 +
			}
394 +
		}
395 +
		if (opcode == 0x00 || opcode == 0x02) {
396 +
			// BRK and COP instructions are 2 bytes long according to WDC datasheet.
397 +
			// CPU skips the second byte when it executes a BRK / COP.
398 +
			length = 2;
399 +
		}
400 +
	}
401 +
402 +
	if (where) {
403 +
		free((char *) mnemonic);
404 +
	}
405 +
406 +
	if (*eff_addr >= 0x10000) {
407 +
		*eff_addr &= 0xffff;
408 +
	}
409 +
410 +
	return length;
411 +
}
added src/disasm.h +12 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _DISASM_H_
6 +
#define _DISASM_H_
7 +
8 +
#include <stdint.h>
9 +
10 +
int disasm(uint16_t pc, uint8_t bank, uint8_t *RAM, char *line, unsigned int max_line, int16_t x16Bank, uint8_t implied_status, int32_t *eff_addr);
11 +
12 +
#endif
added src/endian.h +27 −0
1 +
#pragma once
2 +
3 +
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
4 +
#define LOW_HIGH_UNION(name, low, high) \
5 +
    union { \
6 +
        struct { \
7 +
            uint8_t low; \
8 +
            uint8_t high; \
9 +
        }; \
10 +
        uint16_t name; \
11 +
    }
12 +
#define ENDIAN_STRUCT_MEMBERS(low, high) \
13 +
    low; \
14 +
    high
15 +
#else
16 +
#define LOW_HIGH_UNION(name, low, high) \
17 +
    union { \
18 +
        struct { \
19 +
            uint8_t high; \
20 +
            uint8_t low; \
21 +
        }; \
22 +
        uint16_t name; \
23 +
    }
24 +
#define ENDIAN_STRUCT_MEMBERS(low, high) \
25 +
    high; \
26 +
    low
27 +
#endif
added src/extern/.editorconfig +1 −0
1 +
root = true
added src/extern/include/gif.h +804 −0
1 +
//
2 +
// gif.h
3 +
// by Charlie Tangora
4 +
// Public domain.
5 +
// Email me : ctangora -at- gmail -dot- com
6 +
//
7 +
// This file offers a simple, very limited way to create animated GIFs directly
8 +
// in code.
9 +
//
10 +
// Those looking for particular cleverness are likely to be disappointed; it's
11 +
// pretty
12 +
// much a straight-ahead implementation of the GIF format with optional
13 +
// Floyd-Steinberg
14 +
// dithering. (It does at least use delta encoding - only the changed portions
15 +
// of each
16 +
// frame are saved.)
17 +
//
18 +
// So resulting files are often quite large. The hope is that it will be handy
19 +
// nonetheless
20 +
// as a quick and easily-integrated way for programs to spit out animations.
21 +
//
22 +
// Only RGBA8 is currently supported as an input format. (The alpha is ignored.)
23 +
//
24 +
// USAGE:
25 +
// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the
26 +
// header.
27 +
// Pass subsequent frames to GifWriteFrame().
28 +
// Finally, call GifEnd() to close the file handle and free memory.
29 +
//
30 +
31 +
#ifndef gif_h
32 +
#define gif_h
33 +
34 +
#include <stdio.h>   // for FILE*
35 +
#include <string.h>  // for memcpy and bzero
36 +
#include <stdint.h>  // for integer typedefs
37 +
38 +
// Define these macros to hook into a custom memory allocator.
39 +
// TEMP_MALLOC and TEMP_FREE will only be called in stack fashion - frees in the
40 +
// reverse order of mallocs
41 +
// and any temp memory allocated by a function will be freed before it exits.
42 +
// MALLOC and FREE are used only by GifBegin and GifEnd respectively (to
43 +
// allocate a buffer the size of the image, which
44 +
// is used to find changed pixels for delta-encoding.)
45 +
46 +
#ifndef GIF_TEMP_MALLOC
47 +
#include <stdlib.h>
48 +
#define GIF_TEMP_MALLOC malloc
49 +
#endif
50 +
51 +
#ifndef GIF_TEMP_FREE
52 +
#include <stdlib.h>
53 +
#define GIF_TEMP_FREE free
54 +
#endif
55 +
56 +
#ifndef GIF_MALLOC
57 +
#include <stdlib.h>
58 +
#define GIF_MALLOC malloc
59 +
#endif
60 +
61 +
#ifndef GIF_FREE
62 +
#include <stdlib.h>
63 +
#define GIF_FREE free
64 +
#endif
65 +
66 +
const int kGifTransIndex = 0;
67 +
68 +
typedef struct {
69 +
  int bitDepth;
70 +
71 +
  uint8_t r[256];
72 +
  uint8_t g[256];
73 +
  uint8_t b[256];
74 +
75 +
  // k-d tree over RGB space, organized in heap fashion
76 +
  // i.e. left child of node i is node i*2, right child is node i*2+1
77 +
  // nodes 256-511 are implicitly the leaves, containing a color
78 +
  uint8_t treeSplitElt[255];
79 +
  uint8_t treeSplit[255];
80 +
} GifPalette;
81 +
82 +
// max, min, and abs functions
83 +
int GifIMax(int l, int r) { return l > r ? l : r; }
84 +
int GifIMin(int l, int r) { return l < r ? l : r; }
85 +
int GifIAbs(int i) { return i < 0 ? -i : i; }
86 +
87 +
// walks the k-d tree to pick the palette entry for a desired color.
88 +
// Takes as in/out parameters the current best color and its error -
89 +
// only changes them if it finds a better color in its subtree.
90 +
// this is the major hotspot in the code at the moment.
91 +
void GifGetClosestPaletteColor(GifPalette* pPal, int r, int g, int b,
92 +
                               int* bestInd, int* bestDiff, int treeRoot) {
93 +
  // base case, reached the bottom of the tree
94 +
  if (treeRoot > (1 << pPal->bitDepth) - 1) {
95 +
    int ind = treeRoot - (1 << pPal->bitDepth);
96 +
    if (ind == kGifTransIndex) return;
97 +
98 +
    // check whether this color is better than the current winner
99 +
    int r_err = r - ((int32_t)pPal->r[ind]);
100 +
    int g_err = g - ((int32_t)pPal->g[ind]);
101 +
    int b_err = b - ((int32_t)pPal->b[ind]);
102 +
    int diff = GifIAbs(r_err) + GifIAbs(g_err) + GifIAbs(b_err);
103 +
104 +
    if (diff < *bestDiff) {
105 +
      *bestInd = ind;
106 +
      *bestDiff = diff;
107 +
    }
108 +
109 +
    return;
110 +
  }
111 +
112 +
  // take the appropriate color (r, g, or b) for this node of the k-d tree
113 +
  int comps[3];
114 +
  comps[0] = r;
115 +
  comps[1] = g;
116 +
  comps[2] = b;
117 +
  int splitComp = comps[pPal->treeSplitElt[treeRoot]];
118 +
119 +
  int splitPos = pPal->treeSplit[treeRoot];
120 +
  if (splitPos > splitComp) {
121 +
    // check the left subtree
122 +
    GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2);
123 +
    if (*bestDiff > splitPos - splitComp) {
124 +
      // cannot prove there's not a better value in the right subtree, check
125 +
      // that too
126 +
      GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff,
127 +
                                treeRoot * 2 + 1);
128 +
    }
129 +
  } else {
130 +
    GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff,
131 +
                              treeRoot * 2 + 1);
132 +
    if (*bestDiff > splitComp - splitPos) {
133 +
      GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2);
134 +
    }
135 +
  }
136 +
}
137 +
138 +
void GifSwapPixels(uint8_t* image, int pixA, int pixB) {
139 +
  uint8_t rA = image[pixA * 4];
140 +
  uint8_t gA = image[pixA * 4 + 1];
141 +
  uint8_t bA = image[pixA * 4 + 2];
142 +
  uint8_t aA = image[pixA * 4 + 3];
143 +
144 +
  uint8_t rB = image[pixB * 4];
145 +
  uint8_t gB = image[pixB * 4 + 1];
146 +
  uint8_t bB = image[pixB * 4 + 2];
147 +
  uint8_t aB = image[pixA * 4 + 3];
148 +
149 +
  image[pixA * 4] = rB;
150 +
  image[pixA * 4 + 1] = gB;
151 +
  image[pixA * 4 + 2] = bB;
152 +
  image[pixA * 4 + 3] = aB;
153 +
154 +
  image[pixB * 4] = rA;
155 +
  image[pixB * 4 + 1] = gA;
156 +
  image[pixB * 4 + 2] = bA;
157 +
  image[pixB * 4 + 3] = aA;
158 +
}
159 +
160 +
// just the partition operation from quicksort
161 +
int GifPartition(uint8_t* image, const int left, const int right, const int elt,
162 +
                 int pivotIndex) {
163 +
  const int pivotValue = image[(pivotIndex)*4 + elt];
164 +
  GifSwapPixels(image, pivotIndex, right - 1);
165 +
  int storeIndex = left;
166 +
  bool split = 0;
167 +
  for (int ii = left; ii < right - 1; ++ii) {
168 +
    int arrayVal = image[ii * 4 + elt];
169 +
    if (arrayVal < pivotValue) {
170 +
      GifSwapPixels(image, ii, storeIndex);
171 +
      ++storeIndex;
172 +
    } else if (arrayVal == pivotValue) {
173 +
      if (split) {
174 +
        GifSwapPixels(image, ii, storeIndex);
175 +
        ++storeIndex;
176 +
      }
177 +
      split = !split;
178 +
    }
179 +
  }
180 +
  GifSwapPixels(image, storeIndex, right - 1);
181 +
  return storeIndex;
182 +
}
183 +
184 +
// Perform an incomplete sort, finding all elements above and below the desired
185 +
// median
186 +
void GifPartitionByMedian(uint8_t* image, int left, int right, int com,
187 +
                          int neededCenter) {
188 +
  if (left < right - 1) {
189 +
    int pivotIndex = left + (right - left) / 2;
190 +
191 +
    pivotIndex = GifPartition(image, left, right, com, pivotIndex);
192 +
193 +
    // Only "sort" the section of the array that contains the median
194 +
    if (pivotIndex > neededCenter)
195 +
      GifPartitionByMedian(image, left, pivotIndex, com, neededCenter);
196 +
197 +
    if (pivotIndex < neededCenter)
198 +
      GifPartitionByMedian(image, pivotIndex + 1, right, com, neededCenter);
199 +
  }
200 +
}
201 +
202 +
// Builds a palette by creating a balanced k-d tree of all pixels in the image
203 +
void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt,
204 +
                     int splitElt, int splitDist, int treeNode,
205 +
                     bool buildForDither, GifPalette* pal) {
206 +
  if (lastElt <= firstElt || numPixels == 0) return;
207 +
208 +
  // base case, bottom of the tree
209 +
  if (lastElt == firstElt + 1) {
210 +
    if (buildForDither) {
211 +
      // Dithering needs at least one color as dark as anything
212 +
      // in the image and at least one brightest color -
213 +
      // otherwise it builds up error and produces strange artifacts
214 +
      if (firstElt == 1) {
215 +
        // special case: the darkest color in the image
216 +
        uint32_t r = 255, g = 255, b = 255;
217 +
        for (int ii = 0; ii < numPixels; ++ii) {
218 +
          r = GifIMin(r, image[ii * 4 + 0]);
219 +
          g = GifIMin(g, image[ii * 4 + 1]);
220 +
          b = GifIMin(b, image[ii * 4 + 2]);
221 +
        }
222 +
223 +
        pal->r[firstElt] = r;
224 +
        pal->g[firstElt] = g;
225 +
        pal->b[firstElt] = b;
226 +
227 +
        return;
228 +
      }
229 +
230 +
      if (firstElt == (1 << pal->bitDepth) - 1) {
231 +
        // special case: the lightest color in the image
232 +
        uint32_t r = 0, g = 0, b = 0;
233 +
        for (int ii = 0; ii < numPixels; ++ii) {
234 +
          r = GifIMax(r, image[ii * 4 + 0]);
235 +
          g = GifIMax(g, image[ii * 4 + 1]);
236 +
          b = GifIMax(b, image[ii * 4 + 2]);
237 +
        }
238 +
239 +
        pal->r[firstElt] = r;
240 +
        pal->g[firstElt] = g;
241 +
        pal->b[firstElt] = b;
242 +
243 +
        return;
244 +
      }
245 +
    }
246 +
247 +
    // otherwise, take the average of all colors in this subcube
248 +
    uint64_t r = 0, g = 0, b = 0;
249 +
    for (int ii = 0; ii < numPixels; ++ii) {
250 +
      r += image[ii * 4 + 0];
251 +
      g += image[ii * 4 + 1];
252 +
      b += image[ii * 4 + 2];
253 +
    }
254 +
255 +
    r += numPixels / 2;  // round to nearest
256 +
    g += numPixels / 2;
257 +
    b += numPixels / 2;
258 +
259 +
    r /= numPixels;
260 +
    g /= numPixels;
261 +
    b /= numPixels;
262 +
263 +
    pal->r[firstElt] = (uint8_t)r;
264 +
    pal->g[firstElt] = (uint8_t)g;
265 +
    pal->b[firstElt] = (uint8_t)b;
266 +
267 +
    return;
268 +
  }
269 +
270 +
  // Find the axis with the largest range
271 +
  int minR = 255, maxR = 0;
272 +
  int minG = 255, maxG = 0;
273 +
  int minB = 255, maxB = 0;
274 +
  for (int ii = 0; ii < numPixels; ++ii) {
275 +
    int r = image[ii * 4 + 0];
276 +
    int g = image[ii * 4 + 1];
277 +
    int b = image[ii * 4 + 2];
278 +
279 +
    if (r > maxR) maxR = r;
280 +
    if (r < minR) minR = r;
281 +
282 +
    if (g > maxG) maxG = g;
283 +
    if (g < minG) minG = g;
284 +
285 +
    if (b > maxB) maxB = b;
286 +
    if (b < minB) minB = b;
287 +
  }
288 +
289 +
  int rRange = maxR - minR;
290 +
  int gRange = maxG - minG;
291 +
  int bRange = maxB - minB;
292 +
293 +
  // and split along that axis. (incidentally, this means this isn't a "proper"
294 +
  // k-d tree but I don't know what else to call it)
295 +
  int splitCom = 1;
296 +
  if (bRange > gRange) splitCom = 2;
297 +
  if (rRange > bRange && rRange > gRange) splitCom = 0;
298 +
299 +
  int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt);
300 +
  int subPixelsB = numPixels - subPixelsA;
301 +
302 +
  GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA);
303 +
304 +
  pal->treeSplitElt[treeNode] = splitCom;
305 +
  pal->treeSplit[treeNode] = image[subPixelsA * 4 + splitCom];
306 +
307 +
  GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt - splitDist,
308 +
                  splitDist / 2, treeNode * 2, buildForDither, pal);
309 +
  GifSplitPalette(image + subPixelsA * 4, subPixelsB, splitElt, lastElt,
310 +
                  splitElt + splitDist, splitDist / 2, treeNode * 2 + 1,
311 +
                  buildForDither, pal);
312 +
}
313 +
314 +
// Finds all pixels that have changed from the previous image and
315 +
// moves them to the fromt of th buffer.
316 +
// This allows us to build a palette optimized for the colors of the
317 +
// changed pixels only.
318 +
int GifPickChangedPixels(const uint8_t* lastFrame, uint8_t* frame,
319 +
                         int numPixels) {
320 +
  int numChanged = 0;
321 +
  uint8_t* writeIter = frame;
322 +
323 +
  for (int ii = 0; ii < numPixels; ++ii) {
324 +
    if (lastFrame[0] != frame[0] || lastFrame[1] != frame[1] ||
325 +
        lastFrame[2] != frame[2]) {
326 +
      writeIter[0] = frame[0];
327 +
      writeIter[1] = frame[1];
328 +
      writeIter[2] = frame[2];
329 +
      ++numChanged;
330 +
      writeIter += 4;
331 +
    }
332 +
    lastFrame += 4;
333 +
    frame += 4;
334 +
  }
335 +
336 +
  return numChanged;
337 +
}
338 +
339 +
// Creates a palette by placing all the image pixels in a k-d tree and then
340 +
// averaging the blocks at the bottom.
341 +
// This is known as the "modified median split" technique
342 +
void GifMakePalette(const uint8_t* lastFrame, const uint8_t* nextFrame,
343 +
                    uint32_t width, uint32_t height, int bitDepth,
344 +
                    bool buildForDither, GifPalette* pPal) {
345 +
  pPal->bitDepth = bitDepth;
346 +
347 +
  // SplitPalette is destructive (it sorts the pixels by color) so
348 +
  // we must create a copy of the image for it to destroy
349 +
  int imageSize = width * height * 4 * sizeof(uint8_t);
350 +
  uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize);
351 +
  memcpy(destroyableImage, nextFrame, imageSize);
352 +
353 +
  int numPixels = width * height;
354 +
  if (lastFrame)
355 +
    numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels);
356 +
357 +
  const int lastElt = 1 << bitDepth;
358 +
  const int splitElt = lastElt / 2;
359 +
  const int splitDist = splitElt / 2;
360 +
361 +
  GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist,
362 +
                  1, buildForDither, pPal);
363 +
364 +
  GIF_TEMP_FREE(destroyableImage);
365 +
366 +
  // add the bottom node for the transparency index
367 +
  pPal->treeSplit[1 << (bitDepth - 1)] = 0;
368 +
  pPal->treeSplitElt[1 << (bitDepth - 1)] = 0;
369 +
370 +
  pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;
371 +
}
372 +
373 +
// Implements Floyd-Steinberg dithering, writes palette value to alpha
374 +
void GifDitherImage(const uint8_t* lastFrame, const uint8_t* nextFrame,
375 +
                    uint8_t* outFrame, uint32_t width, uint32_t height,
376 +
                    GifPalette* pPal) {
377 +
  int numPixels = width * height;
378 +
379 +
  // quantPixels initially holds color*256 for all pixels
380 +
  // The extra 8 bits of precision allow for sub-single-color error values
381 +
  // to be propagated
382 +
  int32_t* quantPixels =
383 +
      (int32_t*)GIF_TEMP_MALLOC(sizeof(int32_t) * numPixels * 4);
384 +
385 +
  for (int ii = 0; ii < numPixels * 4; ++ii) {
386 +
    uint8_t pix = nextFrame[ii];
387 +
    int32_t pix16 = (int32_t)pix * 256;
388 +
    quantPixels[ii] = pix16;
389 +
  }
390 +
391 +
  for (uint32_t yy = 0; yy < height; ++yy) {
392 +
    for (uint32_t xx = 0; xx < width; ++xx) {
393 +
      int32_t* nextPix = quantPixels + 4 * (yy * width + xx);
394 +
      const uint8_t* lastPix =
395 +
          lastFrame ? lastFrame + 4 * (yy * width + xx) : NULL;
396 +
397 +
      // Compute the colors we want (rounding to nearest)
398 +
      int32_t rr = (nextPix[0] + 127) / 256;
399 +
      int32_t gg = (nextPix[1] + 127) / 256;
400 +
      int32_t bb = (nextPix[2] + 127) / 256;
401 +
402 +
      // if it happens that we want the color from last frame, then just write
403 +
      // out
404 +
      // a transparent pixel
405 +
      if (lastFrame && lastPix[0] == rr && lastPix[1] == gg &&
406 +
          lastPix[2] == bb) {
407 +
        nextPix[0] = rr;
408 +
        nextPix[1] = gg;
409 +
        nextPix[2] = bb;
410 +
        nextPix[3] = kGifTransIndex;
411 +
        continue;
412 +
      }
413 +
414 +
      int32_t bestDiff = 1000000;
415 +
      int32_t bestInd = kGifTransIndex;
416 +
417 +
      // Search the palete
418 +
      GifGetClosestPaletteColor(pPal, rr, gg, bb, &bestInd, &bestDiff, 1);
419 +
420 +
      // Write the result to the temp buffer
421 +
      int32_t r_err = nextPix[0] - (int32_t)(pPal->r[bestInd]) * 256;
422 +
      int32_t g_err = nextPix[1] - (int32_t)(pPal->g[bestInd]) * 256;
423 +
      int32_t b_err = nextPix[2] - (int32_t)(pPal->b[bestInd]) * 256;
424 +
425 +
      nextPix[0] = pPal->r[bestInd];
426 +
      nextPix[1] = pPal->g[bestInd];
427 +
      nextPix[2] = pPal->b[bestInd];
428 +
      nextPix[3] = bestInd;
429 +
430 +
      // Propagate the error to the four adjacent locations
431 +
      // that we haven't touched yet
432 +
      int quantloc_7 = (yy * width + xx + 1);
433 +
      int quantloc_3 = (yy * width + width + xx - 1);
434 +
      int quantloc_5 = (yy * width + width + xx);
435 +
      int quantloc_1 = (yy * width + width + xx + 1);
436 +
437 +
      if (quantloc_7 < numPixels) {
438 +
        int32_t* pix7 = quantPixels + 4 * quantloc_7;
439 +
        pix7[0] += GifIMax(-pix7[0], r_err * 7 / 16);
440 +
        pix7[1] += GifIMax(-pix7[1], g_err * 7 / 16);
441 +
        pix7[2] += GifIMax(-pix7[2], b_err * 7 / 16);
442 +
      }
443 +
444 +
      if (quantloc_3 < numPixels) {
445 +
        int32_t* pix3 = quantPixels + 4 * quantloc_3;
446 +
        pix3[0] += GifIMax(-pix3[0], r_err * 3 / 16);
447 +
        pix3[1] += GifIMax(-pix3[1], g_err * 3 / 16);
448 +
        pix3[2] += GifIMax(-pix3[2], b_err * 3 / 16);
449 +
      }
450 +
451 +
      if (quantloc_5 < numPixels) {
452 +
        int32_t* pix5 = quantPixels + 4 * quantloc_5;
453 +
        pix5[0] += GifIMax(-pix5[0], r_err * 5 / 16);
454 +
        pix5[1] += GifIMax(-pix5[1], g_err * 5 / 16);
455 +
        pix5[2] += GifIMax(-pix5[2], b_err * 5 / 16);
456 +
      }
457 +
458 +
      if (quantloc_1 < numPixels) {
459 +
        int32_t* pix1 = quantPixels + 4 * quantloc_1;
460 +
        pix1[0] += GifIMax(-pix1[0], r_err / 16);
461 +
        pix1[1] += GifIMax(-pix1[1], g_err / 16);
462 +
        pix1[2] += GifIMax(-pix1[2], b_err / 16);
463 +
      }
464 +
    }
465 +
  }
466 +
467 +
  // Copy the palettized result to the output buffer
468 +
  for (int ii = 0; ii < numPixels * 4; ++ii) {
469 +
    outFrame[ii] = quantPixels[ii];
470 +
  }
471 +
472 +
  GIF_TEMP_FREE(quantPixels);
473 +
}
474 +
475 +
// Picks palette colors for the image using simple thresholding, no dithering
476 +
void GifThresholdImage(const uint8_t* lastFrame, const uint8_t* nextFrame,
477 +
                       uint8_t* outFrame, uint32_t width, uint32_t height,
478 +
                       GifPalette* pPal) {
479 +
  uint32_t numPixels = width * height;
480 +
  for (uint32_t ii = 0; ii < numPixels; ++ii) {
481 +
    // if a previous color is available, and it matches the current color,
482 +
    // set the pixel to transparent
483 +
    if (lastFrame && lastFrame[0] == nextFrame[0] &&
484 +
        lastFrame[1] == nextFrame[1] && lastFrame[2] == nextFrame[2]) {
485 +
      outFrame[0] = lastFrame[0];
486 +
      outFrame[1] = lastFrame[1];
487 +
      outFrame[2] = lastFrame[2];
488 +
      outFrame[3] = kGifTransIndex;
489 +
    } else {
490 +
      // palettize the pixel
491 +
      int32_t bestDiff = 1000000;
492 +
      int32_t bestInd = 1;
493 +
      GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2],
494 +
                                &bestInd, &bestDiff, 1);
495 +
496 +
      // Write the resulting color to the output buffer
497 +
      outFrame[0] = pPal->r[bestInd];
498 +
      outFrame[1] = pPal->g[bestInd];
499 +
      outFrame[2] = pPal->b[bestInd];
500 +
      outFrame[3] = bestInd;
501 +
    }
502 +
503 +
    if (lastFrame) lastFrame += 4;
504 +
    outFrame += 4;
505 +
    nextFrame += 4;
506 +
  }
507 +
}
508 +
509 +
// Simple structure to write out the LZW-compressed portion of the image
510 +
// one bit at a time
511 +
typedef struct {
512 +
  uint8_t bitIndex;  // how many bits in the partial byte written so far
513 +
  uint8_t byte;      // current partial byte
514 +
515 +
  uint32_t chunkIndex;
516 +
  uint8_t chunk[256];  // bytes are written in here until we have 256 of them,
517 +
                       // then written to the file
518 +
} GifBitStatus;
519 +
520 +
// insert a single bit
521 +
void GifWriteBit(GifBitStatus* stat, uint32_t bit) {
522 +
  bit = bit & 1;
523 +
  bit = bit << stat->bitIndex;
524 +
  stat->byte |= bit;
525 +
526 +
  ++stat->bitIndex;
527 +
  if (stat->bitIndex > 7) {
528 +
    // move the newly-finished byte to the chunk buffer
529 +
    stat->chunk[stat->chunkIndex++] = stat->byte;
530 +
    // and start a new byte
531 +
    stat->bitIndex = 0;
532 +
    stat->byte = 0;
533 +
  }
534 +
}
535 +
536 +
// write all bytes so far to the file
537 +
void GifWriteChunk(FILE* f, GifBitStatus* stat) {
538 +
  fputc(stat->chunkIndex, f);
539 +
  fwrite(stat->chunk, 1, stat->chunkIndex, f);
540 +
541 +
  stat->bitIndex = 0;
542 +
  stat->byte = 0;
543 +
  stat->chunkIndex = 0;
544 +
}
545 +
546 +
void GifWriteCode(FILE* f, GifBitStatus* stat, uint32_t code, uint32_t length) {
547 +
  for (uint32_t ii = 0; ii < length; ++ii) {
548 +
    GifWriteBit(stat, code);
549 +
    code = code >> 1;
550 +
551 +
    if (stat->chunkIndex == 255) {
552 +
      GifWriteChunk(f, stat);
553 +
    }
554 +
  }
555 +
}
556 +
557 +
// The LZW dictionary is a 256-ary tree constructed as the file is encoded,
558 +
// this is one node
559 +
typedef struct {
560 +
  uint16_t m_next[256];
561 +
} GifLzwNode;
562 +
563 +
// write a 256-color (8-bit) image palette to the file
564 +
void GifWritePalette(const GifPalette* pPal, FILE* f) {
565 +
  fputc(0, f);  // first color: transparency
566 +
  fputc(0, f);
567 +
  fputc(0, f);
568 +
569 +
  for (int ii = 1; ii < (1 << pPal->bitDepth); ++ii) {
570 +
    uint32_t r = pPal->r[ii];
571 +
    uint32_t g = pPal->g[ii];
572 +
    uint32_t b = pPal->b[ii];
573 +
574 +
    fputc(b, f);
575 +
    fputc(g, f);
576 +
    fputc(r, f);
577 +
  }
578 +
}
579 +
580 +
// write the image header, LZW-compress and write out the image
581 +
void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top,
582 +
                      uint32_t width, uint32_t height, uint32_t delay,
583 +
                      GifPalette* pPal) {
584 +
  // graphics control extension
585 +
  fputc(0x21, f);
586 +
  fputc(0xf9, f);
587 +
  fputc(0x04, f);
588 +
  fputc(0x05, f);  // leave prev frame in place, this frame has transparency
589 +
  fputc(delay & 0xff, f);
590 +
  fputc((delay >> 8) & 0xff, f);
591 +
  fputc(kGifTransIndex, f);  // transparent color index
592 +
  fputc(0, f);
593 +
594 +
  fputc(0x2c, f);  // image descriptor block
595 +
596 +
  fputc(left & 0xff, f);  // corner of image in canvas space
597 +
  fputc((left >> 8) & 0xff, f);
598 +
  fputc(top & 0xff, f);
599 +
  fputc((top >> 8) & 0xff, f);
600 +
601 +
  fputc(width & 0xff, f);  // width and height of image
602 +
  fputc((width >> 8) & 0xff, f);
603 +
  fputc(height & 0xff, f);
604 +
  fputc((height >> 8) & 0xff, f);
605 +
606 +
  // fputc(0, f); // no local color table, no transparency
607 +
  // fputc(0x80, f); // no local color table, but transparency
608 +
609 +
  fputc(0x80 + pPal->bitDepth - 1,
610 +
        f);  // local color table present, 2 ^ bitDepth entries
611 +
  GifWritePalette(pPal, f);
612 +
613 +
  const int minCodeSize = pPal->bitDepth;
614 +
  const uint32_t clearCode = 1 << pPal->bitDepth;
615 +
616 +
  fputc(minCodeSize, f);  // min code size 8 bits
617 +
618 +
  GifLzwNode* codetree =
619 +
      (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode) * 4096);
620 +
621 +
  memset(codetree, 0, sizeof(GifLzwNode) * 4096);
622 +
  int32_t curCode = -1;
623 +
  uint32_t codeSize = minCodeSize + 1;
624 +
  uint32_t maxCode = clearCode + 1;
625 +
626 +
  GifBitStatus stat;
627 +
  stat.byte = 0;
628 +
  stat.bitIndex = 0;
629 +
  stat.chunkIndex = 0;
630 +
631 +
  GifWriteCode(f, &stat, clearCode,
632 +
               codeSize);  // start with a fresh LZW dictionary
633 +
634 +
  for (uint32_t yy = 0; yy < height; ++yy) {
635 +
    for (uint32_t xx = 0; xx < width; ++xx) {
636 +
      uint8_t nextValue = image[(yy * width + xx) * 4 + 3];
637 +
638 +
      // "loser mode" - no compression, every single code is followed
639 +
      // immediately by a clear
640 +
      // WriteCode( f, stat, nextValue, codeSize );
641 +
      // WriteCode( f, stat, 256, codeSize );
642 +
643 +
      if (curCode < 0) {
644 +
        // first value in a new run
645 +
        curCode = nextValue;
646 +
      } else if (codetree[curCode].m_next[nextValue]) {
647 +
        // current run already in the dictionary
648 +
        curCode = codetree[curCode].m_next[nextValue];
649 +
      } else {
650 +
        // finish the current run, write a code
651 +
        GifWriteCode(f, &stat, curCode, codeSize);
652 +
653 +
        // insert the new run into the dictionary
654 +
        codetree[curCode].m_next[nextValue] = ++maxCode;
655 +
656 +
        if (maxCode >= (1ul << codeSize)) {
657 +
          // dictionary entry count has broken a size barrier,
658 +
          // we need more bits for codes
659 +
          codeSize++;
660 +
        }
661 +
        if (maxCode == 4095) {
662 +
          // the dictionary is full, clear it out and begin anew
663 +
          GifWriteCode(f, &stat, clearCode, codeSize);  // clear tree
664 +
665 +
          memset(codetree, 0, sizeof(GifLzwNode) * 4096);
666 +
          curCode = -1;
667 +
          codeSize = minCodeSize + 1;
668 +
          maxCode = clearCode + 1;
669 +
        }
670 +
671 +
        curCode = nextValue;
672 +
      }
673 +
    }
674 +
  }
675 +
676 +
  // compression footer
677 +
  GifWriteCode(f, &stat, curCode, codeSize);
678 +
  GifWriteCode(f, &stat, clearCode, codeSize);
679 +
  GifWriteCode(f, &stat, clearCode + 1, minCodeSize + 1);
680 +
681 +
  // write out the last partial chunk
682 +
  while (stat.bitIndex) GifWriteBit(&stat, 0);
683 +
  if (stat.chunkIndex) GifWriteChunk(f, &stat);
684 +
685 +
  fputc(0, f);  // image block terminator
686 +
687 +
  GIF_TEMP_FREE(codetree);
688 +
}
689 +
690 +
typedef struct {
691 +
  FILE* f;
692 +
  uint8_t* oldImage;
693 +
  bool firstFrame;
694 +
} GifWriter;
695 +
696 +
// Creates a gif file.
697 +
// The input GIFWriter is assumed to be uninitialized.
698 +
// The delay value is the time between frames in hundredths of a second - note
699 +
// that not all viewers pay much attention to this value.
700 +
bool GifBegin(GifWriter* writer, const char* filename, uint32_t width,
701 +
              uint32_t height, uint32_t delay, int32_t bitDepth,
702 +
              bool dither) {
703 +
#if _MSC_VER >= 1400
704 +
  writer->f = 0;
705 +
  fopen_s(&writer->f, filename, "wb");
706 +
#else
707 +
  writer->f = fopen(filename, "wb");
708 +
#endif
709 +
  if (!writer->f) return false;
710 +
711 +
  writer->firstFrame = true;
712 +
713 +
  // allocate
714 +
  writer->oldImage = (uint8_t*)GIF_MALLOC(width * height * 4);
715 +
716 +
  fputs("GIF89a", writer->f);
717 +
718 +
  // screen descriptor
719 +
  fputc(width & 0xff, writer->f);
720 +
  fputc((width >> 8) & 0xff, writer->f);
721 +
  fputc(height & 0xff, writer->f);
722 +
  fputc((height >> 8) & 0xff, writer->f);
723 +
724 +
  fputc(0xf0,
725 +
        writer->f);     // there is an unsorted global color table of 2 entries
726 +
  fputc(0, writer->f);  // background color
727 +
  fputc(0, writer->f);  // pixels are square (we need to specify this because
728 +
                        // it's 1989)
729 +
730 +
  // now the "global" palette (really just a dummy palette)
731 +
  // color 0: black
732 +
  fputc(0, writer->f);
733 +
  fputc(0, writer->f);
734 +
  fputc(0, writer->f);
735 +
  // color 1: also black
736 +
  fputc(0, writer->f);
737 +
  fputc(0, writer->f);
738 +
  fputc(0, writer->f);
739 +
740 +
  if (delay != 0) {
741 +
    // animation header
742 +
    fputc(0x21, writer->f);           // extension
743 +
    fputc(0xff, writer->f);           // application specific
744 +
    fputc(11, writer->f);             // length 11
745 +
    fputs("NETSCAPE2.0", writer->f);  // yes, really
746 +
    fputc(3, writer->f);              // 3 bytes of NETSCAPE2.0 data
747 +
748 +
    fputc(1, writer->f);  // JUST BECAUSE
749 +
    fputc(0, writer->f);  // loop infinitely (byte 0)
750 +
    fputc(0, writer->f);  // loop infinitely (byte 1)
751 +
752 +
    fputc(0, writer->f);  // block terminator
753 +
  }
754 +
755 +
  return true;
756 +
}
757 +
758 +
// Writes out a new frame to a GIF in progress.
759 +
// The GIFWriter should have been created by GIFBegin.
760 +
// AFAIK, it is legal to use different bit depths for different frames of an
761 +
// image -
762 +
// this may be handy to save bits in animations that don't change much.
763 +
bool GifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width,
764 +
                   uint32_t height, uint32_t delay, int bitDepth,
765 +
                   bool dither) {
766 +
  if (!writer->f) return false;
767 +
768 +
  const uint8_t* oldImage = writer->firstFrame ? NULL : writer->oldImage;
769 +
  writer->firstFrame = false;
770 +
771 +
  GifPalette pal;
772 +
  GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth,
773 +
                 dither, &pal);
774 +
775 +
  if (dither)
776 +
    GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal);
777 +
  else
778 +
    GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal);
779 +
780 +
  GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay,
781 +
                   &pal);
782 +
783 +
  return true;
784 +
}
785 +
786 +
// Writes the EOF code, closes the file handle, and frees temp memory used by a
787 +
// GIF.
788 +
// Many if not most viewers will still display a GIF properly if the EOF code is
789 +
// missing,
790 +
// but it's still a good idea to write it out.
791 +
bool GifEnd(GifWriter* writer) {
792 +
  if (!writer->f) return false;
793 +
794 +
  fputc(0x3b, writer->f);  // end of file
795 +
  fclose(writer->f);
796 +
  GIF_FREE(writer->oldImage);
797 +
798 +
  writer->f = NULL;
799 +
  writer->oldImage = NULL;
800 +
801 +
  return true;
802 +
}
803 +
804 +
#endif
added src/extern/include/stb_image_write.h +1724 −0
1 +
/* stb_image_write - v1.16 - public domain - http://nothings.org/stb
2 +
   writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
3 +
                                     no warranty implied; use at your own risk
4 +
5 +
   Before #including,
6 +
7 +
       #define STB_IMAGE_WRITE_IMPLEMENTATION
8 +
9 +
   in the file that you want to have the implementation.
10 +
11 +
   Will probably not work correctly with strict-aliasing optimizations.
12 +
13 +
ABOUT:
14 +
15 +
   This header file is a library for writing images to C stdio or a callback.
16 +
17 +
   The PNG output is not optimal; it is 20-50% larger than the file
18 +
   written by a decent optimizing implementation; though providing a custom
19 +
   zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that.
20 +
   This library is designed for source code compactness and simplicity,
21 +
   not optimal image file size or run-time performance.
22 +
23 +
BUILDING:
24 +
25 +
   You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h.
26 +
   You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace
27 +
   malloc,realloc,free.
28 +
   You can #define STBIW_MEMMOVE() to replace memmove()
29 +
   You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function
30 +
   for PNG compression (instead of the builtin one), it must have the following signature:
31 +
   unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality);
32 +
   The returned data will be freed with STBIW_FREE() (free() by default),
33 +
   so it must be heap allocated with STBIW_MALLOC() (malloc() by default),
34 +
35 +
UNICODE:
36 +
37 +
   If compiling for Windows and you wish to use Unicode filenames, compile
38 +
   with
39 +
       #define STBIW_WINDOWS_UTF8
40 +
   and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert
41 +
   Windows wchar_t filenames to utf8.
42 +
43 +
USAGE:
44 +
45 +
   There are five functions, one for each image file format:
46 +
47 +
     int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
48 +
     int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
49 +
     int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
50 +
     int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality);
51 +
     int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
52 +
53 +
     void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically
54 +
55 +
   There are also five equivalent functions that use an arbitrary write function. You are
56 +
   expected to open/close your file-equivalent before and after calling these:
57 +
58 +
     int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data, int stride_in_bytes);
59 +
     int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
60 +
     int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
61 +
     int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
62 +
     int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);
63 +
64 +
   where the callback is:
65 +
      void stbi_write_func(void *context, void *data, int size);
66 +
67 +
   You can configure it with these global variables:
68 +
      int stbi_write_tga_with_rle;             // defaults to true; set to 0 to disable RLE
69 +
      int stbi_write_png_compression_level;    // defaults to 8; set to higher for more compression
70 +
      int stbi_write_force_png_filter;         // defaults to -1; set to 0..5 to force a filter mode
71 +
72 +
73 +
   You can define STBI_WRITE_NO_STDIO to disable the file variant of these
74 +
   functions, so the library will not use stdio.h at all. However, this will
75 +
   also disable HDR writing, because it requires stdio for formatted output.
76 +
77 +
   Each function returns 0 on failure and non-0 on success.
78 +
79 +
   The functions create an image file defined by the parameters. The image
80 +
   is a rectangle of pixels stored from left-to-right, top-to-bottom.
81 +
   Each pixel contains 'comp' channels of data stored interleaved with 8-bits
82 +
   per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
83 +
   monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
84 +
   The *data pointer points to the first byte of the top-left-most pixel.
85 +
   For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
86 +
   a row of pixels to the first byte of the next row of pixels.
87 +
88 +
   PNG creates output files with the same number of components as the input.
89 +
   The BMP format expands Y to RGB in the file format and does not
90 +
   output alpha.
91 +
92 +
   PNG supports writing rectangles of data even when the bytes storing rows of
93 +
   data are not consecutive in memory (e.g. sub-rectangles of a larger image),
94 +
   by supplying the stride between the beginning of adjacent rows. The other
95 +
   formats do not. (Thus you cannot write a native-format BMP through the BMP
96 +
   writer, both because it is in BGR order and because it may have padding
97 +
   at the end of the line.)
98 +
99 +
   PNG allows you to set the deflate compression level by setting the global
100 +
   variable 'stbi_write_png_compression_level' (it defaults to 8).
101 +
102 +
   HDR expects linear float data. Since the format is always 32-bit rgb(e)
103 +
   data, alpha (if provided) is discarded, and for monochrome data it is
104 +
   replicated across all three channels.
105 +
106 +
   TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed
107 +
   data, set the global variable 'stbi_write_tga_with_rle' to 0.
108 +
109 +
   JPEG does ignore alpha channels in input data; quality is between 1 and 100.
110 +
   Higher quality looks better but results in a bigger image.
111 +
   JPEG baseline (no JPEG progressive).
112 +
113 +
CREDITS:
114 +
115 +
116 +
   Sean Barrett           -    PNG/BMP/TGA
117 +
   Baldur Karlsson        -    HDR
118 +
   Jean-Sebastien Guay    -    TGA monochrome
119 +
   Tim Kelsey             -    misc enhancements
120 +
   Alan Hickman           -    TGA RLE
121 +
   Emmanuel Julien        -    initial file IO callback implementation
122 +
   Jon Olick              -    original jo_jpeg.cpp code
123 +
   Daniel Gibson          -    integrate JPEG, allow external zlib
124 +
   Aarni Koskela          -    allow choosing PNG filter
125 +
126 +
   bugfixes:
127 +
      github:Chribba
128 +
      Guillaume Chereau
129 +
      github:jry2
130 +
      github:romigrou
131 +
      Sergio Gonzalez
132 +
      Jonas Karlsson
133 +
      Filip Wasil
134 +
      Thatcher Ulrich
135 +
      github:poppolopoppo
136 +
      Patrick Boettcher
137 +
      github:xeekworx
138 +
      Cap Petschulat
139 +
      Simon Rodriguez
140 +
      Ivan Tikhonov
141 +
      github:ignotion
142 +
      Adam Schackart
143 +
      Andrew Kensler
144 +
145 +
LICENSE
146 +
147 +
  See end of file for license information.
148 +
149 +
*/
150 +
151 +
#ifndef INCLUDE_STB_IMAGE_WRITE_H
152 +
#define INCLUDE_STB_IMAGE_WRITE_H
153 +
154 +
#include <stdlib.h>
155 +
156 +
// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline'
157 +
#ifndef STBIWDEF
158 +
#ifdef STB_IMAGE_WRITE_STATIC
159 +
#define STBIWDEF  static
160 +
#else
161 +
#ifdef __cplusplus
162 +
#define STBIWDEF  extern "C"
163 +
#else
164 +
#define STBIWDEF  extern
165 +
#endif
166 +
#endif
167 +
#endif
168 +
169 +
#ifndef STB_IMAGE_WRITE_STATIC  // C++ forbids static forward declarations
170 +
STBIWDEF int stbi_write_tga_with_rle;
171 +
STBIWDEF int stbi_write_png_compression_level;
172 +
STBIWDEF int stbi_write_force_png_filter;
173 +
#endif
174 +
175 +
#ifndef STBI_WRITE_NO_STDIO
176 +
STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void  *data, int stride_in_bytes);
177 +
STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void  *data);
178 +
STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void  *data);
179 +
STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
180 +
STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void  *data, int quality);
181 +
182 +
#ifdef STBIW_WINDOWS_UTF8
183 +
STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
184 +
#endif
185 +
#endif
186 +
187 +
typedef void stbi_write_func(void *context, void *data, int size);
188 +
189 +
STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data, int stride_in_bytes);
190 +
STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
191 +
STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
192 +
STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
193 +
STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void  *data, int quality);
194 +
195 +
STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
196 +
197 +
#endif//INCLUDE_STB_IMAGE_WRITE_H
198 +
199 +
#ifdef STB_IMAGE_WRITE_IMPLEMENTATION
200 +
201 +
#ifdef _WIN32
202 +
   #ifndef _CRT_SECURE_NO_WARNINGS
203 +
   #define _CRT_SECURE_NO_WARNINGS
204 +
   #endif
205 +
   #ifndef _CRT_NONSTDC_NO_DEPRECATE
206 +
   #define _CRT_NONSTDC_NO_DEPRECATE
207 +
   #endif
208 +
#endif
209 +
210 +
#ifndef STBI_WRITE_NO_STDIO
211 +
#include <stdio.h>
212 +
#endif // STBI_WRITE_NO_STDIO
213 +
214 +
#include <stdarg.h>
215 +
#include <stdlib.h>
216 +
#include <string.h>
217 +
#include <math.h>
218 +
219 +
#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED))
220 +
// ok
221 +
#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED)
222 +
// ok
223 +
#else
224 +
#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)."
225 +
#endif
226 +
227 +
#ifndef STBIW_MALLOC
228 +
#define STBIW_MALLOC(sz)        malloc(sz)
229 +
#define STBIW_REALLOC(p,newsz)  realloc(p,newsz)
230 +
#define STBIW_FREE(p)           free(p)
231 +
#endif
232 +
233 +
#ifndef STBIW_REALLOC_SIZED
234 +
#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz)
235 +
#endif
236 +
237 +
238 +
#ifndef STBIW_MEMMOVE
239 +
#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz)
240 +
#endif
241 +
242 +
243 +
#ifndef STBIW_ASSERT
244 +
#include <assert.h>
245 +
#define STBIW_ASSERT(x) assert(x)
246 +
#endif
247 +
248 +
#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff)
249 +
250 +
#ifdef STB_IMAGE_WRITE_STATIC
251 +
static int stbi_write_png_compression_level = 8;
252 +
static int stbi_write_tga_with_rle = 1;
253 +
static int stbi_write_force_png_filter = -1;
254 +
#else
255 +
int stbi_write_png_compression_level = 8;
256 +
int stbi_write_tga_with_rle = 1;
257 +
int stbi_write_force_png_filter = -1;
258 +
#endif
259 +
260 +
static int stbi__flip_vertically_on_write = 0;
261 +
262 +
STBIWDEF void stbi_flip_vertically_on_write(int flag)
263 +
{
264 +
   stbi__flip_vertically_on_write = flag;
265 +
}
266 +
267 +
typedef struct
268 +
{
269 +
   stbi_write_func *func;
270 +
   void *context;
271 +
   unsigned char buffer[64];
272 +
   int buf_used;
273 +
} stbi__write_context;
274 +
275 +
// initialize a callback-based context
276 +
static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context)
277 +
{
278 +
   s->func    = c;
279 +
   s->context = context;
280 +
}
281 +
282 +
#ifndef STBI_WRITE_NO_STDIO
283 +
284 +
static void stbi__stdio_write(void *context, void *data, int size)
285 +
{
286 +
   fwrite(data,1,size,(FILE*) context);
287 +
}
288 +
289 +
#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
290 +
#ifdef __cplusplus
291 +
#define STBIW_EXTERN extern "C"
292 +
#else
293 +
#define STBIW_EXTERN extern
294 +
#endif
295 +
STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
296 +
STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
297 +
298 +
STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
299 +
{
300 +
   return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
301 +
}
302 +
#endif
303 +
304 +
static FILE *stbiw__fopen(char const *filename, char const *mode)
305 +
{
306 +
   FILE *f;
307 +
#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
308 +
   wchar_t wMode[64];
309 +
   wchar_t wFilename[1024];
310 +
   if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
311 +
      return 0;
312 +
313 +
   if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
314 +
      return 0;
315 +
316 +
#if defined(_MSC_VER) && _MSC_VER >= 1400
317 +
   if (0 != _wfopen_s(&f, wFilename, wMode))
318 +
      f = 0;
319 +
#else
320 +
   f = _wfopen(wFilename, wMode);
321 +
#endif
322 +
323 +
#elif defined(_MSC_VER) && _MSC_VER >= 1400
324 +
   if (0 != fopen_s(&f, filename, mode))
325 +
      f=0;
326 +
#else
327 +
   f = fopen(filename, mode);
328 +
#endif
329 +
   return f;
330 +
}
331 +
332 +
static int stbi__start_write_file(stbi__write_context *s, const char *filename)
333 +
{
334 +
   FILE *f = stbiw__fopen(filename, "wb");
335 +
   stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f);
336 +
   return f != NULL;
337 +
}
338 +
339 +
static void stbi__end_write_file(stbi__write_context *s)
340 +
{
341 +
   fclose((FILE *)s->context);
342 +
}
343 +
344 +
#endif // !STBI_WRITE_NO_STDIO
345 +
346 +
typedef unsigned int stbiw_uint32;
347 +
typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
348 +
349 +
static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v)
350 +
{
351 +
   while (*fmt) {
352 +
      switch (*fmt++) {
353 +
         case ' ': break;
354 +
         case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int));
355 +
                     s->func(s->context,&x,1);
356 +
                     break; }
357 +
         case '2': { int x = va_arg(v,int);
358 +
                     unsigned char b[2];
359 +
                     b[0] = STBIW_UCHAR(x);
360 +
                     b[1] = STBIW_UCHAR(x>>8);
361 +
                     s->func(s->context,b,2);
362 +
                     break; }
363 +
         case '4': { stbiw_uint32 x = va_arg(v,int);
364 +
                     unsigned char b[4];
365 +
                     b[0]=STBIW_UCHAR(x);
366 +
                     b[1]=STBIW_UCHAR(x>>8);
367 +
                     b[2]=STBIW_UCHAR(x>>16);
368 +
                     b[3]=STBIW_UCHAR(x>>24);
369 +
                     s->func(s->context,b,4);
370 +
                     break; }
371 +
         default:
372 +
            STBIW_ASSERT(0);
373 +
            return;
374 +
      }
375 +
   }
376 +
}
377 +
378 +
static void stbiw__writef(stbi__write_context *s, const char *fmt, ...)
379 +
{
380 +
   va_list v;
381 +
   va_start(v, fmt);
382 +
   stbiw__writefv(s, fmt, v);
383 +
   va_end(v);
384 +
}
385 +
386 +
static void stbiw__write_flush(stbi__write_context *s)
387 +
{
388 +
   if (s->buf_used) {
389 +
      s->func(s->context, &s->buffer, s->buf_used);
390 +
      s->buf_used = 0;
391 +
   }
392 +
}
393 +
394 +
static void stbiw__putc(stbi__write_context *s, unsigned char c)
395 +
{
396 +
   s->func(s->context, &c, 1);
397 +
}
398 +
399 +
static void stbiw__write1(stbi__write_context *s, unsigned char a)
400 +
{
401 +
   if ((size_t)s->buf_used + 1 > sizeof(s->buffer))
402 +
      stbiw__write_flush(s);
403 +
   s->buffer[s->buf_used++] = a;
404 +
}
405 +
406 +
static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c)
407 +
{
408 +
   int n;
409 +
   if ((size_t)s->buf_used + 3 > sizeof(s->buffer))
410 +
      stbiw__write_flush(s);
411 +
   n = s->buf_used;
412 +
   s->buf_used = n+3;
413 +
   s->buffer[n+0] = a;
414 +
   s->buffer[n+1] = b;
415 +
   s->buffer[n+2] = c;
416 +
}
417 +
418 +
static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d)
419 +
{
420 +
   unsigned char bg[3] = { 255, 0, 255}, px[3];
421 +
   int k;
422 +
423 +
   if (write_alpha < 0)
424 +
      stbiw__write1(s, d[comp - 1]);
425 +
426 +
   switch (comp) {
427 +
      case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case
428 +
      case 1:
429 +
         if (expand_mono)
430 +
            stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp
431 +
         else
432 +
            stbiw__write1(s, d[0]);  // monochrome TGA
433 +
         break;
434 +
      case 4:
435 +
         if (!write_alpha) {
436 +
            // composite against pink background
437 +
            for (k = 0; k < 3; ++k)
438 +
               px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255;
439 +
            stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]);
440 +
            break;
441 +
         }
442 +
         /* FALLTHROUGH */
443 +
      case 3:
444 +
         stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]);
445 +
         break;
446 +
   }
447 +
   if (write_alpha > 0)
448 +
      stbiw__write1(s, d[comp - 1]);
449 +
}
450 +
451 +
static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
452 +
{
453 +
   stbiw_uint32 zero = 0;
454 +
   int i,j, j_end;
455 +
456 +
   if (y <= 0)
457 +
      return;
458 +
459 +
   if (stbi__flip_vertically_on_write)
460 +
      vdir *= -1;
461 +
462 +
   if (vdir < 0) {
463 +
      j_end = -1; j = y-1;
464 +
   } else {
465 +
      j_end =  y; j = 0;
466 +
   }
467 +
468 +
   for (; j != j_end; j += vdir) {
469 +
      for (i=0; i < x; ++i) {
470 +
         unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
471 +
         stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d);
472 +
      }
473 +
      stbiw__write_flush(s);
474 +
      s->func(s->context, &zero, scanline_pad);
475 +
   }
476 +
}
477 +
478 +
static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...)
479 +
{
480 +
   if (y < 0 || x < 0) {
481 +
      return 0;
482 +
   } else {
483 +
      va_list v;
484 +
      va_start(v, fmt);
485 +
      stbiw__writefv(s, fmt, v);
486 +
      va_end(v);
487 +
      stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono);
488 +
      return 1;
489 +
   }
490 +
}
491 +
492 +
static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data)
493 +
{
494 +
   if (comp != 4) {
495 +
      // write RGB bitmap
496 +
      int pad = (-x*3) & 3;
497 +
      return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
498 +
              "11 4 22 4" "4 44 22 444444",
499 +
              'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40,  // file header
500 +
               40, x,y, 1,24, 0,0,0,0,0,0);             // bitmap header
501 +
   } else {
502 +
      // RGBA bitmaps need a v4 header
503 +
      // use BI_BITFIELDS mode with 32bpp and alpha mask
504 +
      // (straight BI_RGB with alpha mask doesn't work in most readers)
505 +
      return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0,
506 +
         "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444",
507 +
         'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header
508 +
         108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header
509 +
   }
510 +
}
511 +
512 +
STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
513 +
{
514 +
   stbi__write_context s = { 0 };
515 +
   stbi__start_write_callbacks(&s, func, context);
516 +
   return stbi_write_bmp_core(&s, x, y, comp, data);
517 +
}
518 +
519 +
#ifndef STBI_WRITE_NO_STDIO
520 +
STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
521 +
{
522 +
   stbi__write_context s = { 0 };
523 +
   if (stbi__start_write_file(&s,filename)) {
524 +
      int r = stbi_write_bmp_core(&s, x, y, comp, data);
525 +
      stbi__end_write_file(&s);
526 +
      return r;
527 +
   } else
528 +
      return 0;
529 +
}
530 +
#endif //!STBI_WRITE_NO_STDIO
531 +
532 +
static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data)
533 +
{
534 +
   int has_alpha = (comp == 2 || comp == 4);
535 +
   int colorbytes = has_alpha ? comp-1 : comp;
536 +
   int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3
537 +
538 +
   if (y < 0 || x < 0)
539 +
      return 0;
540 +
541 +
   if (!stbi_write_tga_with_rle) {
542 +
      return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0,
543 +
         "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8);
544 +
   } else {
545 +
      int i,j,k;
546 +
      int jend, jdir;
547 +
548 +
      stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8);
549 +
550 +
      if (stbi__flip_vertically_on_write) {
551 +
         j = 0;
552 +
         jend = y;
553 +
         jdir = 1;
554 +
      } else {
555 +
         j = y-1;
556 +
         jend = -1;
557 +
         jdir = -1;
558 +
      }
559 +
      for (; j != jend; j += jdir) {
560 +
         unsigned char *row = (unsigned char *) data + j * x * comp;
561 +
         int len;
562 +
563 +
         for (i = 0; i < x; i += len) {
564 +
            unsigned char *begin = row + i * comp;
565 +
            int diff = 1;
566 +
            len = 1;
567 +
568 +
            if (i < x - 1) {
569 +
               ++len;
570 +
               diff = memcmp(begin, row + (i + 1) * comp, comp);
571 +
               if (diff) {
572 +
                  const unsigned char *prev = begin;
573 +
                  for (k = i + 2; k < x && len < 128; ++k) {
574 +
                     if (memcmp(prev, row + k * comp, comp)) {
575 +
                        prev += comp;
576 +
                        ++len;
577 +
                     } else {
578 +
                        --len;
579 +
                        break;
580 +
                     }
581 +
                  }
582 +
               } else {
583 +
                  for (k = i + 2; k < x && len < 128; ++k) {
584 +
                     if (!memcmp(begin, row + k * comp, comp)) {
585 +
                        ++len;
586 +
                     } else {
587 +
                        break;
588 +
                     }
589 +
                  }
590 +
               }
591 +
            }
592 +
593 +
            if (diff) {
594 +
               unsigned char header = STBIW_UCHAR(len - 1);
595 +
               stbiw__write1(s, header);
596 +
               for (k = 0; k < len; ++k) {
597 +
                  stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp);
598 +
               }
599 +
            } else {
600 +
               unsigned char header = STBIW_UCHAR(len - 129);
601 +
               stbiw__write1(s, header);
602 +
               stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
603 +
            }
604 +
         }
605 +
      }
606 +
      stbiw__write_flush(s);
607 +
   }
608 +
   return 1;
609 +
}
610 +
611 +
STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
612 +
{
613 +
   stbi__write_context s = { 0 };
614 +
   stbi__start_write_callbacks(&s, func, context);
615 +
   return stbi_write_tga_core(&s, x, y, comp, (void *) data);
616 +
}
617 +
618 +
#ifndef STBI_WRITE_NO_STDIO
619 +
STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
620 +
{
621 +
   stbi__write_context s = { 0 };
622 +
   if (stbi__start_write_file(&s,filename)) {
623 +
      int r = stbi_write_tga_core(&s, x, y, comp, (void *) data);
624 +
      stbi__end_write_file(&s);
625 +
      return r;
626 +
   } else
627 +
      return 0;
628 +
}
629 +
#endif
630 +
631 +
// *************************************************************************************************
632 +
// Radiance RGBE HDR writer
633 +
// by Baldur Karlsson
634 +
635 +
#define stbiw__max(a, b)  ((a) > (b) ? (a) : (b))
636 +
637 +
#ifndef STBI_WRITE_NO_STDIO
638 +
639 +
static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
640 +
{
641 +
   int exponent;
642 +
   float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2]));
643 +
644 +
   if (maxcomp < 1e-32f) {
645 +
      rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
646 +
   } else {
647 +
      float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp;
648 +
649 +
      rgbe[0] = (unsigned char)(linear[0] * normalize);
650 +
      rgbe[1] = (unsigned char)(linear[1] * normalize);
651 +
      rgbe[2] = (unsigned char)(linear[2] * normalize);
652 +
      rgbe[3] = (unsigned char)(exponent + 128);
653 +
   }
654 +
}
655 +
656 +
static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte)
657 +
{
658 +
   unsigned char lengthbyte = STBIW_UCHAR(length+128);
659 +
   STBIW_ASSERT(length+128 <= 255);
660 +
   s->func(s->context, &lengthbyte, 1);
661 +
   s->func(s->context, &databyte, 1);
662 +
}
663 +
664 +
static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data)
665 +
{
666 +
   unsigned char lengthbyte = STBIW_UCHAR(length);
667 +
   STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code
668 +
   s->func(s->context, &lengthbyte, 1);
669 +
   s->func(s->context, data, length);
670 +
}
671 +
672 +
static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline)
673 +
{
674 +
   unsigned char scanlineheader[4] = { 2, 2, 0, 0 };
675 +
   unsigned char rgbe[4];
676 +
   float linear[3];
677 +
   int x;
678 +
679 +
   scanlineheader[2] = (width&0xff00)>>8;
680 +
   scanlineheader[3] = (width&0x00ff);
681 +
682 +
   /* skip RLE for images too small or large */
683 +
   if (width < 8 || width >= 32768) {
684 +
      for (x=0; x < width; x++) {
685 +
         switch (ncomp) {
686 +
            case 4: /* fallthrough */
687 +
            case 3: linear[2] = scanline[x*ncomp + 2];
688 +
                    linear[1] = scanline[x*ncomp + 1];
689 +
                    linear[0] = scanline[x*ncomp + 0];
690 +
                    break;
691 +
            default:
692 +
                    linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
693 +
                    break;
694 +
         }
695 +
         stbiw__linear_to_rgbe(rgbe, linear);
696 +
         s->func(s->context, rgbe, 4);
697 +
      }
698 +
   } else {
699 +
      int c,r;
700 +
      /* encode into scratch buffer */
701 +
      for (x=0; x < width; x++) {
702 +
         switch(ncomp) {
703 +
            case 4: /* fallthrough */
704 +
            case 3: linear[2] = scanline[x*ncomp + 2];
705 +
                    linear[1] = scanline[x*ncomp + 1];
706 +
                    linear[0] = scanline[x*ncomp + 0];
707 +
                    break;
708 +
            default:
709 +
                    linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
710 +
                    break;
711 +
         }
712 +
         stbiw__linear_to_rgbe(rgbe, linear);
713 +
         scratch[x + width*0] = rgbe[0];
714 +
         scratch[x + width*1] = rgbe[1];
715 +
         scratch[x + width*2] = rgbe[2];
716 +
         scratch[x + width*3] = rgbe[3];
717 +
      }
718 +
719 +
      s->func(s->context, scanlineheader, 4);
720 +
721 +
      /* RLE each component separately */
722 +
      for (c=0; c < 4; c++) {
723 +
         unsigned char *comp = &scratch[width*c];
724 +
725 +
         x = 0;
726 +
         while (x < width) {
727 +
            // find first run
728 +
            r = x;
729 +
            while (r+2 < width) {
730 +
               if (comp[r] == comp[r+1] && comp[r] == comp[r+2])
731 +
                  break;
732 +
               ++r;
733 +
            }
734 +
            if (r+2 >= width)
735 +
               r = width;
736 +
            // dump up to first run
737 +
            while (x < r) {
738 +
               int len = r-x;
739 +
               if (len > 128) len = 128;
740 +
               stbiw__write_dump_data(s, len, &comp[x]);
741 +
               x += len;
742 +
            }
743 +
            // if there's a run, output it
744 +
            if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd
745 +
               // find next byte after run
746 +
               while (r < width && comp[r] == comp[x])
747 +
                  ++r;
748 +
               // output run up to r
749 +
               while (x < r) {
750 +
                  int len = r-x;
751 +
                  if (len > 127) len = 127;
752 +
                  stbiw__write_run_data(s, len, comp[x]);
753 +
                  x += len;
754 +
               }
755 +
            }
756 +
         }
757 +
      }
758 +
   }
759 +
}
760 +
761 +
static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data)
762 +
{
763 +
   if (y <= 0 || x <= 0 || data == NULL)
764 +
      return 0;
765 +
   else {
766 +
      // Each component is stored separately. Allocate scratch space for full output scanline.
767 +
      unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4);
768 +
      int i, len;
769 +
      char buffer[128];
770 +
      char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n";
771 +
      s->func(s->context, header, sizeof(header)-1);
772 +
773 +
#ifdef __STDC_LIB_EXT1__
774 +
      len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE=          1.0000000000000\n\n-Y %d +X %d\n", y, x);
775 +
#else
776 +
      len = sprintf(buffer, "EXPOSURE=          1.0000000000000\n\n-Y %d +X %d\n", y, x);
777 +
#endif
778 +
      s->func(s->context, buffer, len);
779 +
780 +
      for(i=0; i < y; i++)
781 +
         stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i));
782 +
      STBIW_FREE(scratch);
783 +
      return 1;
784 +
   }
785 +
}
786 +
787 +
STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data)
788 +
{
789 +
   stbi__write_context s = { 0 };
790 +
   stbi__start_write_callbacks(&s, func, context);
791 +
   return stbi_write_hdr_core(&s, x, y, comp, (float *) data);
792 +
}
793 +
794 +
STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
795 +
{
796 +
   stbi__write_context s = { 0 };
797 +
   if (stbi__start_write_file(&s,filename)) {
798 +
      int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data);
799 +
      stbi__end_write_file(&s);
800 +
      return r;
801 +
   } else
802 +
      return 0;
803 +
}
804 +
#endif // STBI_WRITE_NO_STDIO
805 +
806 +
807 +
//////////////////////////////////////////////////////////////////////////////
808 +
//
809 +
// PNG writer
810 +
//
811 +
812 +
#ifndef STBIW_ZLIB_COMPRESS
813 +
// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
814 +
#define stbiw__sbraw(a) ((int *) (void *) (a) - 2)
815 +
#define stbiw__sbm(a)   stbiw__sbraw(a)[0]
816 +
#define stbiw__sbn(a)   stbiw__sbraw(a)[1]
817 +
818 +
#define stbiw__sbneedgrow(a,n)  ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a))
819 +
#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0)
820 +
#define stbiw__sbgrow(a,n)  stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a)))
821 +
822 +
#define stbiw__sbpush(a, v)      (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v))
823 +
#define stbiw__sbcount(a)        ((a) ? stbiw__sbn(a) : 0)
824 +
#define stbiw__sbfree(a)         ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0)
825 +
826 +
static void *stbiw__sbgrowf(void **arr, int increment, int itemsize)
827 +
{
828 +
   int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1;
829 +
   void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2);
830 +
   STBIW_ASSERT(p);
831 +
   if (p) {
832 +
      if (!*arr) ((int *) p)[1] = 0;
833 +
      *arr = (void *) ((int *) p + 2);
834 +
      stbiw__sbm(*arr) = m;
835 +
   }
836 +
   return *arr;
837 +
}
838 +
839 +
static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
840 +
{
841 +
   while (*bitcount >= 8) {
842 +
      stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer));
843 +
      *bitbuffer >>= 8;
844 +
      *bitcount -= 8;
845 +
   }
846 +
   return data;
847 +
}
848 +
849 +
static int stbiw__zlib_bitrev(int code, int codebits)
850 +
{
851 +
   int res=0;
852 +
   while (codebits--) {
853 +
      res = (res << 1) | (code & 1);
854 +
      code >>= 1;
855 +
   }
856 +
   return res;
857 +
}
858 +
859 +
static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit)
860 +
{
861 +
   int i;
862 +
   for (i=0; i < limit && i < 258; ++i)
863 +
      if (a[i] != b[i]) break;
864 +
   return i;
865 +
}
866 +
867 +
static unsigned int stbiw__zhash(unsigned char *data)
868 +
{
869 +
   stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
870 +
   hash ^= hash << 3;
871 +
   hash += hash >> 5;
872 +
   hash ^= hash << 4;
873 +
   hash += hash >> 17;
874 +
   hash ^= hash << 25;
875 +
   hash += hash >> 6;
876 +
   return hash;
877 +
}
878 +
879 +
#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount))
880 +
#define stbiw__zlib_add(code,codebits) \
881 +
      (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush())
882 +
#define stbiw__zlib_huffa(b,c)  stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c)
883 +
// default huffman tables
884 +
#define stbiw__zlib_huff1(n)  stbiw__zlib_huffa(0x30 + (n), 8)
885 +
#define stbiw__zlib_huff2(n)  stbiw__zlib_huffa(0x190 + (n)-144, 9)
886 +
#define stbiw__zlib_huff3(n)  stbiw__zlib_huffa(0 + (n)-256,7)
887 +
#define stbiw__zlib_huff4(n)  stbiw__zlib_huffa(0xc0 + (n)-280,8)
888 +
#define stbiw__zlib_huff(n)  ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n))
889 +
#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n))
890 +
891 +
#define stbiw__ZHASH   16384
892 +
893 +
#endif // STBIW_ZLIB_COMPRESS
894 +
895 +
STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
896 +
{
897 +
#ifdef STBIW_ZLIB_COMPRESS
898 +
   // user provided a zlib compress implementation, use that
899 +
   return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality);
900 +
#else // use builtin
901 +
   static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 };
902 +
   static unsigned char  lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,  4,  5,  5,  5,  5,  0 };
903 +
   static unsigned short distc[]   = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 };
904 +
   static unsigned char  disteb[]  = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 };
905 +
   unsigned int bitbuf=0;
906 +
   int i,j, bitcount=0;
907 +
   unsigned char *out = NULL;
908 +
   unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**));
909 +
   if (hash_table == NULL)
910 +
      return NULL;
911 +
   if (quality < 5) quality = 5;
912 +
913 +
   stbiw__sbpush(out, 0x78);   // DEFLATE 32K window
914 +
   stbiw__sbpush(out, 0x5e);   // FLEVEL = 1
915 +
   stbiw__zlib_add(1,1);  // BFINAL = 1
916 +
   stbiw__zlib_add(1,2);  // BTYPE = 1 -- fixed huffman
917 +
918 +
   for (i=0; i < stbiw__ZHASH; ++i)
919 +
      hash_table[i] = NULL;
920 +
921 +
   i=0;
922 +
   while (i < data_len-3) {
923 +
      // hash next 3 bytes of data to be compressed
924 +
      int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3;
925 +
      unsigned char *bestloc = 0;
926 +
      unsigned char **hlist = hash_table[h];
927 +
      int n = stbiw__sbcount(hlist);
928 +
      for (j=0; j < n; ++j) {
929 +
         if (hlist[j]-data > i-32768) { // if entry lies within window
930 +
            int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i);
931 +
            if (d >= best) { best=d; bestloc=hlist[j]; }
932 +
         }
933 +
      }
934 +
      // when hash table entry is too long, delete half the entries
935 +
      if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) {
936 +
         STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
937 +
         stbiw__sbn(hash_table[h]) = quality;
938 +
      }
939 +
      stbiw__sbpush(hash_table[h],data+i);
940 +
941 +
      if (bestloc) {
942 +
         // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
943 +
         h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1);
944 +
         hlist = hash_table[h];
945 +
         n = stbiw__sbcount(hlist);
946 +
         for (j=0; j < n; ++j) {
947 +
            if (hlist[j]-data > i-32767) {
948 +
               int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1);
949 +
               if (e > best) { // if next match is better, bail on current match
950 +
                  bestloc = NULL;
951 +
                  break;
952 +
               }
953 +
            }
954 +
         }
955 +
      }
956 +
957 +
      if (bestloc) {
958 +
         int d = (int) (data+i - bestloc); // distance back
959 +
         STBIW_ASSERT(d <= 32767 && best <= 258);
960 +
         for (j=0; best > lengthc[j+1]-1; ++j);
961 +
         stbiw__zlib_huff(j+257);
962 +
         if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]);
963 +
         for (j=0; d > distc[j+1]-1; ++j);
964 +
         stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5);
965 +
         if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]);
966 +
         i += best;
967 +
      } else {
968 +
         stbiw__zlib_huffb(data[i]);
969 +
         ++i;
970 +
      }
971 +
   }
972 +
   // write out final bytes
973 +
   for (;i < data_len; ++i)
974 +
      stbiw__zlib_huffb(data[i]);
975 +
   stbiw__zlib_huff(256); // end of block
976 +
   // pad with 0 bits to byte boundary
977 +
   while (bitcount)
978 +
      stbiw__zlib_add(0,1);
979 +
980 +
   for (i=0; i < stbiw__ZHASH; ++i)
981 +
      (void) stbiw__sbfree(hash_table[i]);
982 +
   STBIW_FREE(hash_table);
983 +
984 +
   // store uncompressed instead if compression was worse
985 +
   if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) {
986 +
      stbiw__sbn(out) = 2;  // truncate to DEFLATE 32K window and FLEVEL = 1
987 +
      for (j = 0; j < data_len;) {
988 +
         int blocklen = data_len - j;
989 +
         if (blocklen > 32767) blocklen = 32767;
990 +
         stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression
991 +
         stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN
992 +
         stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8));
993 +
         stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN
994 +
         stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8));
995 +
         memcpy(out+stbiw__sbn(out), data+j, blocklen);
996 +
         stbiw__sbn(out) += blocklen;
997 +
         j += blocklen;
998 +
      }
999 +
   }
1000 +
1001 +
   {
1002 +
      // compute adler32 on input
1003 +
      unsigned int s1=1, s2=0;
1004 +
      int blocklen = (int) (data_len % 5552);
1005 +
      j=0;
1006 +
      while (j < data_len) {
1007 +
         for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; }
1008 +
         s1 %= 65521; s2 %= 65521;
1009 +
         j += blocklen;
1010 +
         blocklen = 5552;
1011 +
      }
1012 +
      stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8));
1013 +
      stbiw__sbpush(out, STBIW_UCHAR(s2));
1014 +
      stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8));
1015 +
      stbiw__sbpush(out, STBIW_UCHAR(s1));
1016 +
   }
1017 +
   *out_len = stbiw__sbn(out);
1018 +
   // make returned pointer freeable
1019 +
   STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len);
1020 +
   return (unsigned char *) stbiw__sbraw(out);
1021 +
#endif // STBIW_ZLIB_COMPRESS
1022 +
}
1023 +
1024 +
static unsigned int stbiw__crc32(unsigned char *buffer, int len)
1025 +
{
1026 +
#ifdef STBIW_CRC32
1027 +
    return STBIW_CRC32(buffer, len);
1028 +
#else
1029 +
   static unsigned int crc_table[256] =
1030 +
   {
1031 +
      0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
1032 +
      0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
1033 +
      0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
1034 +
      0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
1035 +
      0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
1036 +
      0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
1037 +
      0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
1038 +
      0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
1039 +
      0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
1040 +
      0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
1041 +
      0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
1042 +
      0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
1043 +
      0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
1044 +
      0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
1045 +
      0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
1046 +
      0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
1047 +
      0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
1048 +
      0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
1049 +
      0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
1050 +
      0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
1051 +
      0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
1052 +
      0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
1053 +
      0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
1054 +
      0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
1055 +
      0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
1056 +
      0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
1057 +
      0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
1058 +
      0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
1059 +
      0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
1060 +
      0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
1061 +
      0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
1062 +
      0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
1063 +
   };
1064 +
1065 +
   unsigned int crc = ~0u;
1066 +
   int i;
1067 +
   for (i=0; i < len; ++i)
1068 +
      crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
1069 +
   return ~crc;
1070 +
#endif
1071 +
}
1072 +
1073 +
#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4)
1074 +
#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
1075 +
#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3])
1076 +
1077 +
static void stbiw__wpcrc(unsigned char **data, int len)
1078 +
{
1079 +
   unsigned int crc = stbiw__crc32(*data - len - 4, len+4);
1080 +
   stbiw__wp32(*data, crc);
1081 +
}
1082 +
1083 +
static unsigned char stbiw__paeth(int a, int b, int c)
1084 +
{
1085 +
   int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c);
1086 +
   if (pa <= pb && pa <= pc) return STBIW_UCHAR(a);
1087 +
   if (pb <= pc) return STBIW_UCHAR(b);
1088 +
   return STBIW_UCHAR(c);
1089 +
}
1090 +
1091 +
// @OPTIMIZE: provide an option that always forces left-predict or paeth predict
1092 +
static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer)
1093 +
{
1094 +
   static int mapping[] = { 0,1,2,3,4 };
1095 +
   static int firstmap[] = { 0,1,0,5,6 };
1096 +
   int *mymap = (y != 0) ? mapping : firstmap;
1097 +
   int i;
1098 +
   int type = mymap[filter_type];
1099 +
   unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y);
1100 +
   int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
1101 +
1102 +
   if (type==0) {
1103 +
      memcpy(line_buffer, z, width*n);
1104 +
      return;
1105 +
   }
1106 +
1107 +
   // first loop isn't optimized since it's just one pixel
1108 +
   for (i = 0; i < n; ++i) {
1109 +
      switch (type) {
1110 +
         case 1: line_buffer[i] = z[i]; break;
1111 +
         case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break;
1112 +
         case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break;
1113 +
         case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break;
1114 +
         case 5: line_buffer[i] = z[i]; break;
1115 +
         case 6: line_buffer[i] = z[i]; break;
1116 +
      }
1117 +
   }
1118 +
   switch (type) {
1119 +
      case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break;
1120 +
      case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break;
1121 +
      case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break;
1122 +
      case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break;
1123 +
      case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break;
1124 +
      case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break;
1125 +
   }
1126 +
}
1127 +
1128 +
STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
1129 +
{
1130 +
   int force_filter = stbi_write_force_png_filter;
1131 +
   int ctype[5] = { -1, 0, 4, 2, 6 };
1132 +
   unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
1133 +
   unsigned char *out,*o, *filt, *zlib;
1134 +
   signed char *line_buffer;
1135 +
   int j,zlen;
1136 +
1137 +
   if (stride_bytes == 0)
1138 +
      stride_bytes = x * n;
1139 +
1140 +
   if (force_filter >= 5) {
1141 +
      force_filter = -1;
1142 +
   }
1143 +
1144 +
   filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0;
1145 +
   line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; }
1146 +
   for (j=0; j < y; ++j) {
1147 +
      int filter_type;
1148 +
      if (force_filter > -1) {
1149 +
         filter_type = force_filter;
1150 +
         stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer);
1151 +
      } else { // Estimate the best filter by running through all of them:
1152 +
         int best_filter = 0, best_filter_val = 0x7fffffff, est, i;
1153 +
         for (filter_type = 0; filter_type < 5; filter_type++) {
1154 +
            stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer);
1155 +
1156 +
            // Estimate the entropy of the line using this filter; the less, the better.
1157 +
            est = 0;
1158 +
            for (i = 0; i < x*n; ++i) {
1159 +
               est += abs((signed char) line_buffer[i]);
1160 +
            }
1161 +
            if (est < best_filter_val) {
1162 +
               best_filter_val = est;
1163 +
               best_filter = filter_type;
1164 +
            }
1165 +
         }
1166 +
         if (filter_type != best_filter) {  // If the last iteration already got us the best filter, don't redo it
1167 +
            stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer);
1168 +
            filter_type = best_filter;
1169 +
         }
1170 +
      }
1171 +
      // when we get here, filter_type contains the filter type, and line_buffer contains the data
1172 +
      filt[j*(x*n+1)] = (unsigned char) filter_type;
1173 +
      STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n);
1174 +
   }
1175 +
   STBIW_FREE(line_buffer);
1176 +
   zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level);
1177 +
   STBIW_FREE(filt);
1178 +
   if (!zlib) return 0;
1179 +
1180 +
   // each tag requires 12 bytes of overhead
1181 +
   out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12);
1182 +
   if (!out) return 0;
1183 +
   *out_len = 8 + 12+13 + 12+zlen + 12;
1184 +
1185 +
   o=out;
1186 +
   STBIW_MEMMOVE(o,sig,8); o+= 8;
1187 +
   stbiw__wp32(o, 13); // header length
1188 +
   stbiw__wptag(o, "IHDR");
1189 +
   stbiw__wp32(o, x);
1190 +
   stbiw__wp32(o, y);
1191 +
   *o++ = 8;
1192 +
   *o++ = STBIW_UCHAR(ctype[n]);
1193 +
   *o++ = 0;
1194 +
   *o++ = 0;
1195 +
   *o++ = 0;
1196 +
   stbiw__wpcrc(&o,13);
1197 +
1198 +
   stbiw__wp32(o, zlen);
1199 +
   stbiw__wptag(o, "IDAT");
1200 +
   STBIW_MEMMOVE(o, zlib, zlen);
1201 +
   o += zlen;
1202 +
   STBIW_FREE(zlib);
1203 +
   stbiw__wpcrc(&o, zlen);
1204 +
1205 +
   stbiw__wp32(o,0);
1206 +
   stbiw__wptag(o, "IEND");
1207 +
   stbiw__wpcrc(&o,0);
1208 +
1209 +
   STBIW_ASSERT(o == out + *out_len);
1210 +
1211 +
   return out;
1212 +
}
1213 +
1214 +
#ifndef STBI_WRITE_NO_STDIO
1215 +
STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
1216 +
{
1217 +
   FILE *f;
1218 +
   int len;
1219 +
   unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
1220 +
   if (png == NULL) return 0;
1221 +
1222 +
   f = stbiw__fopen(filename, "wb");
1223 +
   if (!f) { STBIW_FREE(png); return 0; }
1224 +
   fwrite(png, 1, len, f);
1225 +
   fclose(f);
1226 +
   STBIW_FREE(png);
1227 +
   return 1;
1228 +
}
1229 +
#endif
1230 +
1231 +
STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
1232 +
{
1233 +
   int len;
1234 +
   unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
1235 +
   if (png == NULL) return 0;
1236 +
   func(context, png, len);
1237 +
   STBIW_FREE(png);
1238 +
   return 1;
1239 +
}
1240 +
1241 +
1242 +
/* ***************************************************************************
1243 +
 *
1244 +
 * JPEG writer
1245 +
 *
1246 +
 * This is based on Jon Olick's jo_jpeg.cpp:
1247 +
 * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html
1248 +
 */
1249 +
1250 +
static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,
1251 +
      24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 };
1252 +
1253 +
static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) {
1254 +
   int bitBuf = *bitBufP, bitCnt = *bitCntP;
1255 +
   bitCnt += bs[1];
1256 +
   bitBuf |= bs[0] << (24 - bitCnt);
1257 +
   while(bitCnt >= 8) {
1258 +
      unsigned char c = (bitBuf >> 16) & 255;
1259 +
      stbiw__putc(s, c);
1260 +
      if(c == 255) {
1261 +
         stbiw__putc(s, 0);
1262 +
      }
1263 +
      bitBuf <<= 8;
1264 +
      bitCnt -= 8;
1265 +
   }
1266 +
   *bitBufP = bitBuf;
1267 +
   *bitCntP = bitCnt;
1268 +
}
1269 +
1270 +
static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) {
1271 +
   float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p;
1272 +
   float z1, z2, z3, z4, z5, z11, z13;
1273 +
1274 +
   float tmp0 = d0 + d7;
1275 +
   float tmp7 = d0 - d7;
1276 +
   float tmp1 = d1 + d6;
1277 +
   float tmp6 = d1 - d6;
1278 +
   float tmp2 = d2 + d5;
1279 +
   float tmp5 = d2 - d5;
1280 +
   float tmp3 = d3 + d4;
1281 +
   float tmp4 = d3 - d4;
1282 +
1283 +
   // Even part
1284 +
   float tmp10 = tmp0 + tmp3;   // phase 2
1285 +
   float tmp13 = tmp0 - tmp3;
1286 +
   float tmp11 = tmp1 + tmp2;
1287 +
   float tmp12 = tmp1 - tmp2;
1288 +
1289 +
   d0 = tmp10 + tmp11;       // phase 3
1290 +
   d4 = tmp10 - tmp11;
1291 +
1292 +
   z1 = (tmp12 + tmp13) * 0.707106781f; // c4
1293 +
   d2 = tmp13 + z1;       // phase 5
1294 +
   d6 = tmp13 - z1;
1295 +
1296 +
   // Odd part
1297 +
   tmp10 = tmp4 + tmp5;       // phase 2
1298 +
   tmp11 = tmp5 + tmp6;
1299 +
   tmp12 = tmp6 + tmp7;
1300 +
1301 +
   // The rotator is modified from fig 4-8 to avoid extra negations.
1302 +
   z5 = (tmp10 - tmp12) * 0.382683433f; // c6
1303 +
   z2 = tmp10 * 0.541196100f + z5; // c2-c6
1304 +
   z4 = tmp12 * 1.306562965f + z5; // c2+c6
1305 +
   z3 = tmp11 * 0.707106781f; // c4
1306 +
1307 +
   z11 = tmp7 + z3;      // phase 5
1308 +
   z13 = tmp7 - z3;
1309 +
1310 +
   *d5p = z13 + z2;         // phase 6
1311 +
   *d3p = z13 - z2;
1312 +
   *d1p = z11 + z4;
1313 +
   *d7p = z11 - z4;
1314 +
1315 +
   *d0p = d0;  *d2p = d2;  *d4p = d4;  *d6p = d6;
1316 +
}
1317 +
1318 +
static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) {
1319 +
   int tmp1 = val < 0 ? -val : val;
1320 +
   val = val < 0 ? val-1 : val;
1321 +
   bits[1] = 1;
1322 +
   while(tmp1 >>= 1) {
1323 +
      ++bits[1];
1324 +
   }
1325 +
   bits[0] = val & ((1<<bits[1])-1);
1326 +
}
1327 +
1328 +
static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, int du_stride, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
1329 +
   const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
1330 +
   const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };
1331 +
   int dataOff, i, j, n, diff, end0pos, x, y;
1332 +
   int DU[64];
1333 +
1334 +
   // DCT rows
1335 +
   for(dataOff=0, n=du_stride*8; dataOff<n; dataOff+=du_stride) {
1336 +
      stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+1], &CDU[dataOff+2], &CDU[dataOff+3], &CDU[dataOff+4], &CDU[dataOff+5], &CDU[dataOff+6], &CDU[dataOff+7]);
1337 +
   }
1338 +
   // DCT columns
1339 +
   for(dataOff=0; dataOff<8; ++dataOff) {
1340 +
      stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+du_stride], &CDU[dataOff+du_stride*2], &CDU[dataOff+du_stride*3], &CDU[dataOff+du_stride*4],
1341 +
                     &CDU[dataOff+du_stride*5], &CDU[dataOff+du_stride*6], &CDU[dataOff+du_stride*7]);
1342 +
   }
1343 +
   // Quantize/descale/zigzag the coefficients
1344 +
   for(y = 0, j=0; y < 8; ++y) {
1345 +
      for(x = 0; x < 8; ++x,++j) {
1346 +
         float v;
1347 +
         i = y*du_stride+x;
1348 +
         v = CDU[i]*fdtbl[j];
1349 +
         // DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
1350 +
         // ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
1351 +
         DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
1352 +
      }
1353 +
   }
1354 +
1355 +
   // Encode DC
1356 +
   diff = DU[0] - DC;
1357 +
   if (diff == 0) {
1358 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]);
1359 +
   } else {
1360 +
      unsigned short bits[2];
1361 +
      stbiw__jpg_calcBits(diff, bits);
1362 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]);
1363 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
1364 +
   }
1365 +
   // Encode ACs
1366 +
   end0pos = 63;
1367 +
   for(; (end0pos>0)&&(DU[end0pos]==0); --end0pos) {
1368 +
   }
1369 +
   // end0pos = first element in reverse order !=0
1370 +
   if(end0pos == 0) {
1371 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
1372 +
      return DU[0];
1373 +
   }
1374 +
   for(i = 1; i <= end0pos; ++i) {
1375 +
      int startpos = i;
1376 +
      int nrzeroes;
1377 +
      unsigned short bits[2];
1378 +
      for (; DU[i]==0 && i<=end0pos; ++i) {
1379 +
      }
1380 +
      nrzeroes = i-startpos;
1381 +
      if ( nrzeroes >= 16 ) {
1382 +
         int lng = nrzeroes>>4;
1383 +
         int nrmarker;
1384 +
         for (nrmarker=1; nrmarker <= lng; ++nrmarker)
1385 +
            stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes);
1386 +
         nrzeroes &= 15;
1387 +
      }
1388 +
      stbiw__jpg_calcBits(DU[i], bits);
1389 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]);
1390 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
1391 +
   }
1392 +
   if(end0pos != 63) {
1393 +
      stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
1394 +
   }
1395 +
   return DU[0];
1396 +
}
1397 +
1398 +
static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) {
1399 +
   // Constants that don't pollute global namespace
1400 +
   static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};
1401 +
   static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
1402 +
   static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d};
1403 +
   static const unsigned char std_ac_luminance_values[] = {
1404 +
      0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
1405 +
      0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
1406 +
      0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
1407 +
      0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
1408 +
      0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
1409 +
      0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
1410 +
      0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
1411 +
   };
1412 +
   static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
1413 +
   static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
1414 +
   static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};
1415 +
   static const unsigned char std_ac_chrominance_values[] = {
1416 +
      0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
1417 +
      0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
1418 +
      0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
1419 +
      0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
1420 +
      0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
1421 +
      0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
1422 +
      0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
1423 +
   };
1424 +
   // Huffman tables
1425 +
   static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}};
1426 +
   static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}};
1427 +
   static const unsigned short YAC_HT[256][2] = {
1428 +
      {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1429 +
      {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1430 +
      {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1431 +
      {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1432 +
      {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1433 +
      {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1434 +
      {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1435 +
      {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1436 +
      {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1437 +
      {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1438 +
      {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1439 +
      {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1440 +
      {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1441 +
      {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1442 +
      {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0},
1443 +
      {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
1444 +
   };
1445 +
   static const unsigned short UVAC_HT[256][2] = {
1446 +
      {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1447 +
      {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1448 +
      {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1449 +
      {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1450 +
      {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1451 +
      {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1452 +
      {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1453 +
      {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1454 +
      {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1455 +
      {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1456 +
      {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1457 +
      {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1458 +
      {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1459 +
      {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
1460 +
      {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0},
1461 +
      {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
1462 +
   };
1463 +
   static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,
1464 +
                             37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99};
1465 +
   static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,
1466 +
                              99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99};
1467 +
   static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
1468 +
                                 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };
1469 +
1470 +
   int row, col, i, k, subsample;
1471 +
   float fdtbl_Y[64], fdtbl_UV[64];
1472 +
   unsigned char YTable[64], UVTable[64];
1473 +
1474 +
   if(!data || !width || !height || comp > 4 || comp < 1) {
1475 +
      return 0;
1476 +
   }
1477 +
1478 +
   quality = quality ? quality : 90;
1479 +
   subsample = quality <= 90 ? 1 : 0;
1480 +
   quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
1481 +
   quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
1482 +
1483 +
   for(i = 0; i < 64; ++i) {
1484 +
      int uvti, yti = (YQT[i]*quality+50)/100;
1485 +
      YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti);
1486 +
      uvti = (UVQT[i]*quality+50)/100;
1487 +
      UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti);
1488 +
   }
1489 +
1490 +
   for(row = 0, k = 0; row < 8; ++row) {
1491 +
      for(col = 0; col < 8; ++col, ++k) {
1492 +
         fdtbl_Y[k]  = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
1493 +
         fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
1494 +
      }
1495 +
   }
1496 +
1497 +
   // Write Headers
1498 +
   {
1499 +
      static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 };
1500 +
      static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
1501 +
      const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width),
1502 +
                                      3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
1503 +
      s->func(s->context, (void*)head0, sizeof(head0));
1504 +
      s->func(s->context, (void*)YTable, sizeof(YTable));
1505 +
      stbiw__putc(s, 1);
1506 +
      s->func(s->context, UVTable, sizeof(UVTable));
1507 +
      s->func(s->context, (void*)head1, sizeof(head1));
1508 +
      s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1);
1509 +
      s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values));
1510 +
      stbiw__putc(s, 0x10); // HTYACinfo
1511 +
      s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1);
1512 +
      s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values));
1513 +
      stbiw__putc(s, 1); // HTUDCinfo
1514 +
      s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1);
1515 +
      s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values));
1516 +
      stbiw__putc(s, 0x11); // HTUACinfo
1517 +
      s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1);
1518 +
      s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values));
1519 +
      s->func(s->context, (void*)head2, sizeof(head2));
1520 +
   }
1521 +
1522 +
   // Encode 8x8 macroblocks
1523 +
   {
1524 +
      static const unsigned short fillBits[] = {0x7F, 7};
1525 +
      int DCY=0, DCU=0, DCV=0;
1526 +
      int bitBuf=0, bitCnt=0;
1527 +
      // comp == 2 is grey+alpha (alpha is ignored)
1528 +
      int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0;
1529 +
      const unsigned char *dataR = (const unsigned char *)data;
1530 +
      const unsigned char *dataG = dataR + ofsG;
1531 +
      const unsigned char *dataB = dataR + ofsB;
1532 +
      int x, y, pos;
1533 +
      if(subsample) {
1534 +
         for(y = 0; y < height; y += 16) {
1535 +
            for(x = 0; x < width; x += 16) {
1536 +
               float Y[256], U[256], V[256];
1537 +
               for(row = y, pos = 0; row < y+16; ++row) {
1538 +
                  // row >= height => use last input row
1539 +
                  int clamped_row = (row < height) ? row : height - 1;
1540 +
                  int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
1541 +
                  for(col = x; col < x+16; ++col, ++pos) {
1542 +
                     // if col >= width => use pixel from last input column
1543 +
                     int p = base_p + ((col < width) ? col : (width-1))*comp;
1544 +
                     float r = dataR[p], g = dataG[p], b = dataB[p];
1545 +
                     Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
1546 +
                     U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
1547 +
                     V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
1548 +
                  }
1549 +
               }
1550 +
               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0,   16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1551 +
               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8,   16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1552 +
               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1553 +
               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
1554 +
1555 +
               // subsample U,V
1556 +
               {
1557 +
                  float subU[64], subV[64];
1558 +
                  int yy, xx;
1559 +
                  for(yy = 0, pos = 0; yy < 8; ++yy) {
1560 +
                     for(xx = 0; xx < 8; ++xx, ++pos) {
1561 +
                        int j = yy*32+xx*2;
1562 +
                        subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
1563 +
                        subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
1564 +
                     }
1565 +
                  }
1566 +
                  DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
1567 +
                  DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
1568 +
               }
1569 +
            }
1570 +
         }
1571 +
      } else {
1572 +
         for(y = 0; y < height; y += 8) {
1573 +
            for(x = 0; x < width; x += 8) {
1574 +
               float Y[64], U[64], V[64];
1575 +
               for(row = y, pos = 0; row < y+8; ++row) {
1576 +
                  // row >= height => use last input row
1577 +
                  int clamped_row = (row < height) ? row : height - 1;
1578 +
                  int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
1579 +
                  for(col = x; col < x+8; ++col, ++pos) {
1580 +
                     // if col >= width => use pixel from last input column
1581 +
                     int p = base_p + ((col < width) ? col : (width-1))*comp;
1582 +
                     float r = dataR[p], g = dataG[p], b = dataB[p];
1583 +
                     Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
1584 +
                     U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
1585 +
                     V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
1586 +
                  }
1587 +
               }
1588 +
1589 +
               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y,  DCY, YDC_HT, YAC_HT);
1590 +
               DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
1591 +
               DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
1592 +
            }
1593 +
         }
1594 +
      }
1595 +
1596 +
      // Do the bit alignment of the EOI marker
1597 +
      stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits);
1598 +
   }
1599 +
1600 +
   // EOI
1601 +
   stbiw__putc(s, 0xFF);
1602 +
   stbiw__putc(s, 0xD9);
1603 +
1604 +
   return 1;
1605 +
}
1606 +
1607 +
STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality)
1608 +
{
1609 +
   stbi__write_context s = { 0 };
1610 +
   stbi__start_write_callbacks(&s, func, context);
1611 +
   return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality);
1612 +
}
1613 +
1614 +
1615 +
#ifndef STBI_WRITE_NO_STDIO
1616 +
STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)
1617 +
{
1618 +
   stbi__write_context s = { 0 };
1619 +
   if (stbi__start_write_file(&s,filename)) {
1620 +
      int r = stbi_write_jpg_core(&s, x, y, comp, data, quality);
1621 +
      stbi__end_write_file(&s);
1622 +
      return r;
1623 +
   } else
1624 +
      return 0;
1625 +
}
1626 +
#endif
1627 +
1628 +
#endif // STB_IMAGE_WRITE_IMPLEMENTATION
1629 +
1630 +
/* Revision history
1631 +
      1.16  (2021-07-11)
1632 +
             make Deflate code emit uncompressed blocks when it would otherwise expand
1633 +
             support writing BMPs with alpha channel
1634 +
      1.15  (2020-07-13) unknown
1635 +
      1.14  (2020-02-02) updated JPEG writer to downsample chroma channels
1636 +
      1.13
1637 +
      1.12
1638 +
      1.11  (2019-08-11)
1639 +
1640 +
      1.10  (2019-02-07)
1641 +
             support utf8 filenames in Windows; fix warnings and platform ifdefs
1642 +
      1.09  (2018-02-11)
1643 +
             fix typo in zlib quality API, improve STB_I_W_STATIC in C++
1644 +
      1.08  (2018-01-29)
1645 +
             add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter
1646 +
      1.07  (2017-07-24)
1647 +
             doc fix
1648 +
      1.06 (2017-07-23)
1649 +
             writing JPEG (using Jon Olick's code)
1650 +
      1.05   ???
1651 +
      1.04 (2017-03-03)
1652 +
             monochrome BMP expansion
1653 +
      1.03   ???
1654 +
      1.02 (2016-04-02)
1655 +
             avoid allocating large structures on the stack
1656 +
      1.01 (2016-01-16)
1657 +
             STBIW_REALLOC_SIZED: support allocators with no realloc support
1658 +
             avoid race-condition in crc initialization
1659 +
             minor compile issues
1660 +
      1.00 (2015-09-14)
1661 +
             installable file IO function
1662 +
      0.99 (2015-09-13)
1663 +
             warning fixes; TGA rle support
1664 +
      0.98 (2015-04-08)
1665 +
             added STBIW_MALLOC, STBIW_ASSERT etc
1666 +
      0.97 (2015-01-18)
1667 +
             fixed HDR asserts, rewrote HDR rle logic
1668 +
      0.96 (2015-01-17)
1669 +
             add HDR output
1670 +
             fix monochrome BMP
1671 +
      0.95 (2014-08-17)
1672 +
             add monochrome TGA output
1673 +
      0.94 (2014-05-31)
1674 +
             rename private functions to avoid conflicts with stb_image.h
1675 +
      0.93 (2014-05-27)
1676 +
             warning fixes
1677 +
      0.92 (2010-08-01)
1678 +
             casts to unsigned char to fix warnings
1679 +
      0.91 (2010-07-17)
1680 +
             first public release
1681 +
      0.90   first internal release
1682 +
*/
1683 +
1684 +
/*
1685 +
------------------------------------------------------------------------------
1686 +
This software is available under 2 licenses -- choose whichever you prefer.
1687 +
------------------------------------------------------------------------------
1688 +
ALTERNATIVE A - MIT License
1689 +
Copyright (c) 2017 Sean Barrett
1690 +
Permission is hereby granted, free of charge, to any person obtaining a copy of
1691 +
this software and associated documentation files (the "Software"), to deal in
1692 +
the Software without restriction, including without limitation the rights to
1693 +
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1694 +
of the Software, and to permit persons to whom the Software is furnished to do
1695 +
so, subject to the following conditions:
1696 +
The above copyright notice and this permission notice shall be included in all
1697 +
copies or substantial portions of the Software.
1698 +
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1699 +
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1700 +
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1701 +
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1702 +
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1703 +
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1704 +
SOFTWARE.
1705 +
------------------------------------------------------------------------------
1706 +
ALTERNATIVE B - Public Domain (www.unlicense.org)
1707 +
This is free and unencumbered software released into the public domain.
1708 +
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
1709 +
software, either in source code form or as a compiled binary, for any purpose,
1710 +
commercial or non-commercial, and by any means.
1711 +
In jurisdictions that recognize copyright laws, the author or authors of this
1712 +
software dedicate any and all copyright interest in the software to the public
1713 +
domain. We make this dedication for the benefit of the public at large and to
1714 +
the detriment of our heirs and successors. We intend this dedication to be an
1715 +
overt act of relinquishment in perpetuity of all present and future rights to
1716 +
this software under copyright law.
1717 +
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1718 +
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1719 +
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1720 +
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1721 +
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1722 +
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1723 +
------------------------------------------------------------------------------
1724 +
*/
added src/extern/ymfm/LICENSE +29 −0
1 +
BSD 3-Clause License
2 +
3 +
Copyright (c) 2021, Aaron Giles
4 +
All rights reserved.
5 +
6 +
Redistribution and use in source and binary forms, with or without
7 +
modification, are permitted provided that the following conditions are met:
8 +
9 +
1. Redistributions of source code must retain the above copyright notice, this
10 +
   list of conditions and the following disclaimer.
11 +
12 +
2. Redistributions in binary form must reproduce the above copyright notice,
13 +
   this list of conditions and the following disclaimer in the documentation
14 +
   and/or other materials provided with the distribution.
15 +
16 +
3. Neither the name of the copyright holder nor the names of its
17 +
   contributors may be used to endorse or promote products derived from
18 +
   this software without specific prior written permission.
19 +
20 +
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
added src/extern/ymfm/src/ymfm.h +566 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#ifndef YMFM_H
32 +
#define YMFM_H
33 +
34 +
#pragma once
35 +
36 +
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
37 +
 #define _CRT_SECURE_NO_WARNINGS
38 +
#endif
39 +
40 +
#include <cassert>
41 +
#include <cstdint>
42 +
#include <cstdio>
43 +
#include <cstring>
44 +
#include <algorithm>
45 +
#include <array>
46 +
#include <memory>
47 +
#include <string>
48 +
#include <vector>
49 +
50 +
namespace ymfm
51 +
{
52 +
53 +
//*********************************************************
54 +
//  DEBUGGING
55 +
//*********************************************************
56 +
57 +
class debug
58 +
{
59 +
public:
60 +
	// masks to help isolate specific channels
61 +
	static constexpr uint32_t GLOBAL_FM_CHANNEL_MASK = 0xffffffff;
62 +
	static constexpr uint32_t GLOBAL_ADPCM_A_CHANNEL_MASK = 0xffffffff;
63 +
	static constexpr uint32_t GLOBAL_ADPCM_B_CHANNEL_MASK = 0xffffffff;
64 +
	static constexpr uint32_t GLOBAL_PCM_CHANNEL_MASK = 0xffffffff;
65 +
66 +
	// types of logging
67 +
	static constexpr bool LOG_FM_WRITES = false;
68 +
	static constexpr bool LOG_KEYON_EVENTS = false;
69 +
	static constexpr bool LOG_UNEXPECTED_READ_WRITES = false;
70 +
71 +
	// helpers to write based on the log type
72 +
	template<typename... Params> static void log_fm_write(Params &&... args) { if (LOG_FM_WRITES) log(args...); }
73 +
	template<typename... Params> static void log_keyon(Params &&... args) { if (LOG_KEYON_EVENTS) log(args...); }
74 +
	template<typename... Params> static void log_unexpected_read_write(Params &&... args) { if  (LOG_UNEXPECTED_READ_WRITES) log(args...); }
75 +
76 +
	// downstream helper to output log data; defaults to printf
77 +
	template<typename... Params> static void log(Params &&... args) { printf(args...); }
78 +
};
79 +
80 +
81 +
82 +
//*********************************************************
83 +
//  GLOBAL HELPERS
84 +
//*********************************************************
85 +
86 +
//-------------------------------------------------
87 +
//  bitfield - extract a bitfield from the given
88 +
//  value, starting at bit 'start' for a length of
89 +
//  'length' bits
90 +
//-------------------------------------------------
91 +
92 +
inline uint32_t bitfield(uint32_t value, int start, int length = 1)
93 +
{
94 +
	return (value >> start) & ((1 << length) - 1);
95 +
}
96 +
97 +
98 +
//-------------------------------------------------
99 +
//  clamp - clamp between the minimum and maximum
100 +
//  values provided
101 +
//-------------------------------------------------
102 +
103 +
inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval)
104 +
{
105 +
	if (value < minval)
106 +
		return minval;
107 +
	if (value > maxval)
108 +
		return maxval;
109 +
	return value;
110 +
}
111 +
112 +
113 +
//-------------------------------------------------
114 +
//  count_leading_zeros - return the number of
115 +
//  leading zeros in a 32-bit value; CPU-optimized
116 +
//  versions for various architectures are included
117 +
//  below
118 +
//-------------------------------------------------
119 +
120 +
#if defined(__GNUC__)
121 +
122 +
inline uint8_t count_leading_zeros(uint32_t value)
123 +
{
124 +
	if (value == 0)
125 +
		return 32;
126 +
	return __builtin_clz(value);
127 +
}
128 +
129 +
#elif defined(_MSC_VER)
130 +
131 +
inline uint8_t count_leading_zeros(uint32_t value)
132 +
{
133 +
	unsigned long index;
134 +
	return _BitScanReverse(&index, value) ? uint8_t(31U - index) : 32U;
135 +
}
136 +
137 +
#else
138 +
139 +
inline uint8_t count_leading_zeros(uint32_t value)
140 +
{
141 +
	if (value == 0)
142 +
		return 32;
143 +
	uint8_t count;
144 +
	for (count = 0; int32_t(value) >= 0; count++)
145 +
		value <<= 1;
146 +
	return count;
147 +
}
148 +
149 +
#endif
150 +
151 +
152 +
// Many of the Yamaha FM chips emit a floating-point value, which is sent to
153 +
// a DAC for processing. The exact format of this floating-point value is
154 +
// documented below. This description only makes sense if the "internal"
155 +
// format treats sign as 1=positive and 0=negative, so the helpers below
156 +
// presume that.
157 +
//
158 +
// Internal OPx data      16-bit signed data     Exp Sign Mantissa
159 +
// =================      =================      === ==== ========
160 +
// 1 1xxxxxxxx------  ->  0 1xxxxxxxx------  ->  111   1  1xxxxxxx
161 +
// 1 01xxxxxxxx-----  ->  0 01xxxxxxxx-----  ->  110   1  1xxxxxxx
162 +
// 1 001xxxxxxxx----  ->  0 001xxxxxxxx----  ->  101   1  1xxxxxxx
163 +
// 1 0001xxxxxxxx---  ->  0 0001xxxxxxxx---  ->  100   1  1xxxxxxx
164 +
// 1 00001xxxxxxxx--  ->  0 00001xxxxxxxx--  ->  011   1  1xxxxxxx
165 +
// 1 000001xxxxxxxx-  ->  0 000001xxxxxxxx-  ->  010   1  1xxxxxxx
166 +
// 1 000000xxxxxxxxx  ->  0 000000xxxxxxxxx  ->  001   1  xxxxxxxx
167 +
// 0 111111xxxxxxxxx  ->  1 111111xxxxxxxxx  ->  001   0  xxxxxxxx
168 +
// 0 111110xxxxxxxx-  ->  1 111110xxxxxxxx-  ->  010   0  0xxxxxxx
169 +
// 0 11110xxxxxxxx--  ->  1 11110xxxxxxxx--  ->  011   0  0xxxxxxx
170 +
// 0 1110xxxxxxxx---  ->  1 1110xxxxxxxx---  ->  100   0  0xxxxxxx
171 +
// 0 110xxxxxxxx----  ->  1 110xxxxxxxx----  ->  101   0  0xxxxxxx
172 +
// 0 10xxxxxxxx-----  ->  1 10xxxxxxxx-----  ->  110   0  0xxxxxxx
173 +
// 0 0xxxxxxxx------  ->  1 0xxxxxxxx------  ->  111   0  0xxxxxxx
174 +
175 +
//-------------------------------------------------
176 +
//  encode_fp - given a 32-bit signed input value
177 +
//  convert it to a signed 3.10 floating-point
178 +
//  value
179 +
//-------------------------------------------------
180 +
181 +
inline int16_t encode_fp(int32_t value)
182 +
{
183 +
	// handle overflows first
184 +
	if (value < -32768)
185 +
		return (7 << 10) | 0x000;
186 +
	if (value > 32767)
187 +
		return (7 << 10) | 0x3ff;
188 +
189 +
	// we need to count the number of leading sign bits after the sign
190 +
	// we can use count_leading_zeros if we invert negative values
191 +
	int32_t scanvalue = value ^ (int32_t(value) >> 31);
192 +
193 +
	// exponent is related to the number of leading bits starting from bit 14
194 +
	int exponent = 7 - count_leading_zeros(scanvalue << 17);
195 +
196 +
	// smallest exponent value allowed is 1
197 +
	exponent = std::max(exponent, 1);
198 +
199 +
	// mantissa
200 +
	int32_t mantissa = value >> (exponent - 1);
201 +
202 +
	// assemble into final form, inverting the sign
203 +
	return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200;
204 +
}
205 +
206 +
207 +
//-------------------------------------------------
208 +
//  decode_fp - given a 3.10 floating-point value,
209 +
//  convert it to a signed 16-bit value
210 +
//-------------------------------------------------
211 +
212 +
inline int16_t decode_fp(int16_t value)
213 +
{
214 +
	// invert the sign and the exponent
215 +
	value ^= 0x1e00;
216 +
217 +
	// shift mantissa up to 16 bits then apply inverted exponent
218 +
	return int16_t(value << 6) >> bitfield(value, 10, 3);
219 +
}
220 +
221 +
222 +
//-------------------------------------------------
223 +
//  roundtrip_fp - compute the result of a round
224 +
//  trip through the encode/decode process above
225 +
//-------------------------------------------------
226 +
227 +
inline int16_t roundtrip_fp(int32_t value)
228 +
{
229 +
	// handle overflows first
230 +
	if (value < -32768)
231 +
		return -32768;
232 +
	if (value > 32767)
233 +
		return 32767;
234 +
235 +
	// we need to count the number of leading sign bits after the sign
236 +
	// we can use count_leading_zeros if we invert negative values
237 +
	int32_t scanvalue = value ^ (int32_t(value) >> 31);
238 +
239 +
	// exponent is related to the number of leading bits starting from bit 14
240 +
	int exponent = 7 - count_leading_zeros(scanvalue << 17);
241 +
242 +
	// smallest exponent value allowed is 1
243 +
	exponent = std::max(exponent, 1);
244 +
245 +
	// apply the shift back and forth to zero out bits that are lost
246 +
	exponent -= 1;
247 +
    int32_t mask = (1 << exponent) - 1;
248 +
	return value & ~mask;
249 +
}
250 +
251 +
252 +
253 +
//*********************************************************
254 +
//  HELPER CLASSES
255 +
//*********************************************************
256 +
257 +
// various envelope states
258 +
enum envelope_state : uint32_t
259 +
{
260 +
	EG_DEPRESS = 0,		// OPLL only; set EG_HAS_DEPRESS to enable
261 +
	EG_ATTACK = 1,
262 +
	EG_DECAY = 2,
263 +
	EG_SUSTAIN = 3,
264 +
	EG_RELEASE = 4,
265 +
	EG_REVERB = 5,		// OPQ/OPZ only; set EG_HAS_REVERB to enable
266 +
	EG_STATES = 6
267 +
};
268 +
269 +
// external I/O access classes
270 +
enum access_class : uint32_t
271 +
{
272 +
	ACCESS_IO = 0,
273 +
	ACCESS_ADPCM_A,
274 +
	ACCESS_ADPCM_B,
275 +
	ACCESS_PCM,
276 +
	ACCESS_CLASSES
277 +
};
278 +
279 +
280 +
281 +
//*********************************************************
282 +
//  HELPER CLASSES
283 +
//*********************************************************
284 +
285 +
// ======================> ymfm_output
286 +
287 +
// struct containing an array of output values
288 +
template<int NumOutputs>
289 +
struct ymfm_output
290 +
{
291 +
	// clear all outputs to 0
292 +
	ymfm_output &clear()
293 +
	{
294 +
		for (uint32_t index = 0; index < NumOutputs; index++)
295 +
			data[index] = 0;
296 +
		return *this;
297 +
	}
298 +
299 +
	// clamp all outputs to a 16-bit signed value
300 +
	ymfm_output &clamp16()
301 +
	{
302 +
		for (uint32_t index = 0; index < NumOutputs; index++)
303 +
			data[index] = clamp(data[index], -32768, 32767);
304 +
		return *this;
305 +
	}
306 +
307 +
	// run each output value through the floating-point processor
308 +
	ymfm_output &roundtrip_fp()
309 +
	{
310 +
		for (uint32_t index = 0; index < NumOutputs; index++)
311 +
			data[index] = ymfm::roundtrip_fp(data[index]);
312 +
		return *this;
313 +
	}
314 +
315 +
	// internal state
316 +
	int32_t data[NumOutputs];
317 +
};
318 +
319 +
320 +
// ======================> ymfm_wavfile
321 +
322 +
// this class is a debugging helper that accumulates data and writes it to wav files
323 +
template<int Channels>
324 +
class ymfm_wavfile
325 +
{
326 +
public:
327 +
	// construction
328 +
	ymfm_wavfile(uint32_t samplerate = 44100) :
329 +
		m_samplerate(samplerate)
330 +
	{
331 +
	}
332 +
333 +
	// configuration
334 +
	ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; }
335 +
	ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; }
336 +
337 +
	// destruction
338 +
	~ymfm_wavfile()
339 +
	{
340 +
		if (!m_buffer.empty())
341 +
		{
342 +
			// create file
343 +
			char name[20];
344 +
			snprintf(&name[0], sizeof(name), "wavlog-%02d.wav", m_index);
345 +
			FILE *out = fopen(name, "wb");
346 +
347 +
			// make the wav file header
348 +
			uint8_t header[44];
349 +
			memcpy(&header[0], "RIFF", 4);
350 +
			*(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8;
351 +
			memcpy(&header[8], "WAVE", 4);
352 +
			memcpy(&header[12], "fmt ", 4);
353 +
			*(uint32_t *)&header[16] = 16;
354 +
			*(uint16_t *)&header[20] = 1;
355 +
			*(uint16_t *)&header[22] = Channels;
356 +
			*(uint32_t *)&header[24] = m_samplerate;
357 +
			*(uint32_t *)&header[28] = m_samplerate * 2 * Channels;
358 +
			*(uint16_t *)&header[32] = 2 * Channels;
359 +
			*(uint16_t *)&header[34] = 16;
360 +
			memcpy(&header[36], "data", 4);
361 +
			*(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44;
362 +
363 +
			// write header then data
364 +
			fwrite(&header[0], 1, sizeof(header), out);
365 +
			fwrite(&m_buffer[0], 2, m_buffer.size(), out);
366 +
			fclose(out);
367 +
		}
368 +
	}
369 +
370 +
	// add data to the file
371 +
	template<int Outputs>
372 +
	void add(ymfm_output<Outputs> output)
373 +
	{
374 +
		int16_t sum[Channels] = { 0 };
375 +
		for (int index = 0; index < Outputs; index++)
376 +
			sum[index % Channels] += output.data[index];
377 +
		for (int index = 0; index < Channels; index++)
378 +
			m_buffer.push_back(sum[index]);
379 +
	}
380 +
381 +
	// add data to the file, using a reference
382 +
	template<int Outputs>
383 +
	void add(ymfm_output<Outputs> output, ymfm_output<Outputs> const &ref)
384 +
	{
385 +
		int16_t sum[Channels] = { 0 };
386 +
		for (int index = 0; index < Outputs; index++)
387 +
			sum[index % Channels] += output.data[index] - ref.data[index];
388 +
		for (int index = 0; index < Channels; index++)
389 +
			m_buffer.push_back(sum[index]);
390 +
	}
391 +
392 +
private:
393 +
	// internal state
394 +
	uint32_t m_index;
395 +
	uint32_t m_samplerate;
396 +
	std::vector<int16_t> m_buffer;
397 +
};
398 +
399 +
400 +
// ======================> ymfm_saved_state
401 +
402 +
// this class contains a managed vector of bytes that is used to save and
403 +
// restore state
404 +
class ymfm_saved_state
405 +
{
406 +
public:
407 +
	// construction
408 +
	ymfm_saved_state(std::vector<uint8_t> &buffer, bool saving) :
409 +
		m_buffer(buffer),
410 +
		m_offset(saving ? -1 : 0)
411 +
	{
412 +
		if (saving)
413 +
			buffer.resize(0);
414 +
	}
415 +
416 +
	// are we saving or restoring?
417 +
	bool saving() const { return (m_offset < 0); }
418 +
419 +
	// generic save/restore
420 +
	template<typename DataType>
421 +
	void save_restore(DataType &data)
422 +
	{
423 +
		if (saving())
424 +
			save(data);
425 +
		else
426 +
			restore(data);
427 +
	}
428 +
429 +
public:
430 +
	// save data to the buffer
431 +
	void save(bool &data) { write(data ? 1 : 0); }
432 +
	void save(int8_t &data) { write(data); }
433 +
	void save(uint8_t &data) { write(data); }
434 +
	void save(int16_t &data) { write(uint8_t(data)).write(data >> 8); }
435 +
	void save(uint16_t &data) { write(uint8_t(data)).write(data >> 8); }
436 +
	void save(int32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
437 +
	void save(uint32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
438 +
	void save(envelope_state &data) { write(uint8_t(data)); }
439 +
	template<typename DataType, int Count>
440 +
	void save(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) save(data[index]); }
441 +
442 +
	// restore data from the buffer
443 +
	void restore(bool &data) { data = read() ? true : false; }
444 +
	void restore(int8_t &data) { data = read(); }
445 +
	void restore(uint8_t &data) { data = read(); }
446 +
	void restore(int16_t &data) { data = read(); data |= read() << 8; }
447 +
	void restore(uint16_t &data) { data = read(); data |= read() << 8; }
448 +
	void restore(int32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
449 +
	void restore(uint32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
450 +
	void restore(envelope_state &data) { data = envelope_state(read()); }
451 +
	template<typename DataType, int Count>
452 +
	void restore(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) restore(data[index]); }
453 +
454 +
	// internal helper
455 +
	ymfm_saved_state &write(uint8_t data) { m_buffer.push_back(data); return *this; }
456 +
	uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; }
457 +
458 +
	// internal state
459 +
	std::vector<uint8_t> &m_buffer;
460 +
	int32_t m_offset;
461 +
};
462 +
463 +
464 +
465 +
//*********************************************************
466 +
//  INTERFACE CLASSES
467 +
//*********************************************************
468 +
469 +
// ======================> ymfm_engine_callbacks
470 +
471 +
// this class represents functions in the engine that the ymfm_interface
472 +
// needs to be able to call; it is represented here as a separate interface
473 +
// that is independent of the actual engine implementation
474 +
class ymfm_engine_callbacks
475 +
{
476 +
public:
477 +
	virtual ~ymfm_engine_callbacks() = default;
478 +
479 +
	// timer callback; called by the interface when a timer fires
480 +
	virtual void engine_timer_expired(uint32_t tnum) = 0;
481 +
482 +
	// check interrupts; called by the interface after synchronization
483 +
	virtual void engine_check_interrupts() = 0;
484 +
485 +
	// mode register write; called by the interface after synchronization
486 +
	virtual void engine_mode_write(uint8_t data) = 0;
487 +
};
488 +
489 +
490 +
// ======================> ymfm_interface
491 +
492 +
// this class represents the interface between the fm_engine and the outside
493 +
// world; it provides hooks for timers, synchronization, and I/O
494 +
class ymfm_interface
495 +
{
496 +
	// the engine is our friend
497 +
	template<typename RegisterType> friend class fm_engine_base;
498 +
499 +
public:
500 +
	virtual ~ymfm_interface() = default;
501 +
502 +
	// the following functions must be implemented by any derived classes; the
503 +
	// default implementations are sufficient for some minimal operation, but will
504 +
	// likely need to be overridden to integrate with the outside world; they are
505 +
	// all prefixed with ymfm_ to reduce the likelihood of namespace collisions
506 +
507 +
	//
508 +
	// timing and synchronizaton
509 +
	//
510 +
511 +
	// the chip implementation calls this when a write happens to the mode
512 +
	// register, which could affect timers and interrupts; our responsibility
513 +
	// is to ensure the system is up to date before calling the engine's
514 +
	// engine_mode_write() method
515 +
	virtual void ymfm_sync_mode_write(uint8_t data) { m_engine->engine_mode_write(data); }
516 +
517 +
	// the chip implementation calls this when the chip's status has changed,
518 +
	// which may affect the interrupt state; our responsibility is to ensure
519 +
	// the system is up to date before calling the engine's
520 +
	// engine_check_interrupts() method
521 +
	virtual void ymfm_sync_check_interrupts() { m_engine->engine_check_interrupts(); }
522 +
523 +
	// the chip implementation calls this when one of the two internal timers
524 +
	// has changed state; our responsibility is to arrange to call the engine's
525 +
	// engine_timer_expired() method after the provided number of clocks; if
526 +
	// duration_in_clocks is negative, we should cancel any outstanding timers
527 +
	virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { }
528 +
529 +
	// the chip implementation calls this to indicate that the chip should be
530 +
	// considered in a busy state until the given number of clocks has passed;
531 +
	// our responsibility is to compute and remember the ending time based on
532 +
	// the chip's clock for later checking
533 +
	virtual void ymfm_set_busy_end(uint32_t clocks) { }
534 +
535 +
	// the chip implementation calls this to see if the chip is still currently
536 +
	// is a busy state, as specified by a previous call to ymfm_set_busy_end();
537 +
	// our responsibility is to compare the current time against the previously
538 +
	// noted busy end time and return true if we haven't yet passed it
539 +
	virtual bool ymfm_is_busy() { return false; }
540 +
541 +
	//
542 +
	// I/O functions
543 +
	//
544 +
545 +
	// the chip implementation calls this when the state of the IRQ signal has
546 +
	// changed due to a status change; our responsibility is to respond as
547 +
	// needed to the change in IRQ state, signaling any consumers
548 +
	virtual void ymfm_update_irq(bool asserted) { }
549 +
550 +
	// the chip implementation calls this whenever data is read from outside
551 +
	// of the chip; our responsibility is to provide the data requested
552 +
	virtual uint8_t ymfm_external_read(access_class type, uint32_t address) { return 0; }
553 +
554 +
	// the chip implementation calls this whenever data is written outside
555 +
	// of the chip; our responsibility is to pass the written data on to any consumers
556 +
	virtual void ymfm_external_write(access_class type, uint32_t address, uint8_t data) { }
557 +
558 +
protected:
559 +
	// pointer to engine callbacks -- this is set directly by the engine at
560 +
	// construction time
561 +
	ymfm_engine_callbacks *m_engine;
562 +
};
563 +
564 +
}
565 +
566 +
#endif // YMFM_H
added src/extern/ymfm/src/ymfm_adpcm.cpp +807 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#include "ymfm_adpcm.h"
32 +
33 +
namespace ymfm
34 +
{
35 +
36 +
//*********************************************************
37 +
// ADPCM "A" REGISTERS
38 +
//*********************************************************
39 +
40 +
//-------------------------------------------------
41 +
//  reset - reset the register state
42 +
//-------------------------------------------------
43 +
44 +
void adpcm_a_registers::reset()
45 +
{
46 +
	std::fill_n(&m_regdata[0], REGISTERS, 0);
47 +
48 +
	// initialize the pans to on by default, and max instrument volume;
49 +
	// some neogeo homebrews (for example ffeast) rely on this
50 +
	m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] =
51 +
	m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf;
52 +
}
53 +
54 +
55 +
//-------------------------------------------------
56 +
//  save_restore - save or restore the data
57 +
//-------------------------------------------------
58 +
59 +
void adpcm_a_registers::save_restore(ymfm_saved_state &state)
60 +
{
61 +
	state.save_restore(m_regdata);
62 +
}
63 +
64 +
65 +
//*********************************************************
66 +
// ADPCM "A" CHANNEL
67 +
//*********************************************************
68 +
69 +
//-------------------------------------------------
70 +
//  adpcm_a_channel - constructor
71 +
//-------------------------------------------------
72 +
73 +
adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) :
74 +
	m_choffs(choffs),
75 +
	m_address_shift(addrshift),
76 +
	m_playing(0),
77 +
	m_curnibble(0),
78 +
	m_curbyte(0),
79 +
	m_curaddress(0),
80 +
	m_accumulator(0),
81 +
	m_step_index(0),
82 +
	m_regs(owner.regs()),
83 +
	m_owner(owner)
84 +
{
85 +
}
86 +
87 +
88 +
//-------------------------------------------------
89 +
//  reset - reset the channel state
90 +
//-------------------------------------------------
91 +
92 +
void adpcm_a_channel::reset()
93 +
{
94 +
	m_playing = 0;
95 +
	m_curnibble = 0;
96 +
	m_curbyte = 0;
97 +
	m_curaddress = 0;
98 +
	m_accumulator = 0;
99 +
	m_step_index = 0;
100 +
}
101 +
102 +
103 +
//-------------------------------------------------
104 +
//  save_restore - save or restore the data
105 +
//-------------------------------------------------
106 +
107 +
void adpcm_a_channel::save_restore(ymfm_saved_state &state)
108 +
{
109 +
	state.save_restore(m_playing);
110 +
	state.save_restore(m_curnibble);
111 +
	state.save_restore(m_curbyte);
112 +
	state.save_restore(m_curaddress);
113 +
	state.save_restore(m_accumulator);
114 +
	state.save_restore(m_step_index);
115 +
}
116 +
117 +
118 +
//-------------------------------------------------
119 +
//  keyonoff - signal key on/off
120 +
//-------------------------------------------------
121 +
122 +
void adpcm_a_channel::keyonoff(bool on)
123 +
{
124 +
	// QUESTION: repeated key ons restart the sample?
125 +
	m_playing = on;
126 +
	if (m_playing)
127 +
	{
128 +
		m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift;
129 +
		m_curnibble = 0;
130 +
		m_curbyte = 0;
131 +
		m_accumulator = 0;
132 +
		m_step_index = 0;
133 +
134 +
		// don't log masked channels
135 +
		if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0)
136 +
			debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n",
137 +
				m_choffs,
138 +
				m_regs.ch_pan_left(m_choffs),
139 +
				m_regs.ch_pan_right(m_choffs),
140 +
				m_regs.ch_start(m_choffs),
141 +
				m_regs.ch_end(m_choffs),
142 +
				m_regs.ch_instrument_level(m_choffs));
143 +
	}
144 +
}
145 +
146 +
147 +
//-------------------------------------------------
148 +
//  clock - master clocking function
149 +
//-------------------------------------------------
150 +
151 +
bool adpcm_a_channel::clock()
152 +
{
153 +
	// if not playing, just output 0
154 +
	if (m_playing == 0)
155 +
	{
156 +
		m_accumulator = 0;
157 +
		return false;
158 +
	}
159 +
160 +
	// if we're about to read nibble 0, fetch the data
161 +
	uint8_t data;
162 +
	if (m_curnibble == 0)
163 +
	{
164 +
		// stop when we hit the end address; apparently only low 20 bits are used for
165 +
		// comparison on the YM2610: this affects sample playback in some games, for
166 +
		// example twinspri character select screen music will skip some samples if
167 +
		// this is not correct
168 +
		//
169 +
		// note also: end address is inclusive, so wait until we are about to fetch
170 +
		// the sample just after the end before stopping; this is needed for nitd's
171 +
		// jump sound, for example
172 +
		uint32_t end = (m_regs.ch_end(m_choffs) + 1) << m_address_shift;
173 +
		if (((m_curaddress ^ end) & 0xfffff) == 0)
174 +
		{
175 +
			m_playing = m_accumulator = 0;
176 +
			return true;
177 +
		}
178 +
179 +
		m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++);
180 +
		data = m_curbyte >> 4;
181 +
		m_curnibble = 1;
182 +
	}
183 +
184 +
	// otherwise just extract from the previosuly-fetched byte
185 +
	else
186 +
	{
187 +
		data = m_curbyte & 0xf;
188 +
		m_curnibble = 0;
189 +
	}
190 +
191 +
	// compute the ADPCM delta
192 +
	static uint16_t const s_steps[49] =
193 +
	{
194 +
		 16,  17,   19,   21,   23,   25,   28,
195 +
		 31,  34,   37,   41,   45,   50,   55,
196 +
		 60,  66,   73,   80,   88,   97,  107,
197 +
		118, 130,  143,  157,  173,  190,  209,
198 +
		230, 253,  279,  307,  337,  371,  408,
199 +
		449, 494,  544,  598,  658,  724,  796,
200 +
		876, 963, 1060, 1166, 1282, 1411, 1552
201 +
	};
202 +
	int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8;
203 +
	if (bitfield(data, 3))
204 +
		delta = -delta;
205 +
206 +
	// the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205)
207 +
	m_accumulator = (m_accumulator + delta) & 0xfff;
208 +
209 +
	// adjust ADPCM step
210 +
	static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 };
211 +
	m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48);
212 +
213 +
	return false;
214 +
}
215 +
216 +
217 +
//-------------------------------------------------
218 +
//  output - return the computed output value, with
219 +
//  panning applied
220 +
//-------------------------------------------------
221 +
222 +
template<int NumOutputs>
223 +
void adpcm_a_channel::output(ymfm_output<NumOutputs> &output) const
224 +
{
225 +
	// volume combines instrument and total levels
226 +
	int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f);
227 +
228 +
	// if combined is maximum, don't add to outputs
229 +
	if (vol >= 63)
230 +
		return;
231 +
232 +
	// convert into a shift and a multiplier
233 +
	// QUESTION: verify this from other sources
234 +
	int8_t mul = 15 - (vol & 7);
235 +
	uint8_t shift = 4 + 1 + (vol >> 3);
236 +
237 +
	// m_accumulator is a 12-bit value; shift up to sign-extend;
238 +
	// the downshift is incorporated into 'shift'
239 +
	int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3;
240 +
241 +
	// apply to left/right as appropriate
242 +
	if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs))
243 +
		output.data[0] += value;
244 +
	if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs))
245 +
		output.data[1] += value;
246 +
}
247 +
248 +
249 +
250 +
//*********************************************************
251 +
// ADPCM "A" ENGINE
252 +
//*********************************************************
253 +
254 +
//-------------------------------------------------
255 +
//  adpcm_a_engine - constructor
256 +
//-------------------------------------------------
257 +
258 +
adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) :
259 +
	m_intf(intf)
260 +
{
261 +
	// create the channels
262 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
263 +
		m_channel[chnum] = std::make_unique<adpcm_a_channel>(*this, chnum, addrshift);
264 +
}
265 +
266 +
267 +
//-------------------------------------------------
268 +
//  reset - reset the engine state
269 +
//-------------------------------------------------
270 +
271 +
void adpcm_a_engine::reset()
272 +
{
273 +
	// reset register state
274 +
	m_regs.reset();
275 +
276 +
	// reset each channel
277 +
	for (auto &chan : m_channel)
278 +
		chan->reset();
279 +
}
280 +
281 +
282 +
//-------------------------------------------------
283 +
//  save_restore - save or restore the data
284 +
//-------------------------------------------------
285 +
286 +
void adpcm_a_engine::save_restore(ymfm_saved_state &state)
287 +
{
288 +
	// save register state
289 +
	m_regs.save_restore(state);
290 +
291 +
	// save channel state
292 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
293 +
		m_channel[chnum]->save_restore(state);
294 +
}
295 +
296 +
297 +
//-------------------------------------------------
298 +
//  clock - master clocking function
299 +
//-------------------------------------------------
300 +
301 +
uint32_t adpcm_a_engine::clock(uint32_t chanmask)
302 +
{
303 +
	// clock each channel, setting a bit in result if it finished
304 +
	uint32_t result = 0;
305 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
306 +
		if (bitfield(chanmask, chnum))
307 +
			if (m_channel[chnum]->clock())
308 +
				result |= 1 << chnum;
309 +
310 +
	// return the bitmask of completed samples
311 +
	return result;
312 +
}
313 +
314 +
315 +
//-------------------------------------------------
316 +
//  update - master update function
317 +
//-------------------------------------------------
318 +
319 +
template<int NumOutputs>
320 +
void adpcm_a_engine::output(ymfm_output<NumOutputs> &output, uint32_t chanmask)
321 +
{
322 +
	// mask out some channels for debug purposes
323 +
	chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK;
324 +
325 +
	// compute the output of each channel
326 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
327 +
		if (bitfield(chanmask, chnum))
328 +
			m_channel[chnum]->output(output);
329 +
}
330 +
331 +
template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask);
332 +
template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask);
333 +
334 +
335 +
//-------------------------------------------------
336 +
//  write - handle writes to the ADPCM-A registers
337 +
//-------------------------------------------------
338 +
339 +
void adpcm_a_engine::write(uint32_t regnum, uint8_t data)
340 +
{
341 +
	// store the raw value to the register array;
342 +
	// most writes are passive, consumed only when needed
343 +
	m_regs.write(regnum, data);
344 +
345 +
	// actively handle writes to the control register
346 +
	if (regnum == 0x00)
347 +
		for (int chnum = 0; chnum < CHANNELS; chnum++)
348 +
			if (bitfield(data, chnum))
349 +
				m_channel[chnum]->keyonoff(bitfield(~data, 7));
350 +
}
351 +
352 +
353 +
354 +
//*********************************************************
355 +
// ADPCM "B" REGISTERS
356 +
//*********************************************************
357 +
358 +
//-------------------------------------------------
359 +
//  reset - reset the register state
360 +
//-------------------------------------------------
361 +
362 +
void adpcm_b_registers::reset()
363 +
{
364 +
	std::fill_n(&m_regdata[0], REGISTERS, 0);
365 +
366 +
	// default limit to wide open
367 +
	m_regdata[0x0c] = m_regdata[0x0d] = 0xff;
368 +
}
369 +
370 +
371 +
//-------------------------------------------------
372 +
//  save_restore - save or restore the data
373 +
//-------------------------------------------------
374 +
375 +
void adpcm_b_registers::save_restore(ymfm_saved_state &state)
376 +
{
377 +
	state.save_restore(m_regdata);
378 +
}
379 +
380 +
381 +
382 +
//*********************************************************
383 +
// ADPCM "B" CHANNEL
384 +
//*********************************************************
385 +
386 +
//-------------------------------------------------
387 +
//  adpcm_b_channel - constructor
388 +
//-------------------------------------------------
389 +
390 +
adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) :
391 +
	m_address_shift(addrshift),
392 +
	m_status(STATUS_BRDY),
393 +
	m_curnibble(0),
394 +
	m_curbyte(0),
395 +
	m_dummy_read(0),
396 +
	m_position(0),
397 +
	m_curaddress(0),
398 +
	m_accumulator(0),
399 +
	m_prev_accum(0),
400 +
	m_adpcm_step(STEP_MIN),
401 +
	m_regs(owner.regs()),
402 +
	m_owner(owner)
403 +
{
404 +
}
405 +
406 +
407 +
//-------------------------------------------------
408 +
//  reset - reset the channel state
409 +
//-------------------------------------------------
410 +
411 +
void adpcm_b_channel::reset()
412 +
{
413 +
	m_status = STATUS_BRDY;
414 +
	m_curnibble = 0;
415 +
	m_curbyte = 0;
416 +
	m_dummy_read = 0;
417 +
	m_position = 0;
418 +
	m_curaddress = 0;
419 +
	m_accumulator = 0;
420 +
	m_prev_accum = 0;
421 +
	m_adpcm_step = STEP_MIN;
422 +
}
423 +
424 +
425 +
//-------------------------------------------------
426 +
//  save_restore - save or restore the data
427 +
//-------------------------------------------------
428 +
429 +
void adpcm_b_channel::save_restore(ymfm_saved_state &state)
430 +
{
431 +
	state.save_restore(m_status);
432 +
	state.save_restore(m_curnibble);
433 +
	state.save_restore(m_curbyte);
434 +
	state.save_restore(m_dummy_read);
435 +
	state.save_restore(m_position);
436 +
	state.save_restore(m_curaddress);
437 +
	state.save_restore(m_accumulator);
438 +
	state.save_restore(m_prev_accum);
439 +
	state.save_restore(m_adpcm_step);
440 +
}
441 +
442 +
443 +
//-------------------------------------------------
444 +
//  clock - master clocking function
445 +
//-------------------------------------------------
446 +
447 +
void adpcm_b_channel::clock()
448 +
{
449 +
	// only process if active and not recording (which we don't support)
450 +
	if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0)
451 +
	{
452 +
		m_status &= ~STATUS_PLAYING;
453 +
		return;
454 +
	}
455 +
456 +
	// otherwise, advance the step
457 +
	uint32_t position = m_position + m_regs.delta_n();
458 +
	m_position = uint16_t(position);
459 +
	if (position < 0x10000)
460 +
		return;
461 +
462 +
	// if we're about to process nibble 0, fetch sample
463 +
	if (m_curnibble == 0)
464 +
	{
465 +
		// playing from RAM/ROM
466 +
		if (m_regs.external())
467 +
			m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress);
468 +
	}
469 +
470 +
	// extract the nibble from our current byte
471 +
	uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4;
472 +
	m_curnibble ^= 1;
473 +
474 +
	// we just processed the last nibble
475 +
	if (m_curnibble == 0)
476 +
	{
477 +
		// if playing from RAM/ROM, check the end/limit address or advance
478 +
		if (m_regs.external())
479 +
		{
480 +
			// handle the sample end, either repeating or stopping
481 +
			if (at_end())
482 +
			{
483 +
				// if repeating, go back to the start
484 +
				if (m_regs.repeat())
485 +
					load_start();
486 +
487 +
				// otherwise, done; set the EOS bit
488 +
				else
489 +
				{
490 +
					m_accumulator = 0;
491 +
					m_prev_accum = 0;
492 +
					m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS;
493 +
					debug::log_keyon("%s\n", "ADPCM EOS");
494 +
					return;
495 +
				}
496 +
			}
497 +
498 +
			// wrap at the limit address
499 +
			else if (at_limit())
500 +
				m_curaddress = 0;
501 +
502 +
			// otherwise, advance the current address
503 +
			else
504 +
			{
505 +
				m_curaddress++;
506 +
				m_curaddress &= 0xffffff;
507 +
			}
508 +
		}
509 +
510 +
		// if CPU-driven, copy the next byte and request more
511 +
		else
512 +
		{
513 +
			m_curbyte = m_regs.cpudata();
514 +
			m_status |= STATUS_BRDY;
515 +
		}
516 +
	}
517 +
518 +
	// remember previous value for interpolation
519 +
	m_prev_accum = m_accumulator;
520 +
521 +
	// forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8
522 +
	int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8;
523 +
	if (bitfield(data, 3))
524 +
		delta = -delta;
525 +
526 +
	// add and clamp to 16 bits
527 +
	m_accumulator = clamp(m_accumulator + delta, -32768, 32767);
528 +
529 +
	// scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4
530 +
	static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 };
531 +
	m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX);
532 +
}
533 +
534 +
535 +
//-------------------------------------------------
536 +
//  output - return the computed output value, with
537 +
//  panning applied
538 +
//-------------------------------------------------
539 +
540 +
template<int NumOutputs>
541 +
void adpcm_b_channel::output(ymfm_output<NumOutputs> &output, uint32_t rshift) const
542 +
{
543 +
	// mask out some channels for debug purposes
544 +
	if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0)
545 +
		return;
546 +
547 +
	// do a linear interpolation between samples
548 +
	int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16;
549 +
550 +
	// apply volume (level) in a linear fashion and reduce
551 +
	result = (result * int32_t(m_regs.level())) >> (8 + rshift);
552 +
553 +
	// apply to left/right
554 +
	if (NumOutputs == 1 || m_regs.pan_left())
555 +
		output.data[0] += result;
556 +
	if (NumOutputs > 1 && m_regs.pan_right())
557 +
		output.data[1] += result;
558 +
}
559 +
560 +
561 +
//-------------------------------------------------
562 +
//  read - handle special register reads
563 +
//-------------------------------------------------
564 +
565 +
uint8_t adpcm_b_channel::read(uint32_t regnum)
566 +
{
567 +
	uint8_t result = 0;
568 +
569 +
	// register 8 reads over the bus under some conditions
570 +
	if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external())
571 +
	{
572 +
		// two dummy reads are consumed first
573 +
		if (m_dummy_read != 0)
574 +
		{
575 +
			load_start();
576 +
			m_dummy_read--;
577 +
		}
578 +
579 +
		// read the data
580 +
		else
581 +
		{
582 +
			// read from outside of the chip
583 +
			result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
584 +
585 +
			// did we hit the end? if so, signal EOS
586 +
			if (at_end())
587 +
			{
588 +
				m_status = STATUS_EOS | STATUS_BRDY;
589 +
				debug::log_keyon("%s\n", "ADPCM EOS");
590 +
			}
591 +
			else
592 +
			{
593 +
				// signal ready
594 +
				m_status = STATUS_BRDY;
595 +
			}
596 +
597 +
			// wrap at the limit address
598 +
			if (at_limit())
599 +
				m_curaddress = 0;
600 +
		}
601 +
	}
602 +
	return result;
603 +
}
604 +
605 +
606 +
//-------------------------------------------------
607 +
//  write - handle special register writes
608 +
//-------------------------------------------------
609 +
610 +
void adpcm_b_channel::write(uint32_t regnum, uint8_t value)
611 +
{
612 +
	// register 0 can do a reset; also use writes here to reset the
613 +
	// dummy read counter
614 +
	if (regnum == 0x00)
615 +
	{
616 +
		if (m_regs.execute())
617 +
		{
618 +
			load_start();
619 +
620 +
			// don't log masked channels
621 +
			if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0)
622 +
				debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n",
623 +
					m_regs.repeat(),
624 +
					m_regs.speaker(),
625 +
					m_regs.pan_left(),
626 +
					m_regs.pan_right(),
627 +
					m_regs.dac_enable(),
628 +
					m_regs.dram_8bit(),
629 +
					m_regs.rom_ram(),
630 +
					m_regs.external(),
631 +
					m_regs.record(),
632 +
					m_regs.start(),
633 +
					m_regs.end(),
634 +
					m_regs.prescale(),
635 +
					m_regs.delta_n(),
636 +
					m_regs.level(),
637 +
					m_regs.limit());
638 +
		}
639 +
		else
640 +
			m_status &= ~STATUS_EOS;
641 +
		if (m_regs.resetflag())
642 +
			reset();
643 +
		if (m_regs.external())
644 +
			m_dummy_read = 2;
645 +
	}
646 +
647 +
	// register 8 writes over the bus under some conditions
648 +
	else if (regnum == 0x08)
649 +
	{
650 +
		// if writing from the CPU during execute, clear the ready flag
651 +
		if (m_regs.execute() && !m_regs.record() && !m_regs.external())
652 +
			m_status &= ~STATUS_BRDY;
653 +
654 +
		// if writing during "record", pass through as data
655 +
		else if (!m_regs.execute() && m_regs.record() && m_regs.external())
656 +
		{
657 +
			// clear out dummy reads and set start address
658 +
			if (m_dummy_read != 0)
659 +
			{
660 +
				load_start();
661 +
				m_dummy_read = 0;
662 +
			}
663 +
664 +
			// did we hit the end? if so, signal EOS
665 +
			if (at_end())
666 +
			{
667 +
				debug::log_keyon("%s\n", "ADPCM EOS");
668 +
				m_status = STATUS_EOS | STATUS_BRDY;
669 +
			}
670 +
671 +
			// otherwise, write the data and signal ready
672 +
			else
673 +
			{
674 +
				m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value);
675 +
				m_status = STATUS_BRDY;
676 +
			}
677 +
		}
678 +
	}
679 +
}
680 +
681 +
682 +
//-------------------------------------------------
683 +
//  address_shift - compute the current address
684 +
//  shift amount based on register settings
685 +
//-------------------------------------------------
686 +
687 +
uint32_t adpcm_b_channel::address_shift() const
688 +
{
689 +
	// if a constant address shift, just provide that
690 +
	if (m_address_shift != 0)
691 +
		return m_address_shift;
692 +
693 +
	// if ROM or 8-bit DRAM, shift is 5 bits
694 +
	if (m_regs.rom_ram())
695 +
		return 5;
696 +
	if (m_regs.dram_8bit())
697 +
		return 5;
698 +
699 +
	// otherwise, shift is 2 bits
700 +
	return 2;
701 +
}
702 +
703 +
704 +
//-------------------------------------------------
705 +
//  load_start - load the start address and
706 +
//  initialize the state
707 +
//-------------------------------------------------
708 +
709 +
void adpcm_b_channel::load_start()
710 +
{
711 +
	m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING;
712 +
	m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0;
713 +
	m_curnibble = 0;
714 +
	m_curbyte = 0;
715 +
	m_position = 0;
716 +
	m_accumulator = 0;
717 +
	m_prev_accum = 0;
718 +
	m_adpcm_step = STEP_MIN;
719 +
}
720 +
721 +
722 +
723 +
//*********************************************************
724 +
// ADPCM "B" ENGINE
725 +
//*********************************************************
726 +
727 +
//-------------------------------------------------
728 +
//  adpcm_b_engine - constructor
729 +
//-------------------------------------------------
730 +
731 +
adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) :
732 +
	m_intf(intf)
733 +
{
734 +
	// create the channel (only one supported for now, but leaving possibilities open)
735 +
	m_channel = std::make_unique<adpcm_b_channel>(*this, addrshift);
736 +
}
737 +
738 +
739 +
//-------------------------------------------------
740 +
//  reset - reset the engine state
741 +
//-------------------------------------------------
742 +
743 +
void adpcm_b_engine::reset()
744 +
{
745 +
	// reset registers
746 +
	m_regs.reset();
747 +
748 +
	// reset each channel
749 +
	m_channel->reset();
750 +
}
751 +
752 +
753 +
//-------------------------------------------------
754 +
//  save_restore - save or restore the data
755 +
//-------------------------------------------------
756 +
757 +
void adpcm_b_engine::save_restore(ymfm_saved_state &state)
758 +
{
759 +
	// save our state
760 +
	m_regs.save_restore(state);
761 +
762 +
	// save channel state
763 +
	m_channel->save_restore(state);
764 +
}
765 +
766 +
767 +
//-------------------------------------------------
768 +
//  clock - master clocking function
769 +
//-------------------------------------------------
770 +
771 +
void adpcm_b_engine::clock()
772 +
{
773 +
	// clock each channel, setting a bit in result if it finished
774 +
	m_channel->clock();
775 +
}
776 +
777 +
778 +
//-------------------------------------------------
779 +
//  output - master output function
780 +
//-------------------------------------------------
781 +
782 +
template<int NumOutputs>
783 +
void adpcm_b_engine::output(ymfm_output<NumOutputs> &output, uint32_t rshift)
784 +
{
785 +
	// compute the output of each channel
786 +
	m_channel->output(output, rshift);
787 +
}
788 +
789 +
template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift);
790 +
template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift);
791 +
792 +
793 +
//-------------------------------------------------
794 +
//  write - handle writes to the ADPCM-B registers
795 +
//-------------------------------------------------
796 +
797 +
void adpcm_b_engine::write(uint32_t regnum, uint8_t data)
798 +
{
799 +
	// store the raw value to the register array;
800 +
	// most writes are passive, consumed only when needed
801 +
	m_regs.write(regnum, data);
802 +
803 +
	// let the channel handle any special writes
804 +
	m_channel->write(regnum, data);
805 +
}
806 +
807 +
}
added src/extern/ymfm/src/ymfm_adpcm.h +411 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#ifndef YMFM_ADPCM_H
32 +
#define YMFM_ADPCM_H
33 +
34 +
#pragma once
35 +
36 +
#include "ymfm.h"
37 +
38 +
namespace ymfm
39 +
{
40 +
41 +
//*********************************************************
42 +
//  INTERFACE CLASSES
43 +
//*********************************************************
44 +
45 +
// forward declarations
46 +
class adpcm_a_engine;
47 +
class adpcm_b_engine;
48 +
49 +
50 +
// ======================> adpcm_a_registers
51 +
52 +
//
53 +
// ADPCM-A register map:
54 +
//
55 +
//      System-wide registers:
56 +
//           00 x------- Dump (disable=1) or keyon (0) control
57 +
//              --xxxxxx Mask of channels to dump or keyon
58 +
//           01 --xxxxxx Total level
59 +
//           02 xxxxxxxx Test register
60 +
//        08-0D x------- Pan left
61 +
//              -x------ Pan right
62 +
//              ---xxxxx Instrument level
63 +
//        10-15 xxxxxxxx Start address (low)
64 +
//        18-1D xxxxxxxx Start address (high)
65 +
//        20-25 xxxxxxxx End address (low)
66 +
//        28-2D xxxxxxxx End address (high)
67 +
//
68 +
class adpcm_a_registers
69 +
{
70 +
public:
71 +
	// constants
72 +
	static constexpr uint32_t OUTPUTS = 2;
73 +
	static constexpr uint32_t CHANNELS = 6;
74 +
	static constexpr uint32_t REGISTERS = 0x30;
75 +
	static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
76 +
77 +
	// constructor
78 +
	adpcm_a_registers() { }
79 +
80 +
	// reset to initial state
81 +
	void reset();
82 +
83 +
	// save/restore
84 +
	void save_restore(ymfm_saved_state &state);
85 +
86 +
	// map channel number to register offset
87 +
	static constexpr uint32_t channel_offset(uint32_t chnum)
88 +
	{
89 +
		assert(chnum < CHANNELS);
90 +
		return chnum;
91 +
	}
92 +
93 +
	// direct read/write access
94 +
	void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
95 +
96 +
	// system-wide registers
97 +
	uint32_t dump() const                               { return bitfield(m_regdata[0x00], 7); }
98 +
	uint32_t dump_mask() const                          { return bitfield(m_regdata[0x00], 0, 6); }
99 +
	uint32_t total_level() const                        { return bitfield(m_regdata[0x01], 0, 6); }
100 +
	uint32_t test() const                               { return m_regdata[0x02]; }
101 +
102 +
	// per-channel registers
103 +
	uint32_t ch_pan_left(uint32_t choffs) const         { return bitfield(m_regdata[choffs + 0x08], 7); }
104 +
	uint32_t ch_pan_right(uint32_t choffs) const        { return bitfield(m_regdata[choffs + 0x08], 6); }
105 +
	uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); }
106 +
	uint32_t ch_start(uint32_t choffs) const            { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); }
107 +
	uint32_t ch_end(uint32_t choffs) const              { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); }
108 +
109 +
	// per-channel writes
110 +
	void write_start(uint32_t choffs, uint32_t address)
111 +
	{
112 +
		write(choffs + 0x10, address);
113 +
		write(choffs + 0x18, address >> 8);
114 +
	}
115 +
	void write_end(uint32_t choffs, uint32_t address)
116 +
	{
117 +
		write(choffs + 0x20, address);
118 +
		write(choffs + 0x28, address >> 8);
119 +
	}
120 +
121 +
private:
122 +
	// internal state
123 +
	uint8_t m_regdata[REGISTERS];         // register data
124 +
};
125 +
126 +
127 +
// ======================> adpcm_a_channel
128 +
129 +
class adpcm_a_channel
130 +
{
131 +
public:
132 +
	// constructor
133 +
	adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift);
134 +
135 +
	// reset the channel state
136 +
	void reset();
137 +
138 +
	// save/restore
139 +
	void save_restore(ymfm_saved_state &state);
140 +
141 +
	// signal key on/off
142 +
	void keyonoff(bool on);
143 +
144 +
	// master clockingfunction
145 +
	bool clock();
146 +
147 +
	// return the computed output value, with panning applied
148 +
	template<int NumOutputs>
149 +
	void output(ymfm_output<NumOutputs> &output) const;
150 +
151 +
private:
152 +
	// internal state
153 +
	uint32_t const m_choffs;              // channel offset
154 +
	uint32_t const m_address_shift;       // address bits shift-left
155 +
	uint32_t m_playing;                   // currently playing?
156 +
	uint32_t m_curnibble;                 // index of the current nibble
157 +
	uint32_t m_curbyte;                   // current byte of data
158 +
	uint32_t m_curaddress;                // current address
159 +
	int32_t m_accumulator;                // accumulator
160 +
	int32_t m_step_index;                 // index in the stepping table
161 +
	adpcm_a_registers &m_regs;            // reference to registers
162 +
	adpcm_a_engine &m_owner;              // reference to our owner
163 +
};
164 +
165 +
166 +
// ======================> adpcm_a_engine
167 +
168 +
class adpcm_a_engine
169 +
{
170 +
public:
171 +
	static constexpr int CHANNELS = adpcm_a_registers::CHANNELS;
172 +
173 +
	// constructor
174 +
	adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift);
175 +
176 +
	// reset our status
177 +
	void reset();
178 +
179 +
	// save/restore
180 +
	void save_restore(ymfm_saved_state &state);
181 +
182 +
	// master clocking function
183 +
	uint32_t clock(uint32_t chanmask);
184 +
185 +
	// compute sum of channel outputs
186 +
	template<int NumOutputs>
187 +
	void output(ymfm_output<NumOutputs> &output, uint32_t chanmask);
188 +
189 +
	// write to the ADPCM-A registers
190 +
	void write(uint32_t regnum, uint8_t data);
191 +
192 +
	// set the start/end address for a channel (for hardcoded YM2608 percussion)
193 +
	void set_start_end(uint8_t chnum, uint16_t start, uint16_t end)
194 +
	{
195 +
		uint32_t choffs = adpcm_a_registers::channel_offset(chnum);
196 +
		m_regs.write_start(choffs, start);
197 +
		m_regs.write_end(choffs, end);
198 +
	}
199 +
200 +
	// return a reference to our interface
201 +
	ymfm_interface &intf() { return m_intf; }
202 +
203 +
	// return a reference to our registers
204 +
	adpcm_a_registers &regs() { return m_regs; }
205 +
206 +
private:
207 +
	// internal state
208 +
	ymfm_interface &m_intf;                                 // reference to the interface
209 +
	std::unique_ptr<adpcm_a_channel> m_channel[CHANNELS]; // array of channels
210 +
	adpcm_a_registers m_regs;                             // registers
211 +
};
212 +
213 +
214 +
// ======================> adpcm_b_registers
215 +
216 +
//
217 +
// ADPCM-B register map:
218 +
//
219 +
//      System-wide registers:
220 +
//           00 x------- Start of synthesis/analysis
221 +
//              -x------ Record
222 +
//              --x----- External/manual driving
223 +
//              ---x---- Repeat playback
224 +
//              ----x--- Speaker off
225 +
//              -------x Reset
226 +
//           01 x------- Pan left
227 +
//              -x------ Pan right
228 +
//              ----x--- Start conversion
229 +
//              -----x-- DAC enable
230 +
//              ------x- DRAM access (1=8-bit granularity; 0=1-bit)
231 +
//              -------x RAM/ROM (1=ROM, 0=RAM)
232 +
//           02 xxxxxxxx Start address (low)
233 +
//           03 xxxxxxxx Start address (high)
234 +
//           04 xxxxxxxx End address (low)
235 +
//           05 xxxxxxxx End address (high)
236 +
//           06 xxxxxxxx Prescale value (low)
237 +
//           07 -----xxx Prescale value (high)
238 +
//           08 xxxxxxxx CPU data/buffer
239 +
//           09 xxxxxxxx Delta-N frequency scale (low)
240 +
//           0a xxxxxxxx Delta-N frequency scale (high)
241 +
//           0b xxxxxxxx Level control
242 +
//           0c xxxxxxxx Limit address (low)
243 +
//           0d xxxxxxxx Limit address (high)
244 +
//           0e xxxxxxxx DAC data [YM2608/10]
245 +
//           0f xxxxxxxx PCM data [YM2608/10]
246 +
//           0e xxxxxxxx DAC data high [Y8950]
247 +
//           0f xx------ DAC data low [Y8950]
248 +
//           10 -----xxx DAC data exponent [Y8950]
249 +
//
250 +
class adpcm_b_registers
251 +
{
252 +
public:
253 +
	// constants
254 +
	static constexpr uint32_t REGISTERS = 0x11;
255 +
256 +
	// constructor
257 +
	adpcm_b_registers() { }
258 +
259 +
	// reset to initial state
260 +
	void reset();
261 +
262 +
	// save/restore
263 +
	void save_restore(ymfm_saved_state &state);
264 +
265 +
	// direct read/write access
266 +
	void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
267 +
268 +
	// system-wide registers
269 +
	uint32_t execute() const          { return bitfield(m_regdata[0x00], 7); }
270 +
	uint32_t record() const           { return bitfield(m_regdata[0x00], 6); }
271 +
	uint32_t external() const         { return bitfield(m_regdata[0x00], 5); }
272 +
	uint32_t repeat() const           { return bitfield(m_regdata[0x00], 4); }
273 +
	uint32_t speaker() const          { return bitfield(m_regdata[0x00], 3); }
274 +
	uint32_t resetflag() const        { return bitfield(m_regdata[0x00], 0); }
275 +
	uint32_t pan_left() const         { return bitfield(m_regdata[0x01], 7); }
276 +
	uint32_t pan_right() const        { return bitfield(m_regdata[0x01], 6); }
277 +
	uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); }
278 +
	uint32_t dac_enable() const       { return bitfield(m_regdata[0x01], 2); }
279 +
	uint32_t dram_8bit() const        { return bitfield(m_regdata[0x01], 1); }
280 +
	uint32_t rom_ram() const          { return bitfield(m_regdata[0x01], 0); }
281 +
	uint32_t start() const            { return m_regdata[0x02] | (m_regdata[0x03] << 8); }
282 +
	uint32_t end() const              { return m_regdata[0x04] | (m_regdata[0x05] << 8); }
283 +
	uint32_t prescale() const         { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); }
284 +
	uint32_t cpudata() const          { return m_regdata[0x08]; }
285 +
	uint32_t delta_n() const          { return m_regdata[0x09] | (m_regdata[0x0a] << 8); }
286 +
	uint32_t level() const            { return m_regdata[0x0b]; }
287 +
	uint32_t limit() const            { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); }
288 +
	uint32_t dac() const              { return m_regdata[0x0e]; }
289 +
	uint32_t pcm() const              { return m_regdata[0x0f]; }
290 +
291 +
private:
292 +
	// internal state
293 +
	uint8_t m_regdata[REGISTERS];         // register data
294 +
};
295 +
296 +
297 +
// ======================> adpcm_b_channel
298 +
299 +
class adpcm_b_channel
300 +
{
301 +
	static constexpr int32_t STEP_MIN = 127;
302 +
	static constexpr int32_t STEP_MAX = 24576;
303 +
304 +
public:
305 +
	static constexpr uint8_t STATUS_EOS = 0x01;
306 +
	static constexpr uint8_t STATUS_BRDY = 0x02;
307 +
	static constexpr uint8_t STATUS_PLAYING = 0x04;
308 +
309 +
	// constructor
310 +
	adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift);
311 +
312 +
	// reset the channel state
313 +
	void reset();
314 +
315 +
	// save/restore
316 +
	void save_restore(ymfm_saved_state &state);
317 +
318 +
	// signal key on/off
319 +
	void keyonoff(bool on);
320 +
321 +
	// master clocking function
322 +
	void clock();
323 +
324 +
	// return the computed output value, with panning applied
325 +
	template<int NumOutputs>
326 +
	void output(ymfm_output<NumOutputs> &output, uint32_t rshift) const;
327 +
328 +
	// return the status register
329 +
	uint8_t status() const { return m_status; }
330 +
331 +
	// handle special register reads
332 +
	uint8_t read(uint32_t regnum);
333 +
334 +
	// handle special register writes
335 +
	void write(uint32_t regnum, uint8_t value);
336 +
337 +
private:
338 +
	// helper - return the current address shift
339 +
	uint32_t address_shift() const;
340 +
341 +
	// load the start address
342 +
	void load_start();
343 +
344 +
	// limit checker; stops at the last byte of the chunk described by address_shift()
345 +
	bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); }
346 +
347 +
	// end checker; stops at the last byte of the chunk described by address_shift()
348 +
	bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); }
349 +
350 +
	// internal state
351 +
	uint32_t const m_address_shift; // address bits shift-left
352 +
	uint32_t m_status;              // currently playing?
353 +
	uint32_t m_curnibble;           // index of the current nibble
354 +
	uint32_t m_curbyte;             // current byte of data
355 +
	uint32_t m_dummy_read;          // dummy read tracker
356 +
	uint32_t m_position;            // current fractional position
357 +
	uint32_t m_curaddress;          // current address
358 +
	int32_t m_accumulator;          // accumulator
359 +
	int32_t m_prev_accum;           // previous accumulator (for linear interp)
360 +
	int32_t m_adpcm_step;           // next forecast
361 +
	adpcm_b_registers &m_regs;      // reference to registers
362 +
	adpcm_b_engine &m_owner;        // reference to our owner
363 +
};
364 +
365 +
366 +
// ======================> adpcm_b_engine
367 +
368 +
class adpcm_b_engine
369 +
{
370 +
public:
371 +
	// constructor
372 +
	adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0);
373 +
374 +
	// reset our status
375 +
	void reset();
376 +
377 +
	// save/restore
378 +
	void save_restore(ymfm_saved_state &state);
379 +
380 +
	// master clocking function
381 +
	void clock();
382 +
383 +
	// compute sum of channel outputs
384 +
	template<int NumOutputs>
385 +
	void output(ymfm_output<NumOutputs> &output, uint32_t rshift);
386 +
387 +
	// read from the ADPCM-B registers
388 +
	uint32_t read(uint32_t regnum) { return m_channel->read(regnum); }
389 +
390 +
	// write to the ADPCM-B registers
391 +
	void write(uint32_t regnum, uint8_t data);
392 +
393 +
	// status
394 +
	uint8_t status() const { return m_channel->status(); }
395 +
396 +
	// return a reference to our interface
397 +
	ymfm_interface &intf() { return m_intf; }
398 +
399 +
	// return a reference to our registers
400 +
	adpcm_b_registers &regs() { return m_regs; }
401 +
402 +
private:
403 +
	// internal state
404 +
	ymfm_interface &m_intf;                     // reference to our interface
405 +
	std::unique_ptr<adpcm_b_channel> m_channel; // channel pointer
406 +
	adpcm_b_registers m_regs;                   // registers
407 +
};
408 +
409 +
}
410 +
411 +
#endif // YMFM_ADPCM_H
added src/extern/ymfm/src/ymfm_fm.h +463 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#ifndef YMFM_FM_H
32 +
#define YMFM_FM_H
33 +
34 +
#pragma once
35 +
36 +
#define YMFM_DEBUG_LOG_WAVFILES (0)
37 +
38 +
namespace ymfm
39 +
{
40 +
41 +
//*********************************************************
42 +
//  GLOBAL ENUMERATORS
43 +
//*********************************************************
44 +
45 +
// three different keyon sources; actual keyon is an OR over all of these
46 +
enum keyon_type : uint32_t
47 +
{
48 +
	KEYON_NORMAL = 0,
49 +
	KEYON_RHYTHM = 1,
50 +
	KEYON_CSM = 2
51 +
};
52 +
53 +
54 +
55 +
//*********************************************************
56 +
//  CORE IMPLEMENTATION
57 +
//*********************************************************
58 +
59 +
// ======================> opdata_cache
60 +
61 +
// this class holds data that is computed once at the start of clocking
62 +
// and remains static during subsequent sound generation
63 +
struct opdata_cache
64 +
{
65 +
	// set phase_step to this value to recalculate it each sample; needed
66 +
	// in the case of PM LFO changes
67 +
	static constexpr uint32_t PHASE_STEP_DYNAMIC = 1;
68 +
69 +
	uint16_t const *waveform;         // base of sine table
70 +
	uint32_t phase_step;              // phase step, or PHASE_STEP_DYNAMIC if PM is active
71 +
	uint32_t total_level;             // total level * 8 + KSL
72 +
	uint32_t block_freq;              // raw block frequency value (used to compute phase_step)
73 +
	int32_t detune;                   // detuning value (used to compute phase_step)
74 +
	uint32_t multiple;                // multiple value (x.1, used to compute phase_step)
75 +
	uint32_t eg_sustain;              // sustain level, shifted up to envelope values
76 +
	uint8_t eg_rate[EG_STATES];       // envelope rate, including KSR
77 +
	uint8_t eg_shift = 0;             // envelope shift amount
78 +
};
79 +
80 +
81 +
// ======================> fm_registers_base
82 +
83 +
// base class for family-specific register classes; this provides a few
84 +
// constants, common defaults, and helpers, but mostly each derived class is
85 +
// responsible for defining all commonly-called methods
86 +
class fm_registers_base
87 +
{
88 +
public:
89 +
	// this value is returned from the write() function for rhythm channels
90 +
	static constexpr uint32_t RHYTHM_CHANNEL = 0xff;
91 +
92 +
	// this is the size of a full sin waveform
93 +
	static constexpr uint32_t WAVEFORM_LENGTH = 0x400;
94 +
95 +
	//
96 +
	// the following constants need to be defined per family:
97 +
	//          uint32_t OUTPUTS: The number of outputs exposed (1-4)
98 +
	//         uint32_t CHANNELS: The number of channels on the chip
99 +
	//     uint32_t ALL_CHANNELS: A bitmask of all channels
100 +
	//        uint32_t OPERATORS: The number of operators on the chip
101 +
	//        uint32_t WAVEFORMS: The number of waveforms offered
102 +
	//        uint32_t REGISTERS: The number of 8-bit registers allocated
103 +
	// uint32_t DEFAULT_PRESCALE: The starting clock prescale
104 +
	// uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator
105 +
	// uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode
106 +
	//         uint32_t REG_MODE: The address of the "mode" register controlling timers
107 +
	//     uint8_t STATUS_TIMERA: Status bit to set when timer A fires
108 +
	//     uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires
109 +
	//       uint8_t STATUS_BUSY: Status bit to set when the chip is busy
110 +
	//        uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled
111 +
	//
112 +
	// the following constants are uncommon:
113 +
	//          bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+)
114 +
	//       bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL)
115 +
	//        bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ)
116 +
	//           bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN)
117 +
	//      bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3)
118 +
	//
119 +
	static constexpr bool DYNAMIC_OPS = false;
120 +
	static constexpr bool EG_HAS_DEPRESS = false;
121 +
	static constexpr bool EG_HAS_REVERB = false;
122 +
	static constexpr bool EG_HAS_SSG = false;
123 +
	static constexpr bool MODULATOR_DELAY = false;
124 +
125 +
	// system-wide register defaults
126 +
	uint32_t status_mask() const                     { return 0; } // OPL only
127 +
	uint32_t irq_reset() const                       { return 0; } // OPL only
128 +
	uint32_t noise_enable() const                    { return 0; } // OPM only
129 +
	uint32_t rhythm_enable() const                   { return 0; } // OPL only
130 +
131 +
	// per-operator register defaults
132 +
	uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only
133 +
	uint32_t op_ssg_eg_mode(uint32_t opoffs) const   { return 0; } // OPN(A) only
134 +
135 +
protected:
136 +
	// helper to encode four operator numbers into a 32-bit value in the
137 +
	// operator maps for each register class
138 +
	static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff)
139 +
	{
140 +
		return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24);
141 +
	}
142 +
143 +
	// helper to apply KSR to the raw ADSR rate, ignoring ksr if the
144 +
	// raw value is 0, and clamping to 63
145 +
	static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr)
146 +
	{
147 +
		return (rawrate == 0) ? 0 : std::min<uint32_t>(rawrate + ksr, 63);
148 +
	}
149 +
};
150 +
151 +
152 +
153 +
//*********************************************************
154 +
//  CORE ENGINE CLASSES
155 +
//*********************************************************
156 +
157 +
// forward declarations
158 +
template<class RegisterType> class fm_engine_base;
159 +
160 +
// ======================> fm_operator
161 +
162 +
// fm_operator represents an FM operator (or "slot" in FM parlance), which
163 +
// produces an output sine wave modulated by an envelope
164 +
template<class RegisterType>
165 +
class fm_operator
166 +
{
167 +
	// "quiet" value, used to optimize when we can skip doing work
168 +
	static constexpr uint32_t EG_QUIET = 0x380;
169 +
170 +
public:
171 +
	// constructor
172 +
	fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs);
173 +
174 +
	// save/restore
175 +
	void save_restore(ymfm_saved_state &state);
176 +
177 +
	// reset the operator state
178 +
	void reset();
179 +
180 +
	// return the operator/channel offset
181 +
	uint32_t opoffs() const { return m_opoffs; }
182 +
	uint32_t choffs() const { return m_choffs; }
183 +
184 +
	// set the current channel
185 +
	void set_choffs(uint32_t choffs) { m_choffs = choffs; }
186 +
187 +
	// prepare prior to clocking
188 +
	bool prepare();
189 +
190 +
	// master clocking function
191 +
	void clock(uint32_t env_counter, int32_t lfo_raw_pm);
192 +
193 +
	// return the current phase value
194 +
	uint32_t phase() const { return m_phase >> 10; }
195 +
196 +
	// compute operator volume
197 +
	int32_t compute_volume(uint32_t phase, uint32_t am_offset) const;
198 +
199 +
	// compute volume for the OPM noise channel
200 +
	int32_t compute_noise_volume(uint32_t am_offset) const;
201 +
202 +
	// key state control
203 +
	void keyonoff(uint32_t on, keyon_type type);
204 +
205 +
	// return a reference to our registers
206 +
	RegisterType &regs() const { return m_regs; }
207 +
208 +
	// simple getters for debugging
209 +
	envelope_state debug_eg_state() const { return m_env_state; }
210 +
	uint16_t debug_eg_attenuation() const { return m_env_attenuation; }
211 +
	uint8_t debug_ssg_inverted() const { return m_ssg_inverted; }
212 +
	opdata_cache &debug_cache() { return m_cache; }
213 +
214 +
private:
215 +
	// start the attack phase
216 +
	void start_attack(bool is_restart = false);
217 +
218 +
	// start the release phase
219 +
	void start_release();
220 +
221 +
	// clock phases
222 +
	void clock_keystate(uint32_t keystate);
223 +
	void clock_ssg_eg_state();
224 +
	void clock_envelope(uint32_t env_counter);
225 +
	void clock_phase(int32_t lfo_raw_pm);
226 +
227 +
	// return effective attenuation of the envelope
228 +
	uint32_t envelope_attenuation(uint32_t am_offset) const;
229 +
230 +
	// internal state
231 +
	uint32_t m_choffs;                     // channel offset in registers
232 +
	uint32_t m_opoffs;                     // operator offset in registers
233 +
	uint32_t m_phase;                      // current phase value (10.10 format)
234 +
	uint16_t m_env_attenuation;            // computed envelope attenuation (4.6 format)
235 +
	envelope_state m_env_state;            // current envelope state
236 +
	uint8_t m_ssg_inverted;                // non-zero if the output should be inverted (bit 0)
237 +
	uint8_t m_key_state;                   // current key state: on or off (bit 0)
238 +
	uint8_t m_keyon_live;                  // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM)
239 +
	opdata_cache m_cache;                  // cached values for performance
240 +
	RegisterType &m_regs;                  // direct reference to registers
241 +
	fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
242 +
};
243 +
244 +
245 +
// ======================> fm_channel
246 +
247 +
// fm_channel represents an FM channel which combines the output of 2 or 4
248 +
// operators into a final result
249 +
template<class RegisterType>
250 +
class fm_channel
251 +
{
252 +
	using output_data = ymfm_output<RegisterType::OUTPUTS>;
253 +
254 +
public:
255 +
	// constructor
256 +
	fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs);
257 +
258 +
	// save/restore
259 +
	void save_restore(ymfm_saved_state &state);
260 +
261 +
	// reset the channel state
262 +
	void reset();
263 +
264 +
	// return the channel offset
265 +
	uint32_t choffs() const { return m_choffs; }
266 +
267 +
	// assign operators
268 +
	void assign(uint32_t index, fm_operator<RegisterType> *op)
269 +
	{
270 +
		assert(index < m_op.size());
271 +
		m_op[index] = op;
272 +
		if (op != nullptr)
273 +
			op->set_choffs(m_choffs);
274 +
	}
275 +
276 +
	// signal key on/off to our operators
277 +
	void keyonoff(uint32_t states, keyon_type type, uint32_t chnum);
278 +
279 +
	// prepare prior to clocking
280 +
	bool prepare();
281 +
282 +
	// master clocking function
283 +
	void clock(uint32_t env_counter, int32_t lfo_raw_pm);
284 +
285 +
	// specific 2-operator and 4-operator output handlers
286 +
	void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const;
287 +
	void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const;
288 +
289 +
	// compute the special OPL rhythm channel outputs
290 +
	void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const;
291 +
	void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
292 +
	void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
293 +
294 +
	// are we a 4-operator channel or a 2-operator one?
295 +
	bool is4op() const
296 +
	{
297 +
		if (RegisterType::DYNAMIC_OPS)
298 +
			return (m_op[2] != nullptr);
299 +
		return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4);
300 +
	}
301 +
302 +
	// return a reference to our registers
303 +
	RegisterType &regs() const { return m_regs; }
304 +
305 +
	// simple getters for debugging
306 +
	fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
307 +
308 +
private:
309 +
	// helper to add values to the outputs based on channel enables
310 +
	void add_to_output(uint32_t choffs, output_data &output, int32_t value) const
311 +
	{
312 +
		// create these constants to appease overzealous compilers checking array
313 +
		// bounds in unreachable code (looking at you, clang)
314 +
		constexpr int out0_index = 0;
315 +
		constexpr int out1_index = 1 % RegisterType::OUTPUTS;
316 +
		constexpr int out2_index = 2 % RegisterType::OUTPUTS;
317 +
		constexpr int out3_index = 3 % RegisterType::OUTPUTS;
318 +
319 +
		if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs))
320 +
			output.data[out0_index] += value;
321 +
		if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs))
322 +
			output.data[out1_index] += value;
323 +
		if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs))
324 +
			output.data[out2_index] += value;
325 +
		if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs))
326 +
			output.data[out3_index] += value;
327 +
	}
328 +
329 +
	// internal state
330 +
	uint32_t m_choffs;                     // channel offset in registers
331 +
	int16_t m_feedback[2];                 // feedback memory for operator 1
332 +
	mutable int16_t m_feedback_in;         // next input value for op 1 feedback (set in output)
333 +
	std::array<fm_operator<RegisterType> *, 4> m_op; // up to 4 operators
334 +
	RegisterType &m_regs;                  // direct reference to registers
335 +
	fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
336 +
};
337 +
338 +
339 +
// ======================> fm_engine_base
340 +
341 +
// fm_engine_base represents a set of operators and channels which together
342 +
// form a Yamaha FM core; chips that implement other engines (ADPCM, wavetable,
343 +
// etc) take this output and combine it with the others externally
344 +
template<class RegisterType>
345 +
class fm_engine_base : public ymfm_engine_callbacks
346 +
{
347 +
public:
348 +
	// expose some constants from the registers
349 +
	static constexpr uint32_t OUTPUTS = RegisterType::OUTPUTS;
350 +
	static constexpr uint32_t CHANNELS = RegisterType::CHANNELS;
351 +
	static constexpr uint32_t ALL_CHANNELS = RegisterType::ALL_CHANNELS;
352 +
	static constexpr uint32_t OPERATORS = RegisterType::OPERATORS;
353 +
354 +
	// also expose status flags for consumers that inject additional bits
355 +
	static constexpr uint8_t STATUS_TIMERA = RegisterType::STATUS_TIMERA;
356 +
	static constexpr uint8_t STATUS_TIMERB = RegisterType::STATUS_TIMERB;
357 +
	static constexpr uint8_t STATUS_BUSY = RegisterType::STATUS_BUSY;
358 +
	static constexpr uint8_t STATUS_IRQ = RegisterType::STATUS_IRQ;
359 +
360 +
	// expose the correct output class
361 +
	using output_data = ymfm_output<OUTPUTS>;
362 +
363 +
	// constructor
364 +
	fm_engine_base(ymfm_interface &intf);
365 +
366 +
	// save/restore
367 +
	void save_restore(ymfm_saved_state &state);
368 +
369 +
	// reset the overall state
370 +
	void reset();
371 +
372 +
	// master clocking function
373 +
	uint32_t clock(uint32_t chanmask);
374 +
375 +
	// compute sum of channel outputs
376 +
	void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const;
377 +
378 +
	// write to the OPN registers
379 +
	void write(uint16_t regnum, uint8_t data);
380 +
381 +
	// return the current status
382 +
	uint8_t status() const;
383 +
384 +
	// set/reset bits in the status register, updating the IRQ status
385 +
	uint8_t set_reset_status(uint8_t set, uint8_t reset)
386 +
	{
387 +
		m_status = (m_status | set) & ~(reset | STATUS_BUSY);
388 +
		m_intf.ymfm_sync_check_interrupts();
389 +
		return m_status & ~m_regs.status_mask();
390 +
	}
391 +
392 +
	// set the IRQ mask
393 +
	void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); }
394 +
395 +
	// return the current clock prescale
396 +
	uint32_t clock_prescale() const { return m_clock_prescale; }
397 +
398 +
	// set prescale factor (2/3/6)
399 +
	void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; }
400 +
401 +
	// compute sample rate
402 +
	uint32_t sample_rate(uint32_t baseclock) const
403 +
	{
404 +
#if (YMFM_DEBUG_LOG_WAVFILES)
405 +
		for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
406 +
			m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS));
407 +
#endif
408 +
		return baseclock / (m_clock_prescale * OPERATORS);
409 +
	}
410 +
411 +
	// return the owning device
412 +
	ymfm_interface &intf() const { return m_intf; }
413 +
414 +
	// return a reference to our registers
415 +
	RegisterType &regs() { return m_regs; }
416 +
417 +
	// invalidate any caches
418 +
	void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
419 +
420 +
	// simple getters for debugging
421 +
	fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); }
422 +
	fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_operator[index].get(); }
423 +
424 +
public:
425 +
	// timer callback; called by the interface when a timer fires
426 +
	virtual void engine_timer_expired(uint32_t tnum) override;
427 +
428 +
	// check interrupts; called by the interface after synchronization
429 +
	virtual void engine_check_interrupts() override;
430 +
431 +
	// mode register write; called by the interface after synchronization
432 +
	virtual void engine_mode_write(uint8_t data) override;
433 +
434 +
protected:
435 +
	// assign the current set of operators to channels
436 +
	void assign_operators();
437 +
438 +
	// update the state of the given timer
439 +
	void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks);
440 +
441 +
	// internal state
442 +
	ymfm_interface &m_intf;          // reference to the system interface
443 +
	uint32_t m_env_counter;          // envelope counter; low 2 bits are sub-counter
444 +
	uint8_t m_status;                // current status register
445 +
	uint8_t m_clock_prescale;        // prescale factor (2/3/6)
446 +
	uint8_t m_irq_mask;              // mask of which bits signal IRQs
447 +
	uint8_t m_irq_state;             // current IRQ state
448 +
	uint8_t m_timer_running[2];      // current timer running state
449 +
	uint8_t m_total_clocks;          // low 8 bits of the total number of clocks processed
450 +
	uint32_t m_active_channels;      // mask of active channels (computed by prepare)
451 +
	uint32_t m_modified_channels;    // mask of channels that have been modified
452 +
	uint32_t m_prepare_count;        // counter to do periodic prepare sweeps
453 +
	RegisterType m_regs;             // register accessor
454 +
	std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
455 +
	std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
456 +
#if (YMFM_DEBUG_LOG_WAVFILES)
457 +
	mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
458 +
#endif
459 +
};
460 +
461 +
}
462 +
463 +
#endif // YMFM_FM_H
added src/extern/ymfm/src/ymfm_fm.ipp +1592 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
namespace ymfm
32 +
{
33 +
34 +
//*********************************************************
35 +
//  GLOBAL TABLE LOOKUPS
36 +
//*********************************************************
37 +
38 +
//-------------------------------------------------
39 +
//  abs_sin_attenuation - given a sin (phase) input
40 +
//  where the range 0-2*PI is mapped onto 10 bits,
41 +
//  return the absolute value of sin(input),
42 +
//  logarithmically-adjusted and treated as an
43 +
//  attenuation value, in 4.8 fixed point format
44 +
//-------------------------------------------------
45 +
46 +
inline uint32_t abs_sin_attenuation(uint32_t input)
47 +
{
48 +
	// the values here are stored as 4.8 logarithmic values for 1/4 phase
49 +
	// this matches the internal format of the OPN chip, extracted from the die
50 +
	static uint16_t const s_sin_table[256] =
51 +
	{
52 +
		0x859,0x6c3,0x607,0x58b,0x52e,0x4e4,0x4a6,0x471,0x443,0x41a,0x3f5,0x3d3,0x3b5,0x398,0x37e,0x365,
53 +
		0x34e,0x339,0x324,0x311,0x2ff,0x2ed,0x2dc,0x2cd,0x2bd,0x2af,0x2a0,0x293,0x286,0x279,0x26d,0x261,
54 +
		0x256,0x24b,0x240,0x236,0x22c,0x222,0x218,0x20f,0x206,0x1fd,0x1f5,0x1ec,0x1e4,0x1dc,0x1d4,0x1cd,
55 +
		0x1c5,0x1be,0x1b7,0x1b0,0x1a9,0x1a2,0x19b,0x195,0x18f,0x188,0x182,0x17c,0x177,0x171,0x16b,0x166,
56 +
		0x160,0x15b,0x155,0x150,0x14b,0x146,0x141,0x13c,0x137,0x133,0x12e,0x129,0x125,0x121,0x11c,0x118,
57 +
		0x114,0x10f,0x10b,0x107,0x103,0x0ff,0x0fb,0x0f8,0x0f4,0x0f0,0x0ec,0x0e9,0x0e5,0x0e2,0x0de,0x0db,
58 +
		0x0d7,0x0d4,0x0d1,0x0cd,0x0ca,0x0c7,0x0c4,0x0c1,0x0be,0x0bb,0x0b8,0x0b5,0x0b2,0x0af,0x0ac,0x0a9,
59 +
		0x0a7,0x0a4,0x0a1,0x09f,0x09c,0x099,0x097,0x094,0x092,0x08f,0x08d,0x08a,0x088,0x086,0x083,0x081,
60 +
		0x07f,0x07d,0x07a,0x078,0x076,0x074,0x072,0x070,0x06e,0x06c,0x06a,0x068,0x066,0x064,0x062,0x060,
61 +
		0x05e,0x05c,0x05b,0x059,0x057,0x055,0x053,0x052,0x050,0x04e,0x04d,0x04b,0x04a,0x048,0x046,0x045,
62 +
		0x043,0x042,0x040,0x03f,0x03e,0x03c,0x03b,0x039,0x038,0x037,0x035,0x034,0x033,0x031,0x030,0x02f,
63 +
		0x02e,0x02d,0x02b,0x02a,0x029,0x028,0x027,0x026,0x025,0x024,0x023,0x022,0x021,0x020,0x01f,0x01e,
64 +
		0x01d,0x01c,0x01b,0x01a,0x019,0x018,0x017,0x017,0x016,0x015,0x014,0x014,0x013,0x012,0x011,0x011,
65 +
		0x010,0x00f,0x00f,0x00e,0x00d,0x00d,0x00c,0x00c,0x00b,0x00a,0x00a,0x009,0x009,0x008,0x008,0x007,
66 +
		0x007,0x007,0x006,0x006,0x005,0x005,0x005,0x004,0x004,0x004,0x003,0x003,0x003,0x002,0x002,0x002,
67 +
		0x002,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000
68 +
	};
69 +
70 +
	// if the top bit is set, we're in the second half of the curve
71 +
	// which is a mirror image, so invert the index
72 +
	if (bitfield(input, 8))
73 +
		input = ~input;
74 +
75 +
	// return the value from the table
76 +
	return s_sin_table[input & 0xff];
77 +
}
78 +
79 +
80 +
//-------------------------------------------------
81 +
//  attenuation_to_volume - given a 5.8 fixed point
82 +
//  logarithmic attenuation value, return a 13-bit
83 +
//  linear volume
84 +
//-------------------------------------------------
85 +
86 +
inline uint32_t attenuation_to_volume(uint32_t input)
87 +
{
88 +
	// the values here are 10-bit mantissas with an implied leading bit
89 +
	// this matches the internal format of the OPN chip, extracted from the die
90 +
91 +
	// as a nod to performance, the implicit 0x400 bit is pre-incorporated, and
92 +
	// the values are left-shifted by 2 so that a simple right shift is all that
93 +
	// is needed; also the order is reversed to save a NOT on the input
94 +
#define X(a) (((a) | 0x400) << 2)
95 +
	static uint16_t const s_power_table[256] =
96 +
	{
97 +
		X(0x3fa),X(0x3f5),X(0x3ef),X(0x3ea),X(0x3e4),X(0x3df),X(0x3da),X(0x3d4),
98 +
		X(0x3cf),X(0x3c9),X(0x3c4),X(0x3bf),X(0x3b9),X(0x3b4),X(0x3ae),X(0x3a9),
99 +
		X(0x3a4),X(0x39f),X(0x399),X(0x394),X(0x38f),X(0x38a),X(0x384),X(0x37f),
100 +
		X(0x37a),X(0x375),X(0x370),X(0x36a),X(0x365),X(0x360),X(0x35b),X(0x356),
101 +
		X(0x351),X(0x34c),X(0x347),X(0x342),X(0x33d),X(0x338),X(0x333),X(0x32e),
102 +
		X(0x329),X(0x324),X(0x31f),X(0x31a),X(0x315),X(0x310),X(0x30b),X(0x306),
103 +
		X(0x302),X(0x2fd),X(0x2f8),X(0x2f3),X(0x2ee),X(0x2e9),X(0x2e5),X(0x2e0),
104 +
		X(0x2db),X(0x2d6),X(0x2d2),X(0x2cd),X(0x2c8),X(0x2c4),X(0x2bf),X(0x2ba),
105 +
		X(0x2b5),X(0x2b1),X(0x2ac),X(0x2a8),X(0x2a3),X(0x29e),X(0x29a),X(0x295),
106 +
		X(0x291),X(0x28c),X(0x288),X(0x283),X(0x27f),X(0x27a),X(0x276),X(0x271),
107 +
		X(0x26d),X(0x268),X(0x264),X(0x25f),X(0x25b),X(0x257),X(0x252),X(0x24e),
108 +
		X(0x249),X(0x245),X(0x241),X(0x23c),X(0x238),X(0x234),X(0x230),X(0x22b),
109 +
		X(0x227),X(0x223),X(0x21e),X(0x21a),X(0x216),X(0x212),X(0x20e),X(0x209),
110 +
		X(0x205),X(0x201),X(0x1fd),X(0x1f9),X(0x1f5),X(0x1f0),X(0x1ec),X(0x1e8),
111 +
		X(0x1e4),X(0x1e0),X(0x1dc),X(0x1d8),X(0x1d4),X(0x1d0),X(0x1cc),X(0x1c8),
112 +
		X(0x1c4),X(0x1c0),X(0x1bc),X(0x1b8),X(0x1b4),X(0x1b0),X(0x1ac),X(0x1a8),
113 +
		X(0x1a4),X(0x1a0),X(0x19c),X(0x199),X(0x195),X(0x191),X(0x18d),X(0x189),
114 +
		X(0x185),X(0x181),X(0x17e),X(0x17a),X(0x176),X(0x172),X(0x16f),X(0x16b),
115 +
		X(0x167),X(0x163),X(0x160),X(0x15c),X(0x158),X(0x154),X(0x151),X(0x14d),
116 +
		X(0x149),X(0x146),X(0x142),X(0x13e),X(0x13b),X(0x137),X(0x134),X(0x130),
117 +
		X(0x12c),X(0x129),X(0x125),X(0x122),X(0x11e),X(0x11b),X(0x117),X(0x114),
118 +
		X(0x110),X(0x10c),X(0x109),X(0x106),X(0x102),X(0x0ff),X(0x0fb),X(0x0f8),
119 +
		X(0x0f4),X(0x0f1),X(0x0ed),X(0x0ea),X(0x0e7),X(0x0e3),X(0x0e0),X(0x0dc),
120 +
		X(0x0d9),X(0x0d6),X(0x0d2),X(0x0cf),X(0x0cc),X(0x0c8),X(0x0c5),X(0x0c2),
121 +
		X(0x0be),X(0x0bb),X(0x0b8),X(0x0b5),X(0x0b1),X(0x0ae),X(0x0ab),X(0x0a8),
122 +
		X(0x0a4),X(0x0a1),X(0x09e),X(0x09b),X(0x098),X(0x094),X(0x091),X(0x08e),
123 +
		X(0x08b),X(0x088),X(0x085),X(0x082),X(0x07e),X(0x07b),X(0x078),X(0x075),
124 +
		X(0x072),X(0x06f),X(0x06c),X(0x069),X(0x066),X(0x063),X(0x060),X(0x05d),
125 +
		X(0x05a),X(0x057),X(0x054),X(0x051),X(0x04e),X(0x04b),X(0x048),X(0x045),
126 +
		X(0x042),X(0x03f),X(0x03c),X(0x039),X(0x036),X(0x033),X(0x030),X(0x02d),
127 +
		X(0x02a),X(0x028),X(0x025),X(0x022),X(0x01f),X(0x01c),X(0x019),X(0x016),
128 +
		X(0x014),X(0x011),X(0x00e),X(0x00b),X(0x008),X(0x006),X(0x003),X(0x000)
129 +
	};
130 +
#undef X
131 +
132 +
	// look up the fractional part, then shift by the whole
133 +
	return s_power_table[input & 0xff] >> (input >> 8);
134 +
}
135 +
136 +
137 +
//-------------------------------------------------
138 +
//  attenuation_increment - given a 6-bit ADSR
139 +
//  rate value and a 3-bit stepping index,
140 +
//  return a 4-bit increment to the attenutaion
141 +
//  for this step (or for the attack case, the
142 +
//  fractional scale factor to decrease by)
143 +
//-------------------------------------------------
144 +
145 +
inline uint32_t attenuation_increment(uint32_t rate, uint32_t index)
146 +
{
147 +
	static uint32_t const s_increment_table[64] =
148 +
	{
149 +
		0x00000000, 0x00000000, 0x10101010, 0x10101010,  // 0-3    (0x00-0x03)
150 +
		0x10101010, 0x10101010, 0x11101110, 0x11101110,  // 4-7    (0x04-0x07)
151 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 8-11   (0x08-0x0B)
152 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 12-15  (0x0C-0x0F)
153 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 16-19  (0x10-0x13)
154 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 20-23  (0x14-0x17)
155 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 24-27  (0x18-0x1B)
156 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 28-31  (0x1C-0x1F)
157 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 32-35  (0x20-0x23)
158 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 36-39  (0x24-0x27)
159 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 40-43  (0x28-0x2B)
160 +
		0x10101010, 0x10111010, 0x11101110, 0x11111110,  // 44-47  (0x2C-0x2F)
161 +
		0x11111111, 0x21112111, 0x21212121, 0x22212221,  // 48-51  (0x30-0x33)
162 +
		0x22222222, 0x42224222, 0x42424242, 0x44424442,  // 52-55  (0x34-0x37)
163 +
		0x44444444, 0x84448444, 0x84848484, 0x88848884,  // 56-59  (0x38-0x3B)
164 +
		0x88888888, 0x88888888, 0x88888888, 0x88888888   // 60-63  (0x3C-0x3F)
165 +
	};
166 +
	return bitfield(s_increment_table[rate], 4*index, 4);
167 +
}
168 +
169 +
170 +
//-------------------------------------------------
171 +
//  detune_adjustment - given a 5-bit key code
172 +
//  value and a 3-bit detune parameter, return a
173 +
//  6-bit signed phase displacement; this table
174 +
//  has been verified against Nuked's equations,
175 +
//  but the equations are rather complicated, so
176 +
//  we'll keep the simplicity of the table
177 +
//-------------------------------------------------
178 +
179 +
inline int32_t detune_adjustment(uint32_t detune, uint32_t keycode)
180 +
{
181 +
	static uint8_t const s_detune_adjustment[32][4] =
182 +
	{
183 +
		{ 0,  0,  1,  2 },  { 0,  0,  1,  2 },  { 0,  0,  1,  2 },  { 0,  0,  1,  2 },
184 +
		{ 0,  1,  2,  2 },  { 0,  1,  2,  3 },  { 0,  1,  2,  3 },  { 0,  1,  2,  3 },
185 +
		{ 0,  1,  2,  4 },  { 0,  1,  3,  4 },  { 0,  1,  3,  4 },  { 0,  1,  3,  5 },
186 +
		{ 0,  2,  4,  5 },  { 0,  2,  4,  6 },  { 0,  2,  4,  6 },  { 0,  2,  5,  7 },
187 +
		{ 0,  2,  5,  8 },  { 0,  3,  6,  8 },  { 0,  3,  6,  9 },  { 0,  3,  7, 10 },
188 +
		{ 0,  4,  8, 11 },  { 0,  4,  8, 12 },  { 0,  4,  9, 13 },  { 0,  5, 10, 14 },
189 +
		{ 0,  5, 11, 16 },  { 0,  6, 12, 17 },  { 0,  6, 13, 19 },  { 0,  7, 14, 20 },
190 +
		{ 0,  8, 16, 22 },  { 0,  8, 16, 22 },  { 0,  8, 16, 22 },  { 0,  8, 16, 22 }
191 +
	};
192 +
	int32_t result = s_detune_adjustment[keycode][detune & 3];
193 +
	return bitfield(detune, 2) ? -result : result;
194 +
}
195 +
196 +
197 +
//-------------------------------------------------
198 +
//  opm_key_code_to_phase_step - converts an
199 +
//  OPM concatenated block (3 bits), keycode
200 +
//  (4 bits) and key fraction (6 bits) to a 0.10
201 +
//  phase step, after applying the given delta;
202 +
//  this applies to OPM and OPZ, so it lives here
203 +
//  in a central location
204 +
//-------------------------------------------------
205 +
206 +
inline uint32_t opm_key_code_to_phase_step(uint32_t block_freq, int32_t delta)
207 +
{
208 +
	// The phase step is essentially the fnum in OPN-speak. To compute this table,
209 +
	// we used the standard formula for computing the frequency of a note, and
210 +
	// then converted that frequency to fnum using the formula documented in the
211 +
	// YM2608 manual.
212 +
	//
213 +
	// However, the YM2608 manual describes everything in terms of a nominal 8MHz
214 +
	// clock, which produces an FM clock of:
215 +
	//
216 +
	//    8000000 / 24(operators) / 6(prescale) = 55555Hz FM clock
217 +
	//
218 +
	// Whereas the descriptions for the YM2151 use a nominal 3.579545MHz clock:
219 +
	//
220 +
	//    3579545 / 32(operators) / 2(prescale) = 55930Hz FM clock
221 +
	//
222 +
	// To correct for this, the YM2608 formula was adjusted to use a clock of
223 +
	// 8053920Hz, giving this equation for the fnum:
224 +
	//
225 +
	//    fnum = (double(144) * freq * (1 << 20)) / double(8053920) / 4;
226 +
	//
227 +
	// Unfortunately, the computed table differs in a few spots from the data
228 +
	// verified from an actual chip. The table below comes from David Viens'
229 +
	// analysis, used with his permission.
230 +
	static const uint32_t s_phase_step[12*64] =
231 +
	{
232 +
		41568,41600,41632,41664,41696,41728,41760,41792,41856,41888,41920,41952,42016,42048,42080,42112,
233 +
		42176,42208,42240,42272,42304,42336,42368,42400,42464,42496,42528,42560,42624,42656,42688,42720,
234 +
		42784,42816,42848,42880,42912,42944,42976,43008,43072,43104,43136,43168,43232,43264,43296,43328,
235 +
		43392,43424,43456,43488,43552,43584,43616,43648,43712,43744,43776,43808,43872,43904,43936,43968,
236 +
		44032,44064,44096,44128,44192,44224,44256,44288,44352,44384,44416,44448,44512,44544,44576,44608,
237 +
		44672,44704,44736,44768,44832,44864,44896,44928,44992,45024,45056,45088,45152,45184,45216,45248,
238 +
		45312,45344,45376,45408,45472,45504,45536,45568,45632,45664,45728,45760,45792,45824,45888,45920,
239 +
		45984,46016,46048,46080,46144,46176,46208,46240,46304,46336,46368,46400,46464,46496,46528,46560,
240 +
		46656,46688,46720,46752,46816,46848,46880,46912,46976,47008,47072,47104,47136,47168,47232,47264,
241 +
		47328,47360,47392,47424,47488,47520,47552,47584,47648,47680,47744,47776,47808,47840,47904,47936,
242 +
		48032,48064,48096,48128,48192,48224,48288,48320,48384,48416,48448,48480,48544,48576,48640,48672,
243 +
		48736,48768,48800,48832,48896,48928,48992,49024,49088,49120,49152,49184,49248,49280,49344,49376,
244 +
		49440,49472,49504,49536,49600,49632,49696,49728,49792,49824,49856,49888,49952,49984,50048,50080,
245 +
		50144,50176,50208,50240,50304,50336,50400,50432,50496,50528,50560,50592,50656,50688,50752,50784,
246 +
		50880,50912,50944,50976,51040,51072,51136,51168,51232,51264,51328,51360,51424,51456,51488,51520,
247 +
		51616,51648,51680,51712,51776,51808,51872,51904,51968,52000,52064,52096,52160,52192,52224,52256,
248 +
		52384,52416,52448,52480,52544,52576,52640,52672,52736,52768,52832,52864,52928,52960,52992,53024,
249 +
		53120,53152,53216,53248,53312,53344,53408,53440,53504,53536,53600,53632,53696,53728,53792,53824,
250 +
		53920,53952,54016,54048,54112,54144,54208,54240,54304,54336,54400,54432,54496,54528,54592,54624,
251 +
		54688,54720,54784,54816,54880,54912,54976,55008,55072,55104,55168,55200,55264,55296,55360,55392,
252 +
		55488,55520,55584,55616,55680,55712,55776,55808,55872,55936,55968,56032,56064,56128,56160,56224,
253 +
		56288,56320,56384,56416,56480,56512,56576,56608,56672,56736,56768,56832,56864,56928,56960,57024,
254 +
		57120,57152,57216,57248,57312,57376,57408,57472,57536,57568,57632,57664,57728,57792,57824,57888,
255 +
		57952,57984,58048,58080,58144,58208,58240,58304,58368,58400,58464,58496,58560,58624,58656,58720,
256 +
		58784,58816,58880,58912,58976,59040,59072,59136,59200,59232,59296,59328,59392,59456,59488,59552,
257 +
		59648,59680,59744,59776,59840,59904,59936,60000,60064,60128,60160,60224,60288,60320,60384,60416,
258 +
		60512,60544,60608,60640,60704,60768,60800,60864,60928,60992,61024,61088,61152,61184,61248,61280,
259 +
		61376,61408,61472,61536,61600,61632,61696,61760,61824,61856,61920,61984,62048,62080,62144,62208,
260 +
		62272,62304,62368,62432,62496,62528,62592,62656,62720,62752,62816,62880,62944,62976,63040,63104,
261 +
		63200,63232,63296,63360,63424,63456,63520,63584,63648,63680,63744,63808,63872,63904,63968,64032,
262 +
		64096,64128,64192,64256,64320,64352,64416,64480,64544,64608,64672,64704,64768,64832,64896,64928,
263 +
		65024,65056,65120,65184,65248,65312,65376,65408,65504,65536,65600,65664,65728,65792,65856,65888,
264 +
		65984,66016,66080,66144,66208,66272,66336,66368,66464,66496,66560,66624,66688,66752,66816,66848,
265 +
		66944,66976,67040,67104,67168,67232,67296,67328,67424,67456,67520,67584,67648,67712,67776,67808,
266 +
		67904,67936,68000,68064,68128,68192,68256,68288,68384,68448,68512,68544,68640,68672,68736,68800,
267 +
		68896,68928,68992,69056,69120,69184,69248,69280,69376,69440,69504,69536,69632,69664,69728,69792,
268 +
		69920,69952,70016,70080,70144,70208,70272,70304,70400,70464,70528,70560,70656,70688,70752,70816,
269 +
		70912,70976,71040,71104,71136,71232,71264,71360,71424,71488,71552,71616,71648,71744,71776,71872,
270 +
		71968,72032,72096,72160,72192,72288,72320,72416,72480,72544,72608,72672,72704,72800,72832,72928,
271 +
		72992,73056,73120,73184,73216,73312,73344,73440,73504,73568,73632,73696,73728,73824,73856,73952,
272 +
		74080,74144,74208,74272,74304,74400,74432,74528,74592,74656,74720,74784,74816,74912,74944,75040,
273 +
		75136,75200,75264,75328,75360,75456,75488,75584,75648,75712,75776,75840,75872,75968,76000,76096,
274 +
		76224,76288,76352,76416,76448,76544,76576,76672,76736,76800,76864,76928,77024,77120,77152,77248,
275 +
		77344,77408,77472,77536,77568,77664,77696,77792,77856,77920,77984,78048,78144,78240,78272,78368,
276 +
		78464,78528,78592,78656,78688,78784,78816,78912,78976,79040,79104,79168,79264,79360,79392,79488,
277 +
		79616,79680,79744,79808,79840,79936,79968,80064,80128,80192,80256,80320,80416,80512,80544,80640,
278 +
		80768,80832,80896,80960,80992,81088,81120,81216,81280,81344,81408,81472,81568,81664,81696,81792,
279 +
		81952,82016,82080,82144,82176,82272,82304,82400,82464,82528,82592,82656,82752,82848,82880,82976
280 +
	};
281 +
282 +
	// extract the block (octave) first
283 +
	uint32_t block = bitfield(block_freq, 10, 3);
284 +
285 +
	// the keycode (bits 6-9) is "gappy", mapping 12 values over 16 in each
286 +
	// octave; to correct for this, we multiply the 4-bit value by 3/4 (or
287 +
	// rather subtract 1/4); note that a (invalid) value of 15 will bleed into
288 +
	// the next octave -- this is confirmed
289 +
	uint32_t adjusted_code = bitfield(block_freq, 6, 4) - bitfield(block_freq, 8, 2);
290 +
291 +
	// now re-insert the 6-bit fraction
292 +
	int32_t eff_freq = (adjusted_code << 6) | bitfield(block_freq, 0, 6);
293 +
294 +
	// now that the gaps are removed, add the delta
295 +
	eff_freq += delta;
296 +
297 +
	// handle over/underflow by adjusting the block:
298 +
	if (uint32_t(eff_freq) >= 768)
299 +
	{
300 +
		// minimum delta is -512 (PM), so we can only underflow by 1 octave
301 +
		if (eff_freq < 0)
302 +
		{
303 +
			eff_freq += 768;
304 +
			if (block-- == 0)
305 +
				return s_phase_step[0] >> 7;
306 +
		}
307 +
308 +
		// maximum delta is +512+608 (PM+detune), so we can overflow by up to 2 octaves
309 +
		else
310 +
		{
311 +
			eff_freq -= 768;
312 +
			if (eff_freq >= 768)
313 +
				block++, eff_freq -= 768;
314 +
			if (block++ >= 7)
315 +
				return s_phase_step[767];
316 +
		}
317 +
	}
318 +
319 +
	// look up the phase shift for the key code, then shift by octave
320 +
	return s_phase_step[eff_freq] >> (block ^ 7);
321 +
}
322 +
323 +
324 +
//-------------------------------------------------
325 +
//  opn_lfo_pm_phase_adjustment - given the 7 most
326 +
//  significant frequency number bits, plus a 3-bit
327 +
//  PM depth value and a signed 5-bit raw PM value,
328 +
//  return a signed PM adjustment to the frequency;
329 +
//  algorithm written to match Nuked behavior
330 +
//-------------------------------------------------
331 +
332 +
inline int32_t opn_lfo_pm_phase_adjustment(uint32_t fnum_bits, uint32_t pm_sensitivity, int32_t lfo_raw_pm)
333 +
{
334 +
	// this table encodes 2 shift values to apply to the top 7 bits
335 +
	// of fnum; it is effectively a cheap multiply by a constant
336 +
	// value containing 0-2 bits
337 +
	static uint8_t const s_lfo_pm_shifts[8][8] =
338 +
	{
339 +
		{ 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77 },
340 +
		{ 0x77, 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x72 },
341 +
		{ 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x17, 0x17 },
342 +
		{ 0x77, 0x77, 0x72, 0x72, 0x17, 0x17, 0x12, 0x12 },
343 +
		{ 0x77, 0x77, 0x72, 0x17, 0x17, 0x17, 0x12, 0x07 },
344 +
		{ 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 },
345 +
		{ 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 },
346 +
		{ 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 }
347 +
	};
348 +
349 +
	// look up the relevant shifts
350 +
	int32_t abs_pm = (lfo_raw_pm < 0) ? -lfo_raw_pm : lfo_raw_pm;
351 +
	uint32_t const shifts = s_lfo_pm_shifts[pm_sensitivity][bitfield(abs_pm, 0, 3)];
352 +
353 +
	// compute the adjustment
354 +
	int32_t adjust = (fnum_bits >> bitfield(shifts, 0, 4)) + (fnum_bits >> bitfield(shifts, 4, 4));
355 +
	if (pm_sensitivity > 5)
356 +
		adjust <<= pm_sensitivity - 5;
357 +
	adjust >>= 2;
358 +
359 +
	// every 16 cycles it inverts sign
360 +
	return (lfo_raw_pm < 0) ? -adjust : adjust;
361 +
}
362 +
363 +
364 +
365 +
//*********************************************************
366 +
//  FM OPERATOR
367 +
//*********************************************************
368 +
369 +
//-------------------------------------------------
370 +
//  fm_operator - constructor
371 +
//-------------------------------------------------
372 +
373 +
template<class RegisterType>
374 +
fm_operator<RegisterType>::fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs) :
375 +
	m_choffs(0),
376 +
	m_opoffs(opoffs),
377 +
	m_phase(0),
378 +
	m_env_attenuation(0x3ff),
379 +
	m_env_state(EG_RELEASE),
380 +
	m_ssg_inverted(false),
381 +
	m_key_state(0),
382 +
	m_keyon_live(0),
383 +
	m_regs(owner.regs()),
384 +
	m_owner(owner)
385 +
{
386 +
}
387 +
388 +
389 +
//-------------------------------------------------
390 +
//  reset - reset the channel state
391 +
//-------------------------------------------------
392 +
393 +
template<class RegisterType>
394 +
void fm_operator<RegisterType>::reset()
395 +
{
396 +
	// reset our data
397 +
	m_phase = 0;
398 +
	m_env_attenuation = 0x3ff;
399 +
	m_env_state = EG_RELEASE;
400 +
	m_ssg_inverted = 0;
401 +
	m_key_state = 0;
402 +
	m_keyon_live = 0;
403 +
}
404 +
405 +
406 +
//-------------------------------------------------
407 +
//  save_restore - save or restore the data
408 +
//-------------------------------------------------
409 +
410 +
template<class RegisterType>
411 +
void fm_operator<RegisterType>::save_restore(ymfm_saved_state &state)
412 +
{
413 +
	state.save_restore(m_phase);
414 +
	state.save_restore(m_env_attenuation);
415 +
	state.save_restore(m_env_state);
416 +
	state.save_restore(m_ssg_inverted);
417 +
	state.save_restore(m_key_state);
418 +
	state.save_restore(m_keyon_live);
419 +
}
420 +
421 +
422 +
//-------------------------------------------------
423 +
//  prepare - prepare for clocking
424 +
//-------------------------------------------------
425 +
426 +
template<class RegisterType>
427 +
bool fm_operator<RegisterType>::prepare()
428 +
{
429 +
	// cache the data
430 +
	m_regs.cache_operator_data(m_choffs, m_opoffs, m_cache);
431 +
432 +
	// clock the key state
433 +
	clock_keystate(uint32_t(m_keyon_live != 0));
434 +
	m_keyon_live &= ~(1 << KEYON_CSM);
435 +
436 +
	// we're active until we're quiet after the release
437 +
	return (m_env_state != (RegisterType::EG_HAS_REVERB ? EG_REVERB : EG_RELEASE) || m_env_attenuation < EG_QUIET);
438 +
}
439 +
440 +
441 +
//-------------------------------------------------
442 +
//  clock - master clocking function
443 +
//-------------------------------------------------
444 +
445 +
template<class RegisterType>
446 +
void fm_operator<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
447 +
{
448 +
	// clock the SSG-EG state (OPN/OPNA)
449 +
	if (m_regs.op_ssg_eg_enable(m_opoffs))
450 +
		clock_ssg_eg_state();
451 +
	else
452 +
		m_ssg_inverted = false;
453 +
454 +
	// clock the envelope if on an envelope cycle; env_counter is a x.2 value
455 +
	if (bitfield(env_counter, 0, 2) == 0)
456 +
		clock_envelope(env_counter >> 2);
457 +
458 +
	// clock the phase
459 +
	clock_phase(lfo_raw_pm);
460 +
}
461 +
462 +
463 +
//-------------------------------------------------
464 +
//  compute_volume - compute the 14-bit signed
465 +
//  volume of this operator, given a phase
466 +
//  modulation and an AM LFO offset
467 +
//-------------------------------------------------
468 +
469 +
template<class RegisterType>
470 +
int32_t fm_operator<RegisterType>::compute_volume(uint32_t phase, uint32_t am_offset) const
471 +
{
472 +
	// the low 10 bits of phase represents a full 2*PI period over
473 +
	// the full sin wave
474 +
475 +
	// early out if the envelope is effectively off
476 +
	if (m_env_attenuation > EG_QUIET)
477 +
		return 0;
478 +
479 +
	// get the absolute value of the sin, as attenuation, as a 4.8 fixed point value
480 +
	uint32_t sin_attenuation = m_cache.waveform[phase & (RegisterType::WAVEFORM_LENGTH - 1)];
481 +
482 +
	// get the attenuation from the evelope generator as a 4.6 value, shifted up to 4.8
483 +
	uint32_t env_attenuation = envelope_attenuation(am_offset) << 2;
484 +
485 +
	// combine into a 5.8 value, then convert from attenuation to 13-bit linear volume
486 +
	int32_t result = attenuation_to_volume((sin_attenuation & 0x7fff) + env_attenuation);
487 +
488 +
	// negate if in the negative part of the sin wave (sign bit gives 14 bits)
489 +
	return bitfield(sin_attenuation, 15) ? -result : result;
490 +
}
491 +
492 +
493 +
//-------------------------------------------------
494 +
//  compute_noise_volume - compute the 14-bit
495 +
//  signed noise volume of this operator, given a
496 +
//  noise input value and an AM offset
497 +
//-------------------------------------------------
498 +
499 +
template<class RegisterType>
500 +
int32_t fm_operator<RegisterType>::compute_noise_volume(uint32_t am_offset) const
501 +
{
502 +
	// application manual says the logarithmic transform is not applied here, so we
503 +
	// just use the raw envelope attenuation, inverted (since 0 attenuation should be
504 +
	// maximum), and shift it up from a 10-bit value to an 11-bit value
505 +
	int32_t result = (envelope_attenuation(am_offset) ^ 0x3ff) << 1;
506 +
507 +
	// QUESTION: is AM applied still?
508 +
509 +
	// negate based on the noise state
510 +
	return bitfield(m_regs.noise_state(), 0) ? -result : result;
511 +
}
512 +
513 +
514 +
//-------------------------------------------------
515 +
//  keyonoff - signal a key on/off event
516 +
//-------------------------------------------------
517 +
518 +
template<class RegisterType>
519 +
void fm_operator<RegisterType>::keyonoff(uint32_t on, keyon_type type)
520 +
{
521 +
	m_keyon_live = (m_keyon_live & ~(1 << int(type))) | (bitfield(on, 0) << int(type));
522 +
}
523 +
524 +
525 +
//-------------------------------------------------
526 +
//  start_attack - start the attack phase; called
527 +
//  when a keyon happens or when an SSG-EG cycle
528 +
//  is complete and restarts
529 +
//-------------------------------------------------
530 +
531 +
template<class RegisterType>
532 +
void fm_operator<RegisterType>::start_attack(bool is_restart)
533 +
{
534 +
	// don't change anything if already in attack state
535 +
	if (m_env_state == EG_ATTACK)
536 +
		return;
537 +
	m_env_state = EG_ATTACK;
538 +
539 +
	// generally not inverted at start, except if SSG-EG is enabled and
540 +
	// one of the inverted modes is specified; leave this alone on a
541 +
	// restart, as it is managed by the clock_ssg_eg_state() code
542 +
	if (RegisterType::EG_HAS_SSG && !is_restart)
543 +
		m_ssg_inverted = m_regs.op_ssg_eg_enable(m_opoffs) & bitfield(m_regs.op_ssg_eg_mode(m_opoffs), 2);
544 +
545 +
	// reset the phase when we start an attack due to a key on
546 +
	// (but not when due to an SSG-EG restart except in certain cases
547 +
	// managed directly by the SSG-EG code)
548 +
	if (!is_restart)
549 +
		m_phase = 0;
550 +
551 +
	// if the attack rate >= 62 then immediately go to max attenuation
552 +
	if (m_cache.eg_rate[EG_ATTACK] >= 62)
553 +
		m_env_attenuation = 0;
554 +
}
555 +
556 +
557 +
//-------------------------------------------------
558 +
//  start_release - start the release phase;
559 +
//  called when a keyoff happens
560 +
//-------------------------------------------------
561 +
562 +
template<class RegisterType>
563 +
void fm_operator<RegisterType>::start_release()
564 +
{
565 +
	// don't change anything if already in release state
566 +
	if (m_env_state >= EG_RELEASE)
567 +
		return;
568 +
	m_env_state = EG_RELEASE;
569 +
570 +
	// if attenuation if inverted due to SSG-EG, snap the inverted attenuation
571 +
	// as the starting point
572 +
	if (RegisterType::EG_HAS_SSG && m_ssg_inverted)
573 +
	{
574 +
		m_env_attenuation = (0x200 - m_env_attenuation) & 0x3ff;
575 +
		m_ssg_inverted = false;
576 +
	}
577 +
}
578 +
579 +
580 +
//-------------------------------------------------
581 +
//  clock_keystate - clock the keystate to match
582 +
//  the incoming keystate
583 +
//-------------------------------------------------
584 +
585 +
template<class RegisterType>
586 +
void fm_operator<RegisterType>::clock_keystate(uint32_t keystate)
587 +
{
588 +
	assert(keystate == 0 || keystate == 1);
589 +
590 +
	// has the key changed?
591 +
	if ((keystate ^ m_key_state) != 0)
592 +
	{
593 +
		m_key_state = keystate;
594 +
595 +
		// if the key has turned on, start the attack
596 +
		if (keystate != 0)
597 +
		{
598 +
			// OPLL has a DP ("depress"?) state to bring the volume
599 +
			// down before starting the attack
600 +
			if (RegisterType::EG_HAS_DEPRESS && m_env_attenuation < 0x200)
601 +
				m_env_state = EG_DEPRESS;
602 +
			else
603 +
				start_attack();
604 +
		}
605 +
606 +
		// otherwise, start the release
607 +
		else
608 +
			start_release();
609 +
	}
610 +
}
611 +
612 +
613 +
//-------------------------------------------------
614 +
//  clock_ssg_eg_state - clock the SSG-EG state;
615 +
//  should only be called if SSG-EG is enabled
616 +
//-------------------------------------------------
617 +
618 +
template<class RegisterType>
619 +
void fm_operator<RegisterType>::clock_ssg_eg_state()
620 +
{
621 +
	// work only happens once the attenuation crosses above 0x200
622 +
	if (!bitfield(m_env_attenuation, 9))
623 +
		return;
624 +
625 +
	// 8 SSG-EG modes:
626 +
	//    000: repeat normally
627 +
	//    001: run once, hold low
628 +
	//    010: repeat, alternating between inverted/non-inverted
629 +
	//    011: run once, hold high
630 +
	//    100: inverted repeat normally
631 +
	//    101: inverted run once, hold low
632 +
	//    110: inverted repeat, alternating between inverted/non-inverted
633 +
	//    111: inverted run once, hold high
634 +
	uint32_t mode = m_regs.op_ssg_eg_mode(m_opoffs);
635 +
636 +
	// hold modes (1/3/5/7)
637 +
	if (bitfield(mode, 0))
638 +
	{
639 +
		// set the inverted flag to the end state (0 for modes 1/7, 1 for modes 3/5)
640 +
		m_ssg_inverted = bitfield(mode, 2) ^ bitfield(mode, 1);
641 +
642 +
		// if holding, force the attenuation to the expected value once we're
643 +
		// past the attack phase
644 +
		if (m_env_state != EG_ATTACK)
645 +
			m_env_attenuation = m_ssg_inverted ? 0x200 : 0x3ff;
646 +
	}
647 +
648 +
	// continuous modes (0/2/4/6)
649 +
	else
650 +
	{
651 +
		// toggle invert in alternating mode (even in attack state)
652 +
		m_ssg_inverted ^= bitfield(mode, 1);
653 +
654 +
		// restart attack if in decay/sustain states
655 +
		if (m_env_state == EG_DECAY || m_env_state == EG_SUSTAIN)
656 +
			start_attack(true);
657 +
658 +
		// phase is reset to 0 in modes 0/4
659 +
		if (bitfield(mode, 1) == 0)
660 +
			m_phase = 0;
661 +
	}
662 +
663 +
	// in all modes, once we hit release state, attenuation is forced to maximum
664 +
	if (m_env_state == EG_RELEASE)
665 +
		m_env_attenuation = 0x3ff;
666 +
}
667 +
668 +
669 +
//-------------------------------------------------
670 +
//  clock_envelope - clock the envelope state
671 +
//  according to the given count
672 +
//-------------------------------------------------
673 +
674 +
template<class RegisterType>
675 +
void fm_operator<RegisterType>::clock_envelope(uint32_t env_counter)
676 +
{
677 +
	// handle attack->decay transitions
678 +
	if (m_env_state == EG_ATTACK && m_env_attenuation == 0)
679 +
		m_env_state = EG_DECAY;
680 +
681 +
	// handle decay->sustain transitions; it is important to do this immediately
682 +
	// after the attack->decay transition above in the event that the sustain level
683 +
	// is set to 0 (in which case we will skip right to sustain without doing any
684 +
	// decay); as an example where this can be heard, check the cymbals sound
685 +
	// in channel 0 of shinobi's test mode sound #5
686 +
	if (m_env_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
687 +
		m_env_state = EG_SUSTAIN;
688 +
689 +
	// fetch the appropriate 6-bit rate value from the cache
690 +
	uint32_t rate = m_cache.eg_rate[m_env_state];
691 +
692 +
	// compute the rate shift value; this is the shift needed to
693 +
	// apply to the env_counter such that it becomes a 5.11 fixed
694 +
	// point number
695 +
	uint32_t rate_shift = rate >> 2;
696 +
	env_counter <<= rate_shift;
697 +
698 +
	// see if the fractional part is 0; if not, it's not time to clock
699 +
	if (bitfield(env_counter, 0, 11) != 0)
700 +
		return;
701 +
702 +
	// determine the increment based on the non-fractional part of env_counter
703 +
	uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
704 +
	uint32_t increment = attenuation_increment(rate, relevant_bits);
705 +
706 +
	// attack is the only one that increases
707 +
	if (m_env_state == EG_ATTACK)
708 +
	{
709 +
		// glitch means that attack rates of 62/63 don't increment if
710 +
		// changed after the initial key on (where they are handled
711 +
		// specially); nukeykt confirms this happens on OPM, OPN, OPL/OPLL
712 +
		// at least so assuming it is true for everyone
713 +
		if (rate < 62)
714 +
			m_env_attenuation += (~m_env_attenuation * increment) >> 4;
715 +
	}
716 +
717 +
	// all other cases are similar
718 +
	else
719 +
	{
720 +
		// non-SSG-EG cases just apply the increment
721 +
		if (!m_regs.op_ssg_eg_enable(m_opoffs))
722 +
			m_env_attenuation += increment;
723 +
724 +
		// SSG-EG only applies if less than mid-point, and then at 4x
725 +
		else if (m_env_attenuation < 0x200)
726 +
			m_env_attenuation += 4 * increment;
727 +
728 +
		// clamp the final attenuation
729 +
		if (m_env_attenuation >= 0x400)
730 +
			m_env_attenuation = 0x3ff;
731 +
732 +
		// transition from depress to attack
733 +
		if (RegisterType::EG_HAS_DEPRESS && m_env_state == EG_DEPRESS && m_env_attenuation >= 0x200)
734 +
			start_attack();
735 +
736 +
		// transition from release to reverb, should switch at -18dB
737 +
		if (RegisterType::EG_HAS_REVERB && m_env_state == EG_RELEASE && m_env_attenuation >= 0xc0)
738 +
			m_env_state = EG_REVERB;
739 +
	}
740 +
}
741 +
742 +
743 +
//-------------------------------------------------
744 +
//  clock_phase - clock the 10.10 phase value; the
745 +
//  OPN version of the logic has been verified
746 +
//  against the Nuked phase generator
747 +
//-------------------------------------------------
748 +
749 +
template<class RegisterType>
750 +
void fm_operator<RegisterType>::clock_phase(int32_t lfo_raw_pm)
751 +
{
752 +
	// read from the cache, or recalculate if PM active
753 +
	uint32_t phase_step = m_cache.phase_step;
754 +
	if (phase_step == opdata_cache::PHASE_STEP_DYNAMIC)
755 +
		phase_step = m_regs.compute_phase_step(m_choffs, m_opoffs, m_cache, lfo_raw_pm);
756 +
757 +
	// finally apply the step to the current phase value
758 +
	m_phase += phase_step;
759 +
}
760 +
761 +
762 +
//-------------------------------------------------
763 +
//  envelope_attenuation - return the effective
764 +
//  attenuation of the envelope
765 +
//-------------------------------------------------
766 +
767 +
template<class RegisterType>
768 +
uint32_t fm_operator<RegisterType>::envelope_attenuation(uint32_t am_offset) const
769 +
{
770 +
	uint32_t result = m_env_attenuation >> m_cache.eg_shift;
771 +
772 +
	// invert if necessary due to SSG-EG
773 +
	if (RegisterType::EG_HAS_SSG && m_ssg_inverted)
774 +
		result = (0x200 - result) & 0x3ff;
775 +
776 +
	// add in LFO AM modulation
777 +
	if (m_regs.op_lfo_am_enable(m_opoffs))
778 +
		result += am_offset;
779 +
780 +
	// add in total level and KSL from the cache
781 +
	result += m_cache.total_level;
782 +
783 +
	// clamp to max, apply shift, and return
784 +
	return std::min<uint32_t>(result, 0x3ff);
785 +
}
786 +
787 +
788 +
789 +
//*********************************************************
790 +
//  FM CHANNEL
791 +
//*********************************************************
792 +
793 +
//-------------------------------------------------
794 +
//  fm_channel - constructor
795 +
//-------------------------------------------------
796 +
797 +
template<class RegisterType>
798 +
fm_channel<RegisterType>::fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs) :
799 +
	m_choffs(choffs),
800 +
	m_feedback{ 0, 0 },
801 +
	m_feedback_in(0),
802 +
	m_op{ nullptr, nullptr, nullptr, nullptr },
803 +
	m_regs(owner.regs()),
804 +
	m_owner(owner)
805 +
{
806 +
}
807 +
808 +
809 +
//-------------------------------------------------
810 +
//  reset - reset the channel state
811 +
//-------------------------------------------------
812 +
813 +
template<class RegisterType>
814 +
void fm_channel<RegisterType>::reset()
815 +
{
816 +
	// reset our data
817 +
	m_feedback[0] = m_feedback[1] = 0;
818 +
	m_feedback_in = 0;
819 +
}
820 +
821 +
822 +
//-------------------------------------------------
823 +
//  save_restore - save or restore the data
824 +
//-------------------------------------------------
825 +
826 +
template<class RegisterType>
827 +
void fm_channel<RegisterType>::save_restore(ymfm_saved_state &state)
828 +
{
829 +
	state.save_restore(m_feedback[0]);
830 +
	state.save_restore(m_feedback[1]);
831 +
	state.save_restore(m_feedback_in);
832 +
}
833 +
834 +
835 +
//-------------------------------------------------
836 +
//  keyonoff - signal key on/off to our operators
837 +
//-------------------------------------------------
838 +
839 +
template<class RegisterType>
840 +
void fm_channel<RegisterType>::keyonoff(uint32_t states, keyon_type type, uint32_t chnum)
841 +
{
842 +
	for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
843 +
		if (m_op[opnum] != nullptr)
844 +
			m_op[opnum]->keyonoff(bitfield(states, opnum), type);
845 +
846 +
	if (debug::LOG_KEYON_EVENTS && ((debug::GLOBAL_FM_CHANNEL_MASK >> chnum) & 1) != 0)
847 +
		for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
848 +
			if (m_op[opnum] != nullptr)
849 +
				debug::log_keyon("%c%s\n", bitfield(states, opnum) ? '+' : '-', m_regs.log_keyon(m_choffs, m_op[opnum]->opoffs()).c_str());
850 +
}
851 +
852 +
853 +
//-------------------------------------------------
854 +
//  prepare - prepare for clocking
855 +
//-------------------------------------------------
856 +
857 +
template<class RegisterType>
858 +
bool fm_channel<RegisterType>::prepare()
859 +
{
860 +
	uint32_t active_mask = 0;
861 +
862 +
	// prepare all operators and determine if they are active
863 +
	for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
864 +
		if (m_op[opnum] != nullptr)
865 +
			if (m_op[opnum]->prepare())
866 +
				active_mask |= 1 << opnum;
867 +
868 +
	return (active_mask != 0);
869 +
}
870 +
871 +
872 +
//-------------------------------------------------
873 +
//  clock - master clock of all operators
874 +
//-------------------------------------------------
875 +
876 +
template<class RegisterType>
877 +
void fm_channel<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
878 +
{
879 +
	// clock the feedback through
880 +
	m_feedback[0] = m_feedback[1];
881 +
	m_feedback[1] = m_feedback_in;
882 +
883 +
	for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
884 +
		if (m_op[opnum] != nullptr)
885 +
			m_op[opnum]->clock(env_counter, lfo_raw_pm);
886 +
887 +
/*
888 +
useful temporary code for envelope debugging
889 +
if (m_choffs == 0x101)
890 +
{
891 +
	for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
892 +
	{
893 +
		auto &op = *m_op[((opnum & 1) << 1) | ((opnum >> 1) & 1)];
894 +
		printf(" %c%03X%c%c ",
895 +
			"PADSRV"[op.debug_eg_state()],
896 +
			op.debug_eg_attenuation(),
897 +
			op.debug_ssg_inverted() ? '-' : '+',
898 +
			m_regs.op_ssg_eg_enable(op.opoffs()) ? '0' + m_regs.op_ssg_eg_mode(op.opoffs()) : ' ');
899 +
	}
900 +
printf(" -- ");
901 +
}
902 +
*/
903 +
}
904 +
905 +
906 +
//-------------------------------------------------
907 +
//  output_2op - combine 4 operators according to
908 +
//  the specified algorithm, returning a sum
909 +
//  according to the rshift and clipmax parameters,
910 +
//  which vary between different implementations
911 +
//-------------------------------------------------
912 +
913 +
template<class RegisterType>
914 +
void fm_channel<RegisterType>::output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const
915 +
{
916 +
	// The first 2 operators should be populated
917 +
	assert(m_op[0] != nullptr);
918 +
	assert(m_op[1] != nullptr);
919 +
920 +
	// AM amount is the same across all operators; compute it once
921 +
	uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
922 +
923 +
	// operator 1 has optional self-feedback
924 +
	int32_t opmod = 0;
925 +
	uint32_t feedback = m_regs.ch_feedback(m_choffs);
926 +
	if (feedback != 0)
927 +
		opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback);
928 +
929 +
	// compute the 14-bit volume/value of operator 1 and update the feedback
930 +
	int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset);
931 +
932 +
	// now that the feedback has been computed, skip the rest if all volumes
933 +
	// are clear; no need to do all this work for nothing
934 +
	if (m_regs.ch_output_any(m_choffs) == 0)
935 +
		return;
936 +
937 +
	// Algorithms for two-operator case:
938 +
	//    0: O1 -> O2 -> out
939 +
	//    1: (O1 + O2) -> out
940 +
	int32_t result;
941 +
	if (bitfield(m_regs.ch_algorithm(m_choffs), 0) == 0)
942 +
	{
943 +
		// some OPL chips use the previous sample for modulation instead of
944 +
		// the current sample
945 +
		opmod = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> 1;
946 +
		result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift;
947 +
	}
948 +
	else
949 +
	{
950 +
		result = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> rshift;
951 +
		result += m_op[1]->compute_volume(m_op[1]->phase(), am_offset) >> rshift;
952 +
		int32_t clipmin = -clipmax - 1;
953 +
		result = clamp(result, clipmin, clipmax);
954 +
	}
955 +
956 +
	// add to the output
957 +
	add_to_output(m_choffs, output, result);
958 +
}
959 +
960 +
961 +
//-------------------------------------------------
962 +
//  output_4op - combine 4 operators according to
963 +
//  the specified algorithm, returning a sum
964 +
//  according to the rshift and clipmax parameters,
965 +
//  which vary between different implementations
966 +
//-------------------------------------------------
967 +
968 +
template<class RegisterType>
969 +
void fm_channel<RegisterType>::output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const
970 +
{
971 +
	// all 4 operators should be populated
972 +
	assert(m_op[0] != nullptr);
973 +
	assert(m_op[1] != nullptr);
974 +
	assert(m_op[2] != nullptr);
975 +
	assert(m_op[3] != nullptr);
976 +
977 +
	// AM amount is the same across all operators; compute it once
978 +
	uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
979 +
980 +
	// operator 1 has optional self-feedback
981 +
	int32_t opmod = 0;
982 +
	uint32_t feedback = m_regs.ch_feedback(m_choffs);
983 +
	if (feedback != 0)
984 +
		opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback);
985 +
986 +
	// compute the 14-bit volume/value of operator 1 and update the feedback
987 +
	int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset);
988 +
989 +
	// now that the feedback has been computed, skip the rest if all volumes
990 +
	// are clear; no need to do all this work for nothing
991 +
	if (m_regs.ch_output_any(m_choffs) == 0)
992 +
		return;
993 +
994 +
	// OPM/OPN offer 8 different connection algorithms for 4 operators,
995 +
	// and OPL3 offers 4 more, which we designate here as 8-11.
996 +
	//
997 +
	// The operators are computed in order, with the inputs pulled from
998 +
	// an array of values (opout) that is populated as we go:
999 +
	//    0 = 0
1000 +
	//    1 = O1
1001 +
	//    2 = O2
1002 +
	//    3 = O3
1003 +
	//    4 = (O4)
1004 +
	//    5 = O1+O2
1005 +
	//    6 = O1+O3
1006 +
	//    7 = O2+O3
1007 +
	//
1008 +
	// The s_algorithm_ops table describes the inputs and outputs of each
1009 +
	// algorithm as follows:
1010 +
	//
1011 +
	//      ---------x use opout[x] as operator 2 input
1012 +
	//      ------xxx- use opout[x] as operator 3 input
1013 +
	//      ---xxx---- use opout[x] as operator 4 input
1014 +
	//      --x------- include opout[1] in final sum
1015 +
	//      -x-------- include opout[2] in final sum
1016 +
	//      x--------- include opout[3] in final sum
1017 +
	#define ALGORITHM(op2in, op3in, op4in, op1out, op2out, op3out) \
1018 +
		((op2in) | ((op3in) << 1) | ((op4in) << 4) | ((op1out) << 7) | ((op2out) << 8) | ((op3out) << 9))
1019 +
	static uint16_t const s_algorithm_ops[8+4] =
1020 +
	{
1021 +
		ALGORITHM(1,2,3, 0,0,0),    //  0: O1 -> O2 -> O3 -> O4 -> out (O4)
1022 +
		ALGORITHM(0,5,3, 0,0,0),    //  1: (O1 + O2) -> O3 -> O4 -> out (O4)
1023 +
		ALGORITHM(0,2,6, 0,0,0),    //  2: (O1 + (O2 -> O3)) -> O4 -> out (O4)
1024 +
		ALGORITHM(1,0,7, 0,0,0),    //  3: ((O1 -> O2) + O3) -> O4 -> out (O4)
1025 +
		ALGORITHM(1,0,3, 0,1,0),    //  4: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4)
1026 +
		ALGORITHM(1,1,1, 0,1,1),    //  5: ((O1 -> O2) + (O1 -> O3) + (O1 -> O4)) -> out (O2+O3+O4)
1027 +
		ALGORITHM(1,0,0, 0,1,1),    //  6: ((O1 -> O2) + O3 + O4) -> out (O2+O3+O4)
1028 +
		ALGORITHM(0,0,0, 1,1,1),    //  7: (O1 + O2 + O3 + O4) -> out (O1+O2+O3+O4)
1029 +
		ALGORITHM(1,2,3, 0,0,0),    //  8: O1 -> O2 -> O3 -> O4 -> out (O4)         [same as 0]
1030 +
		ALGORITHM(0,2,3, 1,0,0),    //  9: (O1 + (O2 -> O3 -> O4)) -> out (O1+O4)   [unique]
1031 +
		ALGORITHM(1,0,3, 0,1,0),    // 10: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4) [same as 4]
1032 +
		ALGORITHM(0,2,0, 1,0,1)     // 11: (O1 + (O2 -> O3) + O4) -> out (O1+O3+O4) [unique]
1033 +
	};
1034 +
	uint32_t algorithm_ops = s_algorithm_ops[m_regs.ch_algorithm(m_choffs)];
1035 +
1036 +
	// populate the opout table
1037 +
	int16_t opout[8];
1038 +
	opout[0] = 0;
1039 +
	opout[1] = op1value;
1040 +
1041 +
	// compute the 14-bit volume/value of operator 2
1042 +
	opmod = opout[bitfield(algorithm_ops, 0, 1)] >> 1;
1043 +
	opout[2] = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset);
1044 +
	opout[5] = opout[1] + opout[2];
1045 +
1046 +
	// compute the 14-bit volume/value of operator 3
1047 +
	opmod = opout[bitfield(algorithm_ops, 1, 3)] >> 1;
1048 +
	opout[3] = m_op[2]->compute_volume(m_op[2]->phase() + opmod, am_offset);
1049 +
	opout[6] = opout[1] + opout[3];
1050 +
	opout[7] = opout[2] + opout[3];
1051 +
1052 +
	// compute the 14-bit volume/value of operator 4; this could be a noise
1053 +
	// value on the OPM; all algorithms consume OP4 output at a minimum
1054 +
	int32_t result;
1055 +
	if (m_regs.noise_enable() && m_choffs == 7)
1056 +
		result = m_op[3]->compute_noise_volume(am_offset);
1057 +
	else
1058 +
	{
1059 +
		opmod = opout[bitfield(algorithm_ops, 4, 3)] >> 1;
1060 +
		result = m_op[3]->compute_volume(m_op[3]->phase() + opmod, am_offset);
1061 +
	}
1062 +
	result >>= rshift;
1063 +
1064 +
	// optionally add OP1, OP2, OP3
1065 +
	int32_t clipmin = -clipmax - 1;
1066 +
	if (bitfield(algorithm_ops, 7) != 0)
1067 +
		result = clamp(result + (opout[1] >> rshift), clipmin, clipmax);
1068 +
	if (bitfield(algorithm_ops, 8) != 0)
1069 +
		result = clamp(result + (opout[2] >> rshift), clipmin, clipmax);
1070 +
	if (bitfield(algorithm_ops, 9) != 0)
1071 +
		result = clamp(result + (opout[3] >> rshift), clipmin, clipmax);
1072 +
1073 +
	// add to the output
1074 +
	add_to_output(m_choffs, output, result);
1075 +
}
1076 +
1077 +
1078 +
//-------------------------------------------------
1079 +
//  output_rhythm_ch6 - special case output
1080 +
//  computation for OPL channel 6 in rhythm mode,
1081 +
//  which outputs a Bass Drum instrument
1082 +
//-------------------------------------------------
1083 +
1084 +
template<class RegisterType>
1085 +
void fm_channel<RegisterType>::output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const
1086 +
{
1087 +
	// AM amount is the same across all operators; compute it once
1088 +
	uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
1089 +
1090 +
	// Bass Drum: this uses operators 12 and 15 (i.e., channel 6)
1091 +
	// in an almost-normal way, except that if the algorithm is 1,
1092 +
	// the first operator is ignored instead of added in
1093 +
1094 +
	// operator 1 has optional self-feedback
1095 +
	int32_t opmod = 0;
1096 +
	uint32_t feedback = m_regs.ch_feedback(m_choffs);
1097 +
	if (feedback != 0)
1098 +
		opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback);
1099 +
1100 +
	// compute the 14-bit volume/value of operator 1 and update the feedback
1101 +
	int32_t opout1 = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset);
1102 +
1103 +
	// compute the 14-bit volume/value of operator 2, which is the result
1104 +
	opmod = bitfield(m_regs.ch_algorithm(m_choffs), 0) ? 0 : (opout1 >> 1);
1105 +
	int32_t result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift;
1106 +
1107 +
	// add to the output
1108 +
	add_to_output(m_choffs, output, result * 2);
1109 +
}
1110 +
1111 +
1112 +
//-------------------------------------------------
1113 +
//  output_rhythm_ch7 - special case output
1114 +
//  computation for OPL channel 7 in rhythm mode,
1115 +
//  which outputs High Hat and Snare Drum
1116 +
//  instruments
1117 +
//-------------------------------------------------
1118 +
1119 +
template<class RegisterType>
1120 +
void fm_channel<RegisterType>::output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const
1121 +
{
1122 +
	// AM amount is the same across all operators; compute it once
1123 +
	uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
1124 +
	uint32_t noise_state = bitfield(m_regs.noise_state(), 0);
1125 +
1126 +
	// High Hat: this uses the envelope from operator 13 (channel 7),
1127 +
	// and a combination of noise and the operator 13/17 phase select
1128 +
	// to compute the phase
1129 +
	uint32_t phase = (phase_select << 9) | (0xd0 >> (2 * (noise_state ^ phase_select)));
1130 +
	int32_t result = m_op[0]->compute_volume(phase, am_offset) >> rshift;
1131 +
1132 +
	// Snare Drum: this uses the envelope from operator 16 (channel 7),
1133 +
	// and a combination of noise and operator 13 phase to pick a phase
1134 +
	uint32_t op13phase = m_op[0]->phase();
1135 +
	phase = (0x100 << bitfield(op13phase, 8)) ^ (noise_state << 8);
1136 +
	result += m_op[1]->compute_volume(phase, am_offset) >> rshift;
1137 +
	result = clamp(result, -clipmax - 1, clipmax);
1138 +
1139 +
	// add to the output
1140 +
	add_to_output(m_choffs, output, result * 2);
1141 +
}
1142 +
1143 +
1144 +
//-------------------------------------------------
1145 +
//  output_rhythm_ch8 - special case output
1146 +
//  computation for OPL channel 8 in rhythm mode,
1147 +
//  which outputs Tom Tom and Top Cymbal instruments
1148 +
//-------------------------------------------------
1149 +
1150 +
template<class RegisterType>
1151 +
void fm_channel<RegisterType>::output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const
1152 +
{
1153 +
	// AM amount is the same across all operators; compute it once
1154 +
	uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
1155 +
1156 +
	// Tom Tom: this is just a single operator processed normally
1157 +
	int32_t result = m_op[0]->compute_volume(m_op[0]->phase(), am_offset) >> rshift;
1158 +
1159 +
	// Top Cymbal: this uses the envelope from operator 17 (channel 8),
1160 +
	// and the operator 13/17 phase select to compute the phase
1161 +
	uint32_t phase = 0x100 | (phase_select << 9);
1162 +
	result += m_op[1]->compute_volume(phase, am_offset) >> rshift;
1163 +
	result = clamp(result, -clipmax - 1, clipmax);
1164 +
1165 +
	// add to the output
1166 +
	add_to_output(m_choffs, output, result * 2);
1167 +
}
1168 +
1169 +
1170 +
1171 +
//*********************************************************
1172 +
//  FM ENGINE BASE
1173 +
//*********************************************************
1174 +
1175 +
//-------------------------------------------------
1176 +
//  fm_engine_base - constructor
1177 +
//-------------------------------------------------
1178 +
1179 +
template<class RegisterType>
1180 +
fm_engine_base<RegisterType>::fm_engine_base(ymfm_interface &intf) :
1181 +
	m_intf(intf),
1182 +
	m_env_counter(0),
1183 +
	m_status(0),
1184 +
	m_clock_prescale(RegisterType::DEFAULT_PRESCALE),
1185 +
	m_irq_mask(STATUS_TIMERA | STATUS_TIMERB),
1186 +
	m_irq_state(0),
1187 +
	m_timer_running{0,0},
1188 +
	m_total_clocks(0),
1189 +
	m_active_channels(ALL_CHANNELS),
1190 +
	m_modified_channels(ALL_CHANNELS),
1191 +
	m_prepare_count(0)
1192 +
{
1193 +
	// inform the interface of their engine
1194 +
	m_intf.m_engine = this;
1195 +
1196 +
	// create the channels
1197 +
	for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1198 +
		m_channel[chnum] = std::make_unique<fm_channel<RegisterType>>(*this, RegisterType::channel_offset(chnum));
1199 +
1200 +
	// create the operators
1201 +
	for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
1202 +
		m_operator[opnum] = std::make_unique<fm_operator<RegisterType>>(*this, RegisterType::operator_offset(opnum));
1203 +
1204 +
#if (YMFM_DEBUG_LOG_WAVFILES)
1205 +
	for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1206 +
		m_wavfile[chnum].set_index(chnum);
1207 +
#endif
1208 +
1209 +
	// do the initial operator assignment
1210 +
	assign_operators();
1211 +
}
1212 +
1213 +
1214 +
//-------------------------------------------------
1215 +
//  reset - reset the overall state
1216 +
//-------------------------------------------------
1217 +
1218 +
template<class RegisterType>
1219 +
void fm_engine_base<RegisterType>::reset()
1220 +
{
1221 +
	// reset all status bits
1222 +
	set_reset_status(0, 0xff);
1223 +
1224 +
	// register type-specific initialization
1225 +
	m_regs.reset();
1226 +
1227 +
	// explicitly write to the mode register since it has side-effects
1228 +
	// QUESTION: old cores initialize this to 0x30 -- who is right?
1229 +
	write(RegisterType::REG_MODE, 0);
1230 +
1231 +
	// reset the channels
1232 +
	for (auto &chan : m_channel)
1233 +
		chan->reset();
1234 +
1235 +
	// reset the operators
1236 +
	for (auto &op : m_operator)
1237 +
		op->reset();
1238 +
}
1239 +
1240 +
1241 +
//-------------------------------------------------
1242 +
//  save_restore - save or restore the data
1243 +
//-------------------------------------------------
1244 +
1245 +
template<class RegisterType>
1246 +
void fm_engine_base<RegisterType>::save_restore(ymfm_saved_state &state)
1247 +
{
1248 +
	// save our data
1249 +
	state.save_restore(m_env_counter);
1250 +
	state.save_restore(m_status);
1251 +
	state.save_restore(m_clock_prescale);
1252 +
	state.save_restore(m_irq_mask);
1253 +
	state.save_restore(m_irq_state);
1254 +
	state.save_restore(m_timer_running[0]);
1255 +
	state.save_restore(m_timer_running[1]);
1256 +
	state.save_restore(m_total_clocks);
1257 +
1258 +
	// save the register/family data
1259 +
	m_regs.save_restore(state);
1260 +
1261 +
	// save channel data
1262 +
	for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1263 +
		m_channel[chnum]->save_restore(state);
1264 +
1265 +
	// save operator data
1266 +
	for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
1267 +
		m_operator[opnum]->save_restore(state);
1268 +
1269 +
	// invalidate any caches
1270 +
	invalidate_caches();
1271 +
}
1272 +
1273 +
1274 +
//-------------------------------------------------
1275 +
//  clock - iterate over all channels, clocking
1276 +
//  them forward one step
1277 +
//-------------------------------------------------
1278 +
1279 +
template<class RegisterType>
1280 +
uint32_t fm_engine_base<RegisterType>::clock(uint32_t chanmask)
1281 +
{
1282 +
	// update the clock counter
1283 +
	m_total_clocks++;
1284 +
1285 +
	// if something was modified, prepare
1286 +
	// also prepare every 4k samples to catch ending notes
1287 +
	if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
1288 +
	{
1289 +
		// reassign operators to channels if dynamic
1290 +
		if (RegisterType::DYNAMIC_OPS)
1291 +
			assign_operators();
1292 +
1293 +
		// call each channel to prepare
1294 +
		m_active_channels = 0;
1295 +
		for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1296 +
			if (bitfield(chanmask, chnum))
1297 +
				if (m_channel[chnum]->prepare())
1298 +
					m_active_channels |= 1 << chnum;
1299 +
1300 +
		// reset the modified channels and prepare count
1301 +
		m_modified_channels = m_prepare_count = 0;
1302 +
	}
1303 +
1304 +
	// if the envelope clock divider is 1, just increment by 4;
1305 +
	// otherwise, increment by 1 and manually wrap when we reach the divide count
1306 +
	if (RegisterType::EG_CLOCK_DIVIDER == 1)
1307 +
		m_env_counter += 4;
1308 +
	else if (bitfield(++m_env_counter, 0, 2) == RegisterType::EG_CLOCK_DIVIDER)
1309 +
		m_env_counter += 4 - RegisterType::EG_CLOCK_DIVIDER;
1310 +
1311 +
	// clock the noise generator
1312 +
	int32_t lfo_raw_pm = m_regs.clock_noise_and_lfo();
1313 +
1314 +
	// now update the state of all the channels and operators
1315 +
	for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1316 +
		if (bitfield(chanmask, chnum))
1317 +
			m_channel[chnum]->clock(m_env_counter, lfo_raw_pm);
1318 +
1319 +
	// return the envelope counter as it is used to clock ADPCM-A
1320 +
	return m_env_counter;
1321 +
}
1322 +
1323 +
1324 +
//-------------------------------------------------
1325 +
//  output - compute a sum over the relevant
1326 +
//  channels
1327 +
//-------------------------------------------------
1328 +
1329 +
template<class RegisterType>
1330 +
void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const
1331 +
{
1332 +
	// mask out some channels for debug purposes
1333 +
	chanmask &= debug::GLOBAL_FM_CHANNEL_MASK;
1334 +
1335 +
	// mask out inactive channels
1336 +
	if (!YMFM_DEBUG_LOG_WAVFILES)
1337 +
		chanmask &= m_active_channels;
1338 +
1339 +
	// handle the rhythm case, where some of the operators are dedicated
1340 +
	// to percussion (this is an OPL-specific feature)
1341 +
	if (m_regs.rhythm_enable())
1342 +
	{
1343 +
		// we don't support the OPM noise channel here; ensure it is off
1344 +
		assert(m_regs.noise_enable() == 0);
1345 +
1346 +
		// precompute the operator 13+17 phase selection value
1347 +
		uint32_t op13phase = m_operator[13]->phase();
1348 +
		uint32_t op17phase = m_operator[17]->phase();
1349 +
		uint32_t phase_select = (bitfield(op13phase, 2) ^ bitfield(op13phase, 7)) | bitfield(op13phase, 3) | (bitfield(op17phase, 5) ^ bitfield(op17phase, 3));
1350 +
1351 +
		// sum over all the desired channels
1352 +
		for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1353 +
			if (bitfield(chanmask, chnum))
1354 +
			{
1355 +
#if (YMFM_DEBUG_LOG_WAVFILES)
1356 +
				auto reference = output;
1357 +
#endif
1358 +
				if (chnum == 6)
1359 +
					m_channel[chnum]->output_rhythm_ch6(output, rshift, clipmax);
1360 +
				else if (chnum == 7)
1361 +
					m_channel[chnum]->output_rhythm_ch7(phase_select, output, rshift, clipmax);
1362 +
				else if (chnum == 8)
1363 +
					m_channel[chnum]->output_rhythm_ch8(phase_select, output, rshift, clipmax);
1364 +
				else if (m_channel[chnum]->is4op())
1365 +
					m_channel[chnum]->output_4op(output, rshift, clipmax);
1366 +
				else
1367 +
					m_channel[chnum]->output_2op(output, rshift, clipmax);
1368 +
#if (YMFM_DEBUG_LOG_WAVFILES)
1369 +
				m_wavfile[chnum].add(output, reference);
1370 +
#endif
1371 +
			}
1372 +
	}
1373 +
	else
1374 +
	{
1375 +
		// sum over all the desired channels
1376 +
		for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1377 +
			if (bitfield(chanmask, chnum))
1378 +
			{
1379 +
#if (YMFM_DEBUG_LOG_WAVFILES)
1380 +
				auto reference = output;
1381 +
#endif
1382 +
				if (m_channel[chnum]->is4op())
1383 +
					m_channel[chnum]->output_4op(output, rshift, clipmax);
1384 +
				else
1385 +
					m_channel[chnum]->output_2op(output, rshift, clipmax);
1386 +
#if (YMFM_DEBUG_LOG_WAVFILES)
1387 +
				m_wavfile[chnum].add(output, reference);
1388 +
#endif
1389 +
			}
1390 +
	}
1391 +
}
1392 +
1393 +
1394 +
//-------------------------------------------------
1395 +
//  write - handle writes to the OPN registers
1396 +
//-------------------------------------------------
1397 +
1398 +
template<class RegisterType>
1399 +
void fm_engine_base<RegisterType>::write(uint16_t regnum, uint8_t data)
1400 +
{
1401 +
	debug::log_fm_write("%03X = %02X\n", regnum, data);
1402 +
1403 +
	// special case: writes to the mode register can impact IRQs;
1404 +
	// schedule these writes to ensure ordering with timers
1405 +
	if (regnum == RegisterType::REG_MODE)
1406 +
	{
1407 +
		m_intf.ymfm_sync_mode_write(data);
1408 +
		return;
1409 +
	}
1410 +
1411 +
	// for now just mark all channels as modified
1412 +
	m_modified_channels = ALL_CHANNELS;
1413 +
1414 +
	// most writes are passive, consumed only when needed
1415 +
	uint32_t keyon_channel;
1416 +
	uint32_t keyon_opmask;
1417 +
	if (m_regs.write(regnum, data, keyon_channel, keyon_opmask))
1418 +
	{
1419 +
		// handle writes to the keyon register(s)
1420 +
		if (keyon_channel < CHANNELS)
1421 +
		{
1422 +
			// normal channel on/off
1423 +
			m_channel[keyon_channel]->keyonoff(keyon_opmask, KEYON_NORMAL, keyon_channel);
1424 +
		}
1425 +
		else if (CHANNELS >= 9 && keyon_channel == RegisterType::RHYTHM_CHANNEL)
1426 +
		{
1427 +
			// special case for the OPL rhythm channels
1428 +
			m_channel[6]->keyonoff(bitfield(keyon_opmask, 4) ? 3 : 0, KEYON_RHYTHM, 6);
1429 +
			m_channel[7]->keyonoff(bitfield(keyon_opmask, 0) | (bitfield(keyon_opmask, 3) << 1), KEYON_RHYTHM, 7);
1430 +
			m_channel[8]->keyonoff(bitfield(keyon_opmask, 2) | (bitfield(keyon_opmask, 1) << 1), KEYON_RHYTHM, 8);
1431 +
		}
1432 +
	}
1433 +
}
1434 +
1435 +
1436 +
//-------------------------------------------------
1437 +
//  status - return the current state of the
1438 +
//  status flags
1439 +
//-------------------------------------------------
1440 +
1441 +
template<class RegisterType>
1442 +
uint8_t fm_engine_base<RegisterType>::status() const
1443 +
{
1444 +
	return m_status & ~STATUS_BUSY & ~m_regs.status_mask();
1445 +
}
1446 +
1447 +
1448 +
//-------------------------------------------------
1449 +
//  assign_operators - get the current mapping of
1450 +
//  operators to channels and assign them all
1451 +
//-------------------------------------------------
1452 +
1453 +
template<class RegisterType>
1454 +
void fm_engine_base<RegisterType>::assign_operators()
1455 +
{
1456 +
	typename RegisterType::operator_mapping map;
1457 +
	m_regs.operator_map(map);
1458 +
1459 +
	for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1460 +
		for (uint32_t index = 0; index < 4; index++)
1461 +
		{
1462 +
			uint32_t opnum = bitfield(map.chan[chnum], 8 * index, 8);
1463 +
			m_channel[chnum]->assign(index, (opnum == 0xff) ? nullptr : m_operator[opnum].get());
1464 +
		}
1465 +
}
1466 +
1467 +
1468 +
//-------------------------------------------------
1469 +
//  update_timer - update the state of the given
1470 +
//  timer
1471 +
//-------------------------------------------------
1472 +
1473 +
template<class RegisterType>
1474 +
void fm_engine_base<RegisterType>::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks)
1475 +
{
1476 +
	// if the timer is live, but not currently enabled, set the timer
1477 +
	if (enable && !m_timer_running[tnum])
1478 +
	{
1479 +
		// period comes from the registers, and is different for each
1480 +
		uint32_t period = (tnum == 0) ? (1024 - m_regs.timer_a_value()) : 16 * (256 - m_regs.timer_b_value());
1481 +
1482 +
		// caller can also specify a delta to account for other effects
1483 +
		period += delta_clocks;
1484 +
1485 +
		// reset it
1486 +
		m_intf.ymfm_set_timer(tnum, period * OPERATORS * m_clock_prescale);
1487 +
		m_timer_running[tnum] = 1;
1488 +
	}
1489 +
1490 +
	// if the timer is not live, ensure it is not enabled
1491 +
	else if (!enable)
1492 +
	{
1493 +
		m_intf.ymfm_set_timer(tnum, -1);
1494 +
		m_timer_running[tnum] = 0;
1495 +
	}
1496 +
}
1497 +
1498 +
1499 +
//-------------------------------------------------
1500 +
//  engine_timer_expired - timer has expired - signal
1501 +
//  status and possibly IRQs
1502 +
//-------------------------------------------------
1503 +
1504 +
template<class RegisterType>
1505 +
void fm_engine_base<RegisterType>::engine_timer_expired(uint32_t tnum)
1506 +
{
1507 +
	assert(tnum == 0 || tnum == 1);
1508 +
1509 +
	// update status
1510 +
	if (tnum == 0 && m_regs.enable_timer_a())
1511 +
		set_reset_status(STATUS_TIMERA, 0);
1512 +
	else if (tnum == 1 && m_regs.enable_timer_b())
1513 +
		set_reset_status(STATUS_TIMERB, 0);
1514 +
1515 +
	// if timer A fired in CSM mode, trigger CSM on all relevant channels
1516 +
	if (tnum == 0 && m_regs.csm())
1517 +
		for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
1518 +
			if (bitfield(RegisterType::CSM_TRIGGER_MASK, chnum))
1519 +
			{
1520 +
				m_channel[chnum]->keyonoff(0xf, KEYON_CSM, chnum);
1521 +
				m_modified_channels |= 1 << chnum;
1522 +
			}
1523 +
1524 +
	// reset
1525 +
	m_timer_running[tnum] = false;
1526 +
	update_timer(tnum, 1, 0);
1527 +
}
1528 +
1529 +
1530 +
//-------------------------------------------------
1531 +
//  check_interrupts - check the interrupt sources
1532 +
//  for interrupts
1533 +
//-------------------------------------------------
1534 +
1535 +
template<class RegisterType>
1536 +
void fm_engine_base<RegisterType>::engine_check_interrupts()
1537 +
{
1538 +
	// update the state
1539 +
	uint8_t old_state = m_irq_state;
1540 +
	m_irq_state = ((m_status & m_irq_mask & ~m_regs.status_mask()) != 0);
1541 +
1542 +
	// set the IRQ status bit
1543 +
	if (m_irq_state)
1544 +
		m_status |= STATUS_IRQ;
1545 +
	else
1546 +
		m_status &= ~STATUS_IRQ;
1547 +
1548 +
	// if changed, signal the new state
1549 +
	if (old_state != m_irq_state)
1550 +
		m_intf.ymfm_update_irq(m_irq_state ? true : false);
1551 +
}
1552 +
1553 +
1554 +
//-------------------------------------------------
1555 +
//  engine_mode_write - handle a mode register write
1556 +
//  via timer callback
1557 +
//-------------------------------------------------
1558 +
1559 +
template<class RegisterType>
1560 +
void fm_engine_base<RegisterType>::engine_mode_write(uint8_t data)
1561 +
{
1562 +
	// mark all channels as modified
1563 +
	m_modified_channels = ALL_CHANNELS;
1564 +
1565 +
	// actually write the mode register now
1566 +
	uint32_t dummy1, dummy2;
1567 +
	m_regs.write(RegisterType::REG_MODE, data, dummy1, dummy2);
1568 +
1569 +
	// reset IRQ status -- when written, all other bits are ignored
1570 +
	// QUESTION: should this maybe just reset the IRQ bit and not all the bits?
1571 +
	//   That is, check_interrupts would only set, this would only clear?
1572 +
	if (m_regs.irq_reset())
1573 +
		set_reset_status(0, 0x78);
1574 +
	else
1575 +
	{
1576 +
		// reset timer status
1577 +
		uint8_t reset_mask = 0;
1578 +
		if (m_regs.reset_timer_b())
1579 +
			reset_mask |= RegisterType::STATUS_TIMERB;
1580 +
		if (m_regs.reset_timer_a())
1581 +
			reset_mask |= RegisterType::STATUS_TIMERA;
1582 +
		set_reset_status(0, reset_mask);
1583 +
1584 +
		// load timers; note that timer B gets a small negative adjustment because
1585 +
		// the *16 multiplier is free-running, so the first tick of the clock
1586 +
		// is a bit shorter
1587 +
		update_timer(1, m_regs.load_timer_b(), -(m_total_clocks & 15));
1588 +
		update_timer(0, m_regs.load_timer_a(), 0);
1589 +
	}
1590 +
}
1591 +
1592 +
}
added src/extern/ymfm/src/ymfm_opl.cpp +2220 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#include "ymfm_opl.h"
32 +
#include "ymfm_fm.ipp"
33 +
34 +
namespace ymfm
35 +
{
36 +
37 +
//-------------------------------------------------
38 +
//  opl_key_scale_atten - converts an
39 +
//  OPL concatenated block (3 bits) and fnum
40 +
//  (10 bits) into an attenuation offset; values
41 +
//  here are for 6dB/octave, in 0.75dB units
42 +
//  (matching total level LSB)
43 +
//-------------------------------------------------
44 +
45 +
inline uint32_t opl_key_scale_atten(uint32_t block, uint32_t fnum_4msb)
46 +
{
47 +
	// this table uses the top 4 bits of FNUM and are the maximal values
48 +
	// (for when block == 7). Values for other blocks can be computed by
49 +
	// subtracting 8 for each block below 7.
50 +
	static uint8_t const fnum_to_atten[16] = { 0,24,32,37,40,43,45,47,48,50,51,52,53,54,55,56 };
51 +
	int32_t result = fnum_to_atten[fnum_4msb] - 8 * (block ^ 7);
52 +
	return std::max<int32_t>(0, result);
53 +
}
54 +
55 +
56 +
//*********************************************************
57 +
//  OPL REGISTERS
58 +
//*********************************************************
59 +
60 +
//-------------------------------------------------
61 +
//  opl_registers_base - constructor
62 +
//-------------------------------------------------
63 +
64 +
template<int Revision>
65 +
opl_registers_base<Revision>::opl_registers_base() :
66 +
	m_lfo_am_counter(0),
67 +
	m_lfo_pm_counter(0),
68 +
	m_noise_lfsr(1),
69 +
	m_lfo_am(0)
70 +
{
71 +
	// create these pointers to appease overzealous compilers checking array
72 +
	// bounds in unreachable code (looking at you, clang)
73 +
	uint16_t *wf0 = &m_waveform[0][0];
74 +
	uint16_t *wf1 = &m_waveform[1 % WAVEFORMS][0];
75 +
	uint16_t *wf2 = &m_waveform[2 % WAVEFORMS][0];
76 +
	uint16_t *wf3 = &m_waveform[3 % WAVEFORMS][0];
77 +
	uint16_t *wf4 = &m_waveform[4 % WAVEFORMS][0];
78 +
	uint16_t *wf5 = &m_waveform[5 % WAVEFORMS][0];
79 +
	uint16_t *wf6 = &m_waveform[6 % WAVEFORMS][0];
80 +
	uint16_t *wf7 = &m_waveform[7 % WAVEFORMS][0];
81 +
82 +
	// create the waveforms
83 +
	for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
84 +
		wf0[index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
85 +
86 +
	if (WAVEFORMS >= 4)
87 +
	{
88 +
		uint16_t zeroval = wf0[0];
89 +
		for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
90 +
		{
91 +
			wf1[index] = bitfield(index, 9) ? zeroval : wf0[index];
92 +
			wf2[index] = wf0[index] & 0x7fff;
93 +
			wf3[index] = bitfield(index, 8) ? zeroval : (wf0[index] & 0x7fff);
94 +
			if (WAVEFORMS >= 8)
95 +
			{
96 +
				wf4[index] = bitfield(index, 9) ? zeroval : wf0[index * 2];
97 +
				wf5[index] = bitfield(index, 9) ? zeroval : wf0[(index * 2) & 0x1ff];
98 +
				wf6[index] = bitfield(index, 9) << 15;
99 +
				wf7[index] = (bitfield(index, 9) ? (index ^ 0x13ff) : index) << 3;
100 +
			}
101 +
		}
102 +
	}
103 +
104 +
	// OPL3/OPL4 have dynamic operators, so initialize the fourop_enable value here
105 +
	// since operator_map() is called right away, prior to reset()
106 +
	if (Revision > 2)
107 +
		m_regdata[0x104 % REGISTERS] = 0;
108 +
}
109 +
110 +
111 +
//-------------------------------------------------
112 +
//  reset - reset to initial state
113 +
//-------------------------------------------------
114 +
115 +
template<int Revision>
116 +
void opl_registers_base<Revision>::reset()
117 +
{
118 +
	std::fill_n(&m_regdata[0], REGISTERS, 0);
119 +
}
120 +
121 +
122 +
//-------------------------------------------------
123 +
//  save_restore - save or restore the data
124 +
//-------------------------------------------------
125 +
126 +
template<int Revision>
127 +
void opl_registers_base<Revision>::save_restore(ymfm_saved_state &state)
128 +
{
129 +
	state.save_restore(m_lfo_am_counter);
130 +
	state.save_restore(m_lfo_pm_counter);
131 +
	state.save_restore(m_lfo_am);
132 +
	state.save_restore(m_noise_lfsr);
133 +
	state.save_restore(m_regdata);
134 +
}
135 +
136 +
137 +
//-------------------------------------------------
138 +
//  operator_map - return an array of operator
139 +
//  indices for each channel; for OPL this is fixed
140 +
//-------------------------------------------------
141 +
142 +
template<int Revision>
143 +
void opl_registers_base<Revision>::operator_map(operator_mapping &dest) const
144 +
{
145 +
	if (Revision <= 2)
146 +
	{
147 +
		// OPL/OPL2 has a fixed map, all 2 operators
148 +
		static const operator_mapping s_fixed_map =
149 +
		{ {
150 +
			operator_list(  0,  3 ),  // Channel 0 operators
151 +
			operator_list(  1,  4 ),  // Channel 1 operators
152 +
			operator_list(  2,  5 ),  // Channel 2 operators
153 +
			operator_list(  6,  9 ),  // Channel 3 operators
154 +
			operator_list(  7, 10 ),  // Channel 4 operators
155 +
			operator_list(  8, 11 ),  // Channel 5 operators
156 +
			operator_list( 12, 15 ),  // Channel 6 operators
157 +
			operator_list( 13, 16 ),  // Channel 7 operators
158 +
			operator_list( 14, 17 ),  // Channel 8 operators
159 +
		} };
160 +
		dest = s_fixed_map;
161 +
	}
162 +
	else
163 +
	{
164 +
		// OPL3/OPL4 can be configured for 2 or 4 operators
165 +
		uint32_t fourop = fourop_enable();
166 +
167 +
		dest.chan[ 0] = bitfield(fourop, 0) ? operator_list(  0,  3,  6,  9 ) : operator_list(  0,  3 );
168 +
		dest.chan[ 1] = bitfield(fourop, 1) ? operator_list(  1,  4,  7, 10 ) : operator_list(  1,  4 );
169 +
		dest.chan[ 2] = bitfield(fourop, 2) ? operator_list(  2,  5,  8, 11 ) : operator_list(  2,  5 );
170 +
		dest.chan[ 3] = bitfield(fourop, 0) ? operator_list() : operator_list(  6,  9 );
171 +
		dest.chan[ 4] = bitfield(fourop, 1) ? operator_list() : operator_list(  7, 10 );
172 +
		dest.chan[ 5] = bitfield(fourop, 2) ? operator_list() : operator_list(  8, 11 );
173 +
		dest.chan[ 6] = operator_list( 12, 15 );
174 +
		dest.chan[ 7] = operator_list( 13, 16 );
175 +
		dest.chan[ 8] = operator_list( 14, 17 );
176 +
177 +
		dest.chan[ 9] = bitfield(fourop, 3) ? operator_list( 18, 21, 24, 27 ) : operator_list( 18, 21 );
178 +
		dest.chan[10] = bitfield(fourop, 4) ? operator_list( 19, 22, 25, 28 ) : operator_list( 19, 22 );
179 +
		dest.chan[11] = bitfield(fourop, 5) ? operator_list( 20, 23, 26, 29 ) : operator_list( 20, 23 );
180 +
		dest.chan[12] = bitfield(fourop, 3) ? operator_list() : operator_list( 24, 27 );
181 +
		dest.chan[13] = bitfield(fourop, 4) ? operator_list() : operator_list( 25, 28 );
182 +
		dest.chan[14] = bitfield(fourop, 5) ? operator_list() : operator_list( 26, 29 );
183 +
		dest.chan[15] = operator_list( 30, 33 );
184 +
		dest.chan[16] = operator_list( 31, 34 );
185 +
		dest.chan[17] = operator_list( 32, 35 );
186 +
	}
187 +
}
188 +
189 +
190 +
//-------------------------------------------------
191 +
//  write - handle writes to the register array
192 +
//-------------------------------------------------
193 +
194 +
template<int Revision>
195 +
bool opl_registers_base<Revision>::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
196 +
{
197 +
	assert(index < REGISTERS);
198 +
199 +
	// writes to the mode register with high bit set ignore the low bits
200 +
	if (index == REG_MODE && bitfield(data, 7) != 0)
201 +
		m_regdata[index] |= 0x80;
202 +
	else
203 +
		m_regdata[index] = data;
204 +
205 +
	// handle writes to the rhythm keyons
206 +
	if (index == 0xbd)
207 +
	{
208 +
		channel = RHYTHM_CHANNEL;
209 +
		opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0;
210 +
		return true;
211 +
	}
212 +
213 +
	// handle writes to the channel keyons
214 +
	if ((index & 0xf0) == 0xb0)
215 +
	{
216 +
		channel = index & 0x0f;
217 +
		if (channel < 9)
218 +
		{
219 +
			if (IsOpl3Plus)
220 +
				channel += 9 * bitfield(index, 8);
221 +
			opmask = bitfield(data, 5) ? 15 : 0;
222 +
			return true;
223 +
		}
224 +
	}
225 +
	return false;
226 +
}
227 +
228 +
229 +
//-------------------------------------------------
230 +
//  clock_noise_and_lfo - clock the noise and LFO,
231 +
//  handling clock division, depth, and waveform
232 +
//  computations
233 +
//-------------------------------------------------
234 +
235 +
static int32_t opl_clock_noise_and_lfo(uint32_t &noise_lfsr, uint16_t &lfo_am_counter, uint16_t &lfo_pm_counter, uint8_t &lfo_am, uint32_t am_depth, uint32_t pm_depth)
236 +
{
237 +
	// OPL has a 23-bit noise generator for the rhythm section, running at
238 +
	// a constant rate, used only for percussion input
239 +
	noise_lfsr <<= 1;
240 +
	noise_lfsr |= bitfield(noise_lfsr, 23) ^ bitfield(noise_lfsr, 9) ^ bitfield(noise_lfsr, 8) ^ bitfield(noise_lfsr, 1);
241 +
242 +
	// OPL has two fixed-frequency LFOs, one for AM, one for PM
243 +
244 +
	// the AM LFO has 210*64 steps; at a nominal 50kHz output,
245 +
	// this equates to a period of 50000/(210*64) = 3.72Hz
246 +
	uint32_t am_counter = lfo_am_counter++;
247 +
	if (am_counter >= 210*64 - 1)
248 +
		lfo_am_counter = 0;
249 +
250 +
	// low 8 bits are fractional; depth 0 is divided by 2, while depth 1 is times 2
251 +
	int shift = 9 - 2 * am_depth;
252 +
253 +
	// AM value is the upper bits of the value, inverted across the midpoint
254 +
	// to produce a triangle
255 +
	lfo_am = ((am_counter < 105*64) ? am_counter : (210*64+63 - am_counter)) >> shift;
256 +
257 +
	// the PM LFO has 8192 steps, or a nominal period of 6.1Hz
258 +
	uint32_t pm_counter = lfo_pm_counter++;
259 +
260 +
	// PM LFO is broken into 8 chunks, each lasting 1024 steps; the PM value
261 +
	// depends on the upper bits of FNUM, so this value is a fraction and
262 +
	// sign to apply to that value, as a 1.3 value
263 +
	static int8_t const pm_scale[8] = { 8, 4, 0, -4, -8, -4, 0, 4 };
264 +
	return pm_scale[bitfield(pm_counter, 10, 3)] >> (pm_depth ^ 1);
265 +
}
266 +
267 +
template<int Revision>
268 +
int32_t opl_registers_base<Revision>::clock_noise_and_lfo()
269 +
{
270 +
	return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, lfo_am_depth(), lfo_pm_depth());
271 +
}
272 +
273 +
274 +
//-------------------------------------------------
275 +
//  cache_operator_data - fill the operator cache
276 +
//  with prefetched data; note that this code is
277 +
//  also used by ymopna_registers, so it must
278 +
//  handle upper channels cleanly
279 +
//-------------------------------------------------
280 +
281 +
template<int Revision>
282 +
void opl_registers_base<Revision>::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
283 +
{
284 +
	// set up the easy stuff
285 +
	cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0];
286 +
287 +
	// get frequency from the channel
288 +
	uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
289 +
290 +
	// compute the keycode: block_freq is:
291 +
	//
292 +
	//     111  |
293 +
	//     21098|76543210
294 +
	//     BBBFF|FFFFFFFF
295 +
	//     ^^^??
296 +
	//
297 +
	// the 4-bit keycode uses the top 3 bits plus one of the next two bits
298 +
	uint32_t keycode = bitfield(block_freq, 10, 3) << 1;
299 +
300 +
	// lowest bit is determined by note_select(); note that it is
301 +
	// actually reversed from what the manual says, however
302 +
	keycode |= bitfield(block_freq, 9 - note_select(), 1);
303 +
304 +
	// no detune adjustment on OPL
305 +
	cache.detune = 0;
306 +
307 +
	// multiple value, as an x.1 value (0 means 0.5)
308 +
	// replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15
309 +
	uint32_t multiple = op_multiple(opoffs);
310 +
	cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2;
311 +
	if (cache.multiple == 0)
312 +
		cache.multiple = 1;
313 +
314 +
	// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on block_freq, detune,
315 +
	// and multiple, so compute it after we've done those
316 +
	if (op_lfo_pm_enable(opoffs) == 0)
317 +
		cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
318 +
	else
319 +
		cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
320 +
321 +
	// total level, scaled by 8
322 +
	cache.total_level = op_total_level(opoffs) << 3;
323 +
324 +
	// pre-add key scale level
325 +
	uint32_t ksl = op_ksl(opoffs);
326 +
	if (ksl != 0)
327 +
		cache.total_level += opl_key_scale_atten(bitfield(block_freq, 10, 3), bitfield(block_freq, 6, 4)) << ksl;
328 +
329 +
	// 4-bit sustain level, but 15 means 31 so effectively 5 bits
330 +
	cache.eg_sustain = op_sustain_level(opoffs);
331 +
	cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
332 +
	cache.eg_sustain <<= 5;
333 +
334 +
	// determine KSR adjustment for enevlope rates
335 +
	uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1));
336 +
	cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval);
337 +
	cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval);
338 +
	cache.eg_rate[EG_SUSTAIN] = op_eg_sustain(opoffs) ? 0 : effective_rate(op_release_rate(opoffs) * 4, ksrval);
339 +
	cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4, ksrval);
340 +
	cache.eg_rate[EG_DEPRESS] = 0x3f;
341 +
}
342 +
343 +
344 +
//-------------------------------------------------
345 +
//  compute_phase_step - compute the phase step
346 +
//-------------------------------------------------
347 +
348 +
static uint32_t opl_compute_phase_step(uint32_t block_freq, uint32_t multiple, int32_t lfo_raw_pm)
349 +
{
350 +
	// OPL phase calculation has no detuning, but uses FNUMs like
351 +
	// the OPN version, and computes PM a bit differently
352 +
353 +
	// extract frequency number as a 12-bit fraction
354 +
	uint32_t fnum = bitfield(block_freq, 0, 10) << 2;
355 +
356 +
	// apply the phase adjustment based on the upper 3 bits
357 +
	// of FNUM and the PM depth parameters
358 +
	fnum += (lfo_raw_pm * bitfield(block_freq, 7, 3)) >> 1;
359 +
360 +
	// keep fnum to 12 bits
361 +
	fnum &= 0xfff;
362 +
363 +
	// apply block shift to compute phase step
364 +
	uint32_t block = bitfield(block_freq, 10, 3);
365 +
	uint32_t phase_step = (fnum << block) >> 2;
366 +
367 +
	// apply frequency multiplier (which is cached as an x.1 value)
368 +
	return (phase_step * multiple) >> 1;
369 +
}
370 +
371 +
template<int Revision>
372 +
uint32_t opl_registers_base<Revision>::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
373 +
{
374 +
	return opl_compute_phase_step(cache.block_freq, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0);
375 +
}
376 +
377 +
378 +
//-------------------------------------------------
379 +
//  log_keyon - log a key-on event
380 +
//-------------------------------------------------
381 +
382 +
template<int Revision>
383 +
std::string opl_registers_base<Revision>::log_keyon(uint32_t choffs, uint32_t opoffs)
384 +
{
385 +
	uint32_t chnum = (choffs & 15) + 9 * bitfield(choffs, 8);
386 +
	uint32_t opnum = (opoffs & 31) - 2 * ((opoffs & 31) / 8) + 18 * bitfield(opoffs, 8);
387 +
388 +
	char buffer[256];
389 +
	int end = 0;
390 +
391 +
	end += snprintf(&buffer[end], sizeof(buffer) - end, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u",
392 +
		chnum, opnum,
393 +
		ch_block_freq(choffs),
394 +
		ch_feedback(choffs),
395 +
		ch_algorithm(choffs),
396 +
		op_multiple(opoffs),
397 +
		op_total_level(opoffs),
398 +
		op_ksr(opoffs),
399 +
		note_select(),
400 +
		op_ksl(opoffs),
401 +
		op_attack_rate(opoffs),
402 +
		op_decay_rate(opoffs),
403 +
		op_release_rate(opoffs),
404 +
		op_sustain_level(opoffs),
405 +
		op_eg_sustain(opoffs));
406 +
407 +
	if (OUTPUTS > 1)
408 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c%c%c",
409 +
			ch_output_0(choffs) ? 'L' : '-',
410 +
			ch_output_1(choffs) ? 'R' : '-',
411 +
			ch_output_2(choffs) ? '0' : '-',
412 +
			ch_output_3(choffs) ? '1' : '-');
413 +
	if (op_lfo_am_enable(opoffs) != 0)
414 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", lfo_am_depth());
415 +
	if (op_lfo_pm_enable(opoffs) != 0)
416 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", lfo_pm_depth());
417 +
	if (waveform_enable() && op_waveform(opoffs) != 0)
418 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs));
419 +
	if (is_rhythm(choffs))
420 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1");
421 +
	if (DYNAMIC_OPS)
422 +
	{
423 +
		operator_mapping map;
424 +
		operator_map(map);
425 +
		if (bitfield(map.chan[chnum], 16, 8) != 0xff)
426 +
			end += snprintf(&buffer[end], sizeof(buffer) - end, " 4op");
427 +
	}
428 +
429 +
	return buffer;
430 +
}
431 +
432 +
433 +
//*********************************************************
434 +
//  OPLL SPECIFICS
435 +
//*********************************************************
436 +
437 +
//-------------------------------------------------
438 +
//  opll_registers - constructor
439 +
//-------------------------------------------------
440 +
441 +
opll_registers::opll_registers() :
442 +
	m_lfo_am_counter(0),
443 +
	m_lfo_pm_counter(0),
444 +
	m_noise_lfsr(1),
445 +
	m_lfo_am(0)
446 +
{
447 +
	// create the waveforms
448 +
	for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
449 +
		m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
450 +
451 +
	uint16_t zeroval = m_waveform[0][0];
452 +
	for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
453 +
		m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
454 +
455 +
	// initialize the instruments to something sane
456 +
	for (uint32_t choffs = 0; choffs < CHANNELS; choffs++)
457 +
		m_chinst[choffs] = &m_regdata[0];
458 +
	for (uint32_t opoffs = 0; opoffs < OPERATORS; opoffs++)
459 +
		m_opinst[opoffs] = &m_regdata[bitfield(opoffs, 0)];
460 +
}
461 +
462 +
463 +
//-------------------------------------------------
464 +
//  reset - reset to initial state
465 +
//-------------------------------------------------
466 +
467 +
void opll_registers::reset()
468 +
{
469 +
	std::fill_n(&m_regdata[0], REGISTERS, 0);
470 +
}
471 +
472 +
473 +
//-------------------------------------------------
474 +
//  save_restore - save or restore the data
475 +
//-------------------------------------------------
476 +
477 +
void opll_registers::save_restore(ymfm_saved_state &state)
478 +
{
479 +
	state.save_restore(m_lfo_am_counter);
480 +
	state.save_restore(m_lfo_pm_counter);
481 +
	state.save_restore(m_lfo_am);
482 +
	state.save_restore(m_noise_lfsr);
483 +
	state.save_restore(m_regdata);
484 +
}
485 +
486 +
487 +
//-------------------------------------------------
488 +
//  operator_map - return an array of operator
489 +
//  indices for each channel; for OPLL this is fixed
490 +
//-------------------------------------------------
491 +
492 +
void opll_registers::operator_map(operator_mapping &dest) const
493 +
{
494 +
	static const operator_mapping s_fixed_map =
495 +
	{ {
496 +
		operator_list(  0,  1 ),  // Channel 0 operators
497 +
		operator_list(  2,  3 ),  // Channel 1 operators
498 +
		operator_list(  4,  5 ),  // Channel 2 operators
499 +
		operator_list(  6,  7 ),  // Channel 3 operators
500 +
		operator_list(  8,  9 ),  // Channel 4 operators
501 +
		operator_list( 10, 11 ),  // Channel 5 operators
502 +
		operator_list( 12, 13 ),  // Channel 6 operators
503 +
		operator_list( 14, 15 ),  // Channel 7 operators
504 +
		operator_list( 16, 17 ),  // Channel 8 operators
505 +
	} };
506 +
	dest = s_fixed_map;
507 +
}
508 +
509 +
510 +
//-------------------------------------------------
511 +
//  write - handle writes to the register array;
512 +
//  note that this code is also used by
513 +
//  ymopl3_registers, so it must handle upper
514 +
//  channels cleanly
515 +
//-------------------------------------------------
516 +
517 +
bool opll_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
518 +
{
519 +
	// unclear the address is masked down to 6 bits or if writes above
520 +
	// the register top are ignored; assuming the latter for now
521 +
	if (index >= REGISTERS)
522 +
		return false;
523 +
524 +
	// write the new data
525 +
	m_regdata[index] = data;
526 +
527 +
	// handle writes to the rhythm keyons
528 +
	if (index == 0x0e)
529 +
	{
530 +
		channel = RHYTHM_CHANNEL;
531 +
		opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0;
532 +
		return true;
533 +
	}
534 +
535 +
	// handle writes to the channel keyons
536 +
	if ((index & 0xf0) == 0x20)
537 +
	{
538 +
		channel = index & 0x0f;
539 +
		if (channel < CHANNELS)
540 +
		{
541 +
			opmask = bitfield(data, 4) ? 3 : 0;
542 +
			return true;
543 +
		}
544 +
	}
545 +
	return false;
546 +
}
547 +
548 +
549 +
//-------------------------------------------------
550 +
//  clock_noise_and_lfo - clock the noise and LFO,
551 +
//  handling clock division, depth, and waveform
552 +
//  computations
553 +
//-------------------------------------------------
554 +
555 +
int32_t opll_registers::clock_noise_and_lfo()
556 +
{
557 +
	// implementation is the same as OPL with fixed depths
558 +
	return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, 1, 1);
559 +
}
560 +
561 +
562 +
//-------------------------------------------------
563 +
//  cache_operator_data - fill the operator cache
564 +
//  with prefetched data; note that this code is
565 +
//  also used by ymopna_registers, so it must
566 +
//  handle upper channels cleanly
567 +
//-------------------------------------------------
568 +
569 +
void opll_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
570 +
{
571 +
	// first set up the instrument data
572 +
	uint32_t instrument = ch_instrument(choffs);
573 +
	if (rhythm_enable() && choffs >= 6)
574 +
		m_chinst[choffs] = &m_instdata[8 * (15 + (choffs - 6))];
575 +
	else
576 +
		m_chinst[choffs] = (instrument == 0) ? &m_regdata[0] : &m_instdata[8 * (instrument - 1)];
577 +
	m_opinst[opoffs] = m_chinst[choffs] + bitfield(opoffs, 0);
578 +
579 +
	// set up the easy stuff
580 +
	cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0];
581 +
582 +
	// get frequency from the channel
583 +
	uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
584 +
585 +
	// compute the keycode: block_freq is:
586 +
	//
587 +
	//     11  |
588 +
	//     1098|76543210
589 +
	//     BBBF|FFFFFFFF
590 +
	//     ^^^^
591 +
	//
592 +
	// the 4-bit keycode uses the top 4 bits
593 +
	uint32_t keycode = bitfield(block_freq, 8, 4);
594 +
595 +
	// no detune adjustment on OPLL
596 +
	cache.detune = 0;
597 +
598 +
	// multiple value, as an x.1 value (0 means 0.5)
599 +
	// replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15
600 +
	uint32_t multiple = op_multiple(opoffs);
601 +
	cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2;
602 +
	if (cache.multiple == 0)
603 +
		cache.multiple = 1;
604 +
605 +
	// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
606 +
	// block_freq, detune, and multiple, so compute it after we've done those
607 +
	if (op_lfo_pm_enable(opoffs) == 0)
608 +
		cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
609 +
	else
610 +
		cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
611 +
612 +
	// total level, scaled by 8; for non-rhythm operator 0, this is the total
613 +
	// level from the instrument data; for other operators it is 4*volume
614 +
	if (bitfield(opoffs, 0) == 1 || (rhythm_enable() && choffs >= 7))
615 +
		cache.total_level = op_volume(opoffs) * 4;
616 +
	else
617 +
		cache.total_level = ch_total_level(choffs);
618 +
	cache.total_level <<= 3;
619 +
620 +
	// pre-add key scale level
621 +
	uint32_t ksl = op_ksl(opoffs);
622 +
	if (ksl != 0)
623 +
		cache.total_level += opl_key_scale_atten(bitfield(block_freq, 9, 3), bitfield(block_freq, 5, 4)) << ksl;
624 +
625 +
	// 4-bit sustain level, but 15 means 31 so effectively 5 bits
626 +
	cache.eg_sustain = op_sustain_level(opoffs);
627 +
	cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
628 +
	cache.eg_sustain <<= 5;
629 +
630 +
	// The envelope diagram in the YM2413 datasheet gives values for these
631 +
	// in ms from 0->48dB. The attack/decay tables give values in ms from
632 +
	// 0->96dB, so to pick an equivalent decay rate, we want to find the
633 +
	// closest match that is 2x the 0->48dB value:
634 +
	//
635 +
	//     DP =   10ms (0->48db) ->   20ms (0->96db); decay of 12 gives   19.20ms
636 +
	//     RR =  310ms (0->48db) ->  620ms (0->96db); decay of  7 gives  613.76ms
637 +
	//     RS = 1200ms (0->48db) -> 2400ms (0->96db); decay of  5 gives 2455.04ms
638 +
	//
639 +
	// The envelope diagram for percussive sounds (eg_sustain() == 0) also uses
640 +
	// "RR" to mean both the constant RR above and the Release Rate specified in
641 +
	// the instrument data. In this case, Relief Pitcher's credit sound bears out
642 +
	// that the Release Rate is used during sustain, and that the constant RR
643 +
	// (or RS) is used during the release phase.
644 +
	constexpr uint8_t DP = 12 * 4;
645 +
	constexpr uint8_t RR = 7 * 4;
646 +
	constexpr uint8_t RS = 5 * 4;
647 +
648 +
	// determine KSR adjustment for envelope rates
649 +
	uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1));
650 +
	cache.eg_rate[EG_DEPRESS] = DP;
651 +
	cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval);
652 +
	cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval);
653 +
	if (op_eg_sustain(opoffs))
654 +
	{
655 +
		cache.eg_rate[EG_SUSTAIN] = 0;
656 +
		cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : effective_rate(op_release_rate(opoffs) * 4, ksrval);
657 +
	}
658 +
	else
659 +
	{
660 +
		cache.eg_rate[EG_SUSTAIN] = effective_rate(op_release_rate(opoffs) * 4, ksrval);
661 +
		cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : RR;
662 +
	}
663 +
}
664 +
665 +
666 +
//-------------------------------------------------
667 +
//  compute_phase_step - compute the phase step
668 +
//-------------------------------------------------
669 +
670 +
uint32_t opll_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
671 +
{
672 +
	// phase step computation is the same as OPL but the block_freq has one
673 +
	// more bit, which we shift in
674 +
	return opl_compute_phase_step(cache.block_freq << 1, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0);
675 +
}
676 +
677 +
678 +
//-------------------------------------------------
679 +
//  log_keyon - log a key-on event
680 +
//-------------------------------------------------
681 +
682 +
std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
683 +
{
684 +
	uint32_t chnum = choffs;
685 +
	uint32_t opnum = opoffs;
686 +
687 +
	char buffer[256];
688 +
	int end = 0;
689 +
690 +
	end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X inst=%X fb=%u mul=%X",
691 +
		chnum, opnum,
692 +
		ch_block_freq(choffs),
693 +
		ch_instrument(choffs),
694 +
		ch_feedback(choffs),
695 +
		op_multiple(opoffs));
696 +
697 +
	if (bitfield(opoffs, 0) == 1 || (is_rhythm(choffs) && choffs >= 6))
698 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " vol=%X", op_volume(opoffs));
699 +
	else
700 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " tl=%02X", ch_total_level(choffs));
701 +
702 +
	end += snprintf(&buffer[end], sizeof(buffer) - end, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u",
703 +
		op_ksr(opoffs),
704 +
		op_ksl(opoffs),
705 +
		op_attack_rate(opoffs),
706 +
		op_decay_rate(opoffs),
707 +
		op_release_rate(opoffs),
708 +
		op_sustain_level(opoffs),
709 +
		op_eg_sustain(opoffs),
710 +
		ch_sustain(choffs));
711 +
712 +
	if (op_lfo_am_enable(opoffs))
713 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " am=1");
714 +
	if (op_lfo_pm_enable(opoffs))
715 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=1");
716 +
	if (op_waveform(opoffs) != 0)
717 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=1");
718 +
	if (is_rhythm(choffs))
719 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1");
720 +
721 +
	return buffer;
722 +
}
723 +
724 +
725 +
726 +
//*********************************************************
727 +
//  YM3526
728 +
//*********************************************************
729 +
730 +
//-------------------------------------------------
731 +
//  ym3526 - constructor
732 +
//-------------------------------------------------
733 +
734 +
ym3526::ym3526(ymfm_interface &intf) :
735 +
	m_address(0),
736 +
	m_fm(intf)
737 +
{
738 +
}
739 +
740 +
741 +
//-------------------------------------------------
742 +
//  reset - reset the system
743 +
//-------------------------------------------------
744 +
745 +
void ym3526::reset()
746 +
{
747 +
	// reset the engines
748 +
	m_fm.reset();
749 +
}
750 +
751 +
752 +
//-------------------------------------------------
753 +
//  save_restore - save or restore the data
754 +
//-------------------------------------------------
755 +
756 +
void ym3526::save_restore(ymfm_saved_state &state)
757 +
{
758 +
	state.save_restore(m_address);
759 +
	m_fm.save_restore(state);
760 +
}
761 +
762 +
763 +
//-------------------------------------------------
764 +
//  read_status - read the status register
765 +
//-------------------------------------------------
766 +
767 +
uint8_t ym3526::read_status()
768 +
{
769 +
	return m_fm.status() | 0x06;
770 +
}
771 +
772 +
773 +
//-------------------------------------------------
774 +
//  read - handle a read from the device
775 +
//-------------------------------------------------
776 +
777 +
uint8_t ym3526::read(uint32_t offset)
778 +
{
779 +
	uint8_t result = 0xff;
780 +
	switch (offset & 1)
781 +
	{
782 +
		case 0: // status port
783 +
			result = read_status();
784 +
			break;
785 +
786 +
		case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed"
787 +
			break;
788 +
	}
789 +
	return result;
790 +
}
791 +
792 +
793 +
//-------------------------------------------------
794 +
//  write_address - handle a write to the address
795 +
//  register
796 +
//-------------------------------------------------
797 +
798 +
void ym3526::write_address(uint8_t data)
799 +
{
800 +
	// YM3526 doesn't expose a busy signal, and the datasheets don't indicate
801 +
	// delays, but all other OPL chips need 12 cycles for address writes
802 +
	m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
803 +
804 +
	// just set the address
805 +
	m_address = data;
806 +
}
807 +
808 +
809 +
//-------------------------------------------------
810 +
//  write - handle a write to the register
811 +
//  interface
812 +
//-------------------------------------------------
813 +
814 +
void ym3526::write_data(uint8_t data)
815 +
{
816 +
	// YM3526 doesn't expose a busy signal, and the datasheets don't indicate
817 +
	// delays, but all other OPL chips need 84 cycles for data writes
818 +
	m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale());
819 +
820 +
	// write to FM
821 +
	m_fm.write(m_address, data);
822 +
}
823 +
824 +
825 +
//-------------------------------------------------
826 +
//  write - handle a write to the register
827 +
//  interface
828 +
//-------------------------------------------------
829 +
830 +
void ym3526::write(uint32_t offset, uint8_t data)
831 +
{
832 +
	switch (offset & 1)
833 +
	{
834 +
		case 0: // address port
835 +
			write_address(data);
836 +
			break;
837 +
838 +
		case 1: // data port
839 +
			write_data(data);
840 +
			break;
841 +
	}
842 +
}
843 +
844 +
845 +
//-------------------------------------------------
846 +
//  generate - generate samples of sound
847 +
//-------------------------------------------------
848 +
849 +
void ym3526::generate(output_data *output, uint32_t numsamples)
850 +
{
851 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
852 +
	{
853 +
		// clock the system
854 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
855 +
856 +
		// update the FM content; mixing details for YM3526 need verification
857 +
		m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS);
858 +
859 +
		// YM3526 uses an external DAC (YM3014) with mantissa/exponent format
860 +
		// convert to 10.3 floating point value and back to simulate truncation
861 +
		output->roundtrip_fp();
862 +
	}
863 +
}
864 +
865 +
866 +
867 +
//*********************************************************
868 +
//  Y8950
869 +
//*********************************************************
870 +
871 +
//-------------------------------------------------
872 +
//  y8950 - constructor
873 +
//-------------------------------------------------
874 +
875 +
y8950::y8950(ymfm_interface &intf) :
876 +
	m_address(0),
877 +
	m_io_ddr(0),
878 +
	m_fm(intf),
879 +
	m_adpcm_b(intf)
880 +
{
881 +
}
882 +
883 +
884 +
//-------------------------------------------------
885 +
//  reset - reset the system
886 +
//-------------------------------------------------
887 +
888 +
void y8950::reset()
889 +
{
890 +
	// reset the engines
891 +
	m_fm.reset();
892 +
	m_adpcm_b.reset();
893 +
}
894 +
895 +
896 +
//-------------------------------------------------
897 +
//  save_restore - save or restore the data
898 +
//-------------------------------------------------
899 +
900 +
void y8950::save_restore(ymfm_saved_state &state)
901 +
{
902 +
	state.save_restore(m_address);
903 +
	state.save_restore(m_io_ddr);
904 +
	m_fm.save_restore(state);
905 +
}
906 +
907 +
908 +
//-------------------------------------------------
909 +
//  read_status - read the status register
910 +
//-------------------------------------------------
911 +
912 +
uint8_t y8950::read_status()
913 +
{
914 +
	// start with current FM status, masking out bits we might set
915 +
	uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING);
916 +
917 +
	// insert the live ADPCM status bits
918 +
	uint8_t adpcm_status = m_adpcm_b.status();
919 +
	if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0)
920 +
		status |= STATUS_ADPCM_B_EOS;
921 +
	if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0)
922 +
		status |= STATUS_ADPCM_B_BRDY;
923 +
	if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0)
924 +
		status |= STATUS_ADPCM_B_PLAYING;
925 +
926 +
	// run it through the FM engine to handle interrupts for us
927 +
	return m_fm.set_reset_status(status, ~status);
928 +
}
929 +
930 +
931 +
//-------------------------------------------------
932 +
//  read_data - read the data port
933 +
//-------------------------------------------------
934 +
935 +
uint8_t y8950::read_data()
936 +
{
937 +
	uint8_t result = 0xff;
938 +
	switch (m_address)
939 +
	{
940 +
		case 0x05:  // keyboard in
941 +
			result = m_fm.intf().ymfm_external_read(ACCESS_IO, 1);
942 +
			break;
943 +
944 +
		case 0x09:  // ADPCM data
945 +
		case 0x1a:
946 +
			result = m_adpcm_b.read(m_address - 0x07);
947 +
			break;
948 +
949 +
		case 0x19:  // I/O data
950 +
			result = m_fm.intf().ymfm_external_read(ACCESS_IO, 0);
951 +
			break;
952 +
953 +
		default:
954 +
			debug::log_unexpected_read_write("Unexpected read from Y8950 data port %02X\n", m_address);
955 +
			break;
956 +
	}
957 +
	return result;
958 +
}
959 +
960 +
961 +
//-------------------------------------------------
962 +
//  read - handle a read from the device
963 +
//-------------------------------------------------
964 +
965 +
uint8_t y8950::read(uint32_t offset)
966 +
{
967 +
	uint8_t result = 0xff;
968 +
	switch (offset & 1)
969 +
	{
970 +
		case 0: // status port
971 +
			result = read_status();
972 +
			break;
973 +
974 +
		case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed"
975 +
			result = read_data();
976 +
			break;
977 +
	}
978 +
	return result;
979 +
}
980 +
981 +
982 +
//-------------------------------------------------
983 +
//  write_address - handle a write to the address
984 +
//  register
985 +
//-------------------------------------------------
986 +
987 +
void y8950::write_address(uint8_t data)
988 +
{
989 +
	// Y8950 doesn't expose a busy signal, but it does indicate that
990 +
	// address writes should be no faster than every 12 clocks
991 +
	m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
992 +
993 +
	// just set the address
994 +
	m_address = data;
995 +
}
996 +
997 +
998 +
//-------------------------------------------------
999 +
//  write - handle a write to the register
1000 +
//  interface
1001 +
//-------------------------------------------------
1002 +
1003 +
void y8950::write_data(uint8_t data)
1004 +
{
1005 +
	// Y8950 doesn't expose a busy signal, but it does indicate that
1006 +
	// data writes should be no faster than every 12 clocks for
1007 +
	// registers 00-1A, or every 84 clocks for other registers
1008 +
	m_fm.intf().ymfm_set_busy_end(((m_address <= 0x1a) ? 12 : 84) * m_fm.clock_prescale());
1009 +
1010 +
	// handle special addresses
1011 +
	switch (m_address)
1012 +
	{
1013 +
		case 0x04:  // IRQ control
1014 +
			m_fm.write(m_address, data);
1015 +
			read_status();
1016 +
			break;
1017 +
1018 +
		case 0x06:  // keyboard out
1019 +
			m_fm.intf().ymfm_external_write(ACCESS_IO, 1, data);
1020 +
			break;
1021 +
1022 +
		case 0x08:  // split FM/ADPCM-B
1023 +
			m_adpcm_b.write(m_address - 0x07, (data & 0x0f) | 0x80);
1024 +
			m_fm.write(m_address, data & 0xc0);
1025 +
			break;
1026 +
1027 +
		case 0x07:  // ADPCM-B registers
1028 +
		case 0x09:
1029 +
		case 0x0a:
1030 +
		case 0x0b:
1031 +
		case 0x0c:
1032 +
		case 0x0d:
1033 +
		case 0x0e:
1034 +
		case 0x0f:
1035 +
		case 0x10:
1036 +
		case 0x11:
1037 +
		case 0x12:
1038 +
		case 0x15:
1039 +
		case 0x16:
1040 +
		case 0x17:
1041 +
			m_adpcm_b.write(m_address - 0x07, data);
1042 +
			break;
1043 +
1044 +
		case 0x18:  // I/O direction
1045 +
			m_io_ddr = data & 0x0f;
1046 +
			break;
1047 +
1048 +
		case 0x19:  // I/O data
1049 +
			m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data & m_io_ddr);
1050 +
			break;
1051 +
1052 +
		default:    // everything else to FM
1053 +
			m_fm.write(m_address, data);
1054 +
			break;
1055 +
	}
1056 +
}
1057 +
1058 +
1059 +
//-------------------------------------------------
1060 +
//  write - handle a write to the register
1061 +
//  interface
1062 +
//-------------------------------------------------
1063 +
1064 +
void y8950::write(uint32_t offset, uint8_t data)
1065 +
{
1066 +
	switch (offset & 1)
1067 +
	{
1068 +
		case 0: // address port
1069 +
			write_address(data);
1070 +
			break;
1071 +
1072 +
		case 1: // data port
1073 +
			write_data(data);
1074 +
			break;
1075 +
	}
1076 +
}
1077 +
1078 +
1079 +
//-------------------------------------------------
1080 +
//  generate - generate samples of sound
1081 +
//-------------------------------------------------
1082 +
1083 +
void y8950::generate(output_data *output, uint32_t numsamples)
1084 +
{
1085 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
1086 +
	{
1087 +
		// clock the system
1088 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
1089 +
		m_adpcm_b.clock();
1090 +
1091 +
		// update the FM content; clipping need verification
1092 +
		m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS);
1093 +
1094 +
		// mix in the ADPCM; ADPCM-B is stereo, but only one channel
1095 +
		// not sure how it's wired up internally
1096 +
		m_adpcm_b.output(*output, 3);
1097 +
1098 +
		// Y8950 uses an external DAC (YM3014) with mantissa/exponent format
1099 +
		// convert to 10.3 floating point value and back to simulate truncation
1100 +
		output->roundtrip_fp();
1101 +
	}
1102 +
}
1103 +
1104 +
1105 +
1106 +
//*********************************************************
1107 +
//  YM3812
1108 +
//*********************************************************
1109 +
1110 +
//-------------------------------------------------
1111 +
//  ym3812 - constructor
1112 +
//-------------------------------------------------
1113 +
1114 +
ym3812::ym3812(ymfm_interface &intf) :
1115 +
	m_address(0),
1116 +
	m_fm(intf)
1117 +
{
1118 +
}
1119 +
1120 +
1121 +
//-------------------------------------------------
1122 +
//  reset - reset the system
1123 +
//-------------------------------------------------
1124 +
1125 +
void ym3812::reset()
1126 +
{
1127 +
	// reset the engines
1128 +
	m_fm.reset();
1129 +
}
1130 +
1131 +
1132 +
//-------------------------------------------------
1133 +
//  save_restore - save or restore the data
1134 +
//-------------------------------------------------
1135 +
1136 +
void ym3812::save_restore(ymfm_saved_state &state)
1137 +
{
1138 +
	state.save_restore(m_address);
1139 +
	m_fm.save_restore(state);
1140 +
}
1141 +
1142 +
1143 +
//-------------------------------------------------
1144 +
//  read_status - read the status register
1145 +
//-------------------------------------------------
1146 +
1147 +
uint8_t ym3812::read_status()
1148 +
{
1149 +
	return m_fm.status() | 0x06;
1150 +
}
1151 +
1152 +
1153 +
//-------------------------------------------------
1154 +
//  read - handle a read from the device
1155 +
//-------------------------------------------------
1156 +
1157 +
uint8_t ym3812::read(uint32_t offset)
1158 +
{
1159 +
	uint8_t result = 0xff;
1160 +
	switch (offset & 1)
1161 +
	{
1162 +
		case 0: // status port
1163 +
			result = read_status();
1164 +
			break;
1165 +
1166 +
		case 1: // "inhibit" according to datasheet
1167 +
			break;
1168 +
	}
1169 +
	return result;
1170 +
}
1171 +
1172 +
1173 +
//-------------------------------------------------
1174 +
//  write_address - handle a write to the address
1175 +
//  register
1176 +
//-------------------------------------------------
1177 +
1178 +
void ym3812::write_address(uint8_t data)
1179 +
{
1180 +
	// YM3812 doesn't expose a busy signal, but it does indicate that
1181 +
	// address writes should be no faster than every 12 clocks
1182 +
	m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
1183 +
1184 +
	// just set the address
1185 +
	m_address = data;
1186 +
}
1187 +
1188 +
1189 +
//-------------------------------------------------
1190 +
//  write - handle a write to the register
1191 +
//  interface
1192 +
//-------------------------------------------------
1193 +
1194 +
void ym3812::write_data(uint8_t data)
1195 +
{
1196 +
	// YM3812 doesn't expose a busy signal, but it does indicate that
1197 +
	// data writes should be no faster than every 84 clocks
1198 +
	m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale());
1199 +
1200 +
	// write to FM
1201 +
	m_fm.write(m_address, data);
1202 +
}
1203 +
1204 +
1205 +
//-------------------------------------------------
1206 +
//  write - handle a write to the register
1207 +
//  interface
1208 +
//-------------------------------------------------
1209 +
1210 +
void ym3812::write(uint32_t offset, uint8_t data)
1211 +
{
1212 +
	switch (offset & 1)
1213 +
	{
1214 +
		case 0: // address port
1215 +
			write_address(data);
1216 +
			break;
1217 +
1218 +
		case 1: // data port
1219 +
			write_data(data);
1220 +
			break;
1221 +
	}
1222 +
}
1223 +
1224 +
1225 +
//-------------------------------------------------
1226 +
//  generate - generate samples of sound
1227 +
//-------------------------------------------------
1228 +
1229 +
void ym3812::generate(output_data *output, uint32_t numsamples)
1230 +
{
1231 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
1232 +
	{
1233 +
		// clock the system
1234 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
1235 +
1236 +
		// update the FM content; mixing details for YM3812 need verification
1237 +
		m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS);
1238 +
1239 +
		// YM3812 uses an external DAC (YM3014) with mantissa/exponent format
1240 +
		// convert to 10.3 floating point value and back to simulate truncation
1241 +
		output->roundtrip_fp();
1242 +
	}
1243 +
}
1244 +
1245 +
1246 +
1247 +
//*********************************************************
1248 +
//  YMF262
1249 +
//*********************************************************
1250 +
1251 +
//-------------------------------------------------
1252 +
//  ymf262 - constructor
1253 +
//-------------------------------------------------
1254 +
1255 +
ymf262::ymf262(ymfm_interface &intf) :
1256 +
	m_address(0),
1257 +
	m_fm(intf)
1258 +
{
1259 +
}
1260 +
1261 +
1262 +
//-------------------------------------------------
1263 +
//  reset - reset the system
1264 +
//-------------------------------------------------
1265 +
1266 +
void ymf262::reset()
1267 +
{
1268 +
	// reset the engines
1269 +
	m_fm.reset();
1270 +
}
1271 +
1272 +
1273 +
//-------------------------------------------------
1274 +
//  save_restore - save or restore the data
1275 +
//-------------------------------------------------
1276 +
1277 +
void ymf262::save_restore(ymfm_saved_state &state)
1278 +
{
1279 +
	state.save_restore(m_address);
1280 +
	m_fm.save_restore(state);
1281 +
}
1282 +
1283 +
1284 +
//-------------------------------------------------
1285 +
//  read_status - read the status register
1286 +
//-------------------------------------------------
1287 +
1288 +
uint8_t ymf262::read_status()
1289 +
{
1290 +
	return m_fm.status();
1291 +
}
1292 +
1293 +
1294 +
//-------------------------------------------------
1295 +
//  read - handle a read from the device
1296 +
//-------------------------------------------------
1297 +
1298 +
uint8_t ymf262::read(uint32_t offset)
1299 +
{
1300 +
	uint8_t result = 0xff;
1301 +
	switch (offset & 3)
1302 +
	{
1303 +
		case 0: // status port
1304 +
			result = read_status();
1305 +
			break;
1306 +
1307 +
		case 1:
1308 +
		case 2:
1309 +
		case 3:
1310 +
			debug::log_unexpected_read_write("Unexpected read from YMF262 offset %d\n", offset & 3);
1311 +
			break;
1312 +
	}
1313 +
	return result;
1314 +
}
1315 +
1316 +
1317 +
//-------------------------------------------------
1318 +
//  write_address - handle a write to the address
1319 +
//  register
1320 +
//-------------------------------------------------
1321 +
1322 +
void ymf262::write_address(uint8_t data)
1323 +
{
1324 +
	// YMF262 doesn't expose a busy signal, but it does indicate that
1325 +
	// address writes should be no faster than every 32 clocks
1326 +
	m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
1327 +
1328 +
	// just set the address
1329 +
	m_address = data;
1330 +
}
1331 +
1332 +
1333 +
//-------------------------------------------------
1334 +
//  write_data - handle a write to the data
1335 +
//  register
1336 +
//-------------------------------------------------
1337 +
1338 +
void ymf262::write_data(uint8_t data)
1339 +
{
1340 +
	// YMF262 doesn't expose a busy signal, but it does indicate that
1341 +
	// data writes should be no faster than every 32 clocks
1342 +
	m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
1343 +
1344 +
	// write to FM
1345 +
	m_fm.write(m_address, data);
1346 +
}
1347 +
1348 +
1349 +
//-------------------------------------------------
1350 +
//  write_address_hi - handle a write to the upper
1351 +
//  address register
1352 +
//-------------------------------------------------
1353 +
1354 +
void ymf262::write_address_hi(uint8_t data)
1355 +
{
1356 +
	// YMF262 doesn't expose a busy signal, but it does indicate that
1357 +
	// address writes should be no faster than every 32 clocks
1358 +
	m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
1359 +
1360 +
	// just set the address
1361 +
	m_address = data | 0x100;
1362 +
1363 +
	// tests reveal that in compatibility mode, upper bit is masked
1364 +
	// except for register 0x105
1365 +
	if (m_fm.regs().newflag() == 0 && m_address != 0x105)
1366 +
		m_address &= 0xff;
1367 +
}
1368 +
1369 +
1370 +
//-------------------------------------------------
1371 +
//  write - handle a write to the register
1372 +
//  interface
1373 +
//-------------------------------------------------
1374 +
1375 +
void ymf262::write(uint32_t offset, uint8_t data)
1376 +
{
1377 +
	switch (offset & 3)
1378 +
	{
1379 +
		case 0: // address port
1380 +
			write_address(data);
1381 +
			break;
1382 +
1383 +
		case 1: // data port
1384 +
			write_data(data);
1385 +
			break;
1386 +
1387 +
		case 2: // address port
1388 +
			write_address_hi(data);
1389 +
			break;
1390 +
1391 +
		case 3: // data port
1392 +
			write_data(data);
1393 +
			break;
1394 +
	}
1395 +
}
1396 +
1397 +
1398 +
//-------------------------------------------------
1399 +
//  generate - generate samples of sound
1400 +
//-------------------------------------------------
1401 +
1402 +
void ymf262::generate(output_data *output, uint32_t numsamples)
1403 +
{
1404 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
1405 +
	{
1406 +
		// clock the system
1407 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
1408 +
1409 +
		// update the FM content; mixing details for YMF262 need verification
1410 +
		m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
1411 +
1412 +
		// YMF262 output is 16-bit offset serial via YAC512 DAC
1413 +
		output->clamp16();
1414 +
	}
1415 +
}
1416 +
1417 +
1418 +
1419 +
//*********************************************************
1420 +
//  YMF289B
1421 +
//*********************************************************
1422 +
1423 +
// YMF289B is a YMF262 with the following changes:
1424 +
//   * "Power down" mode added
1425 +
//   * Bulk register clear added
1426 +
//   * Busy flag added to the status register
1427 +
//   * Shorter busy times
1428 +
//   * All registers can be read
1429 +
//   * Only 2 outputs exposed
1430 +
1431 +
//-------------------------------------------------
1432 +
//  ymf289b - constructor
1433 +
//-------------------------------------------------
1434 +
1435 +
ymf289b::ymf289b(ymfm_interface &intf) :
1436 +
	m_address(0),
1437 +
	m_fm(intf)
1438 +
{
1439 +
}
1440 +
1441 +
1442 +
//-------------------------------------------------
1443 +
//  reset - reset the system
1444 +
//-------------------------------------------------
1445 +
1446 +
void ymf289b::reset()
1447 +
{
1448 +
	// reset the engines
1449 +
	m_fm.reset();
1450 +
}
1451 +
1452 +
1453 +
//-------------------------------------------------
1454 +
//  save_restore - save or restore the data
1455 +
//-------------------------------------------------
1456 +
1457 +
void ymf289b::save_restore(ymfm_saved_state &state)
1458 +
{
1459 +
	state.save_restore(m_address);
1460 +
	m_fm.save_restore(state);
1461 +
}
1462 +
1463 +
1464 +
//-------------------------------------------------
1465 +
//  read_status - read the status register
1466 +
//-------------------------------------------------
1467 +
1468 +
uint8_t ymf289b::read_status()
1469 +
{
1470 +
	uint8_t result = m_fm.status();
1471 +
1472 +
	// YMF289B adds a busy flag
1473 +
	if (ymf289b_mode() && m_fm.intf().ymfm_is_busy())
1474 +
		result |= STATUS_BUSY_FLAGS;
1475 +
	return result;
1476 +
}
1477 +
1478 +
1479 +
//-------------------------------------------------
1480 +
//  read_data - read the data register
1481 +
//-------------------------------------------------
1482 +
1483 +
uint8_t ymf289b::read_data()
1484 +
{
1485 +
	uint8_t result = 0xff;
1486 +
1487 +
	// YMF289B can read register data back
1488 +
	if (ymf289b_mode())
1489 +
		result = m_fm.regs().read(m_address);
1490 +
	return result;
1491 +
}
1492 +
1493 +
1494 +
//-------------------------------------------------
1495 +
//  read - handle a read from the device
1496 +
//-------------------------------------------------
1497 +
1498 +
uint8_t ymf289b::read(uint32_t offset)
1499 +
{
1500 +
	uint8_t result = 0xff;
1501 +
	switch (offset & 3)
1502 +
	{
1503 +
		case 0: // status port
1504 +
			result = read_status();
1505 +
			break;
1506 +
1507 +
		case 1:	// data port
1508 +
			result = read_data();
1509 +
			break;
1510 +
1511 +
		case 2:
1512 +
		case 3:
1513 +
			debug::log_unexpected_read_write("Unexpected read from YMF289B offset %d\n", offset & 3);
1514 +
			break;
1515 +
	}
1516 +
	return result;
1517 +
}
1518 +
1519 +
1520 +
//-------------------------------------------------
1521 +
//  write_address - handle a write to the address
1522 +
//  register
1523 +
//-------------------------------------------------
1524 +
1525 +
void ymf289b::write_address(uint8_t data)
1526 +
{
1527 +
	m_address = data;
1528 +
1529 +
	// count busy time
1530 +
	m_fm.intf().ymfm_set_busy_end(56);
1531 +
}
1532 +
1533 +
1534 +
//-------------------------------------------------
1535 +
//  write_data - handle a write to the data
1536 +
//  register
1537 +
//-------------------------------------------------
1538 +
1539 +
void ymf289b::write_data(uint8_t data)
1540 +
{
1541 +
	// write to FM
1542 +
	m_fm.write(m_address, data);
1543 +
1544 +
	// writes to 0x108 with the CLR flag set clear the registers
1545 +
	if (m_address == 0x108 && bitfield(data, 2) != 0)
1546 +
		m_fm.regs().reset();
1547 +
1548 +
	// count busy time
1549 +
	m_fm.intf().ymfm_set_busy_end(56);
1550 +
}
1551 +
1552 +
1553 +
//-------------------------------------------------
1554 +
//  write_address_hi - handle a write to the upper
1555 +
//  address register
1556 +
//-------------------------------------------------
1557 +
1558 +
void ymf289b::write_address_hi(uint8_t data)
1559 +
{
1560 +
	// just set the address
1561 +
	m_address = data | 0x100;
1562 +
1563 +
	// tests reveal that in compatibility mode, upper bit is masked
1564 +
	// except for register 0x105
1565 +
	if (m_fm.regs().newflag() == 0 && m_address != 0x105)
1566 +
		m_address &= 0xff;
1567 +
1568 +
	// count busy time
1569 +
	m_fm.intf().ymfm_set_busy_end(56);
1570 +
}
1571 +
1572 +
1573 +
//-------------------------------------------------
1574 +
//  write - handle a write to the register
1575 +
//  interface
1576 +
//-------------------------------------------------
1577 +
1578 +
void ymf289b::write(uint32_t offset, uint8_t data)
1579 +
{
1580 +
	switch (offset & 3)
1581 +
	{
1582 +
		case 0: // address port
1583 +
			write_address(data);
1584 +
			break;
1585 +
1586 +
		case 1: // data port
1587 +
			write_data(data);
1588 +
			break;
1589 +
1590 +
		case 2: // address port
1591 +
			write_address_hi(data);
1592 +
			break;
1593 +
1594 +
		case 3: // data port
1595 +
			write_data(data);
1596 +
			break;
1597 +
	}
1598 +
}
1599 +
1600 +
1601 +
//-------------------------------------------------
1602 +
//  generate - generate samples of sound
1603 +
//-------------------------------------------------
1604 +
1605 +
void ymf289b::generate(output_data *output, uint32_t numsamples)
1606 +
{
1607 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
1608 +
	{
1609 +
		// clock the system
1610 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
1611 +
1612 +
		// update the FM content; mixing details for YMF262 need verification
1613 +
		fm_engine::output_data full;
1614 +
		m_fm.output(full.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
1615 +
1616 +
		// YMF278B output is 16-bit offset serial via YAC512 DAC, but
1617 +
		// only 2 of the 4 outputs are exposed
1618 +
		output->data[0] = full.data[0];
1619 +
		output->data[1] = full.data[1];
1620 +
		output->clamp16();
1621 +
	}
1622 +
}
1623 +
1624 +
1625 +
1626 +
//*********************************************************
1627 +
//  YMF278B
1628 +
//*********************************************************
1629 +
1630 +
//-------------------------------------------------
1631 +
//  ymf278b - constructor
1632 +
//-------------------------------------------------
1633 +
1634 +
ymf278b::ymf278b(ymfm_interface &intf) :
1635 +
	m_address(0),
1636 +
	m_fm_pos(0),
1637 +
	m_load_remaining(0),
1638 +
	m_next_status_id(false),
1639 +
	m_fm(intf),
1640 +
	m_pcm(intf)
1641 +
{
1642 +
}
1643 +
1644 +
1645 +
//-------------------------------------------------
1646 +
//  reset - reset the system
1647 +
//-------------------------------------------------
1648 +
1649 +
void ymf278b::reset()
1650 +
{
1651 +
	// reset the engines
1652 +
	m_fm.reset();
1653 +
	m_pcm.reset();
1654 +
1655 +
	// next status read will return ID
1656 +
	m_next_status_id = true;
1657 +
}
1658 +
1659 +
1660 +
//-------------------------------------------------
1661 +
//  save_restore - save or restore the data
1662 +
//-------------------------------------------------
1663 +
1664 +
void ymf278b::save_restore(ymfm_saved_state &state)
1665 +
{
1666 +
	state.save_restore(m_address);
1667 +
	state.save_restore(m_fm_pos);
1668 +
	state.save_restore(m_load_remaining);
1669 +
	state.save_restore(m_next_status_id);
1670 +
	m_fm.save_restore(state);
1671 +
	m_pcm.save_restore(state);
1672 +
}
1673 +
1674 +
1675 +
//-------------------------------------------------
1676 +
//  read_status - read the status register
1677 +
//-------------------------------------------------
1678 +
1679 +
uint8_t ymf278b::read_status()
1680 +
{
1681 +
	uint8_t result;
1682 +
1683 +
	// first status read after initialization returns a chip ID, which
1684 +
	// varies based on the "new" flags, indicating the mode
1685 +
	if (m_next_status_id)
1686 +
	{
1687 +
		if (m_fm.regs().new2flag())
1688 +
			result = 0x02;
1689 +
		else if (m_fm.regs().newflag())
1690 +
			result = 0x00;
1691 +
		else
1692 +
			result = 0x06;
1693 +
		m_next_status_id = false;
1694 +
	}
1695 +
	else
1696 +
	{
1697 +
		result = m_fm.status();
1698 +
		if (m_fm.intf().ymfm_is_busy())
1699 +
			result |= STATUS_BUSY;
1700 +
		if (m_load_remaining != 0)
1701 +
			result |= STATUS_LD;
1702 +
1703 +
		// if new2 flag is not set, we're in OPL2 or OPL3 mode
1704 +
		if (!m_fm.regs().new2flag())
1705 +
			result &= ~(STATUS_BUSY | STATUS_LD);
1706 +
	}
1707 +
	return result;
1708 +
}
1709 +
1710 +
1711 +
//-------------------------------------------------
1712 +
//  write_data_pcm - handle a write to the PCM data
1713 +
//  register
1714 +
//-------------------------------------------------
1715 +
1716 +
uint8_t ymf278b::read_data_pcm()
1717 +
{
1718 +
	// read from PCM
1719 +
	if (bitfield(m_address, 9) != 0)
1720 +
	{
1721 +
		uint8_t result = m_pcm.read(m_address & 0xff);
1722 +
		if ((m_address & 0xff) == 0x02)
1723 +
			result |= 0x20;
1724 +
1725 +
		return result;
1726 +
	}
1727 +
	return 0;
1728 +
}
1729 +
1730 +
1731 +
//-------------------------------------------------
1732 +
//  read - handle a read from the device
1733 +
//-------------------------------------------------
1734 +
1735 +
uint8_t ymf278b::read(uint32_t offset)
1736 +
{
1737 +
	uint8_t result = 0xff;
1738 +
	switch (offset & 7)
1739 +
	{
1740 +
		case 0: // status port
1741 +
			result = read_status();
1742 +
			break;
1743 +
1744 +
		case 5: // PCM data port
1745 +
			result = read_data_pcm();
1746 +
			break;
1747 +
1748 +
		default:
1749 +
			debug::log_unexpected_read_write("Unexpected read from ymf278b offset %d\n", offset & 3);
1750 +
			break;
1751 +
	}
1752 +
	return result;
1753 +
}
1754 +
1755 +
1756 +
//-------------------------------------------------
1757 +
//  write_address - handle a write to the address
1758 +
//  register
1759 +
//-------------------------------------------------
1760 +
1761 +
void ymf278b::write_address(uint8_t data)
1762 +
{
1763 +
	// just set the address
1764 +
	m_address = data;
1765 +
}
1766 +
1767 +
1768 +
//-------------------------------------------------
1769 +
//  write_data - handle a write to the data
1770 +
//  register
1771 +
//-------------------------------------------------
1772 +
1773 +
void ymf278b::write_data(uint8_t data)
1774 +
{
1775 +
	// write to FM
1776 +
	if (bitfield(m_address, 9) == 0)
1777 +
	{
1778 +
		uint8_t old = m_fm.regs().new2flag();
1779 +
		m_fm.write(m_address, data);
1780 +
1781 +
		// changing NEW2 from 0->1 causes the next status read to
1782 +
		// return the chip ID
1783 +
		if (old == 0 && m_fm.regs().new2flag() != 0)
1784 +
			m_next_status_id = true;
1785 +
	}
1786 +
1787 +
	// BUSY goes for 56 clocks on FM writes
1788 +
	m_fm.intf().ymfm_set_busy_end(56);
1789 +
}
1790 +
1791 +
1792 +
//-------------------------------------------------
1793 +
//  write_address_hi - handle a write to the upper
1794 +
//  address register
1795 +
//-------------------------------------------------
1796 +
1797 +
void ymf278b::write_address_hi(uint8_t data)
1798 +
{
1799 +
	// just set the address
1800 +
	m_address = data | 0x100;
1801 +
1802 +
	// YMF262, in compatibility mode, treats the upper bit as masked
1803 +
	// except for register 0x105; assuming YMF278B works the same way?
1804 +
	if (m_fm.regs().newflag() == 0 && m_address != 0x105)
1805 +
		m_address &= 0xff;
1806 +
}
1807 +
1808 +
1809 +
//-------------------------------------------------
1810 +
//  write_address_pcm - handle a write to the upper
1811 +
//  address register
1812 +
//-------------------------------------------------
1813 +
1814 +
void ymf278b::write_address_pcm(uint8_t data)
1815 +
{
1816 +
	// just set the address
1817 +
	m_address = data | 0x200;
1818 +
}
1819 +
1820 +
1821 +
//-------------------------------------------------
1822 +
//  write_data_pcm - handle a write to the PCM data
1823 +
//  register
1824 +
//-------------------------------------------------
1825 +
1826 +
void ymf278b::write_data_pcm(uint8_t data)
1827 +
{
1828 +
	// ignore data writes if new2 is not yet set
1829 +
	if (m_fm.regs().new2flag() == 0)
1830 +
		return;
1831 +
1832 +
	// write to FM
1833 +
	if (bitfield(m_address, 9) != 0)
1834 +
	{
1835 +
		uint8_t addr = m_address & 0xff;
1836 +
		m_pcm.write(addr, data);
1837 +
1838 +
		// writes to the waveform number cause loads to happen for "about 300usec"
1839 +
		// which is ~13 samples at the nominal output frequency of 44.1kHz
1840 +
		if (addr >= 0x08 && addr <= 0x1f)
1841 +
			m_load_remaining = 13;
1842 +
	}
1843 +
1844 +
	// BUSY goes for 88 clocks on PCM writes
1845 +
	m_fm.intf().ymfm_set_busy_end(88);
1846 +
}
1847 +
1848 +
1849 +
//-------------------------------------------------
1850 +
//  write - handle a write to the register
1851 +
//  interface
1852 +
//-------------------------------------------------
1853 +
1854 +
void ymf278b::write(uint32_t offset, uint8_t data)
1855 +
{
1856 +
	switch (offset & 7)
1857 +
	{
1858 +
		case 0: // address port
1859 +
			write_address(data);
1860 +
			break;
1861 +
1862 +
		case 1: // data port
1863 +
			write_data(data);
1864 +
			break;
1865 +
1866 +
		case 2: // address port
1867 +
			write_address_hi(data);
1868 +
			break;
1869 +
1870 +
		case 3: // data port
1871 +
			write_data(data);
1872 +
			break;
1873 +
1874 +
		case 4: // PCM address port
1875 +
			write_address_pcm(data);
1876 +
			break;
1877 +
1878 +
		case 5: // PCM address port
1879 +
			write_data_pcm(data);
1880 +
			break;
1881 +
1882 +
		default:
1883 +
			debug::log_unexpected_read_write("Unexpected write to ymf278b offset %d\n", offset & 7);
1884 +
			break;
1885 +
	}
1886 +
}
1887 +
1888 +
1889 +
//-------------------------------------------------
1890 +
//  generate - generate one sample of sound
1891 +
//-------------------------------------------------
1892 +
1893 +
void ymf278b::generate(output_data *output, uint32_t numsamples)
1894 +
{
1895 +
	static const int16_t s_mix_scale[8] = { 0x7fa, 0x5a4, 0x3fd, 0x2d2, 0x1fe, 0x169, 0xff, 0 };
1896 +
	int32_t const pcm_l = s_mix_scale[m_pcm.regs().mix_pcm_l()];
1897 +
	int32_t const pcm_r = s_mix_scale[m_pcm.regs().mix_pcm_r()];
1898 +
	int32_t const fm_l = s_mix_scale[m_pcm.regs().mix_fm_l()];
1899 +
	int32_t const fm_r = s_mix_scale[m_pcm.regs().mix_fm_r()];
1900 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
1901 +
	{
1902 +
		// clock the system
1903 +
		m_fm_pos += FM_EXTRA_SAMPLE_STEP;
1904 +
		if (m_fm_pos >= FM_EXTRA_SAMPLE_THRESH)
1905 +
		{
1906 +
			m_fm.clock(fm_engine::ALL_CHANNELS);
1907 +
			m_fm_pos -= FM_EXTRA_SAMPLE_THRESH;
1908 +
		}
1909 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
1910 +
		m_pcm.clock(pcm_engine::ALL_CHANNELS);
1911 +
1912 +
		// update the FM content; mixing details for YMF278B need verification
1913 +
		fm_engine::output_data fmout;
1914 +
		m_fm.output(fmout.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
1915 +
1916 +
		// update the PCM content
1917 +
		pcm_engine::output_data pcmout;
1918 +
		m_pcm.output(pcmout.clear(), pcm_engine::ALL_CHANNELS);
1919 +
1920 +
		// DO0 output: FM channels 2+3 only
1921 +
		output->data[0] = fmout.data[2];
1922 +
		output->data[1] = fmout.data[3];
1923 +
1924 +
		// DO1 output: wavetable channels 2+3 only
1925 +
		output->data[2] = pcmout.data[2];
1926 +
		output->data[3] = pcmout.data[3];
1927 +
1928 +
		// DO2 output: mixed FM channels 0+1 and wavetable channels 0+1
1929 +
		output->data[4] = (fmout.data[0] * fm_l + pcmout.data[0] * pcm_l) >> 11;
1930 +
		output->data[5] = (fmout.data[1] * fm_r + pcmout.data[1] * pcm_r) >> 11;
1931 +
1932 +
		// YMF278B output is 16-bit 2s complement serial
1933 +
		output->clamp16();
1934 +
	}
1935 +
1936 +
	// decrement the load waiting count
1937 +
	if (m_load_remaining > 0)
1938 +
		m_load_remaining -= std::min(m_load_remaining, numsamples);
1939 +
}
1940 +
1941 +
1942 +
1943 +
//*********************************************************
1944 +
//  OPLL BASE
1945 +
//*********************************************************
1946 +
1947 +
//-------------------------------------------------
1948 +
//  opll_base - constructor
1949 +
//-------------------------------------------------
1950 +
1951 +
opll_base::opll_base(ymfm_interface &intf, uint8_t const *instrument_data) :
1952 +
	m_address(0),
1953 +
	m_fm(intf)
1954 +
{
1955 +
	m_fm.regs().set_instrument_data(instrument_data);
1956 +
}
1957 +
1958 +
1959 +
//-------------------------------------------------
1960 +
//  reset - reset the system
1961 +
//-------------------------------------------------
1962 +
1963 +
void opll_base::reset()
1964 +
{
1965 +
	// reset the engines
1966 +
	m_fm.reset();
1967 +
}
1968 +
1969 +
1970 +
//-------------------------------------------------
1971 +
//  save_restore - save or restore the data
1972 +
//-------------------------------------------------
1973 +
1974 +
void opll_base::save_restore(ymfm_saved_state &state)
1975 +
{
1976 +
	state.save_restore(m_address);
1977 +
	m_fm.save_restore(state);
1978 +
}
1979 +
1980 +
1981 +
//-------------------------------------------------
1982 +
//  write_address - handle a write to the address
1983 +
//  register
1984 +
//-------------------------------------------------
1985 +
1986 +
void opll_base::write_address(uint8_t data)
1987 +
{
1988 +
	// OPLL doesn't expose a busy signal, but datasheets are pretty consistent
1989 +
	// in indicating that address writes should be no faster than every 12 clocks
1990 +
	m_fm.intf().ymfm_set_busy_end(12);
1991 +
1992 +
	// just set the address
1993 +
	m_address = data;
1994 +
}
1995 +
1996 +
1997 +
//-------------------------------------------------
1998 +
//  write - handle a write to the register
1999 +
//  interface
2000 +
//-------------------------------------------------
2001 +
2002 +
void opll_base::write_data(uint8_t data)
2003 +
{
2004 +
	// OPLL doesn't expose a busy signal, but datasheets are pretty consistent
2005 +
	// in indicating that address writes should be no faster than every 84 clocks
2006 +
	m_fm.intf().ymfm_set_busy_end(84);
2007 +
2008 +
	// write to FM
2009 +
	m_fm.write(m_address, data);
2010 +
}
2011 +
2012 +
2013 +
//-------------------------------------------------
2014 +
//  write - handle a write to the register
2015 +
//  interface
2016 +
//-------------------------------------------------
2017 +
2018 +
void opll_base::write(uint32_t offset, uint8_t data)
2019 +
{
2020 +
	switch (offset & 1)
2021 +
	{
2022 +
		case 0: // address port
2023 +
			write_address(data);
2024 +
			break;
2025 +
2026 +
		case 1: // data port
2027 +
			write_data(data);
2028 +
			break;
2029 +
	}
2030 +
}
2031 +
2032 +
2033 +
//-------------------------------------------------
2034 +
//  generate - generate one sample of sound
2035 +
//-------------------------------------------------
2036 +
2037 +
void opll_base::generate(output_data *output, uint32_t numsamples)
2038 +
{
2039 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
2040 +
	{
2041 +
		// clock the system
2042 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
2043 +
2044 +
		// update the FM content; OPLL has a built-in 9-bit DAC
2045 +
		m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS);
2046 +
2047 +
		// final output is multiplexed; we don't simulate that here except
2048 +
		// to average over everything
2049 +
		output->data[0] = (output->data[0] * 128) / 9;
2050 +
		output->data[1] = (output->data[1] * 128) / 9;
2051 +
	}
2052 +
}
2053 +
2054 +
2055 +
2056 +
//*********************************************************
2057 +
//  YM2413
2058 +
//*********************************************************
2059 +
2060 +
//-------------------------------------------------
2061 +
//  ym2413 - constructor
2062 +
//-------------------------------------------------
2063 +
2064 +
ym2413::ym2413(ymfm_interface &intf, uint8_t const *instrument_data) :
2065 +
	opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
2066 +
{
2067 +
};
2068 +
2069 +
// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
2070 +
uint8_t const ym2413::s_default_instruments[] =
2071 +
{
2072 +
	//April 2015 David Viens, tweaked May 19-21th 2015 Hubert Lamontagne
2073 +
	0x71, 0x61, 0x1E, 0x17, 0xEF, 0x7F, 0x00, 0x17, //Violin
2074 +
	0x13, 0x41, 0x1A, 0x0D, 0xF8, 0xF7, 0x23, 0x13, //Guitar
2075 +
	0x13, 0x01, 0x99, 0x00, 0xF2, 0xC4, 0x11, 0x23, //Piano
2076 +
	0x31, 0x61, 0x0E, 0x07, 0x98, 0x64, 0x70, 0x27, //Flute
2077 +
	0x22, 0x21, 0x1E, 0x06, 0xBF, 0x76, 0x00, 0x28, //Clarinet
2078 +
	0x31, 0x22, 0x16, 0x05, 0xE0, 0x71, 0x0F, 0x18, //Oboe
2079 +
	0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, //Trumpet
2080 +
	0x23, 0x21, 0x2D, 0x14, 0xFF, 0x7F, 0x00, 0x07, //Organ
2081 +
	0x41, 0x61, 0x1B, 0x06, 0x64, 0x65, 0x10, 0x17, //Horn
2082 +
	0x61, 0x61, 0x0B, 0x18, 0x85, 0xFF, 0x81, 0x07, //Synthesizer
2083 +
	0x13, 0x01, 0x83, 0x11, 0xFA, 0xE4, 0x10, 0x04, //Harpsichord
2084 +
	0x17, 0x81, 0x23, 0x07, 0xF8, 0xF8, 0x22, 0x12, //Vibraphone
2085 +
	0x61, 0x50, 0x0C, 0x05, 0xF2, 0xF5, 0x29, 0x42, //Synthesizer Bass
2086 +
	0x01, 0x01, 0x54, 0x03, 0xC3, 0x92, 0x03, 0x02, //Acoustic Bass
2087 +
	0x41, 0x41, 0x89, 0x03, 0xF1, 0xE5, 0x11, 0x13, //Electric Guitar
2088 +
	0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
2089 +
	0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
2090 +
	0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55  //rhythm 3
2091 +
};
2092 +
2093 +
2094 +
2095 +
//*********************************************************
2096 +
//  YM2423
2097 +
//*********************************************************
2098 +
2099 +
//-------------------------------------------------
2100 +
//  ym2423 - constructor
2101 +
//-------------------------------------------------
2102 +
2103 +
ym2423::ym2423(ymfm_interface &intf, uint8_t const *instrument_data) :
2104 +
	opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
2105 +
{
2106 +
};
2107 +
2108 +
// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
2109 +
uint8_t const ym2423::s_default_instruments[] =
2110 +
{
2111 +
	// May 4-6 2016 Hubert Lamontagne
2112 +
	// Doesn't seem to have any diff between opllx-x and opllx-y
2113 +
	// Drums seem identical to regular opll
2114 +
	0x61, 0x61, 0x1B, 0x07, 0x94, 0x5F, 0x10, 0x06, //1	Strings	Saw wave with vibrato Violin
2115 +
	0x93, 0xB1, 0x51, 0x04, 0xF3, 0xF2, 0x70, 0xFB, //2	Guitar	Jazz GuitarPiano
2116 +
	0x41, 0x21, 0x11, 0x85, 0xF2, 0xF2, 0x70, 0x75, //3	Electric Guitar	Same as OPLL No.15 Synth
2117 +
	0x93, 0xB2, 0x28, 0x07, 0xF3, 0xF2, 0x70, 0xB4, //4	Electric Piano 2	Slow attack, tremoloDing-a-ling
2118 +
	0x72, 0x31, 0x97, 0x05, 0x51, 0x6F, 0x60, 0x09, //5 	Flute	Same as OPLL No.4Clarinet
2119 +
	0x13, 0x30, 0x18, 0x06, 0xF7, 0xF4, 0x50, 0x85, //6	Marimba 	Also be used as steel drumXyophone
2120 +
	0x51, 0x31, 0x1C, 0x07, 0x51, 0x71, 0x20, 0x26, //7	Trumpet 	Same as OPLL No.7Trumpet
2121 +
	0x41, 0xF4, 0x1B, 0x07, 0x74, 0x34, 0x00, 0x06, //8	Harmonica Harmonica synth
2122 +
	0x50, 0x30, 0x4D, 0x03, 0x42, 0x65, 0x20, 0x06, //9	Tuba Tuba
2123 +
	0x40, 0x20, 0x10, 0x85, 0xF3, 0xF5, 0x20, 0x04, //10 	Synth Brass 2 Synth sweep
2124 +
	0x61, 0x61, 0x1B, 0x07, 0xC5, 0x96, 0xF3, 0xF6, //11 	Short Saw	Saw wave with short envelopeSynth hit
2125 +
	0xF9, 0xF1, 0xDC, 0x00, 0xF5, 0xF3, 0x77, 0xF2, //12 	Vibraphone	Bright vibraphoneVibes
2126 +
	0x60, 0xA2, 0x91, 0x03, 0x94, 0xC1, 0xF7, 0xF7, //13 	Electric Guitar 2	Clean guitar with feedbackHarmonic bass
2127 +
	0x30, 0x30, 0x17, 0x06, 0xF3, 0xF1, 0xB7, 0xFC, //14 	Synth Bass 2Snappy bass
2128 +
	0x31, 0x36, 0x0D, 0x05, 0xF2, 0xF4, 0x27, 0x9C, //15 	Sitar	Also be used as ShamisenBanjo
2129 +
	0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
2130 +
	0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
2131 +
	0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55  //rhythm 3
2132 +
};
2133 +
2134 +
2135 +
2136 +
//*********************************************************
2137 +
//  YMF281
2138 +
//*********************************************************
2139 +
2140 +
//-------------------------------------------------
2141 +
//  ymf281 - constructor
2142 +
//-------------------------------------------------
2143 +
2144 +
ymf281::ymf281(ymfm_interface &intf, uint8_t const *instrument_data) :
2145 +
	opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
2146 +
{
2147 +
};
2148 +
2149 +
// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
2150 +
uint8_t const ymf281::s_default_instruments[] =
2151 +
{
2152 +
	// May 14th 2015 Hubert Lamontagne
2153 +
	0x72, 0x21, 0x1A, 0x07, 0xF6, 0x64, 0x01, 0x16, // Clarinet ~~ Electric String 	Square wave with vibrato
2154 +
	0x00, 0x10, 0x45, 0x00, 0xF6, 0x83, 0x73, 0x63, // Synth Bass ~~ Bow wow 	Triangular wave
2155 +
	0x13, 0x01, 0x96, 0x00, 0xF1, 0xF4, 0x31, 0x23, // Piano ~~ Electric Guitar 	Despite of its name, same as Piano of YM2413.
2156 +
	0x71, 0x21, 0x0B, 0x0F, 0xF9, 0x64, 0x70, 0x17, // Flute ~~ Organ 	Sine wave
2157 +
	0x02, 0x21, 0x1E, 0x06, 0xF9, 0x76, 0x00, 0x28, // Square Wave ~~ Clarinet 	Same as ones of YM2413.
2158 +
	0x00, 0x61, 0x82, 0x0E, 0xF9, 0x61, 0x20, 0x27, // Space Oboe ~~ Saxophone 	Saw wave with vibrato
2159 +
	0x21, 0x61, 0x1B, 0x07, 0x84, 0x8F, 0x10, 0x07, // Trumpet ~~ Trumpet 	Same as ones of YM2413.
2160 +
	0x37, 0x32, 0xCA, 0x02, 0x66, 0x64, 0x47, 0x29, // Wow Bell ~~ Street Organ 	Calliope
2161 +
	0x41, 0x41, 0x07, 0x03, 0xF5, 0x70, 0x51, 0xF5, // Electric Guitar ~~ Synth Brass 	Same as Synthesizer of YM2413.
2162 +
	0x36, 0x01, 0x5E, 0x07, 0xF2, 0xF3, 0xF7, 0xF7, // Vibes ~~ Electric Piano 	Simulate of Rhodes Piano
2163 +
	0x00, 0x00, 0x18, 0x06, 0xC5, 0xF3, 0x20, 0xF2, // Bass ~~ Bass 	Electric bass
2164 +
	0x17, 0x81, 0x25, 0x07, 0xF7, 0xF3, 0x21, 0xF7, // Vibraphone ~~ Vibraphone	Same as ones of YM2413.
2165 +
	0x35, 0x64, 0x00, 0x00, 0xFF, 0xF3, 0x77, 0xF5, // Vibrato Bell ~~ Chime 	Bell
2166 +
	0x11, 0x31, 0x00, 0x07, 0xDD, 0xF3, 0xFF, 0xFB, // Click Sine ~~ Tom Tom II 	Tom
2167 +
	0x3A, 0x21, 0x00, 0x07, 0x95, 0x84, 0x0F, 0xF5, // Noise and Tone ~~ Noise 	for S.E.
2168 +
	0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
2169 +
	0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
2170 +
	0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55  //rhythm 3
2171 +
};
2172 +
2173 +
2174 +
2175 +
//*********************************************************
2176 +
//  DS1001
2177 +
//*********************************************************
2178 +
2179 +
//-------------------------------------------------
2180 +
//  ds1001 - constructor
2181 +
//-------------------------------------------------
2182 +
2183 +
ds1001::ds1001(ymfm_interface &intf, uint8_t const *instrument_data) :
2184 +
	opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
2185 +
{
2186 +
};
2187 +
2188 +
// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
2189 +
uint8_t const ds1001::s_default_instruments[] =
2190 +
{
2191 +
	// May 15th 2015 Hubert Lamontagne & David Viens
2192 +
	0x03, 0x21, 0x05, 0x06, 0xC8, 0x81, 0x42, 0x27, // Buzzy Bell
2193 +
	0x13, 0x41, 0x14, 0x0D, 0xF8, 0xF7, 0x23, 0x12, // Guitar
2194 +
	0x31, 0x11, 0x08, 0x08, 0xFA, 0xC2, 0x28, 0x22, // Wurly
2195 +
	0x31, 0x61, 0x0C, 0x07, 0xF8, 0x64, 0x60, 0x27, // Flute
2196 +
	0x22, 0x21, 0x1E, 0x06, 0xFF, 0x76, 0x00, 0x28, // Clarinet
2197 +
	0x02, 0x01, 0x05, 0x00, 0xAC, 0xF2, 0x03, 0x02, // Synth
2198 +
	0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, // Trumpet
2199 +
	0x23, 0x21, 0x22, 0x17, 0xFF, 0x73, 0x00, 0x17, // Organ
2200 +
	0x15, 0x11, 0x25, 0x00, 0x41, 0x71, 0x00, 0xF1, // Bells
2201 +
	0x95, 0x01, 0x10, 0x0F, 0xB8, 0xAA, 0x50, 0x02, // Vibes
2202 +
	0x17, 0xC1, 0x5E, 0x07, 0xFA, 0xF8, 0x22, 0x12, // Vibraphone
2203 +
	0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x10, 0x16, // Tutti
2204 +
	0x01, 0x02, 0xD3, 0x05, 0xF3, 0x92, 0x83, 0xF2, // Fretless
2205 +
	0x61, 0x63, 0x0C, 0x00, 0xA4, 0xFF, 0x30, 0x06, // Synth Bass
2206 +
	0x21, 0x62, 0x0D, 0x00, 0xA1, 0xFF, 0x50, 0x08, // Sweep
2207 +
	0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
2208 +
	0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
2209 +
	0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55  //rhythm 3
2210 +
};
2211 +
2212 +
2213 +
//*********************************************************
2214 +
//  EXPLICIT INSTANTIATION
2215 +
//*********************************************************
2216 +
2217 +
template class opl_registers_base<4>;
2218 +
template class fm_engine_base<opl_registers_base<4>>;
2219 +
2220 +
}
added src/extern/ymfm/src/ymfm_opl.h +902 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#ifndef YMFM_OPL_H
32 +
#define YMFM_OPL_H
33 +
34 +
#pragma once
35 +
36 +
#include "ymfm.h"
37 +
#include "ymfm_adpcm.h"
38 +
#include "ymfm_fm.h"
39 +
#include "ymfm_pcm.h"
40 +
41 +
namespace ymfm
42 +
{
43 +
44 +
//*********************************************************
45 +
//  REGISTER CLASSES
46 +
//*********************************************************
47 +
48 +
// ======================> opl_registers_base
49 +
50 +
//
51 +
// OPL/OPL2/OPL3/OPL4 register map:
52 +
//
53 +
//      System-wide registers:
54 +
//           01 xxxxxxxx Test register
55 +
//              --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable)
56 +
//           02 xxxxxxxx Timer A value (4 * OPN)
57 +
//           03 xxxxxxxx Timer B value
58 +
//           04 x------- RST
59 +
//              -x------ Mask timer A
60 +
//              --x----- Mask timer B
61 +
//              ------x- Load timer B
62 +
//              -------x Load timer A
63 +
//           08 x------- CSM mode [OPL/OPL2 only]
64 +
//              -x------ Note select
65 +
//           BD x------- AM depth
66 +
//              -x------ PM depth
67 +
//              --x----- Rhythm enable
68 +
//              ---x---- Bass drum key on
69 +
//              ----x--- Snare drum key on
70 +
//              -----x-- Tom key on
71 +
//              ------x- Top cymbal key on
72 +
//              -------x High hat key on
73 +
//          101 --xxxxxx Test register 2 [OPL3 only]
74 +
//          104 --x----- Channel 6 4-operator mode [OPL3 only]
75 +
//              ---x---- Channel 5 4-operator mode [OPL3 only]
76 +
//              ----x--- Channel 4 4-operator mode [OPL3 only]
77 +
//              -----x-- Channel 3 4-operator mode [OPL3 only]
78 +
//              ------x- Channel 2 4-operator mode [OPL3 only]
79 +
//              -------x Channel 1 4-operator mode [OPL3 only]
80 +
//          105 -------x New [OPL3 only]
81 +
//              ------x- New2 [OPL4 only]
82 +
//
83 +
//     Per-channel registers (channel in address bits 0-3)
84 +
//     Note that all these apply to address+100 as well on OPL3+
85 +
//        A0-A8 xxxxxxxx F-number (low 8 bits)
86 +
//        B0-B8 --x----- Key on
87 +
//              ---xxx-- Block (octvate, 0-7)
88 +
//              ------xx F-number (high two bits)
89 +
//        C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
90 +
//              -x------ CHC output (to DO0 pin) [OPL3+ only]
91 +
//              --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
92 +
//              ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
93 +
//              ----xxx- Feedback level for operator 1 (0-7)
94 +
//              -------x Operator connection algorithm
95 +
//
96 +
//     Per-operator registers (operator in bits 0-5)
97 +
//     Note that all these apply to address+100 as well on OPL3+
98 +
//        20-35 x------- AM enable
99 +
//              -x------ PM enable (VIB)
100 +
//              --x----- EG type
101 +
//              ---x---- Key scale rate
102 +
//              ----xxxx Multiple value (0-15)
103 +
//        40-55 xx------ Key scale level (0-3)
104 +
//              --xxxxxx Total level (0-63)
105 +
//        60-75 xxxx---- Attack rate (0-15)
106 +
//              ----xxxx Decay rate (0-15)
107 +
//        80-95 xxxx---- Sustain level (0-15)
108 +
//              ----xxxx Release rate (0-15)
109 +
//        E0-F5 ------xx Wave select (0-3) [OPL2 only]
110 +
//              -----xxx Wave select (0-7) [OPL3+ only]
111 +
//
112 +
113 +
template<int Revision>
114 +
class opl_registers_base : public fm_registers_base
115 +
{
116 +
	static constexpr bool IsOpl2 = (Revision == 2);
117 +
	static constexpr bool IsOpl2Plus = (Revision >= 2);
118 +
	static constexpr bool IsOpl3Plus = (Revision >= 3);
119 +
	static constexpr bool IsOpl4Plus = (Revision >= 4);
120 +
121 +
public:
122 +
	// constants
123 +
	static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1;
124 +
	static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9;
125 +
	static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
126 +
	static constexpr uint32_t OPERATORS = CHANNELS * 2;
127 +
	static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
128 +
	static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
129 +
	static constexpr uint32_t REG_MODE = 0x04;
130 +
	static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
131 +
	static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
132 +
	static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
133 +
	static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
134 +
	static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
135 +
	static constexpr uint8_t STATUS_TIMERA = 0x40;
136 +
	static constexpr uint8_t STATUS_TIMERB = 0x20;
137 +
	static constexpr uint8_t STATUS_BUSY = 0;
138 +
	static constexpr uint8_t STATUS_IRQ = 0x80;
139 +
140 +
	// constructor
141 +
	opl_registers_base();
142 +
143 +
	// reset to initial state
144 +
	void reset();
145 +
146 +
	// save/restore
147 +
	void save_restore(ymfm_saved_state &state);
148 +
149 +
	// map channel number to register offset
150 +
	static constexpr uint32_t channel_offset(uint32_t chnum)
151 +
	{
152 +
		assert(chnum < CHANNELS);
153 +
		if (!IsOpl3Plus)
154 +
			return chnum;
155 +
		else
156 +
			return (chnum % 9) + 0x100 * (chnum / 9);
157 +
	}
158 +
159 +
	// map operator number to register offset
160 +
	static constexpr uint32_t operator_offset(uint32_t opnum)
161 +
	{
162 +
		assert(opnum < OPERATORS);
163 +
		if (!IsOpl3Plus)
164 +
			return opnum + 2 * (opnum / 6);
165 +
		else
166 +
			return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
167 +
	}
168 +
169 +
	// return an array of operator indices for each channel
170 +
	struct operator_mapping { uint32_t chan[CHANNELS]; };
171 +
	void operator_map(operator_mapping &dest) const;
172 +
173 +
	// OPL4 apparently can read back FM registers?
174 +
	uint8_t read(uint16_t index) const { return m_regdata[index]; }
175 +
176 +
	// handle writes to the register array
177 +
	bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
178 +
179 +
	// clock the noise and LFO, if present, returning LFO PM value
180 +
	int32_t clock_noise_and_lfo();
181 +
182 +
	// reset the LFO
183 +
	void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
184 +
185 +
	// return the AM offset from LFO for the given channel
186 +
	// on OPL this is just a fixed value
187 +
	uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
188 +
189 +
	// return LFO/noise states
190 +
	uint32_t noise_state() const { return m_noise_lfsr >> 23; }
191 +
192 +
	// caching helpers
193 +
	void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
194 +
195 +
	// compute the phase step, given a PM value
196 +
	uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
197 +
198 +
	// log a key-on event
199 +
	std::string log_keyon(uint32_t choffs, uint32_t opoffs);
200 +
201 +
	// system-wide registers
202 +
	uint32_t test() const                            { return byte(0x01, 0, 8); }
203 +
	uint32_t waveform_enable() const                 { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
204 +
	uint32_t timer_a_value() const                   { return byte(0x02, 0, 8) * 4; } // 8->10 bits
205 +
	uint32_t timer_b_value() const                   { return byte(0x03, 0, 8); }
206 +
	uint32_t status_mask() const                     { return byte(0x04, 0, 8) & 0x78; }
207 +
	uint32_t irq_reset() const                       { return byte(0x04, 7, 1); }
208 +
	uint32_t reset_timer_b() const                   { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
209 +
	uint32_t reset_timer_a() const                   { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
210 +
	uint32_t enable_timer_b() const                  { return 1; }
211 +
	uint32_t enable_timer_a() const                  { return 1; }
212 +
	uint32_t load_timer_b() const                    { return byte(0x04, 1, 1); }
213 +
	uint32_t load_timer_a() const                    { return byte(0x04, 0, 1); }
214 +
	uint32_t csm() const                             { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
215 +
	uint32_t note_select() const                     { return byte(0x08, 6, 1); }
216 +
	uint32_t lfo_am_depth() const                    { return byte(0xbd, 7, 1); }
217 +
	uint32_t lfo_pm_depth() const                    { return byte(0xbd, 6, 1); }
218 +
	uint32_t rhythm_enable() const                   { return byte(0xbd, 5, 1); }
219 +
	uint32_t rhythm_keyon() const                    { return byte(0xbd, 4, 0); }
220 +
	uint32_t newflag() const                         { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
221 +
	uint32_t new2flag() const                        { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
222 +
	uint32_t fourop_enable() const                   { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
223 +
224 +
	// per-channel registers
225 +
	uint32_t ch_block_freq(uint32_t choffs) const    { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
226 +
	uint32_t ch_feedback(uint32_t choffs) const      { return byte(0xc0, 1, 3, choffs); }
227 +
	uint32_t ch_algorithm(uint32_t choffs) const     { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
228 +
	uint32_t ch_output_any(uint32_t choffs) const    { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
229 +
	uint32_t ch_output_0(uint32_t choffs) const      { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
230 +
	uint32_t ch_output_1(uint32_t choffs) const      { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
231 +
	uint32_t ch_output_2(uint32_t choffs) const      { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
232 +
	uint32_t ch_output_3(uint32_t choffs) const      { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
233 +
234 +
	// per-operator registers
235 +
	uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); }
236 +
	uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); }
237 +
	uint32_t op_eg_sustain(uint32_t opoffs) const    { return byte(0x20, 5, 1, opoffs); }
238 +
	uint32_t op_ksr(uint32_t opoffs) const           { return byte(0x20, 4, 1, opoffs); }
239 +
	uint32_t op_multiple(uint32_t opoffs) const      { return byte(0x20, 0, 4, opoffs); }
240 +
	uint32_t op_ksl(uint32_t opoffs) const           { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); }
241 +
	uint32_t op_total_level(uint32_t opoffs) const   { return byte(0x40, 0, 6, opoffs); }
242 +
	uint32_t op_attack_rate(uint32_t opoffs) const   { return byte(0x60, 4, 4, opoffs); }
243 +
	uint32_t op_decay_rate(uint32_t opoffs) const    { return byte(0x60, 0, 4, opoffs); }
244 +
	uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
245 +
	uint32_t op_release_rate(uint32_t opoffs) const  { return byte(0x80, 0, 4, opoffs); }
246 +
	uint32_t op_waveform(uint32_t opoffs) const      { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
247 +
248 +
protected:
249 +
	// return a bitfield extracted from a byte
250 +
	uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
251 +
	{
252 +
		return bitfield(m_regdata[offset + extra_offset], start, count);
253 +
	}
254 +
255 +
	// return a bitfield extracted from a pair of bytes, MSBs listed first
256 +
	uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
257 +
	{
258 +
		return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
259 +
	}
260 +
261 +
	// helper to determine if the this channel is an active rhythm channel
262 +
	bool is_rhythm(uint32_t choffs) const
263 +
	{
264 +
		return rhythm_enable() && (choffs >= 6 && choffs <= 8);
265 +
	}
266 +
267 +
	// internal state
268 +
	uint16_t m_lfo_am_counter;            // LFO AM counter
269 +
	uint16_t m_lfo_pm_counter;            // LFO PM counter
270 +
	uint32_t m_noise_lfsr;                // noise LFSR state
271 +
	uint8_t m_lfo_am;                     // current LFO AM value
272 +
	uint8_t m_regdata[REGISTERS];         // register data
273 +
	uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
274 +
};
275 +
276 +
using opl_registers = opl_registers_base<1>;
277 +
using opl2_registers = opl_registers_base<2>;
278 +
using opl3_registers = opl_registers_base<3>;
279 +
using opl4_registers = opl_registers_base<4>;
280 +
281 +
282 +
283 +
// ======================> opll_registers
284 +
285 +
//
286 +
// OPLL register map:
287 +
//
288 +
//      System-wide registers:
289 +
//           0E --x----- Rhythm enable
290 +
//              ---x---- Bass drum key on
291 +
//              ----x--- Snare drum key on
292 +
//              -----x-- Tom key on
293 +
//              ------x- Top cymbal key on
294 +
//              -------x High hat key on
295 +
//           0F xxxxxxxx Test register
296 +
//
297 +
//     Per-channel registers (channel in address bits 0-3)
298 +
//        10-18 xxxxxxxx F-number (low 8 bits)
299 +
//        20-28 --x----- Sustain on
300 +
//              ---x---- Key on
301 +
//              --- xxx- Block (octvate, 0-7)
302 +
//              -------x F-number (high bit)
303 +
//        30-38 xxxx---- Instrument selection
304 +
//              ----xxxx Volume
305 +
//
306 +
//     User instrument registers (for carrier, modulator operators)
307 +
//        00-01 x------- AM enable
308 +
//              -x------ PM enable (VIB)
309 +
//              --x----- EG type
310 +
//              ---x---- Key scale rate
311 +
//              ----xxxx Multiple value (0-15)
312 +
//           02 xx------ Key scale level (carrier, 0-3)
313 +
//              --xxxxxx Total level (modulator, 0-63)
314 +
//           03 xx------ Key scale level (modulator, 0-3)
315 +
//              ---x---- Rectified wave (carrier)
316 +
//              ----x--- Rectified wave (modulator)
317 +
//              -----xxx Feedback level for operator 1 (0-7)
318 +
//        04-05 xxxx---- Attack rate (0-15)
319 +
//              ----xxxx Decay rate (0-15)
320 +
//        06-07 xxxx---- Sustain level (0-15)
321 +
//              ----xxxx Release rate (0-15)
322 +
//
323 +
//     Internal (fake) registers:
324 +
//        40-48 xxxxxxxx Current instrument base address
325 +
//        4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
326 +
//        70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
327 +
//
328 +
329 +
class opll_registers : public fm_registers_base
330 +
{
331 +
public:
332 +
	static constexpr uint32_t OUTPUTS = 2;
333 +
	static constexpr uint32_t CHANNELS = 9;
334 +
	static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
335 +
	static constexpr uint32_t OPERATORS = CHANNELS * 2;
336 +
	static constexpr uint32_t WAVEFORMS = 2;
337 +
	static constexpr uint32_t REGISTERS = 0x40;
338 +
	static constexpr uint32_t REG_MODE = 0x3f;
339 +
	static constexpr uint32_t DEFAULT_PRESCALE = 4;
340 +
	static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
341 +
	static constexpr uint32_t CSM_TRIGGER_MASK = 0;
342 +
	static constexpr bool EG_HAS_DEPRESS = true;
343 +
	static constexpr bool MODULATOR_DELAY = true;
344 +
	static constexpr uint8_t STATUS_TIMERA = 0;
345 +
	static constexpr uint8_t STATUS_TIMERB = 0;
346 +
	static constexpr uint8_t STATUS_BUSY = 0;
347 +
	static constexpr uint8_t STATUS_IRQ = 0;
348 +
349 +
	// OPLL-specific constants
350 +
	static constexpr uint32_t INSTDATA_SIZE = 0x90;
351 +
352 +
	// constructor
353 +
	opll_registers();
354 +
355 +
	// reset to initial state
356 +
	void reset();
357 +
358 +
	// save/restore
359 +
	void save_restore(ymfm_saved_state &state);
360 +
361 +
	// map channel number to register offset
362 +
	static constexpr uint32_t channel_offset(uint32_t chnum)
363 +
	{
364 +
		assert(chnum < CHANNELS);
365 +
		return chnum;
366 +
	}
367 +
368 +
	// map operator number to register offset
369 +
	static constexpr uint32_t operator_offset(uint32_t opnum)
370 +
	{
371 +
		assert(opnum < OPERATORS);
372 +
		return opnum;
373 +
	}
374 +
375 +
	// return an array of operator indices for each channel
376 +
	struct operator_mapping { uint32_t chan[CHANNELS]; };
377 +
	void operator_map(operator_mapping &dest) const;
378 +
379 +
	// read a register value
380 +
	uint8_t read(uint16_t index) const { return m_regdata[index]; }
381 +
382 +
	// handle writes to the register array
383 +
	bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
384 +
385 +
	// clock the noise and LFO, if present, returning LFO PM value
386 +
	int32_t clock_noise_and_lfo();
387 +
388 +
	// reset the LFO
389 +
	void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
390 +
391 +
	// return the AM offset from LFO for the given channel
392 +
	// on OPL this is just a fixed value
393 +
	uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
394 +
395 +
	// return LFO/noise states
396 +
	uint32_t noise_state() const { return m_noise_lfsr >> 23; }
397 +
398 +
	// caching helpers
399 +
	void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
400 +
401 +
	// compute the phase step, given a PM value
402 +
	uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
403 +
404 +
	// log a key-on event
405 +
	std::string log_keyon(uint32_t choffs, uint32_t opoffs);
406 +
407 +
	// set the instrument data
408 +
	void set_instrument_data(uint8_t const *data)
409 +
	{
410 +
		std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]);
411 +
	}
412 +
413 +
	// system-wide registers
414 +
	uint32_t rhythm_enable() const                   { return byte(0x0e, 5, 1); }
415 +
	uint32_t rhythm_keyon() const                    { return byte(0x0e, 4, 0); }
416 +
	uint32_t test() const                            { return byte(0x0f, 0, 8); }
417 +
	uint32_t waveform_enable() const                 { return 1; }
418 +
	uint32_t timer_a_value() const                   { return 0; }
419 +
	uint32_t timer_b_value() const                   { return 0; }
420 +
	uint32_t status_mask() const                     { return 0; }
421 +
	uint32_t irq_reset() const                       { return 0; }
422 +
	uint32_t reset_timer_b() const                   { return 0; }
423 +
	uint32_t reset_timer_a() const                   { return 0; }
424 +
	uint32_t enable_timer_b() const                  { return 0; }
425 +
	uint32_t enable_timer_a() const                  { return 0; }
426 +
	uint32_t load_timer_b() const                    { return 0; }
427 +
	uint32_t load_timer_a() const                    { return 0; }
428 +
	uint32_t csm() const                             { return 0; }
429 +
430 +
	// per-channel registers
431 +
	uint32_t ch_block_freq(uint32_t choffs) const    { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
432 +
	uint32_t ch_sustain(uint32_t choffs) const       { return byte(0x20, 5, 1, choffs); }
433 +
	uint32_t ch_total_level(uint32_t choffs) const   { return instchbyte(0x02, 0, 6, choffs); }
434 +
	uint32_t ch_feedback(uint32_t choffs) const      { return instchbyte(0x03, 0, 3, choffs); }
435 +
	uint32_t ch_algorithm(uint32_t choffs) const     { return 0; }
436 +
	uint32_t ch_instrument(uint32_t choffs) const    { return byte(0x30, 4, 4, choffs); }
437 +
	uint32_t ch_output_any(uint32_t choffs) const    { return 1; }
438 +
	uint32_t ch_output_0(uint32_t choffs) const      { return !is_rhythm(choffs); }
439 +
	uint32_t ch_output_1(uint32_t choffs) const      { return is_rhythm(choffs); }
440 +
	uint32_t ch_output_2(uint32_t choffs) const      { return 0; }
441 +
	uint32_t ch_output_3(uint32_t choffs) const      { return 0; }
442 +
443 +
	// per-operator registers
444 +
	uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
445 +
	uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
446 +
	uint32_t op_eg_sustain(uint32_t opoffs) const    { return instopbyte(0x00, 5, 1, opoffs); }
447 +
	uint32_t op_ksr(uint32_t opoffs) const           { return instopbyte(0x00, 4, 1, opoffs); }
448 +
	uint32_t op_multiple(uint32_t opoffs) const      { return instopbyte(0x00, 0, 4, opoffs); }
449 +
	uint32_t op_ksl(uint32_t opoffs) const           { return instopbyte(0x02, 6, 2, opoffs); }
450 +
	uint32_t op_waveform(uint32_t opoffs) const      { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); }
451 +
	uint32_t op_attack_rate(uint32_t opoffs) const   { return instopbyte(0x04, 4, 4, opoffs); }
452 +
	uint32_t op_decay_rate(uint32_t opoffs) const    { return instopbyte(0x04, 0, 4, opoffs); }
453 +
	uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
454 +
	uint32_t op_release_rate(uint32_t opoffs) const  { return instopbyte(0x06, 0, 4, opoffs); }
455 +
	uint32_t op_volume(uint32_t opoffs) const        { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); }
456 +
457 +
private:
458 +
	// return a bitfield extracted from a byte
459 +
	uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
460 +
	{
461 +
		return bitfield(m_regdata[offset + extra_offset], start, count);
462 +
	}
463 +
464 +
	// return a bitfield extracted from a pair of bytes, MSBs listed first
465 +
	uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
466 +
	{
467 +
		return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
468 +
	}
469 +
470 +
	// helpers to read from instrument channel/operator data
471 +
	uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); }
472 +
	uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); }
473 +
474 +
	// helper to determine if the this channel is an active rhythm channel
475 +
	bool is_rhythm(uint32_t choffs) const
476 +
	{
477 +
		return rhythm_enable() && choffs >= 6;
478 +
	}
479 +
480 +
	// internal state
481 +
	uint16_t m_lfo_am_counter;            // LFO AM counter
482 +
	uint16_t m_lfo_pm_counter;            // LFO PM counter
483 +
	uint32_t m_noise_lfsr;                // noise LFSR state
484 +
	uint8_t m_lfo_am;                     // current LFO AM value
485 +
	uint8_t const *m_chinst[CHANNELS];    // pointer to instrument data for each channel
486 +
	uint8_t const *m_opinst[OPERATORS];   // pointer to instrument data for each operator
487 +
	uint8_t m_regdata[REGISTERS];         // register data
488 +
	uint8_t m_instdata[INSTDATA_SIZE];    // instrument data
489 +
	uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
490 +
};
491 +
492 +
493 +
494 +
//*********************************************************
495 +
//  OPL IMPLEMENTATION CLASSES
496 +
//*********************************************************
497 +
498 +
// ======================> ym3526
499 +
500 +
class ym3526
501 +
{
502 +
public:
503 +
	using fm_engine = fm_engine_base<opl_registers>;
504 +
	using output_data = fm_engine::output_data;
505 +
	static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
506 +
507 +
	// constructor
508 +
	ym3526(ymfm_interface &intf);
509 +
510 +
	// reset
511 +
	void reset();
512 +
513 +
	// save/restore
514 +
	void save_restore(ymfm_saved_state &state);
515 +
516 +
	// pass-through helpers
517 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
518 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
519 +
520 +
	// read access
521 +
	uint8_t read_status();
522 +
	uint8_t read(uint32_t offset);
523 +
524 +
	// write access
525 +
	void write_address(uint8_t data);
526 +
	void write_data(uint8_t data);
527 +
	void write(uint32_t offset, uint8_t data);
528 +
529 +
	// generate samples of sound
530 +
	void generate(output_data *output, uint32_t numsamples = 1);
531 +
protected:
532 +
	// internal state
533 +
	uint8_t m_address;               // address register
534 +
	fm_engine m_fm;                  // core FM engine
535 +
};
536 +
537 +
538 +
// ======================> y8950
539 +
540 +
class y8950
541 +
{
542 +
public:
543 +
	using fm_engine = fm_engine_base<opl_registers>;
544 +
	using output_data = fm_engine::output_data;
545 +
	static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
546 +
547 +
	static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01;
548 +
	static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
549 +
	static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10;
550 +
	static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB;
551 +
552 +
	// constructor
553 +
	y8950(ymfm_interface &intf);
554 +
555 +
	// reset
556 +
	void reset();
557 +
558 +
	// save/restore
559 +
	void save_restore(ymfm_saved_state &state);
560 +
561 +
	// pass-through helpers
562 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
563 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
564 +
565 +
	// read access
566 +
	uint8_t read_status();
567 +
	uint8_t read_data();
568 +
	uint8_t read(uint32_t offset);
569 +
570 +
	// write access
571 +
	void write_address(uint8_t data);
572 +
	void write_data(uint8_t data);
573 +
	void write(uint32_t offset, uint8_t data);
574 +
575 +
	// generate samples of sound
576 +
	void generate(output_data *output, uint32_t numsamples = 1);
577 +
578 +
protected:
579 +
	// internal state
580 +
	uint8_t m_address;               // address register
581 +
	uint8_t m_io_ddr;                // data direction register for I/O
582 +
	fm_engine m_fm;                  // core FM engine
583 +
	adpcm_b_engine m_adpcm_b;        // ADPCM-B engine
584 +
};
585 +
586 +
587 +
588 +
//*********************************************************
589 +
//  OPL2 IMPLEMENTATION CLASSES
590 +
//*********************************************************
591 +
592 +
// ======================> ym3812
593 +
594 +
class ym3812
595 +
{
596 +
public:
597 +
	using fm_engine = fm_engine_base<opl2_registers>;
598 +
	using output_data = fm_engine::output_data;
599 +
	static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
600 +
601 +
	// constructor
602 +
	ym3812(ymfm_interface &intf);
603 +
604 +
	// reset
605 +
	void reset();
606 +
607 +
	// save/restore
608 +
	void save_restore(ymfm_saved_state &state);
609 +
610 +
	// pass-through helpers
611 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
612 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
613 +
614 +
	// read access
615 +
	uint8_t read_status();
616 +
	uint8_t read(uint32_t offset);
617 +
618 +
	// write access
619 +
	void write_address(uint8_t data);
620 +
	void write_data(uint8_t data);
621 +
	void write(uint32_t offset, uint8_t data);
622 +
623 +
	// generate samples of sound
624 +
	void generate(output_data *output, uint32_t numsamples = 1);
625 +
626 +
protected:
627 +
	// internal state
628 +
	uint8_t m_address;               // address register
629 +
	fm_engine m_fm;                  // core FM engine
630 +
};
631 +
632 +
633 +
634 +
//*********************************************************
635 +
//  OPL3 IMPLEMENTATION CLASSES
636 +
//*********************************************************
637 +
638 +
// ======================> ymf262
639 +
640 +
class ymf262
641 +
{
642 +
public:
643 +
	using fm_engine = fm_engine_base<opl3_registers>;
644 +
	using output_data = fm_engine::output_data;
645 +
	static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
646 +
647 +
	// constructor
648 +
	ymf262(ymfm_interface &intf);
649 +
650 +
	// reset
651 +
	void reset();
652 +
653 +
	// save/restore
654 +
	void save_restore(ymfm_saved_state &state);
655 +
656 +
	// pass-through helpers
657 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
658 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
659 +
660 +
	// read access
661 +
	uint8_t read_status();
662 +
	uint8_t read(uint32_t offset);
663 +
664 +
	// write access
665 +
	void write_address(uint8_t data);
666 +
	void write_data(uint8_t data);
667 +
	void write_address_hi(uint8_t data);
668 +
	void write(uint32_t offset, uint8_t data);
669 +
670 +
	// generate samples of sound
671 +
	void generate(output_data *output, uint32_t numsamples = 1);
672 +
673 +
protected:
674 +
	// internal state
675 +
	uint16_t m_address;              // address register
676 +
	fm_engine m_fm;                  // core FM engine
677 +
};
678 +
679 +
680 +
// ======================> ymf289b
681 +
682 +
class ymf289b
683 +
{
684 +
	static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
685 +
686 +
public:
687 +
	using fm_engine = fm_engine_base<opl3_registers>;
688 +
	using output_data = fm_engine::output_data;
689 +
	static constexpr uint32_t OUTPUTS = 2;
690 +
691 +
	// constructor
692 +
	ymf289b(ymfm_interface &intf);
693 +
694 +
	// reset
695 +
	void reset();
696 +
697 +
	// save/restore
698 +
	void save_restore(ymfm_saved_state &state);
699 +
700 +
	// pass-through helpers
701 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
702 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
703 +
704 +
	// read access
705 +
	uint8_t read_status();
706 +
	uint8_t read_data();
707 +
	uint8_t read(uint32_t offset);
708 +
709 +
	// write access
710 +
	void write_address(uint8_t data);
711 +
	void write_data(uint8_t data);
712 +
	void write_address_hi(uint8_t data);
713 +
	void write(uint32_t offset, uint8_t data);
714 +
715 +
	// generate samples of sound
716 +
	void generate(output_data *output, uint32_t numsamples = 1);
717 +
718 +
protected:
719 +
	// internal helpers
720 +
	bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
721 +
722 +
	// internal state
723 +
	uint16_t m_address;              // address register
724 +
	fm_engine m_fm;                  // core FM engine
725 +
};
726 +
727 +
728 +
729 +
//*********************************************************
730 +
//  OPL4 IMPLEMENTATION CLASSES
731 +
//*********************************************************
732 +
733 +
// ======================> ymf278b
734 +
735 +
class ymf278b
736 +
{
737 +
	// Using the nominal datasheet frequency of 33.868MHz, the output of the
738 +
	// chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
739 +
	// internally at clock/(19*36), or 49.515kHz, so the FM output needs to
740 +
	// be downsampled. We treat this as needing to clock the FM engine an
741 +
	// extra tick every few samples. The exact ratio is 768/(19*36) or
742 +
	// 768/684 = 192/171. So if we always clock the FM once, we'll have
743 +
	// 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
744 +
	// it gets above 171, we tick an extra time.
745 +
	static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
746 +
	static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
747 +
748 +
public:
749 +
	using fm_engine = fm_engine_base<opl4_registers>;
750 +
	static constexpr uint32_t OUTPUTS = 6;
751 +
	using output_data = ymfm_output<OUTPUTS>;
752 +
753 +
	static constexpr uint8_t STATUS_BUSY = 0x01;
754 +
	static constexpr uint8_t STATUS_LD = 0x02;
755 +
756 +
	// constructor
757 +
	ymf278b(ymfm_interface &intf);
758 +
759 +
	// reset
760 +
	void reset();
761 +
762 +
	// save/restore
763 +
	void save_restore(ymfm_saved_state &state);
764 +
765 +
	// pass-through helpers
766 +
	uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; }
767 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
768 +
769 +
	// read access
770 +
	uint8_t read_status();
771 +
	uint8_t read_data_pcm();
772 +
	uint8_t read(uint32_t offset);
773 +
774 +
	// write access
775 +
	void write_address(uint8_t data);
776 +
	void write_data(uint8_t data);
777 +
	void write_address_hi(uint8_t data);
778 +
	void write_address_pcm(uint8_t data);
779 +
	void write_data_pcm(uint8_t data);
780 +
	void write(uint32_t offset, uint8_t data);
781 +
782 +
	// generate samples of sound
783 +
	void generate(output_data *output, uint32_t numsamples = 1);
784 +
785 +
protected:
786 +
	// internal state
787 +
	uint16_t m_address;              // address register
788 +
	uint32_t m_fm_pos;               // FM resampling position
789 +
	uint32_t m_load_remaining;       // how many more samples until LD flag clears
790 +
	bool m_next_status_id;           // flag to track which status ID to return
791 +
	fm_engine m_fm;                  // core FM engine
792 +
	pcm_engine m_pcm;                // core PCM engine
793 +
};
794 +
795 +
796 +
797 +
//*********************************************************
798 +
//  OPLL IMPLEMENTATION CLASSES
799 +
//*********************************************************
800 +
801 +
// ======================> opll_base
802 +
803 +
class opll_base
804 +
{
805 +
public:
806 +
	using fm_engine = fm_engine_base<opll_registers>;
807 +
	using output_data = fm_engine::output_data;
808 +
	static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
809 +
810 +
	// constructor
811 +
	opll_base(ymfm_interface &intf, uint8_t const *data);
812 +
813 +
	// configuration
814 +
	void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); }
815 +
816 +
	// reset
817 +
	void reset();
818 +
819 +
	// save/restore
820 +
	void save_restore(ymfm_saved_state &state);
821 +
822 +
	// pass-through helpers
823 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
824 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
825 +
826 +
	// read access -- doesn't really have any, but provide these for consistency
827 +
	uint8_t read_status() { return 0x00; }
828 +
	uint8_t read(uint32_t offset) { return 0x00; }
829 +
830 +
	// write access
831 +
	void write_address(uint8_t data);
832 +
	void write_data(uint8_t data);
833 +
	void write(uint32_t offset, uint8_t data);
834 +
835 +
	// generate samples of sound
836 +
	void generate(output_data *output, uint32_t numsamples = 1);
837 +
838 +
protected:
839 +
	// internal state
840 +
	uint8_t m_address;               // address register
841 +
	fm_engine m_fm;                  // core FM engine
842 +
};
843 +
844 +
845 +
// ======================> ym2413
846 +
847 +
class ym2413 : public opll_base
848 +
{
849 +
public:
850 +
	// constructor
851 +
	ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
852 +
853 +
private:
854 +
	// internal state
855 +
	static uint8_t const s_default_instruments[];
856 +
};
857 +
858 +
859 +
// ======================> ym2413
860 +
861 +
class ym2423 : public opll_base
862 +
{
863 +
public:
864 +
	// constructor
865 +
	ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
866 +
867 +
private:
868 +
	// internal state
869 +
	static uint8_t const s_default_instruments[];
870 +
};
871 +
872 +
873 +
// ======================> ymf281
874 +
875 +
class ymf281 : public opll_base
876 +
{
877 +
public:
878 +
	// constructor
879 +
	ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
880 +
881 +
private:
882 +
	// internal state
883 +
	static uint8_t const s_default_instruments[];
884 +
};
885 +
886 +
887 +
// ======================> ds1001
888 +
889 +
class ds1001 : public opll_base
890 +
{
891 +
public:
892 +
	// constructor
893 +
	ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
894 +
895 +
private:
896 +
	// internal state
897 +
	static uint8_t const s_default_instruments[];
898 +
};
899 +
900 +
}
901 +
902 +
#endif // YMFM_OPL_H
added src/extern/ymfm/src/ymfm_opm.cpp +539 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#include "ymfm_opm.h"
32 +
#include "ymfm_fm.ipp"
33 +
34 +
namespace ymfm
35 +
{
36 +
37 +
//*********************************************************
38 +
//  OPM REGISTERS
39 +
//*********************************************************
40 +
41 +
//-------------------------------------------------
42 +
//  opm_registers - constructor
43 +
//-------------------------------------------------
44 +
45 +
opm_registers::opm_registers() :
46 +
	m_lfo_counter(0),
47 +
	m_noise_lfsr(1),
48 +
	m_noise_counter(0),
49 +
	m_noise_state(0),
50 +
	m_noise_lfo(0),
51 +
	m_lfo_am(0)
52 +
{
53 +
	// create the waveforms
54 +
	for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
55 +
		m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
56 +
57 +
	// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
58 +
	// waveforms are adjusted to match the pictures in the application manual
59 +
	for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
60 +
	{
61 +
		// waveform 0 is a sawtooth
62 +
		uint8_t am = index ^ 0xff;
63 +
		uint8_t pm = index;
64 +
		m_lfo_waveform[0][index] = am | (pm << 8);
65 +
66 +
		// waveform 1 is a square wave
67 +
		am = bitfield(index, 7) ? 0 : 0xff;
68 +
		pm = am ^ 0x80;
69 +
		m_lfo_waveform[1][index] = am | (pm << 8);
70 +
71 +
		// waveform 2 is a triangle wave
72 +
		am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
73 +
		pm = bitfield(index, 6) ? am : ~am;
74 +
		m_lfo_waveform[2][index] = am | (pm << 8);
75 +
76 +
		// waveform 3 is noise; it is filled in dynamically
77 +
		m_lfo_waveform[3][index] = 0;
78 +
	}
79 +
}
80 +
81 +
82 +
//-------------------------------------------------
83 +
//  reset - reset to initial state
84 +
//-------------------------------------------------
85 +
86 +
void opm_registers::reset()
87 +
{
88 +
	std::fill_n(&m_regdata[0], REGISTERS, 0);
89 +
90 +
	// enable output on both channels by default
91 +
	m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0;
92 +
	m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0;
93 +
}
94 +
95 +
96 +
//-------------------------------------------------
97 +
//  save_restore - save or restore the data
98 +
//-------------------------------------------------
99 +
100 +
void opm_registers::save_restore(ymfm_saved_state &state)
101 +
{
102 +
	state.save_restore(m_lfo_counter);
103 +
	state.save_restore(m_lfo_am);
104 +
	state.save_restore(m_noise_lfsr);
105 +
	state.save_restore(m_noise_counter);
106 +
	state.save_restore(m_noise_state);
107 +
	state.save_restore(m_noise_lfo);
108 +
	state.save_restore(m_regdata);
109 +
}
110 +
111 +
112 +
//-------------------------------------------------
113 +
//  operator_map - return an array of operator
114 +
//  indices for each channel; for OPM this is fixed
115 +
//-------------------------------------------------
116 +
117 +
void opm_registers::operator_map(operator_mapping &dest) const
118 +
{
119 +
	// Note that the channel index order is 0,2,1,3, so we bitswap the index.
120 +
	//
121 +
	// This is because the order in the map is:
122 +
	//    carrier 1, carrier 2, modulator 1, modulator 2
123 +
	//
124 +
	// But when wiring up the connections, the more natural order is:
125 +
	//    carrier 1, modulator 1, carrier 2, modulator 2
126 +
	static const operator_mapping s_fixed_map =
127 +
	{ {
128 +
		operator_list(  0, 16,  8, 24 ),  // Channel 0 operators
129 +
		operator_list(  1, 17,  9, 25 ),  // Channel 1 operators
130 +
		operator_list(  2, 18, 10, 26 ),  // Channel 2 operators
131 +
		operator_list(  3, 19, 11, 27 ),  // Channel 3 operators
132 +
		operator_list(  4, 20, 12, 28 ),  // Channel 4 operators
133 +
		operator_list(  5, 21, 13, 29 ),  // Channel 5 operators
134 +
		operator_list(  6, 22, 14, 30 ),  // Channel 6 operators
135 +
		operator_list(  7, 23, 15, 31 ),  // Channel 7 operators
136 +
	} };
137 +
	dest = s_fixed_map;
138 +
}
139 +
140 +
141 +
//-------------------------------------------------
142 +
//  write - handle writes to the register array
143 +
//-------------------------------------------------
144 +
145 +
bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
146 +
{
147 +
	assert(index < REGISTERS);
148 +
149 +
	// LFO AM/PM depth are written to the same register (0x19);
150 +
	// redirect the PM depth to an unused neighbor (0x1a)
151 +
	if (index == 0x19)
152 +
		m_regdata[index + bitfield(data, 7)] = data;
153 +
	else if (index != 0x1a)
154 +
		m_regdata[index] = data;
155 +
156 +
	// handle writes to the key on index
157 +
	if (index == 0x08)
158 +
	{
159 +
		channel = bitfield(data, 0, 3);
160 +
		opmask = bitfield(data, 3, 4);
161 +
		return true;
162 +
	}
163 +
	return false;
164 +
}
165 +
166 +
167 +
//-------------------------------------------------
168 +
//  clock_noise_and_lfo - clock the noise and LFO,
169 +
//  handling clock division, depth, and waveform
170 +
//  computations
171 +
//-------------------------------------------------
172 +
173 +
int32_t opm_registers::clock_noise_and_lfo()
174 +
{
175 +
	// base noise frequency is measured at 2x 1/2 FM frequency; this
176 +
	// means each tick counts as two steps against the noise counter
177 +
	uint32_t freq = noise_frequency();
178 +
	for (int rep = 0; rep < 2; rep++)
179 +
	{
180 +
		// evidence seems to suggest the LFSR is clocked continually and just
181 +
		// sampled at the noise frequency for output purposes; note that the
182 +
		// low 8 bits are the most recent 8 bits of history while bits 8-24
183 +
		// contain the 17 bit LFSR state
184 +
		m_noise_lfsr <<= 1;
185 +
		m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
186 +
187 +
		// compare against the frequency and latch when we exceed it
188 +
		if (m_noise_counter++ >= freq)
189 +
		{
190 +
			m_noise_counter = 0;
191 +
			m_noise_state = bitfield(m_noise_lfsr, 17);
192 +
		}
193 +
	}
194 +
195 +
	// treat the rate as a 4.4 floating-point step value with implied
196 +
	// leading 1; this matches exactly the frequencies in the application
197 +
	// manual, though it might not be implemented exactly this way on chip
198 +
	uint32_t rate = lfo_rate();
199 +
	m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4);
200 +
201 +
	// bit 1 of the test register is officially undocumented but has been
202 +
	// discovered to hold the LFO in reset while active
203 +
	if (lfo_reset())
204 +
		m_lfo_counter = 0;
205 +
206 +
	// now pull out the non-fractional LFO value
207 +
	uint32_t lfo = bitfield(m_lfo_counter, 22, 8);
208 +
209 +
	// fill in the noise entry 1 ahead of our current position; this
210 +
	// ensures the current value remains stable for a full LFO clock
211 +
	// and effectively latches the running value when the LFO advances
212 +
	uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
213 +
	m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
214 +
215 +
	// fetch the AM/PM values based on the waveform; AM is unsigned and
216 +
	// encoded in the low 8 bits, while PM signed and encoded in the upper
217 +
	// 8 bits
218 +
	int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo];
219 +
220 +
	// apply depth to the AM value and store for later
221 +
	m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7;
222 +
223 +
	// apply depth to the PM value and return it
224 +
	return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7;
225 +
}
226 +
227 +
228 +
//-------------------------------------------------
229 +
//  lfo_am_offset - return the AM offset from LFO
230 +
//  for the given channel
231 +
//-------------------------------------------------
232 +
233 +
uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const
234 +
{
235 +
	// OPM maps AM quite differently from OPN
236 +
237 +
	// shift value for AM sensitivity is [*, 0, 1, 2],
238 +
	// mapping to values of [0, 23.9, 47.8, and 95.6dB]
239 +
	uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
240 +
	if (am_sensitivity == 0)
241 +
		return 0;
242 +
243 +
	// QUESTION: see OPN note below for the dB range mapping; it applies
244 +
	// here as well
245 +
246 +
	// raw LFO AM value on OPM is 0-FF, which is already a factor of 2
247 +
	// larger than the OPN below, putting our staring point at 2x theirs;
248 +
	// this works out since our minimum is 2x their maximum
249 +
	return m_lfo_am << (am_sensitivity - 1);
250 +
}
251 +
252 +
253 +
//-------------------------------------------------
254 +
//  cache_operator_data - fill the operator cache
255 +
//  with prefetched data
256 +
//-------------------------------------------------
257 +
258 +
void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
259 +
{
260 +
	// set up the easy stuff
261 +
	cache.waveform = &m_waveform[0][0];
262 +
263 +
	// get frequency from the channel
264 +
	uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
265 +
266 +
	// compute the keycode: block_freq is:
267 +
	//
268 +
	//     BBBCCCCFFFFFF
269 +
	//     ^^^^^
270 +
	//
271 +
	// the 5-bit keycode is just the top 5 bits (block + top 2 bits
272 +
	// of the key code)
273 +
	uint32_t keycode = bitfield(block_freq, 8, 5);
274 +
275 +
	// detune adjustment
276 +
	cache.detune = detune_adjustment(op_detune(opoffs), keycode);
277 +
278 +
	// multiple value, as an x.1 value (0 means 0.5)
279 +
	cache.multiple = op_multiple(opoffs) * 2;
280 +
	if (cache.multiple == 0)
281 +
		cache.multiple = 1;
282 +
283 +
	// phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
284 +
	// block_freq, detune, and multiple, so compute it after we've done those
285 +
	if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0)
286 +
		cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
287 +
	else
288 +
		cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
289 +
290 +
	// total level, scaled by 8
291 +
	cache.total_level = op_total_level(opoffs) << 3;
292 +
293 +
	// 4-bit sustain level, but 15 means 31 so effectively 5 bits
294 +
	cache.eg_sustain = op_sustain_level(opoffs);
295 +
	cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
296 +
	cache.eg_sustain <<= 5;
297 +
298 +
	// determine KSR adjustment for enevlope rates
299 +
	uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
300 +
	cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
301 +
	cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
302 +
	cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
303 +
	cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
304 +
}
305 +
306 +
307 +
//-------------------------------------------------
308 +
//  compute_phase_step - compute the phase step
309 +
//-------------------------------------------------
310 +
311 +
uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
312 +
{
313 +
	// OPM logic is rather unique here, due to extra detune
314 +
	// and the use of key codes (not to be confused with keycode)
315 +
316 +
	// start with coarse detune delta; table uses cents value from
317 +
	// manual, converted into 1/64ths
318 +
	static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
319 +
	int32_t delta = s_detune2_delta[op_detune2(opoffs)];
320 +
321 +
	// add in the PM delta
322 +
	uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
323 +
	if (pm_sensitivity != 0)
324 +
	{
325 +
		// raw PM value is -127..128 which is +/- 200 cents
326 +
		// manual gives these magnitudes in cents:
327 +
		//    0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
328 +
		// this roughly corresponds to shifting the 200-cent value:
329 +
		//    0  >> 5,  >> 4,  >> 3,  >> 2,  >> 1,   << 1,   << 2
330 +
		if (pm_sensitivity < 6)
331 +
			delta += lfo_raw_pm >> (6 - pm_sensitivity);
332 +
		else
333 +
			delta += uint32_t(lfo_raw_pm) << (pm_sensitivity - 5);
334 +
	}
335 +
336 +
	// apply delta and convert to a frequency number
337 +
	uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
338 +
339 +
	// apply detune based on the keycode
340 +
	phase_step += cache.detune;
341 +
342 +
	// apply frequency multiplier (which is cached as an x.1 value)
343 +
	return (phase_step * cache.multiple) >> 1;
344 +
}
345 +
346 +
347 +
//-------------------------------------------------
348 +
//  log_keyon - log a key-on event
349 +
//-------------------------------------------------
350 +
351 +
std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
352 +
{
353 +
	uint32_t chnum = choffs;
354 +
	uint32_t opnum = opoffs;
355 +
356 +
	char buffer[256];
357 +
	int end = 0;
358 +
359 +
	end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
360 +
		chnum, opnum,
361 +
		ch_block_freq(choffs),
362 +
		op_detune2(opoffs),
363 +
		op_detune(opoffs),
364 +
		ch_feedback(choffs),
365 +
		ch_algorithm(choffs),
366 +
		op_multiple(opoffs),
367 +
		op_total_level(opoffs),
368 +
		op_ksr(opoffs),
369 +
		op_attack_rate(opoffs),
370 +
		op_decay_rate(opoffs),
371 +
		op_sustain_rate(opoffs),
372 +
		op_release_rate(opoffs),
373 +
		op_sustain_level(opoffs),
374 +
		ch_output_0(choffs) ? 'L' : '-',
375 +
		ch_output_1(choffs) ? 'R' : '-');
376 +
377 +
	bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
378 +
	if (am)
379 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
380 +
	bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
381 +
	if (pm)
382 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
383 +
	if (am || pm)
384 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
385 +
	if (noise_enable() && opoffs == 31)
386 +
		end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
387 +
388 +
	return buffer;
389 +
}
390 +
391 +
392 +
393 +
//*********************************************************
394 +
//  YM2151
395 +
//*********************************************************
396 +
397 +
//-------------------------------------------------
398 +
//  ym2151 - constructor
399 +
//-------------------------------------------------
400 +
401 +
ym2151::ym2151(ymfm_interface &intf, opm_variant variant) :
402 +
	m_variant(variant),
403 +
	m_address(0),
404 +
	m_fm(intf)
405 +
{
406 +
}
407 +
408 +
409 +
//-------------------------------------------------
410 +
//  reset - reset the system
411 +
//-------------------------------------------------
412 +
413 +
void ym2151::reset()
414 +
{
415 +
	// reset the engines
416 +
	m_fm.reset();
417 +
}
418 +
419 +
420 +
//-------------------------------------------------
421 +
//  save_restore - save or restore the data
422 +
//-------------------------------------------------
423 +
424 +
void ym2151::save_restore(ymfm_saved_state &state)
425 +
{
426 +
	m_fm.save_restore(state);
427 +
	state.save_restore(m_address);
428 +
}
429 +
430 +
431 +
//-------------------------------------------------
432 +
//  read_status - read the status register
433 +
//-------------------------------------------------
434 +
435 +
uint8_t ym2151::read_status()
436 +
{
437 +
	uint8_t result = m_fm.status();
438 +
	if (m_fm.intf().ymfm_is_busy())
439 +
		result |= fm_engine::STATUS_BUSY;
440 +
	return result;
441 +
}
442 +
443 +
444 +
//-------------------------------------------------
445 +
//  read - handle a read from the device
446 +
//-------------------------------------------------
447 +
448 +
uint8_t ym2151::read(uint32_t offset)
449 +
{
450 +
	uint8_t result = 0xff;
451 +
	switch (offset & 1)
452 +
	{
453 +
		case 0: // data port (unused)
454 +
			debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3);
455 +
			break;
456 +
457 +
		case 1: // status port, YM2203 compatible
458 +
			result = read_status();
459 +
			break;
460 +
	}
461 +
	return result;
462 +
}
463 +
464 +
465 +
//-------------------------------------------------
466 +
//  write_address - handle a write to the address
467 +
//  register
468 +
//-------------------------------------------------
469 +
470 +
void ym2151::write_address(uint8_t data)
471 +
{
472 +
	// just set the address
473 +
	m_address = data;
474 +
}
475 +
476 +
477 +
//-------------------------------------------------
478 +
//  write - handle a write to the register
479 +
//  interface
480 +
//-------------------------------------------------
481 +
482 +
void ym2151::write_data(uint8_t data)
483 +
{
484 +
	// write the FM register
485 +
	m_fm.write(m_address, data);
486 +
487 +
	// special cases
488 +
	if (m_address == 0x1b)
489 +
	{
490 +
		// writes to register 0x1B send the upper 2 bits to the output lines
491 +
		m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
492 +
	}
493 +
494 +
	// mark busy for a bit
495 +
	m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
496 +
}
497 +
498 +
499 +
//-------------------------------------------------
500 +
//  write - handle a write to the register
501 +
//  interface
502 +
//-------------------------------------------------
503 +
504 +
void ym2151::write(uint32_t offset, uint8_t data)
505 +
{
506 +
	switch (offset & 1)
507 +
	{
508 +
		case 0: // address port
509 +
			write_address(data);
510 +
			break;
511 +
512 +
		case 1: // data port
513 +
			write_data(data);
514 +
			break;
515 +
	}
516 +
}
517 +
518 +
519 +
//-------------------------------------------------
520 +
//  generate - generate one sample of sound
521 +
//-------------------------------------------------
522 +
523 +
void ym2151::generate(output_data *output, uint32_t numsamples)
524 +
{
525 +
	for (uint32_t samp = 0; samp < numsamples; samp++, output++)
526 +
	{
527 +
		// clock the system
528 +
		m_fm.clock(fm_engine::ALL_CHANNELS);
529 +
530 +
		// update the FM content; OPM is full 14-bit with no intermediate clipping
531 +
		m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
532 +
533 +
		// YM2151 uses an external DAC (YM3012) with mantissa/exponent format
534 +
		// convert to 10.3 floating point value and back to simulate truncation
535 +
		output->roundtrip_fp();
536 +
	}
537 +
}
538 +
539 +
}
added src/extern/ymfm/src/ymfm_opm.h +322 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#ifndef YMFM_OPM_H
32 +
#define YMFM_OPM_H
33 +
34 +
#pragma once
35 +
36 +
#include "ymfm.h"
37 +
#include "ymfm_fm.h"
38 +
39 +
namespace ymfm
40 +
{
41 +
42 +
//*********************************************************
43 +
//  REGISTER CLASSES
44 +
//*********************************************************
45 +
46 +
// ======================> opm_registers
47 +
48 +
//
49 +
// OPM register map:
50 +
//
51 +
//      System-wide registers:
52 +
//           01 xxxxxx-x Test register
53 +
//              ------x- LFO reset
54 +
//           08 -x------ Key on/off operator 4
55 +
//              --x----- Key on/off operator 3
56 +
//              ---x---- Key on/off operator 2
57 +
//              ----x--- Key on/off operator 1
58 +
//              -----xxx Channel select
59 +
//           0F x------- Noise enable
60 +
//              ---xxxxx Noise frequency
61 +
//           10 xxxxxxxx Timer A value (upper 8 bits)
62 +
//           11 ------xx Timer A value (lower 2 bits)
63 +
//           12 xxxxxxxx Timer B value
64 +
//           14 x------- CSM mode
65 +
//              --x----- Reset timer B
66 +
//              ---x---- Reset timer A
67 +
//              ----x--- Enable timer B
68 +
//              -----x-- Enable timer A
69 +
//              ------x- Load timer B
70 +
//              -------x Load timer A
71 +
//           18 xxxxxxxx LFO frequency
72 +
//           19 0xxxxxxx AM LFO depth
73 +
//              1xxxxxxx PM LFO depth
74 +
//           1B xx------ CT (2 output data lines)
75 +
//              ------xx LFO waveform
76 +
//
77 +
//     Per-channel registers (channel in address bits 0-2)
78 +
//        20-27 x------- Pan right
79 +
//              -x------ Pan left
80 +
//              --xxx--- Feedback level for operator 1 (0-7)
81 +
//              -----xxx Operator connection algorithm (0-7)
82 +
//        28-2F -xxxxxxx Key code
83 +
//        30-37 xxxxxx-- Key fraction
84 +
//        38-3F -xxx---- LFO PM sensitivity
85 +
//              ------xx LFO AM shift
86 +
//
87 +
//     Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
88 +
//        40-5F -xxx---- Detune value (0-7)
89 +
//              ----xxxx Multiple value (0-15)
90 +
//        60-7F -xxxxxxx Total level (0-127)
91 +
//        80-9F xx------ Key scale rate (0-3)
92 +
//              ---xxxxx Attack rate (0-31)
93 +
//        A0-BF x------- LFO AM enable
94 +
//              ---xxxxx Decay rate (0-31)
95 +
//        C0-DF xx------ Detune 2 value (0-3)
96 +
//              ---xxxxx Sustain rate (0-31)
97 +
//        E0-FF xxxx---- Sustain level (0-15)
98 +
//              ----xxxx Release rate (0-15)
99 +
//
100 +
//     Internal (fake) registers:
101 +
//           1A -xxxxxxx PM depth
102 +
//
103 +
104 +
class opm_registers : public fm_registers_base
105 +
{
106 +
	// LFO waveforms are 256 entries long
107 +
	static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
108 +
109 +
public:
110 +
	// constants
111 +
	static constexpr uint32_t OUTPUTS = 2;
112 +
	static constexpr uint32_t CHANNELS = 8;
113 +
	static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
114 +
	static constexpr uint32_t OPERATORS = CHANNELS * 4;
115 +
	static constexpr uint32_t WAVEFORMS = 1;
116 +
	static constexpr uint32_t REGISTERS = 0x100;
117 +
	static constexpr uint32_t DEFAULT_PRESCALE = 2;
118 +
	static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
119 +
	static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
120 +
	static constexpr uint32_t REG_MODE = 0x14;
121 +
	static constexpr uint8_t STATUS_TIMERA = 0x01;
122 +
	static constexpr uint8_t STATUS_TIMERB = 0x02;
123 +
	static constexpr uint8_t STATUS_BUSY = 0x80;
124 +
	static constexpr uint8_t STATUS_IRQ = 0;
125 +
126 +
	// constructor
127 +
	opm_registers();
128 +
129 +
	// reset to initial state
130 +
	void reset();
131 +
132 +
	// save/restore
133 +
	void save_restore(ymfm_saved_state &state);
134 +
135 +
	// map channel number to register offset
136 +
	static constexpr uint32_t channel_offset(uint32_t chnum)
137 +
	{
138 +
		assert(chnum < CHANNELS);
139 +
		return chnum;
140 +
	}
141 +
142 +
	// map operator number to register offset
143 +
	static constexpr uint32_t operator_offset(uint32_t opnum)
144 +
	{
145 +
		assert(opnum < OPERATORS);
146 +
		return opnum;
147 +
	}
148 +
149 +
	// return an array of operator indices for each channel
150 +
	struct operator_mapping { uint32_t chan[CHANNELS]; };
151 +
	void operator_map(operator_mapping &dest) const;
152 +
153 +
	// handle writes to the register array
154 +
	bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
155 +
156 +
	// clock the noise and LFO, if present, returning LFO PM value
157 +
	int32_t clock_noise_and_lfo();
158 +
159 +
	// return the AM offset from LFO for the given channel
160 +
	uint32_t lfo_am_offset(uint32_t choffs) const;
161 +
162 +
	// return the current noise state, gated by the noise clock
163 +
	uint32_t noise_state() const { return m_noise_state; }
164 +
165 +
	// caching helpers
166 +
	void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
167 +
168 +
	// compute the phase step, given a PM value
169 +
	uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
170 +
171 +
	// log a key-on event
172 +
	std::string log_keyon(uint32_t choffs, uint32_t opoffs);
173 +
174 +
	// system-wide registers
175 +
	uint32_t test() const                            { return byte(0x01, 0, 8); }
176 +
	uint32_t lfo_reset() const                       { return byte(0x01, 1, 1); }
177 +
	uint32_t noise_frequency() const                 { return byte(0x0f, 0, 5) ^ 0x1f; }
178 +
	uint32_t noise_enable() const                    { return byte(0x0f, 7, 1); }
179 +
	uint32_t timer_a_value() const                   { return word(0x10, 0, 8, 0x11, 0, 2); }
180 +
	uint32_t timer_b_value() const                   { return byte(0x12, 0, 8); }
181 +
	uint32_t csm() const                             { return byte(0x14, 7, 1); }
182 +
	uint32_t reset_timer_b() const                   { return byte(0x14, 5, 1); }
183 +
	uint32_t reset_timer_a() const                   { return byte(0x14, 4, 1); }
184 +
	uint32_t enable_timer_b() const                  { return byte(0x14, 3, 1); }
185 +
	uint32_t enable_timer_a() const                  { return byte(0x14, 2, 1); }
186 +
	uint32_t load_timer_b() const                    { return byte(0x14, 1, 1); }
187 +
	uint32_t load_timer_a() const                    { return byte(0x14, 0, 1); }
188 +
	uint32_t lfo_rate() const                        { return byte(0x18, 0, 8); }
189 +
	uint32_t lfo_am_depth() const                    { return byte(0x19, 0, 7); }
190 +
	uint32_t lfo_pm_depth() const                    { return byte(0x1a, 0, 7); }
191 +
	uint32_t output_bits() const                     { return byte(0x1b, 6, 2); }
192 +
	uint32_t lfo_waveform() const                    { return byte(0x1b, 0, 2); }
193 +
194 +
	// per-channel registers
195 +
	uint32_t ch_output_any(uint32_t choffs) const    { return byte(0x20, 6, 2, choffs); }
196 +
	uint32_t ch_output_0(uint32_t choffs) const      { return byte(0x20, 6, 1, choffs); }
197 +
	uint32_t ch_output_1(uint32_t choffs) const      { return byte(0x20, 7, 1, choffs); }
198 +
	uint32_t ch_output_2(uint32_t choffs) const      { return 0; }
199 +
	uint32_t ch_output_3(uint32_t choffs) const      { return 0; }
200 +
	uint32_t ch_feedback(uint32_t choffs) const      { return byte(0x20, 3, 3, choffs); }
201 +
	uint32_t ch_algorithm(uint32_t choffs) const     { return byte(0x20, 0, 3, choffs); }
202 +
	uint32_t ch_block_freq(uint32_t choffs) const    { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
203 +
	uint32_t ch_lfo_pm_sens(uint32_t choffs) const   { return byte(0x38, 4, 3, choffs); }
204 +
	uint32_t ch_lfo_am_sens(uint32_t choffs) const   { return byte(0x38, 0, 2, choffs); }
205 +
206 +
	// per-operator registers
207 +
	uint32_t op_detune(uint32_t opoffs) const        { return byte(0x40, 4, 3, opoffs); }
208 +
	uint32_t op_multiple(uint32_t opoffs) const      { return byte(0x40, 0, 4, opoffs); }
209 +
	uint32_t op_total_level(uint32_t opoffs) const   { return byte(0x60, 0, 7, opoffs); }
210 +
	uint32_t op_ksr(uint32_t opoffs) const           { return byte(0x80, 6, 2, opoffs); }
211 +
	uint32_t op_attack_rate(uint32_t opoffs) const   { return byte(0x80, 0, 5, opoffs); }
212 +
	uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
213 +
	uint32_t op_decay_rate(uint32_t opoffs) const    { return byte(0xa0, 0, 5, opoffs); }
214 +
	uint32_t op_detune2(uint32_t opoffs) const       { return byte(0xc0, 6, 2, opoffs); }
215 +
	uint32_t op_sustain_rate(uint32_t opoffs) const  { return byte(0xc0, 0, 5, opoffs); }
216 +
	uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
217 +
	uint32_t op_release_rate(uint32_t opoffs) const  { return byte(0xe0, 0, 4, opoffs); }
218 +
219 +
protected:
220 +
	// return a bitfield extracted from a byte
221 +
	uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
222 +
	{
223 +
		return bitfield(m_regdata[offset + extra_offset], start, count);
224 +
	}
225 +
226 +
	// return a bitfield extracted from a pair of bytes, MSBs listed first
227 +
	uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
228 +
	{
229 +
		return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
230 +
	}
231 +
232 +
	// internal state
233 +
	uint32_t m_lfo_counter;               // LFO counter
234 +
	uint32_t m_noise_lfsr;                // noise LFSR state
235 +
	uint8_t m_noise_counter;              // noise counter
236 +
	uint8_t m_noise_state;                // latched noise state
237 +
	uint8_t m_noise_lfo;                  // latched LFO noise value
238 +
	uint8_t m_lfo_am;                     // current LFO AM value
239 +
	uint8_t m_regdata[REGISTERS];         // register data
240 +
	int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
241 +
	uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
242 +
};
243 +
244 +
245 +
246 +
//*********************************************************
247 +
//  OPM IMPLEMENTATION CLASSES
248 +
//*********************************************************
249 +
250 +
// ======================> ym2151
251 +
252 +
class ym2151
253 +
{
254 +
public:
255 +
	using fm_engine = fm_engine_base<opm_registers>;
256 +
	using output_data = fm_engine::output_data;
257 +
	static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
258 +
259 +
	// constructor
260 +
	ym2151(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2151) { }
261 +
262 +
	// reset
263 +
	void reset();
264 +
265 +
	// save/restore
266 +
	void save_restore(ymfm_saved_state &state);
267 +
268 +
	// pass-through helpers
269 +
	uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
270 +
	void invalidate_caches() { m_fm.invalidate_caches(); }
271 +
272 +
	// read access
273 +
	uint8_t read_status();
274 +
	uint8_t read(uint32_t offset);
275 +
276 +
	// write access
277 +
	void write_address(uint8_t data);
278 +
	void write_data(uint8_t data);
279 +
	void write(uint32_t offset, uint8_t data);
280 +
281 +
	// generate one sample of sound
282 +
	void generate(output_data *output, uint32_t numsamples = 1);
283 +
284 +
protected:
285 +
	// variants
286 +
	enum opm_variant
287 +
	{
288 +
		VARIANT_YM2151,
289 +
		VARIANT_YM2164
290 +
	};
291 +
292 +
	// internal constructor
293 +
	ym2151(ymfm_interface &intf, opm_variant variant);
294 +
295 +
	// internal state
296 +
	opm_variant m_variant;           // chip variant
297 +
	uint8_t m_address;               // address register
298 +
	fm_engine m_fm;                  // core FM engine
299 +
};
300 +
301 +
302 +
303 +
//*********************************************************
304 +
//  OPP IMPLEMENTATION CLASSES
305 +
//*********************************************************
306 +
307 +
// ======================> ym2164
308 +
309 +
// the YM2164 is almost 100% functionally identical to the YM2151, except
310 +
// it apparently has some mystery registers in the 00-07 range, and timer
311 +
// B's frequency is half that of the 2151
312 +
class ym2164 : public ym2151
313 +
{
314 +
public:
315 +
	// constructor
316 +
	ym2164(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2164) { }
317 +
};
318 +
319 +
}
320 +
321 +
322 +
#endif // YMFM_OPM_H
added src/extern/ymfm/src/ymfm_pcm.cpp +714 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#include "ymfm_pcm.h"
32 +
#include "ymfm_fm.h"
33 +
#include "ymfm_fm.ipp"
34 +
35 +
namespace ymfm
36 +
{
37 +
38 +
//*********************************************************
39 +
// PCM REGISTERS
40 +
//*********************************************************
41 +
42 +
//-------------------------------------------------
43 +
//  reset - reset the register state
44 +
//-------------------------------------------------
45 +
46 +
void pcm_registers::reset()
47 +
{
48 +
	std::fill_n(&m_regdata[0], REGISTERS, 0);
49 +
	m_regdata[0xf8] = 0x1b;
50 +
}
51 +
52 +
53 +
//-------------------------------------------------
54 +
//  save_restore - save or restore the data
55 +
//-------------------------------------------------
56 +
57 +
void pcm_registers::save_restore(ymfm_saved_state &state)
58 +
{
59 +
	state.save_restore(m_regdata);
60 +
}
61 +
62 +
63 +
//-------------------------------------------------
64 +
//  cache_channel_data - update the cache with
65 +
//  data from the registers
66 +
//-------------------------------------------------
67 +
68 +
void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
69 +
{
70 +
	// compute step from octave and fnumber; the math here implies
71 +
	// a .18 fraction but .16 should be perfectly fine
72 +
	int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
73 +
	uint32_t fnum = ch_fnumber(choffs);
74 +
	cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
75 +
76 +
	// total level is computed as a .10 value for interpolation
77 +
	cache.total_level = ch_total_level(choffs) << 10;
78 +
79 +
	// compute panning values in terms of envelope attenuation
80 +
	int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
81 +
	if (panpot >= 0)
82 +
	{
83 +
		cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot;
84 +
		cache.pan_right = 0;
85 +
	}
86 +
	else if (panpot >= -7)
87 +
	{
88 +
		cache.pan_left = 0;
89 +
		cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot;
90 +
	}
91 +
	else
92 +
		cache.pan_left = cache.pan_right = 0x3ff;
93 +
94 +
	// determine the LFO stepping value; this how much to add to a running
95 +
	// x.18 value for the LFO; steps were derived from frequencies in the
96 +
	// manual and come out very close with these values
97 +
	static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
98 +
	cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
99 +
100 +
	// AM LFO depth values, derived from the manual; note each has at most
101 +
	// 2 bits to make the "multiply" easy in hardware
102 +
	static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
103 +
	cache.am_depth = s_am_depth[ch_am_depth(choffs)];
104 +
105 +
	// PM LFO depth values; these are converted from the manual's cents values
106 +
	// into f-numbers; the computations come out quite cleanly so pretty sure
107 +
	// these are correct
108 +
	static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
109 +
	cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
110 +
111 +
	// 4-bit sustain level, but 15 means 31 so effectively 5 bits
112 +
	cache.eg_sustain = ch_sustain_level(choffs);
113 +
	cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
114 +
	cache.eg_sustain <<= 5;
115 +
116 +
	// compute the key scaling correction factor; 15 means don't do any correction
117 +
	int32_t correction = ch_rate_correction(choffs);
118 +
	if (correction == 15)
119 +
		correction = 0;
120 +
	else
121 +
		correction = (octave + correction) * 2 + bitfield(fnum, 9);
122 +
123 +
	// compute the envelope generator rates
124 +
	cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
125 +
	cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
126 +
	cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
127 +
	cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
128 +
	cache.eg_rate[EG_REVERB] = 5;
129 +
130 +
	// if damping is on, override some things; essentially decay at a hardcoded
131 +
	// rate of 48 until -12db (0x80), then at maximum rate for the rest
132 +
	if (ch_damp(choffs) != 0)
133 +
	{
134 +
		cache.eg_rate[EG_DECAY] = 48;
135 +
		cache.eg_rate[EG_SUSTAIN] = 63;
136 +
		cache.eg_rate[EG_RELEASE] = 63;
137 +
		cache.eg_sustain = 0x80;
138 +
	}
139 +
}
140 +
141 +
142 +
//-------------------------------------------------
143 +
//  effective_rate - return the effective rate,
144 +
//  clamping and applying corrections as needed
145 +
//-------------------------------------------------
146 +
147 +
uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
148 +
{
149 +
	// raw rates of 0 and 15 just pin to min/max
150 +
	if (raw == 0)
151 +
		return 0;
152 +
	if (raw == 15)
153 +
		return 63;
154 +
155 +
	// otherwise add the correction and clamp to range
156 +
	return clamp(raw * 4 + correction, 0, 63);
157 +
}
158 +
159 +
160 +
161 +
//*********************************************************
162 +
// PCM CHANNEL
163 +
//*********************************************************
164 +
165 +
//-------------------------------------------------
166 +
//  pcm_channel - constructor
167 +
//-------------------------------------------------
168 +
169 +
pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
170 +
	m_choffs(choffs),
171 +
	m_baseaddr(0),
172 +
	m_endpos(0),
173 +
	m_looppos(0),
174 +
	m_curpos(0),
175 +
	m_nextpos(0),
176 +
	m_lfo_counter(0),
177 +
	m_eg_state(EG_RELEASE),
178 +
	m_env_attenuation(0x3ff),
179 +
	m_total_level(0x7f << 10),
180 +
	m_format(0),
181 +
	m_key_state(0),
182 +
	m_regs(owner.regs()),
183 +
	m_owner(owner)
184 +
{
185 +
}
186 +
187 +
188 +
//-------------------------------------------------
189 +
//  reset - reset the channel state
190 +
//-------------------------------------------------
191 +
192 +
void pcm_channel::reset()
193 +
{
194 +
	m_baseaddr = 0;
195 +
	m_endpos = 0;
196 +
	m_looppos = 0;
197 +
	m_curpos = 0;
198 +
	m_nextpos = 0;
199 +
	m_lfo_counter = 0;
200 +
	m_eg_state = EG_RELEASE;
201 +
	m_env_attenuation = 0x3ff;
202 +
	m_total_level = 0x7f << 10;
203 +
	m_format = 0;
204 +
	m_key_state = 0;
205 +
}
206 +
207 +
208 +
//-------------------------------------------------
209 +
//  save_restore - save or restore the data
210 +
//-------------------------------------------------
211 +
212 +
void pcm_channel::save_restore(ymfm_saved_state &state)
213 +
{
214 +
	state.save_restore(m_baseaddr);
215 +
	state.save_restore(m_endpos);
216 +
	state.save_restore(m_looppos);
217 +
	state.save_restore(m_curpos);
218 +
	state.save_restore(m_nextpos);
219 +
	state.save_restore(m_lfo_counter);
220 +
	state.save_restore(m_eg_state);
221 +
	state.save_restore(m_env_attenuation);
222 +
	state.save_restore(m_total_level);
223 +
	state.save_restore(m_format);
224 +
	state.save_restore(m_key_state);
225 +
}
226 +
227 +
228 +
//-------------------------------------------------
229 +
//  prepare - prepare for clocking
230 +
//-------------------------------------------------
231 +
232 +
bool pcm_channel::prepare()
233 +
{
234 +
	// cache the data
235 +
	m_regs.cache_channel_data(m_choffs, m_cache);
236 +
237 +
	// clock the key state
238 +
	if ((m_key_state & KEY_PENDING) != 0)
239 +
	{
240 +
		uint8_t oldstate = m_key_state;
241 +
		m_key_state = (m_key_state >> 1) & KEY_ON;
242 +
		if (((oldstate ^ m_key_state) & KEY_ON) != 0)
243 +
		{
244 +
			if ((m_key_state & KEY_ON) != 0)
245 +
				start_attack();
246 +
			else
247 +
				start_release();
248 +
		}
249 +
	}
250 +
251 +
	// set the total level directly if not interpolating
252 +
	if (m_regs.ch_level_direct(m_choffs))
253 +
		m_total_level = m_cache.total_level;
254 +
255 +
	// we're active until we're quiet after the release
256 +
	return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
257 +
}
258 +
259 +
260 +
//-------------------------------------------------
261 +
//  clock - master clocking function
262 +
//-------------------------------------------------
263 +
264 +
void pcm_channel::clock(uint32_t env_counter)
265 +
{
266 +
	// clock the LFO, which is an x.18 value incremented based on the
267 +
	// LFO speed value
268 +
	m_lfo_counter += m_cache.lfo_step;
269 +
270 +
	// clock the envelope
271 +
	clock_envelope(env_counter);
272 +
273 +
	// determine the step after applying vibrato
274 +
	uint32_t step = m_cache.step;
275 +
	if (m_cache.pm_depth != 0)
276 +
	{
277 +
		// shift the LFO by 1/4 cycle for PM so that it starts at 0
278 +
		uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
279 +
		int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
280 +
		if (bitfield(lfo_shifted, 17) != 0)
281 +
			lfo_value ^= 0x7f;
282 +
		lfo_value -= 0x40;
283 +
		step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
284 +
	}
285 +
286 +
	// advance the sample step and loop as needed
287 +
	m_curpos = m_nextpos;
288 +
	m_nextpos = m_curpos + step;
289 +
	if (m_nextpos >= m_endpos)
290 +
		m_nextpos += m_looppos - m_endpos;
291 +
292 +
	// interpolate total level if needed
293 +
	if (m_total_level != m_cache.total_level)
294 +
	{
295 +
		// max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
296 +
		// min->max volume is half that, so advance by 38/1024 per sample
297 +
		if (m_total_level < m_cache.total_level)
298 +
			m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
299 +
		else
300 +
			m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
301 +
	}
302 +
}
303 +
304 +
305 +
//-------------------------------------------------
306 +
//  output - return the computed output value, with
307 +
//  panning applied
308 +
//-------------------------------------------------
309 +
310 +
void pcm_channel::output(output_data &output) const
311 +
{
312 +
	// early out if the envelope is effectively off
313 +
	uint32_t envelope = m_env_attenuation;
314 +
	if (envelope > EG_QUIET)
315 +
		return;
316 +
317 +
	// add in LFO AM modulation
318 +
	if (m_cache.am_depth != 0)
319 +
	{
320 +
		uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
321 +
		if (bitfield(m_lfo_counter, 17) != 0)
322 +
			lfo_value ^= 0x7f;
323 +
		envelope += (lfo_value * m_cache.am_depth) >> 7;
324 +
	}
325 +
326 +
	// add in the current interpolated total level value, which is a .10
327 +
	// value shifted left by 2
328 +
	envelope += m_total_level >> 8;
329 +
330 +
	// add in panning effect and clamp
331 +
	uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
332 +
	uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
333 +
334 +
	// convert to volume as a .11 fraction
335 +
	int32_t lvol = attenuation_to_volume(lenv << 2);
336 +
	int32_t rvol = attenuation_to_volume(renv << 2);
337 +
338 +
	// fetch current sample and add
339 +
	int16_t sample = fetch_sample();
340 +
	uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
341 +
	output.data[outnum + 0] += (lvol * sample) >> 15;
342 +
	output.data[outnum + 1] += (rvol * sample) >> 15;
343 +
}
344 +
345 +
346 +
//-------------------------------------------------
347 +
//  keyonoff - signal key on/off
348 +
//-------------------------------------------------
349 +
350 +
void pcm_channel::keyonoff(bool on)
351 +
{
352 +
	// mark the key state as pending
353 +
	m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
354 +
355 +
	// don't log masked channels
356 +
	if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
357 +
	{
358 +
		debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
359 +
			m_choffs,
360 +
			m_regs.ch_wave_table_num(m_choffs),
361 +
			int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
362 +
			m_regs.ch_fnumber(m_choffs),
363 +
			m_regs.ch_total_level(m_choffs),
364 +
			m_regs.ch_level_direct(m_choffs) ? '!' : '/',
365 +
			m_regs.ch_attack_rate(m_choffs),
366 +
			m_regs.ch_decay_rate(m_choffs),
367 +
			m_regs.ch_sustain_rate(m_choffs),
368 +
			m_regs.ch_release_rate(m_choffs),
369 +
			m_regs.ch_sustain_level(m_choffs));
370 +
371 +
		if (m_regs.ch_rate_correction(m_choffs) != 15)
372 +
			debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
373 +
374 +
		if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
375 +
			debug::log_keyon(" %s", "REV");
376 +
		if (m_regs.ch_damp(m_choffs) != 0)
377 +
			debug::log_keyon(" %s", "DAMP");
378 +
379 +
		if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
380 +
		{
381 +
			if (m_regs.ch_vibrato(m_choffs) != 0)
382 +
				debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
383 +
			if (m_regs.ch_am_depth(m_choffs) != 0)
384 +
				debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
385 +
			debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
386 +
		}
387 +
		debug::log_keyon("%s", "\n");
388 +
	}
389 +
}
390 +
391 +
392 +
//-------------------------------------------------
393 +
//  load_wavetable - load a wavetable by fetching
394 +
//  its data from external memory
395 +
//-------------------------------------------------
396 +
397 +
void pcm_channel::load_wavetable()
398 +
{
399 +
	// determine the address of the wave table header
400 +
	uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
401 +
	uint32_t wavheader = 12 * wavnum;
402 +
403 +
	// above 384 it may be in a different bank
404 +
	if (wavnum >= 384)
405 +
	{
406 +
		uint32_t bank = m_regs.wave_table_header();
407 +
		if (bank != 0)
408 +
			wavheader = 512*1024 * bank + (wavnum - 384) * 12;
409 +
	}
410 +
411 +
	// fetch the 22-bit base address and 2-bit format
412 +
	uint8_t byte = read_pcm(wavheader + 0);
413 +
	m_format = bitfield(byte, 6, 2);
414 +
	m_baseaddr = bitfield(byte, 0, 6) << 16;
415 +
	m_baseaddr |= read_pcm(wavheader + 1) << 8;
416 +
	m_baseaddr |= read_pcm(wavheader + 2) << 0;
417 +
418 +
	// fetch the 16-bit loop position
419 +
	m_looppos = read_pcm(wavheader + 3) << 8;
420 +
	m_looppos |= read_pcm(wavheader + 4);
421 +
	m_looppos <<= 16;
422 +
423 +
	// fetch the 16-bit end position, which is stored as a negative value
424 +
	// for some reason that is unclear
425 +
	m_endpos = read_pcm(wavheader + 5) << 8;
426 +
	m_endpos |= read_pcm(wavheader + 6);
427 +
	m_endpos = -int32_t(m_endpos) << 16;
428 +
429 +
	// remaining data values set registers
430 +
	m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
431 +
	m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
432 +
	m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
433 +
	m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
434 +
	m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
435 +
436 +
	// reset the envelope so we don't continue playing mid-sample from previous key ons
437 +
	m_env_attenuation = 0x3ff;
438 +
}
439 +
440 +
441 +
//-------------------------------------------------
442 +
//  read_pcm - read a byte from the external PCM
443 +
//  memory interface
444 +
//-------------------------------------------------
445 +
446 +
uint8_t pcm_channel::read_pcm(uint32_t address) const
447 +
{
448 +
	return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
449 +
}
450 +
451 +
452 +
//-------------------------------------------------
453 +
//  start_attack - start the attack phase
454 +
//-------------------------------------------------
455 +
456 +
void pcm_channel::start_attack()
457 +
{
458 +
	// don't change anything if already in attack state
459 +
	if (m_eg_state == EG_ATTACK)
460 +
		return;
461 +
	m_eg_state = EG_ATTACK;
462 +
463 +
	// reset the LFO if requested
464 +
	if (m_regs.ch_lfo_reset(m_choffs))
465 +
		m_lfo_counter = 0;
466 +
467 +
	// if the attack rate == 63 then immediately go to max attenuation
468 +
	if (m_cache.eg_rate[EG_ATTACK] == 63)
469 +
		m_env_attenuation = 0;
470 +
471 +
	// reset the positions
472 +
	m_curpos = m_nextpos = 0;
473 +
}
474 +
475 +
476 +
//-------------------------------------------------
477 +
//  start_release - start the release phase
478 +
//-------------------------------------------------
479 +
480 +
void pcm_channel::start_release()
481 +
{
482 +
	// don't change anything if already in release or reverb state
483 +
	if (m_eg_state >= EG_RELEASE)
484 +
		return;
485 +
	m_eg_state = EG_RELEASE;
486 +
}
487 +
488 +
489 +
//-------------------------------------------------
490 +
//  clock_envelope - clock the envelope generator
491 +
//-------------------------------------------------
492 +
493 +
void pcm_channel::clock_envelope(uint32_t env_counter)
494 +
{
495 +
	// handle attack->decay transitions
496 +
	if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
497 +
		m_eg_state = EG_DECAY;
498 +
499 +
	// handle decay->sustain transitions
500 +
	if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
501 +
		m_eg_state = EG_SUSTAIN;
502 +
503 +
	// fetch the appropriate 6-bit rate value from the cache
504 +
	uint32_t rate = m_cache.eg_rate[m_eg_state];
505 +
506 +
	// compute the rate shift value; this is the shift needed to
507 +
	// apply to the env_counter such that it becomes a 5.11 fixed
508 +
	// point number
509 +
	uint32_t rate_shift = rate >> 2;
510 +
	env_counter <<= rate_shift;
511 +
512 +
	// see if the fractional part is 0; if not, it's not time to clock
513 +
	if (bitfield(env_counter, 0, 11) != 0)
514 +
		return;
515 +
516 +
	// determine the increment based on the non-fractional part of env_counter
517 +
	uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
518 +
	uint32_t increment = attenuation_increment(rate, relevant_bits);
519 +
520 +
	// attack is the only one that increases
521 +
	if (m_eg_state == EG_ATTACK)
522 +
		m_env_attenuation += (~m_env_attenuation * increment) >> 4;
523 +
524 +
	// all other cases are similar
525 +
	else
526 +
	{
527 +
		// apply the increment
528 +
		m_env_attenuation += increment;
529 +
530 +
		// clamp the final attenuation
531 +
		if (m_env_attenuation >= 0x400)
532 +
			m_env_attenuation = 0x3ff;
533 +
534 +
		// transition to reverb at -18dB if enabled
535 +
		if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
536 +
			m_eg_state = EG_REVERB;
537 +
	}
538 +
}
539 +
540 +
541 +
//-------------------------------------------------
542 +
//  fetch_sample - fetch a sample at the current
543 +
//  position
544 +
//-------------------------------------------------
545 +
546 +
int16_t pcm_channel::fetch_sample() const
547 +
{
548 +
	uint32_t addr = m_baseaddr;
549 +
	uint32_t pos = m_curpos >> 16;
550 +
551 +
	// 8-bit PCM: shift up by 8
552 +
	if (m_format == 0)
553 +
		return read_pcm(addr + pos) << 8;
554 +
555 +
	// 16-bit PCM: assemble from 2 halves
556 +
	if (m_format == 2)
557 +
	{
558 +
		addr += pos * 2;
559 +
		return (read_pcm(addr) << 8) | read_pcm(addr + 1);
560 +
	}
561 +
562 +
	// 12-bit PCM: assemble out of half of 3 bytes
563 +
	addr += (pos / 2) * 3;
564 +
	if ((pos & 1) == 0)
565 +
		return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
566 +
	else
567 +
		return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
568 +
}
569 +
570 +
571 +
572 +
//*********************************************************
573 +
// PCM ENGINE
574 +
//*********************************************************
575 +
576 +
//-------------------------------------------------
577 +
//  pcm_engine - constructor
578 +
//-------------------------------------------------
579 +
580 +
pcm_engine::pcm_engine(ymfm_interface &intf) :
581 +
	m_intf(intf),
582 +
	m_env_counter(0),
583 +
	m_modified_channels(ALL_CHANNELS),
584 +
	m_active_channels(ALL_CHANNELS)
585 +
{
586 +
	// create the channels
587 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
588 +
		m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
589 +
}
590 +
591 +
592 +
//-------------------------------------------------
593 +
//  reset - reset the engine state
594 +
//-------------------------------------------------
595 +
596 +
void pcm_engine::reset()
597 +
{
598 +
	// reset register state
599 +
	m_regs.reset();
600 +
601 +
	// reset each channel
602 +
	for (auto &chan : m_channel)
603 +
		chan->reset();
604 +
}
605 +
606 +
607 +
//-------------------------------------------------
608 +
//  save_restore - save or restore the data
609 +
//-------------------------------------------------
610 +
611 +
void pcm_engine::save_restore(ymfm_saved_state &state)
612 +
{
613 +
	// save our data
614 +
	state.save_restore(m_env_counter);
615 +
616 +
	// save channel state
617 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
618 +
		m_channel[chnum]->save_restore(state);
619 +
}
620 +
621 +
622 +
//-------------------------------------------------
623 +
//  clock - master clocking function
624 +
//-------------------------------------------------
625 +
626 +
void pcm_engine::clock(uint32_t chanmask)
627 +
{
628 +
	// if something was modified, prepare
629 +
	// also prepare every 4k samples to catch ending notes
630 +
	if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
631 +
	{
632 +
		// call each channel to prepare
633 +
		m_active_channels = 0;
634 +
		for (int chnum = 0; chnum < CHANNELS; chnum++)
635 +
			if (bitfield(chanmask, chnum))
636 +
				if (m_channel[chnum]->prepare())
637 +
					m_active_channels |= 1 << chnum;
638 +
639 +
		// reset the modified channels and prepare count
640 +
		m_modified_channels = m_prepare_count = 0;
641 +
	}
642 +
643 +
	// increment the envelope counter; the envelope generator
644 +
	// only clocks every other sample in order to make the PCM
645 +
	// envelopes line up with the FM envelopes (after taking into
646 +
	// account the different FM sampling rate)
647 +
	m_env_counter++;
648 +
649 +
	// now update the state of all the channels and operators
650 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
651 +
		if (bitfield(chanmask, chnum))
652 +
			m_channel[chnum]->clock(m_env_counter >> 1);
653 +
}
654 +
655 +
656 +
//-------------------------------------------------
657 +
//  update - master update function
658 +
//-------------------------------------------------
659 +
660 +
void pcm_engine::output(output_data &output, uint32_t chanmask)
661 +
{
662 +
	// mask out some channels for debug purposes
663 +
	chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
664 +
665 +
	// compute the output of each channel
666 +
	for (int chnum = 0; chnum < CHANNELS; chnum++)
667 +
		if (bitfield(chanmask, chnum))
668 +
			m_channel[chnum]->output(output);
669 +
}
670 +
671 +
672 +
//-------------------------------------------------
673 +
//  read - handle reads from the PCM registers
674 +
//-------------------------------------------------
675 +
676 +
uint8_t pcm_engine::read(uint32_t regnum)
677 +
{
678 +
	// handle reads from the data register
679 +
	if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
680 +
		return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
681 +
682 +
	return m_regs.read(regnum);
683 +
}
684 +
685 +
686 +
//-------------------------------------------------
687 +
//  write - handle writes to the PCM registers
688 +
//-------------------------------------------------
689 +
690 +
void pcm_engine::write(uint32_t regnum, uint8_t data)
691 +
{
692 +
	// handle reads to the data register
693 +
	if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
694 +
	{
695 +
		m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
696 +
		return;
697 +
	}
698 +
699 +
	// for now just mark all channels as modified
700 +
	m_modified_channels = ALL_CHANNELS;
701 +
702 +
	// most writes are passive, consumed only when needed
703 +
	m_regs.write(regnum, data);
704 +
705 +
	// however, process keyons immediately
706 +
	if (regnum >= 0x68 && regnum <= 0x7f)
707 +
		m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
708 +
709 +
	// and also wavetable writes
710 +
	else if (regnum >= 0x08 && regnum <= 0x1f)
711 +
		m_channel[regnum - 0x08]->load_wavetable();
712 +
}
713 +
714 +
}
added src/extern/ymfm/src/ymfm_pcm.h +347 −0
1 +
// BSD 3-Clause License
2 +
//
3 +
// Copyright (c) 2021, Aaron Giles
4 +
// All rights reserved.
5 +
//
6 +
// Redistribution and use in source and binary forms, with or without
7 +
// modification, are permitted provided that the following conditions are met:
8 +
//
9 +
// 1. Redistributions of source code must retain the above copyright notice, this
10 +
//    list of conditions and the following disclaimer.
11 +
//
12 +
// 2. Redistributions in binary form must reproduce the above copyright notice,
13 +
//    this list of conditions and the following disclaimer in the documentation
14 +
//    and/or other materials provided with the distribution.
15 +
//
16 +
// 3. Neither the name of the copyright holder nor the names of its
17 +
//    contributors may be used to endorse or promote products derived from
18 +
//    this software without specific prior written permission.
19 +
//
20 +
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 +
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 +
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 +
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 +
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 +
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 +
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 +
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 +
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 +
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 +
31 +
#ifndef YMFM_PCM_H
32 +
#define YMFM_PCM_H
33 +
34 +
#pragma once
35 +
36 +
#include "ymfm.h"
37 +
38 +
namespace ymfm
39 +
{
40 +
41 +
/*
42 +
Note to self: Sega "Multi-PCM" is almost identical to this
43 +
44 +
28 channels
45 +
46 +
Writes:
47 +
00 = data reg, causes write
48 +
01 = target slot = data - (data / 8)
49 +
02 = address (clamped to 7)
50 +
51 +
Slot data (registers with ADSR/KSR seem to be inaccessible):
52 +
0: xxxx---- panpot
53 +
1: xxxxxxxx wavetable low
54 +
2: xxxxxx-- pitch low
55 +
   -------x wavetable high
56 +
3: xxxx---- octave
57 +
   ----xxxx pitch hi
58 +
4: x------- key on
59 +
5: xxxxxxx- total level
60 +
   -------x level direct (0=interpolate)
61 +
6: --xxx--- LFO frequency
62 +
   -----xxx PM sensitivity
63 +
7: -----xxx AM sensitivity
64 +
65 +
Sample data:
66 +
+00: start hi
67 +
+01: start mid
68 +
+02: start low
69 +
+03: loop hi
70 +
+04: loop low
71 +
+05: -end hi
72 +
+06: -end low
73 +
+07: vibrato (reg 6)
74 +
+08: attack/decay
75 +
+09: sustain level/rate
76 +
+0A: ksr/release
77 +
+0B: LFO amplitude (reg 7)
78 +
79 +
*/
80 +
81 +
//*********************************************************
82 +
//  INTERFACE CLASSES
83 +
//*********************************************************
84 +
85 +
class pcm_engine;
86 +
87 +
88 +
// ======================> pcm_cache
89 +
90 +
// this class holds data that is computed once at the start of clocking
91 +
// and remains static during subsequent sound generation
92 +
struct pcm_cache
93 +
{
94 +
	uint32_t step;                    // sample position step, as a .16 value
95 +
	uint32_t total_level;             // target total level, as a .10 value
96 +
	uint32_t pan_left;                // left panning attenuation
97 +
	uint32_t pan_right;               // right panning attenuation
98 +
	uint32_t eg_sustain;              // sustain level, shifted up to envelope values
99 +
	uint8_t eg_rate[EG_STATES];       // envelope rate, including KSR
100 +
	uint8_t lfo_step;                 // stepping value for LFO
101 +
	uint8_t am_depth;                 // scale value for AM LFO
102 +
	uint8_t pm_depth;                 // scale value for PM LFO
103 +
};
104 +
105 +
106 +
// ======================> pcm_registers
107 +
108 +
//
109 +
// PCM register map:
110 +
//
111 +
//      System-wide registers:
112 +
//        00-01 xxxxxxxx LSI Test
113 +
//           02 -------x Memory access mode (0=sound gen, 1=read/write)
114 +
//              ------x- Memory type (0=ROM, 1=ROM+SRAM)
115 +
//              ---xxx-- Wave table header
116 +
//              xxx----- Device ID (=1 for YMF278B)
117 +
//           03 --xxxxxx Memory address high
118 +
//           04 xxxxxxxx Memory address mid
119 +
//           05 xxxxxxxx Memory address low
120 +
//           06 xxxxxxxx Memory data
121 +
//           F8 --xxx--- Mix control (FM_R)
122 +
//              -----xxx Mix control (FM_L)
123 +
//           F9 --xxx--- Mix control (PCM_R)
124 +
//              -----xxx Mix control (PCM_L)
125 +
//
126 +
//      Channel-specific registers:
127 +
//        08-1F xxxxxxxx Wave table number low
128 +
//        20-37 -------x Wave table number high
129 +
//              xxxxxxx- F-number low
130 +
//        38-4F -----xxx F-number high
131 +
//              ----x--- Pseudo-reverb
132 +
//              xxxx---- Octave
133 +
//        50-67 xxxxxxx- Total level
134 +
//              -------x Level direct
135 +
//        68-7F x------- Key on
136 +
//              -x------ Damp
137 +
//              --x----- LFO reset
138 +
//              ---x---- Output channel
139 +
//              ----xxxx Panpot
140 +
//        80-97 --xxx--- LFO speed
141 +
//              -----xxx Vibrato
142 +
//        98-AF xxxx---- Attack rate
143 +
//              ----xxxx Decay rate
144 +
//        B0-C7 xxxx---- Sustain level
145 +
//              ----xxxx Sustain rate
146 +
//        C8-DF xxxx---- Rate correction
147 +
//              ----xxxx Release rate
148 +
//        E0-F7 -----xxx AM depth
149 +
150 +
class pcm_registers
151 +
{
152 +
public:
153 +
	// constants
154 +
	static constexpr uint32_t OUTPUTS = 4;
155 +
	static constexpr uint32_t CHANNELS = 24;
156 +
	static constexpr uint32_t REGISTERS = 0x100;
157 +
	static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
158 +
159 +
	// constructor
160 +
	pcm_registers() { }
161 +
162 +
	// save/restore
163 +
	void save_restore(ymfm_saved_state &state);
164 +
165 +
	// reset to initial state
166 +
	void reset();
167 +
168 +
	// update cache information
169 +
	void cache_channel_data(uint32_t choffs, pcm_cache &cache);
170 +
171 +
	// direct read/write access
172 +
	uint8_t read(uint32_t index ) { return m_regdata[index]; }
173 +
	void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
174 +
175 +
	// system-wide registers
176 +
	uint32_t memory_access_mode() const                 { return bitfield(m_regdata[0x02], 0); }
177 +
	uint32_t memory_type() const                        { return bitfield(m_regdata[0x02], 1); }
178 +
	uint32_t wave_table_header() const                  { return bitfield(m_regdata[0x02], 2, 3); }
179 +
	uint32_t device_id() const                          { return bitfield(m_regdata[0x02], 5, 3); }
180 +
	uint32_t memory_address() const                     { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
181 +
	uint32_t memory_data() const                        { return m_regdata[0x06]; }
182 +
	uint32_t mix_fm_r() const                           { return bitfield(m_regdata[0xf8], 3, 3); }
183 +
	uint32_t mix_fm_l() const                           { return bitfield(m_regdata[0xf8], 0, 3); }
184 +
	uint32_t mix_pcm_r() const                          { return bitfield(m_regdata[0xf9], 3, 3); }
185 +
	uint32_t mix_pcm_l() const                          { return bitfield(m_regdata[0xf9], 0, 3); }
186 +
187 +
	// per-channel registers
188 +
	uint32_t ch_wave_table_num(uint32_t choffs) const   { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
189 +
	uint32_t ch_fnumber(uint32_t choffs) const          { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
190 +
	uint32_t ch_pseudo_reverb(uint32_t choffs) const    { return bitfield(m_regdata[choffs + 0x38], 3); }
191 +
	uint32_t ch_octave(uint32_t choffs) const           { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
192 +
	uint32_t ch_total_level(uint32_t choffs) const      { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
193 +
	uint32_t ch_level_direct(uint32_t choffs) const     { return bitfield(m_regdata[choffs + 0x50], 0); }
194 +
	uint32_t ch_keyon(uint32_t choffs) const            { return bitfield(m_regdata[choffs + 0x68], 7); }
195 +
	uint32_t ch_damp(uint32_t choffs) const             { return bitfield(m_regdata[choffs + 0x68], 6); }
196 +
	uint32_t ch_lfo_reset(uint32_t choffs) const        { return bitfield(m_regdata[choffs + 0x68], 5); }
197 +
	uint32_t ch_output_channel(uint32_t choffs) const   { return bitfield(m_regdata[choffs + 0x68], 4); }
198 +
	uint32_t ch_panpot(uint32_t choffs) const           { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
199 +
	uint32_t ch_lfo_speed(uint32_t choffs) const        { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
200 +
	uint32_t ch_vibrato(uint32_t choffs) const          { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
201 +
	uint32_t ch_attack_rate(uint32_t choffs) const      { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
202 +
	uint32_t ch_decay_rate(uint32_t choffs) const       { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
203 +
	uint32_t ch_sustain_level(uint32_t choffs) const    { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
204 +
	uint32_t ch_sustain_rate(uint32_t choffs) const     { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
205 +
	uint32_t ch_rate_correction(uint32_t choffs) const  { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
206 +
	uint32_t ch_release_rate(uint32_t choffs) const     { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
207 +
	uint32_t ch_am_depth(uint32_t choffs) const         { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
208 +
209 +
	// return the memory address and increment it
210 +
	uint32_t memory_address_autoinc()
211 +
	{
212 +
		uint32_t result = memory_address();
213 +
		uint32_t newval = result + 1;
214 +
		m_regdata[0x05] = newval >> 0;
215 +
		m_regdata[0x04] = newval >> 8;
216 +
		m_regdata[0x03] = (newval >> 16) & 0x3f;
217 +
		return result;
218 +
	}
219 +
220 +
private:
221 +
	// internal helpers
222 +
	uint32_t effective_rate(uint32_t raw, uint32_t correction);
223 +
224 +
	// internal state
225 +
	uint8_t m_regdata[REGISTERS];         // register data
226 +
};
227 +
228 +
229 +
// ======================> pcm_channel
230 +
231 +
class pcm_channel
232 +
{
233 +
	static constexpr uint8_t KEY_ON = 0x01;
234 +
	static constexpr uint8_t KEY_PENDING_ON = 0x02;
235 +
	static constexpr uint8_t KEY_PENDING = 0x04;
236 +
237 +
	// "quiet" value, used to optimize when we can skip doing working
238 +
	static constexpr uint32_t EG_QUIET = 0x200;
239 +
240 +
public:
241 +
	using output_data = ymfm_output<pcm_registers::OUTPUTS>;
242 +
243 +
	// constructor
244 +
	pcm_channel(pcm_engine &owner, uint32_t choffs);
245 +
246 +
	// save/restore
247 +
	void save_restore(ymfm_saved_state &state);
248 +
249 +
	// reset the channel state
250 +
	void reset();
251 +
252 +
	// return the channel offset
253 +
	uint32_t choffs() const { return m_choffs; }
254 +
255 +
	// prepare prior to clocking
256 +
	bool prepare();
257 +
258 +
	// master clocking function
259 +
	void clock(uint32_t env_counter);
260 +
261 +
	// return the computed output value, with panning applied
262 +
	void output(output_data &output) const;
263 +
264 +
	// signal key on/off
265 +
	void keyonoff(bool on);
266 +
267 +
	// load a new wavetable entry
268 +
	void load_wavetable();
269 +
270 +
private:
271 +
	// internal helpers
272 +
	void start_attack();
273 +
	void start_release();
274 +
	void clock_envelope(uint32_t env_counter);
275 +
	int16_t fetch_sample() const;
276 +
	uint8_t read_pcm(uint32_t address) const;
277 +
278 +
	// internal state
279 +
	uint32_t const m_choffs;              // channel offset
280 +
	uint32_t m_baseaddr;                  // base address
281 +
	uint32_t m_endpos;                    // ending position
282 +
	uint32_t m_looppos;                   // loop position
283 +
	uint32_t m_curpos;                    // current position
284 +
	uint32_t m_nextpos;                   // next position
285 +
	uint32_t m_lfo_counter;               // LFO counter
286 +
	envelope_state m_eg_state;            // envelope state
287 +
	uint16_t m_env_attenuation;           // computed envelope attenuation
288 +
	uint32_t m_total_level;               // total level with as 7.10 for interp
289 +
	uint8_t m_format;                     // sample format
290 +
	uint8_t m_key_state;                  // current key state
291 +
	pcm_cache m_cache;                    // cached data
292 +
	pcm_registers &m_regs;                // reference to registers
293 +
	pcm_engine &m_owner;                  // reference to our owner
294 +
};
295 +
296 +
297 +
// ======================> pcm_engine
298 +
299 +
class pcm_engine
300 +
{
301 +
public:
302 +
	static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
303 +
	static constexpr int CHANNELS = pcm_registers::CHANNELS;
304 +
	static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
305 +
	using output_data = pcm_channel::output_data;
306 +
307 +
	// constructor
308 +
	pcm_engine(ymfm_interface &intf);
309 +
310 +
	// reset our status
311 +
	void reset();
312 +
313 +
	// save/restore
314 +
	void save_restore(ymfm_saved_state &state);
315 +
316 +
	// master clocking function
317 +
	void clock(uint32_t chanmask);
318 +
319 +
	// compute sum of channel outputs
320 +
	void output(output_data &output, uint32_t chanmask);
321 +
322 +
	// read from the PCM registers
323 +
	uint8_t read(uint32_t regnum);
324 +
325 +
	// write to the PCM registers
326 +
	void write(uint32_t regnum, uint8_t data);
327 +
328 +
	// return a reference to our interface
329 +
	ymfm_interface &intf() { return m_intf; }
330 +
331 +
	// return a reference to our registers
332 +
	pcm_registers &regs() { return m_regs; }
333 +
334 +
private:
335 +
	// internal state
336 +
	ymfm_interface &m_intf;                           // reference to the interface
337 +
	uint32_t m_env_counter;                           // envelope counter
338 +
	uint32_t m_modified_channels;                     // bitmask of modified channels
339 +
	uint32_t m_active_channels;                       // bitmask of active channels
340 +
	uint32_t m_prepare_count;                         // counter to do periodic prepare sweeps
341 +
	std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
342 +
	pcm_registers m_regs;                             // registers
343 +
};
344 +
345 +
}
346 +
347 +
#endif // YMFM_PCM_H
added src/files.c +341 −0
1 +
#ifndef __APPLE__
2 +
#define _XOPEN_SOURCE   600
3 +
#define _POSIX_C_SOURCE 1
4 +
#endif
5 +
6 +
#include "files.h"
7 +
8 +
#include <limits.h>
9 +
#include <string.h>
10 +
#include <stdlib.h>
11 +
#include <unistd.h>
12 +
#include <stdio.h>
13 +
#include <SDL.h>
14 +
#include <zlib.h>
15 +
#include <inttypes.h>
16 +
17 +
struct x16file
18 +
{
19 +
	char path[PATH_MAX];
20 +
21 +
	SDL_RWops *file;
22 +
	int64_t size;
23 +
	int64_t pos;
24 +
	bool modified;
25 +
26 +
	struct x16file *next;
27 +
};
28 +
29 +
struct x16file *open_files = NULL;
30 +
31 +
static bool 
32 +
get_tmp_name(char *path_buffer, const char *original_path, char const *extension)
33 +
{
34 +
	if(strlen(original_path) > PATH_MAX - strlen(extension)) {
35 +
		printf("Path too long, cannot create temp file: %s\n", original_path);
36 +
		return false;
37 +
	}
38 +
39 +
	strcpy(path_buffer, original_path);
40 +
	strcat(path_buffer, extension);
41 +
42 +
	return true;
43 +
}
44 +
45 +
bool 
46 +
file_is_compressed_type(char const *path)
47 +
{
48 +
	int len = (int)strlen(path);
49 +
50 +
	if (strcmp(path + len - 3, ".gz") == 0 || strcmp(path + len - 3, "-gz") == 0) {
51 +
		return true;
52 +
	} else if (strcmp(path + len - 2, ".z") == 0 || strcmp(path + len - 2, "-z") == 0 || strcmp(path + len - 2, "_z") == 0 || strcmp(path + len - 2, ".Z") == 0) {
53 +
		return true;
54 +
	}
55 +
56 +
	return false;
57 +
}
58 +
59 +
const char *
60 +
file_find_extension(const char *path, const char *mark)
61 +
{
62 +
	if(path == NULL) {
63 +
		return NULL;
64 +
	}
65 +
66 +
	if(mark == NULL) {
67 +
		mark = path + strlen(path);
68 +
		if(file_is_compressed_type(path)) {
69 +
			mark -= 3;
70 +
		}
71 +
	}
72 +
73 +
	while(mark > path) {
74 +
		if(*mark == '.') {
75 +
			return mark;
76 +
		}
77 +
		--mark;
78 +
	}
79 +
80 +
	return NULL;
81 +
}
82 +
83 +
84 +
void
85 +
files_shutdown()
86 +
{
87 +
	struct x16file *f = open_files;
88 +
	struct x16file *next_f = NULL;
89 +
	for(; f != NULL; f = next_f) {
90 +
		next_f = f->next;
91 +
		x16close(f);
92 +
	}
93 +
}
94 +
95 +
struct x16file *
96 +
x16open(const char *path, const char *attribs)
97 +
{
98 +
	struct x16file *f = malloc(sizeof(struct x16file));
99 +
	strcpy(f->path, path);
100 +
101 +
	if(file_is_compressed_type(path)) {
102 +
		char tmp_path[PATH_MAX];
103 +
		if(!get_tmp_name(tmp_path, path, ".tmp")) {
104 +
			printf("Path too long, cannot create temp file: %s\n", path);
105 +
			goto error;
106 +
		}
107 +
108 +
		gzFile zfile = gzopen(path, "rb");
109 +
		if(zfile == Z_NULL) {
110 +
			printf("Could not open file for decompression: %s\n", path);
111 +
			goto error;
112 +
		}
113 +
114 +
		SDL_RWops *tfile = SDL_RWFromFile(tmp_path, "wb");
115 +
		if(tfile == NULL) {
116 +
			gzclose(zfile);
117 +
			printf("Could not open file for write: %s\n", tmp_path);
118 +
			goto error;
119 +
		}
120 +
121 +
		printf("Decompressing %s\n", path);
122 +
123 +
		const int buffer_size = 16 * 1024 * 1024;
124 +
		char *buffer = malloc(buffer_size);
125 +
126 +
		int read = gzread(zfile, buffer, buffer_size);
127 +
		int64_t total_read = read;
128 +
		const int64_t progress_increment = 128 * 1024 * 1024;
129 +
		int64_t progress_threshold = progress_increment;
130 +
		while(read > 0) {
131 +
			if(total_read > progress_threshold) {
132 +
				printf("%" PRId64 " MB\n", total_read / (1024 * 1024));
133 +
				progress_threshold += progress_increment;
134 +
			}
135 +
			SDL_RWwrite(tfile, buffer, read, 1);
136 +
			read = gzread(zfile, buffer, buffer_size);
137 +
			total_read += read;
138 +
		}
139 +
		printf("%" PRId64 " MB\n", total_read / (1024 * 1024));
140 +
141 +
		SDL_RWclose(tfile);
142 +
		gzclose(zfile);
143 +
		free(buffer);
144 +
145 +
		f->file = SDL_RWFromFile(tmp_path, attribs);
146 +
		if(f->file == NULL) {
147 +
			unlink(tmp_path);
148 +
			goto error;
149 +
		}
150 +
		f->size = total_read;
151 +
	} else {
152 +
		f->file = SDL_RWFromFile(path, attribs);
153 +
		if(f->file == NULL) {
154 +
			goto error;
155 +
		}
156 +
		f->size = SDL_RWsize(f->file);
157 +
	}
158 +
	f->pos = 0;
159 +
	f->modified = false;
160 +
	f->next = open_files ? open_files : NULL;
161 +
	open_files = f;
162 +
163 +
	return f;
164 +
165 +
error:
166 +
	free(f);
167 +
	return NULL;
168 +
}
169 +
170 +
void 
171 +
x16close(struct x16file *f)
172 +
{
173 +
	if(f == NULL) {
174 +
		return;
175 +
	}
176 +
177 +
	SDL_RWclose(f->file);
178 +
179 +
	if(file_is_compressed_type(f->path)) {
180 +
		char tmp_path[PATH_MAX];
181 +
		if(!get_tmp_name(tmp_path, f->path, ".tmp")) {
182 +
			printf("Path too long, cannot create temp file: %s\n", f->path);
183 +
			goto tmp_path_error;
184 +
		}
185 +
186 +
		if(f->modified == false) {
187 +
			goto zfile_clean;
188 +
		}
189 +
190 +
		gzFile zfile = gzopen(f->path, "wb6");
191 +
		if(zfile == Z_NULL) {
192 +
			printf("Could not open file for compression: %s\n", f->path);
193 +
			goto zfile_error;
194 +
		}
195 +
196 +
		SDL_RWops *tfile = SDL_RWFromFile(tmp_path, "rb");
197 +
		if(tfile == NULL) {
198 +
			gzclose(zfile);
199 +
			printf("Could not open file for read: %s\n", tmp_path);
200 +
			goto tfile_error;
201 +
		}
202 +
203 +
		printf("Recompressing %s\n", f->path);
204 +
205 +
		const int buffer_size = 16 * 1024 * 1024;
206 +
		char *buffer = malloc(buffer_size);
207 +
208 +
		const int64_t progress_increment = 128 * 1024 * 1024;
209 +
		int64_t progress_threshold = progress_increment;
210 +
		int read = SDL_RWread(tfile, buffer, 1, buffer_size);
211 +
		int64_t total_read = read;
212 +
		while(read > 0) {
213 +
			if(total_read > progress_threshold) {
214 +
				printf("%d%%\n", (int)(total_read * 100 / f->size));
215 +
				progress_threshold += progress_increment;
216 +
			}
217 +
			gzwrite(zfile, buffer, read);
218 +
			read = SDL_RWread(tfile, buffer, 1, buffer_size);
219 +
			total_read += read;
220 +
		}
221 +
222 +
		free(buffer);
223 +
224 +
		if(tfile != NULL) {
225 +
			SDL_RWclose(tfile);
226 +
		}
227 +
228 +
	tfile_error:
229 +
		if(zfile != Z_NULL) {
230 +
			gzclose(zfile);
231 +
		}
232 +
233 +
	zfile_error: // fall-through
234 +
	zfile_clean:
235 +
		unlink(tmp_path);
236 +
	}
237 +
tmp_path_error:
238 +
	if(f == open_files) {
239 +
		open_files = f->next;
240 +
	} else {
241 +
		for(struct x16file *fi = open_files; fi != NULL; fi = fi->next) {
242 +
			if(fi->next == f) {
243 +
				fi->next = f->next;
244 +
				break;
245 +
			}
246 +
		}
247 +
	}
248 +
	free(f);
249 +
}
250 +
251 +
int64_t 
252 +
x16size(struct x16file *f)
253 +
{
254 +
	if(f == NULL) {
255 +
		return 0;
256 +
	}
257 +
258 +
	return f->size;
259 +
}
260 +
261 +
int 
262 +
x16seek(struct x16file *f, int64_t pos, int origin)
263 +
{
264 +
	if(f == NULL) {
265 +
		return 0;
266 +
	}
267 +
	switch(origin) {
268 +
		case SEEK_SET:
269 +
			f->pos = (pos > f->size) ? f->size : pos;
270 +
			break;
271 +
		case SEEK_CUR:
272 +
			f->pos += pos;
273 +
			if(f->pos > f->size || f->pos < 0) {
274 +
				f->pos = f->size;
275 +
			}
276 +
			break;
277 +
		case SEEK_END:
278 +
			f->pos = f->size - pos;
279 +
			if(f->pos < 0) {
280 +
				f->pos = f->size;
281 +
			}
282 +
	}
283 +
	return SDL_RWseek(f->file, f->pos, SEEK_SET);
284 +
}
285 +
286 +
int64_t 
287 +
x16tell(struct x16file *f)
288 +
{
289 +
	if(f == NULL) {
290 +
		return 0;
291 +
	}
292 +
	return f->pos;
293 +
}
294 +
295 +
int 
296 +
x16write8(struct x16file *f, uint8_t val)
297 +
{
298 +
	if(f == NULL) {
299 +
		return 0;
300 +
	}
301 +
	int written = SDL_RWwrite(f->file, &val, 1, 1);
302 +
	f->pos += written;
303 +
	return written;
304 +
}
305 +
306 +
uint8_t 
307 +
x16read8(struct x16file *f)
308 +
{
309 +
	if(f == NULL) {
310 +
		return 0;
311 +
	}
312 +
	uint8_t val;
313 +
	int read = SDL_RWread(f->file, &val, 1, 1);
314 +
	f->pos += read;
315 +
	return read;
316 +
}
317 +
318 +
uint64_t 
319 +
x16write(struct x16file *f, const uint8_t *data, uint64_t data_size, uint64_t data_count)
320 +
{
321 +
	if(f == NULL) {
322 +
		return 0;
323 +
	}
324 +
	int64_t written = SDL_RWwrite(f->file, data, data_size, data_count);
325 +
	if(written) {
326 +
		f->modified = true;
327 +
	}
328 +
	f->pos += written * data_size;
329 +
	return written;
330 +
}
331 +
332 +
uint64_t 
333 +
x16read(struct x16file *f, uint8_t *data, uint64_t data_size, uint64_t data_count)
334 +
{
335 +
	if(f == NULL) {
336 +
		return 0;
337 +
	}
338 +
	int64_t read = SDL_RWread(f->file, data, data_size, data_count);
339 +
	f->pos += read * data_size;
340 +
	return read;
341 +
}
added src/files.h +27 −0
1 +
#include <stdbool.h>
2 +
#include <stdint.h>
3 +
4 +
struct x16file;
5 +
6 +
#define XSEEK_SET	0
7 +
#define XSEEK_END	1
8 +
#define XSEEK_CUR	2
9 +
10 +
bool file_is_compressed_type(char const *path);
11 +
const char *file_find_extension(const char *path, const char *mark);
12 +
13 +
void files_shutdown();
14 +
15 +
struct x16file *x16open(const char *path, const char *attribs);
16 +
void x16close(struct x16file *f);
17 +
18 +
int64_t x16size(struct x16file *f);
19 +
int x16seek(struct x16file *f, int64_t pos, int origin);
20 +
int64_t x16tell(struct x16file *f);
21 +
22 +
23 +
int x16write8(struct x16file *f, uint8_t val);
24 +
uint8_t x16read8(struct x16file *f);
25 +
26 +
uint64_t x16write(struct x16file *f, const uint8_t *data, uint64_t data_size, uint64_t data_count);
27 +
uint64_t x16read(struct x16file *f, uint8_t *data, uint64_t data_size, uint64_t data_count);
added src/glue.h +115 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _GLUE_H_
6 +
#define _GLUE_H_
7 +
8 +
#include <stdint.h>
9 +
#include <stdbool.h>
10 +
#include <SDL.h>
11 +
12 +
#include "cpu/registers.h"
13 +
14 +
//#define TRACE
15 +
//#define PERFSTAT
16 +
17 +
#define NUM_MAX_BANKS 256
18 +
#define NUM_MAX_RAM_BANKS 256
19 +
#define NUM_ROM_BANKS 32
20 +
#define NUM_CART_BANKS (256 - 32)
21 +
22 +
#define RAM_SIZE (num_banks * BANK_SIZE)    /* $0000-$9EFF, $9F00-$FFFF "wasted", then optionally $010000+ */
23 +
#define BRAM_SIZE (num_ram_banks * 8192)    /* banks at $A000-$BFFF */
24 +
#define ROM_SIZE (NUM_ROM_BANKS * 16384)    /* banks at $C000-$FFFF */
25 +
#define CART_SIZE (NUM_CART_BANKS * 16384)  /* expansion banks at $C000-$FFFF */
26 +
27 +
#define WINDOW_TITLE "Commander X16"
28 +
29 +
#ifdef __APPLE__
30 +
#define MOUSE_GRAB_MSG " (\xE2\x87\xA7\xE2\x8C\x98M to end mouse/keyboard capture)"
31 +
#else
32 +
#define MOUSE_GRAB_MSG " (Ctrl+M to end mouse/keyboard capture)"
33 +
#endif
34 +
35 +
typedef enum {
36 +
	ECHO_MODE_NONE,
37 +
	ECHO_MODE_RAW,
38 +
	ECHO_MODE_COOKED,
39 +
	ECHO_MODE_ISO,
40 +
} echo_mode_t;
41 +
42 +
// GIF recorder commands
43 +
typedef enum {
44 +
	RECORD_GIF_PAUSE,
45 +
	RECORD_GIF_SNAP,
46 +
	RECORD_GIF_RESUME
47 +
} gif_recorder_command_t;
48 +
49 +
// GIF recorder states
50 +
typedef enum {
51 +
	RECORD_GIF_DISABLED,
52 +
	RECORD_GIF_PAUSED,
53 +
	RECORD_GIF_SINGLE,
54 +
	RECORD_GIF_ACTIVE
55 +
} gif_recorder_state_t;
56 +
57 +
extern struct regs regs;
58 +
extern uint16_t opcode_addr;
59 +
extern uint8_t *RAM;
60 +
extern uint8_t *BRAM;
61 +
extern uint8_t ROM[];
62 +
extern uint8_t *CART;
63 +
64 +
extern uint16_t num_banks;
65 +
extern uint16_t num_ram_banks;
66 +
67 +
extern bool debugger_enabled;
68 +
extern bool log_video;
69 +
extern bool log_keyboard;
70 +
extern bool log_speed;
71 +
extern echo_mode_t echo_mode;
72 +
extern bool save_on_exit;
73 +
extern bool disable_emu_cmd_keys;
74 +
extern gif_recorder_state_t record_gif;
75 +
extern char *gif_path;
76 +
extern uint8_t *fsroot_path;
77 +
extern uint8_t *startin_path;
78 +
extern uint8_t keymap;
79 +
extern bool warp_mode;
80 +
extern bool grab_mouse;
81 +
extern bool testbench;
82 +
extern bool has_via2;
83 +
extern uint32_t host_sample_rate;
84 +
extern bool enable_midline;
85 +
86 +
extern bool has_midi_card;
87 +
extern uint16_t midi_card_addr;
88 +
89 +
extern void machine_dump(const char* reason);
90 +
extern void machine_reset();
91 +
extern void machine_nmi();
92 +
extern void machine_paste(char *text, bool handle_free);
93 +
extern void machine_toggle_warp();
94 +
extern void init_audio();
95 +
extern void main_shutdown();
96 +
97 +
extern bool video_is_tilemap_address(int addr);
98 +
extern bool video_is_tiledata_address(int addr);
99 +
extern bool video_is_special_address(int addr);
100 +
101 +
extern int ieee_unit;
102 +
extern bool using_hostfs;
103 +
extern uint8_t activity_led;
104 +
extern bool nvram_dirty;
105 +
extern uint8_t nvram[0x40];
106 +
107 +
extern uint8_t MHZ;
108 +
109 +
extern bool mouse_grabbed;
110 +
extern bool no_keyboard_capture;
111 +
extern bool kernal_mouse_enabled;
112 +
extern char window_title[];
113 +
extern bool pwr_long_press;
114 +
extern bool is_gen2;
115 +
#endif
added src/i2c.c +412 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2021 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdio.h>
6 +
#include <stdbool.h>
7 +
#include "i2c.h"
8 +
#include "smc.h"
9 +
#include "rtc.h"
10 +
11 +
#define LOG_LEVEL 0
12 +
13 +
#define DEVICE_SMC 0x42
14 +
#define DEVICE_RTC 0x6F
15 +
16 +
#define STATE_START 0
17 +
#define STATE_STOP -1
18 +
19 +
i2c_port_t i2c_port;
20 +
21 +
static int state = STATE_STOP;
22 +
static bool read_mode = false;
23 +
static uint8_t value = 0;
24 +
static int count = 0;
25 +
static uint8_t device;
26 +
27 +
#define KBD_SIZE 16
28 +
uint8_t kbd_buffer[KBD_SIZE];						//Ring buffer for key codes
29 +
30 +
#define MSE_SIZE 16
31 +
uint8_t mse_buffer[MSE_SIZE];						//Ring buffer for mouse movement data
32 +
33 +
void i2c_reset_state() {
34 +
	state = STATE_STOP;
35 +
	read_mode = false;
36 +
	value = 0;
37 +
	count = 0;
38 +
	smc_requested_reset = false;
39 +
	i2c_mse_buffer_flush();
40 +
	i2c_kbd_buffer_flush();
41 +
}
42 +
43 +
static void
44 +
i2c_data(uint8_t device, uint8_t v) {
45 +
	switch (device) {
46 +
		case DEVICE_SMC:
47 +
			smc_i2c_data(v);
48 +
			break;
49 +
		case DEVICE_RTC:
50 +
			rtc_i2c_data(v);
51 +
			break;
52 +
		default:
53 +
			break;
54 +
	}
55 +
#if LOG_LEVEL >= 1
56 +
	printf("I2C DATA($%02X) => $%02X\n", device, value);
57 +
#endif
58 +
}
59 +
60 +
static uint8_t
61 +
i2c_read(uint8_t device) {
62 +
	uint8_t value;
63 +
	switch (device) {
64 +
		case DEVICE_SMC:
65 +
			value = smc_read();
66 +
			break;
67 +
		case DEVICE_RTC:
68 +
			value = rtc_read();
69 +
			break;
70 +
		default:
71 +
			value = 0xff;
72 +
			break;
73 +
	}
74 +
#if LOG_LEVEL >= 1
75 +
	printf("I2C READ($%02X) = $%02X\n", device, value);
76 +
#endif
77 +
	return value;
78 +
}
79 +
80 +
static void
81 +
i2c_write(uint8_t device) {
82 +
	switch (device) {
83 +
		case DEVICE_SMC:
84 +
			smc_write();
85 +
			break;
86 +
		case DEVICE_RTC:
87 +
			rtc_write();
88 +
			break;
89 +
			//        default:
90 +
			// no-op
91 +
	}
92 +
#if LOG_LEVEL >= 1
93 +
	printf("I2C WRITE $%02X\n", device);
94 +
#endif
95 +
}
96 +
97 +
void
98 +
i2c_step()
99 +
{
100 +
	static i2c_port_t old_i2c_port;
101 +
102 +
	if (old_i2c_port.clk_in != i2c_port.clk_in || old_i2c_port.data_in != i2c_port.data_in) {
103 +
#if LOG_LEVEL >= 5
104 +
		printf("I2C(%d) C:%d D:%d\n", state, i2c_port.clk_in, i2c_port.data_in);
105 +
#endif
106 +
		if (state == STATE_STOP && i2c_port.clk_in == 0 && i2c_port.data_in == 0) {
107 +
#if LOG_LEVEL >= 2
108 +
			printf("I2C START\n");
109 +
#endif
110 +
			state = STATE_START;
111 +
		}
112 +
113 +
		if (state == 1 && i2c_port.clk_in == 1 &&i2c_port.data_in == 1 &&  old_i2c_port.data_in == 0) {
114 +
#if LOG_LEVEL >= 2
115 +
			printf("I2C STOP\n");
116 +
#endif
117 +
			state = STATE_STOP;
118 +
			count = 0;
119 +
			read_mode = false;
120 +
		}
121 +
122 +
		if (state != STATE_STOP && i2c_port.clk_in == 1 && old_i2c_port.clk_in == 0) {
123 +
			i2c_port.data_out = 1;
124 +
			if (state < 8) {
125 +
				if (read_mode) {
126 +
					if (state == 0) {
127 +
						value = i2c_read(device);
128 +
					}
129 +
					i2c_port.data_out = !!(value & 0x80);
130 +
					value <<= 1;
131 +
#if LOG_LEVEL >= 4
132 +
					printf("I2C OUT#%d: %d\n", state, i2c_port.data_out);
133 +
#endif
134 +
					state++;
135 +
				} else {
136 +
#if LOG_LEVEL >= 4
137 +
					printf("I2C BIT#%d: %d\n", state, i2c_port.data_in);
138 +
#endif
139 +
					value <<= 1;
140 +
					value |= i2c_port.data_in;
141 +
					state++;
142 +
				}
143 +
			} else { // state == 8
144 +
				if (read_mode) {
145 +
					bool nack = i2c_port.data_in;
146 +
					if (nack) {
147 +
#if LOG_LEVEL >= 3
148 +
					printf("I2C OUT DONE (NACK)\n");
149 +
#endif
150 +
						count = 0;
151 +
						read_mode = false;
152 +
					} else {
153 +
#if LOG_LEVEL >= 3
154 +
					printf("I2C OUT DONE (ACK)\n");
155 +
#endif
156 +
					}
157 +
				} else {
158 +
					bool ack = true;
159 +
					switch (count) {
160 +
						case 0:
161 +
							device = value >> 1;
162 +
							read_mode = value & 1;
163 +
							if (device != DEVICE_SMC && device != DEVICE_RTC) {
164 +
								ack = false;
165 +
							}
166 +
							break;
167 +
						case 1:
168 +
							i2c_data(device, value);
169 +
							break;
170 +
						default:
171 +
							i2c_data(device, value);
172 +
							i2c_write(device);
173 +
							break;
174 +
					}
175 +
					if (ack) {
176 +
#if LOG_LEVEL >= 3
177 +
						printf("I2C ACK(%d) $%02X\n", count, value);
178 +
#endif
179 +
						i2c_port.data_out = 0;
180 +
						count++;
181 +
					} else {
182 +
#if LOG_LEVEL >= 3
183 +
						printf("I2C NACK(%d) $%02X\n", count, value);
184 +
#endif
185 +
						count = 0;
186 +
						read_mode = false;
187 +
					}
188 +
				}
189 +
				state = STATE_START;
190 +
			}
191 +
		}
192 +
		old_i2c_port = i2c_port;
193 +
	}
194 +
}
195 +
196 +
197 +
/**
198 +
 * Keyboard buffer functions
199 +
 **/
200 +
uint8_t kbd_head=0;
201 +
uint8_t kbd_tail=0;
202 +
203 +
/**
204 +
 * Adds value to the keyboard ring buffer; the value is discarded if the buffer is full
205 +
 **/
206 +
void i2c_kbd_buffer_add(uint8_t value) {
207 +
	uint8_t next = (kbd_head + 1) & (KBD_SIZE - 1);		//Next available index
208 +
    if (next != kbd_tail) {								//next = tail => buffer full
209 +
    	kbd_buffer[kbd_head] = value;					//Set new value
210 +
        kbd_head = next;								//Update buffer head pointer
211 +
    }
212 +
}
213 +
214 +
/**
215 +
 * Returns the tail value from the keyboard ring buffer; if the buffer is empty, it returns 0
216 +
 **/
217 +
uint8_t i2c_kbd_buffer_next() {
218 +
	uint8_t value = 0;									//Prepare to return 0 of buffer is empty
219 +
	if (kbd_head != kbd_tail) {							//head = tail => empty
220 +
    	value = kbd_buffer[kbd_tail];					//Get value
221 +
        kbd_tail = (kbd_tail + 1) & (KBD_SIZE - 1);		//Update buffer tail pointer
222 +
    }
223 +
	return value;
224 +
}
225 +
226 +
/**
227 +
 * Clears the keyboard ring buffer
228 +
 **/
229 +
void i2c_kbd_buffer_flush() {
230 +
	kbd_tail = kbd_head = 0;
231 +
}
232 +
233 +
/**
234 +
 * Mouse buffer functions
235 +
 **/
236 +
uint8_t mse_head=0;
237 +
uint8_t mse_tail=0;
238 +
239 +
/**
240 +
 * Adds value to the mouse ring buffer; discards the value if the buffer is full
241 +
 **/
242 +
void i2c_mse_buffer_add(uint8_t value) {
243 +
	uint8_t next = (mse_head + 1) & (MSE_SIZE - 1);				//Next available index
244 +
    if (next != mse_tail) {										//next = tail => buffer full
245 +
    	mse_buffer[mse_head] = value;							//Set value
246 +
        mse_head = next;										//Update head pointer
247 +
    }
248 +
}
249 +
250 +
/**
251 +
 * Returns the tail value from the mouse ring buffer; if the buffer is empty, it returns 0
252 +
 **/
253 +
uint8_t i2c_mse_buffer_next() {
254 +
	uint8_t value = 0;											//Prepare to return 0 if buffer empty
255 +
	if (mse_head != mse_tail) {									//head = tail => buffer empty
256 +
    	value = mse_buffer[mse_tail];							//Get tail value
257 +
        mse_tail = (mse_tail + 1) & (MSE_SIZE - 1);				//Update tail pointer
258 +
    }
259 +
	return value;
260 +
}
261 +
262 +
/**
263 +
 * Clears the mouse ring buffer
264 +
 **/
265 +
void i2c_mse_buffer_flush() {
266 +
	mse_tail = mse_head = 0;
267 +
}
268 +
269 +
/**
270 +
 * Returns number of values currently stored in the ring buffer
271 +
 **/
272 +
uint8_t i2c_mse_buffer_count() {
273 +
    return (MSE_SIZE + mse_head - mse_tail) & (MSE_SIZE - 1);
274 +
}
275 +
276 +
277 +
/**
278 +
 *  fake mouse
279 +
 **/
280 +
281 +
static uint8_t buttons;
282 +
static int16_t mouse_diff_x = 0;
283 +
static int16_t mouse_diff_y = 0;
284 +
static int8_t wheel = 0;
285 +
static uint8_t mouse_device_id = 3;
286 +
287 +
// byte 0, bit 7: Y overflow
288 +
// byte 0, bit 6: X overflow
289 +
// byte 0, bit 5: Y sign bit
290 +
// byte 0, bit 4: X sign bit
291 +
// byte 0, bit 3: Always 1
292 +
// byte 0, bit 2: Middle Btn
293 +
// byte 0, bit 1: Right Btn
294 +
// byte 0, bit 0: Left Btn
295 +
// byte 2:        X Movement
296 +
// byte 3:        Y Movement
297 +
298 +
static bool
299 +
mouse_send(int x, int y, int b, uint8_t w)
300 +
{
301 +
	uint8_t psize;
302 +
	if (mouse_device_id == 3 || mouse_device_id == 4) {
303 +
		psize = 4;
304 +
	}
305 +
	else {
306 +
		psize  = 3;
307 +
	}
308 +
309 +
	if (i2c_mse_buffer_count() < MSE_SIZE-psize) {
310 +
		uint8_t byte0 =
311 +
		    ((y >> 9) & 1) << 5 |
312 +
		    ((x >> 9) & 1) << 4 |
313 +
		    1 << 3 |
314 +
		    b;
315 +
		i2c_mse_buffer_add(byte0);
316 +
		i2c_mse_buffer_add(x);
317 +
		i2c_mse_buffer_add(y);
318 +
319 +
		if (mouse_device_id == 3 || mouse_device_id == 4) {
320 +
			i2c_mse_buffer_add(w);
321 +
		}
322 +
323 +
		return true;
324 +
	} else {
325 +
		return false;
326 +
	}
327 +
}
328 +
329 +
void
330 +
mouse_send_state(void)
331 +
{
332 +
	do {
333 +
		int send_diff_x = mouse_diff_x > 255 ? 255 : (mouse_diff_x < -256 ? -256 : mouse_diff_x);
334 +
		int send_diff_y = mouse_diff_y > 255 ? 255 : (mouse_diff_y < -256 ? -256 : mouse_diff_y);
335 +
336 +
		mouse_send(send_diff_x, send_diff_y, buttons, wheel);
337 +
338 +
		mouse_diff_x -= send_diff_x;
339 +
		mouse_diff_y -= send_diff_y;
340 +
		wheel = 0;
341 +
	} while (mouse_diff_x != 0 || mouse_diff_y != 0);   /* flush BOTH axes (was &&:
342 +
	                              a single-axis residual was left undelivered) */
343 +
}
344 +
345 +
void
346 +
mouse_button_down(int num)
347 +
{
348 +
	buttons |= 1 << num;
349 +
}
350 +
351 +
void
352 +
mouse_button_up(int num)
353 +
{
354 +
	buttons &= (1 << num) ^ 0xff;
355 +
}
356 +
357 +
uint8_t
358 +
mouse_get_buttons(void)
359 +
{
360 +
	return buttons;   /* bit0 = left/primary, bit1 = right/secondary, bit2 = middle */
361 +
}
362 +
363 +
void
364 +
mouse_move(int x, int y)
365 +
{
366 +
	mouse_diff_x += x;
367 +
	mouse_diff_y -= y;
368 +
}
369 +
370 +
uint8_t
371 +
mouse_read(uint8_t reg)
372 +
{
373 +
	return 0xff;
374 +
}
375 +
376 +
void 
377 +
mouse_set_wheel(int8_t y)
378 +
{
379 +
	if (mouse_device_id == 3 || mouse_device_id == 4) {
380 +
		if (y < -7) {
381 +
			wheel = 7;
382 +
		}
383 +
		else if (y > 8) {
384 +
			wheel = -8;
385 +
		}
386 +
		else {
387 +
			wheel = -y;
388 +
		}
389 +
	}
390 +
}
391 +
392 +
uint8_t 
393 +
mouse_get_device_id() {
394 +
	return mouse_device_id;
395 +
}
396 +
397 +
void 
398 +
mouse_set_device_id(uint8_t d) {
399 +
	switch(d) {
400 +
		case 0:
401 +
		case 3:
402 +
			mouse_device_id = d;
403 +
			break;
404 +
		case 4:
405 +
			mouse_device_id =3;
406 +
			break;
407 +
		default:
408 +
			mouse_device_id = 0;
409 +
			break;
410 +
	}
411 +
	i2c_mse_buffer_flush();
412 +
}
added src/i2c.h +44 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2021 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _I2C_H_
6 +
#define _I2C_H_
7 +
8 +
#include <stdint.h>
9 +
10 +
#define I2C_DATA_MASK 1
11 +
#define I2C_CLK_MASK 2
12 +
13 +
typedef struct {
14 +
	int clk_in;
15 +
	int data_in;
16 +
	int data_out;
17 +
} i2c_port_t;
18 +
19 +
extern i2c_port_t i2c_port;
20 +
21 +
void i2c_reset_state();
22 +
void i2c_step();
23 +
24 +
void i2c_kbd_buffer_add(uint8_t value);
25 +
uint8_t i2c_kbd_buffer_next();
26 +
void i2c_kbd_buffer_flush();
27 +
28 +
void i2c_mse_buffer_add(uint8_t value);
29 +
uint8_t i2c_mse_buffer_next();
30 +
void i2c_mse_buffer_flush();
31 +
uint8_t i2c_mse_buffer_count();
32 +
33 +
// fake mouse
34 +
void mouse_button_down(int num);
35 +
void mouse_button_up(int num);
36 +
uint8_t mouse_get_buttons(void);
37 +
void mouse_move(int x, int y);
38 +
uint8_t mouse_read(uint8_t reg);
39 +
void mouse_send_state(void);
40 +
void mouse_set_wheel(int8_t y);
41 +
uint8_t mouse_get_device_id();
42 +
void mouse_set_device_id(uint8_t d);
43 +
44 +
#endif
added src/icon.c +230 −0
1 +
#include "icon.h"
2 +
3 +
//#define LOGO_56
4 +
#define LOGO_83
5 +
6 +
#if defined(LOGO_56)
7 +
static char pixels[] =
8 +
	"................................................................................................"
9 +
	"................................................................................................"
10 +
	"................................................................................................"
11 +
	"................................................................................................"
12 +
	"................................................................................................"
13 +
	"................................................................................................"
14 +
	"................................................................................................"
15 +
	"................................................................................................"
16 +
	"................................................................................................"
17 +
	"................................................................................................"
18 +
	"................................................................................................"
19 +
	"................................................................................................"
20 +
	"................................................................................................"
21 +
	"................................................................................................"
22 +
	"................................................................................................"
23 +
	"................................................................................................"
24 +
	"................................................................................................"
25 +
	"................................................................................................"
26 +
	"................................................................................................"
27 +
	"................................................................................................"
28 +
	"................................................................................................"
29 +
	"....................@......................................................@...................."
30 +
	"....................@@....................................................@@...................."
31 +
	"....................@@@..................................................@@@...................."
32 +
	"....................@@@@................................................@@@@...................."
33 +
	"....................@@@@@..............................................@@@@@...................."
34 +
	"....................@@@@@@............................................@@@@@@...................."
35 +
	"....................@@@@@@@..........................................@@@@@@@...................."
36 +
	"......................######........................................######......................"
37 +
	"......................#######......................................#######......................"
38 +
	"......................########....................................########......................"
39 +
	"......................#########..................................#########......................"
40 +
	"......................##########................................##########......................"
41 +
	"......................###########..............................###########......................"
42 +
	"......................############............................############......................"
43 +
	"......................#############..........................#############......................"
44 +
	".......................*************........................*************......................."
45 +
	".......................**************......................**************......................."
46 +
	".......................***************....................***************......................."
47 +
	".......................****************..................****************......................."
48 +
	".......................*****************................*****************......................."
49 +
	".......................******************..............******************......................."
50 +
	".......................*******************............*******************......................."
51 +
	".......................********************..........********************......................."
52 +
	"............................%%%%%%%%%%%%%%%%........%%%%%%%%%%%%%%%%............................"
53 +
	"............................%%%%%%%%%%%%%%%%........%%%%%%%%%%%%%%%%............................"
54 +
	"....................................%%%%%%%%........%%%%%%%%...................................."
55 +
	"....................................%%%%%%%%........%%%%%%%%...................................."
56 +
	"........................................%%%%........%%%%........................................"
57 +
	"........................................%%%%........%%%%........................................"
58 +
	"........................................%%%%........%%%%........................................"
59 +
	"........................................%%%%........%%%%........................................"
60 +
	"........................................&&&&........&&&&........................................"
61 +
	"........................................&&&&........&&&&........................................"
62 +
	"........................................&&&&........&&&&........................................"
63 +
	"........................................&&&&........&&&&........................................"
64 +
	"....................................&&&&&&&&........&&&&&&&&...................................."
65 +
	"....................................&&&&&&&&........&&&&&&&&...................................."
66 +
	"............................&&&&&&&&&&&&&&&&........&&&&&&&&&&&&&&&&............................"
67 +
	"............................&&&&&&&&&&&&&&&&........&&&&&&&&&&&&&&&&............................"
68 +
	"..........................$$$$$$$$$$$$$$$$$$........$$$$$$$$$$$$$$$$$$.........................."
69 +
	"..........................$$$$$$$$$$$$$$$$$..........$$$$$$$$$$$$$$$$$.........................."
70 +
	"..........................$$$$$$$$$$$$$$$$............$$$$$$$$$$$$$$$$.........................."
71 +
	"..........................$$$$$$$$$$$$$$$..............$$$$$$$$$$$$$$$.........................."
72 +
	"..........................$$$$$$$$$$$$$$................$$$$$$$$$$$$$$.........................."
73 +
	"..........................$$$$$$$$$$$$$..................$$$$$$$$$$$$$.........................."
74 +
	"..........................$$$$$$$$$$$$....................$$$$$$$$$$$$.........................."
75 +
	"..........................$$$$$$$$$$$......................$$$$$$$$$$$.........................."
76 +
	".........................+++++++++++........................+++++++++++........................."
77 +
	".........................++++++++++..........................++++++++++........................."
78 +
	".........................+++++++++............................+++++++++........................."
79 +
	".........................++++++++..............................++++++++........................."
80 +
	".........................+++++++................................+++++++........................."
81 +
	".........................++++++..................................++++++........................."
82 +
	".........................+++++....................................+++++........................."
83 +
	".........................++++......................................++++........................."
84 +
	"................................................................................................"
85 +
	"................................................................................................"
86 +
	"................................................................................................"
87 +
	"................................................................................................"
88 +
	"................................................................................................"
89 +
	"................................................................................................"
90 +
	"................................................................................................"
91 +
	"................................................................................................"
92 +
	"................................................................................................"
93 +
	"................................................................................................"
94 +
	"................................................................................................"
95 +
	"................................................................................................"
96 +
	"................................................................................................"
97 +
	"................................................................................................"
98 +
	"................................................................................................"
99 +
	"................................................................................................"
100 +
	"................................................................................................"
101 +
	"................................................................................................"
102 +
	"................................................................................................"
103 +
	"................................................................................................";
104 +
#endif
105 +
106 +
#if defined(LOGO_83)
107 +
static char pixels[] =
108 +
	"................................................................................................"
109 +
	"................................................................................................"
110 +
	"................................................................................................"
111 +
	"................................................................................................"
112 +
	"................................................................................................"
113 +
	"....@@....................................................................................@@...."
114 +
	"....@@@..................................................................................@@@...."
115 +
	"....@@@@................................................................................@@@@...."
116 +
	"....@@@@@..............................................................................@@@@@...."
117 +
	"....@@@@@@............................................................................@@@@@@...."
118 +
	"....@@@@@@@..........................................................................@@@@@@@...."
119 +
	"....@@@@@@@@........................................................................@@@@@@@@...."
120 +
	"....@@@@@@@@@......................................................................@@@@@@@@@...."
121 +
	"....@@@@@@@@@@....................................................................@@@@@@@@@@...."
122 +
	"....@@@@@@@@@@@..................................................................@@@@@@@@@@@...."
123 +
	"....@@@@@@@@@@@@................................................................@@@@@@@@@@@@...."
124 +
	"....@@@@@@@@@@@@@..............................................................@@@@@@@@@@@@@...."
125 +
	"....@@@@@@@@@@@@@@............................................................@@@@@@@@@@@@@@...."
126 +
	"........###########..........................................................###########........"
127 +
	"........############........................................................############........"
128 +
	"........#############......................................................#############........"
129 +
	"........##############....................................................##############........"
130 +
	"........###############..................................................###############........"
131 +
	"........################................................................################........"
132 +
	"........#################..............................................#################........"
133 +
	"........##################............................................##################........"
134 +
	"........###################..........................................###################........"
135 +
	"........####################........................................####################........"
136 +
	"........#####################......................................#####################........"
137 +
	"........######################....................................######################........"
138 +
	"...........********************..................................********************..........."
139 +
	"...........*********************................................*********************..........."
140 +
	"...........**********************..............................**********************..........."
141 +
	"...........***********************............................***********************..........."
142 +
	"...........************************..........................************************..........."
143 +
	"...........*************************........................*************************..........."
144 +
	"...........**************************......................**************************..........."
145 +
	"...........***************************....................***************************..........."
146 +
	"...........****************************..................****************************..........."
147 +
	"...........*****************************................*****************************..........."
148 +
	"...........******************************..............******************************..........."
149 +
	"...........*******************************............*******************************..........."
150 +
	"..................%%%%%%%%%%%%%%%%%%%%%%%%%..........%%%%%%%%%%%%%%%%%%%%%%%%%.................."
151 +
	"..................%%%%%%%%%%%%%%%%%%%%%%%%%..........%%%%%%%%%%%%%%%%%%%%%%%%%.................."
152 +
	"..................%%%%%%%%%%%%%%%%%%%%%%%%%..........%%%%%%%%%%%%%%%%%%%%%%%%%.................."
153 +
	"..............................%%%%%%%%%%%%%..........%%%%%%%%%%%%%.............................."
154 +
	"..............................%%%%%%%%%%%%%..........%%%%%%%%%%%%%.............................."
155 +
	"..............................%%%%%%%%%%%%%..........%%%%%%%%%%%%%.............................."
156 +
	"....................................%%%%%%%..........%%%%%%%...................................."
157 +
	"....................................%%%%%%%..........%%%%%%%...................................."
158 +
	"....................................%%%%%%%..........%%%%%%%...................................."
159 +
	"....................................%%%%%%%..........%%%%%%%...................................."
160 +
	"....................................%%%%%%%..........%%%%%%%...................................."
161 +
	"....................................%%%%%%%..........%%%%%%%...................................."
162 +
	"....................................&&&&&&&..........&&&&&&&...................................."
163 +
	"....................................&&&&&&&..........&&&&&&&...................................."
164 +
	"....................................&&&&&&&..........&&&&&&&...................................."
165 +
	"....................................&&&&&&&..........&&&&&&&...................................."
166 +
	"....................................&&&&&&&..........&&&&&&&...................................."
167 +
	"....................................&&&&&&&..........&&&&&&&...................................."
168 +
	"..............................&&&&&&&&&&&&&..........&&&&&&&&&&&&&.............................."
169 +
	"..............................&&&&&&&&&&&&&..........&&&&&&&&&&&&&.............................."
170 +
	"..............................&&&&&&&&&&&&&..........&&&&&&&&&&&&&.............................."
171 +
	"..................&&&&&&&&&&&&&&&&&&&&&&&&&..........&&&&&&&&&&&&&&&&&&&&&&&&&.................."
172 +
	"..................&&&&&&&&&&&&&&&&&&&&&&&&&..........&&&&&&&&&&&&&&&&&&&&&&&&&.................."
173 +
	"..................&&&&&&&&&&&&&&&&&&&&&&&&&..........&&&&&&&&&&&&&&&&&&&&&&&&&.................."
174 +
	"...............$$$$$$$$$$$$$$$$$$$$$$$$$$$$..........$$$$$$$$$$$$$$$$$$$$$$$$$$$$..............."
175 +
	"...............$$$$$$$$$$$$$$$$$$$$$$$$$$$............$$$$$$$$$$$$$$$$$$$$$$$$$$$..............."
176 +
	"...............$$$$$$$$$$$$$$$$$$$$$$$$$$..............$$$$$$$$$$$$$$$$$$$$$$$$$$..............."
177 +
	"...............$$$$$$$$$$$$$$$$$$$$$$$$$................$$$$$$$$$$$$$$$$$$$$$$$$$..............."
178 +
	"...............$$$$$$$$$$$$$$$$$$$$$$$$..................$$$$$$$$$$$$$$$$$$$$$$$$..............."
179 +
	"...............$$$$$$$$$$$$$$$$$$$$$$$....................$$$$$$$$$$$$$$$$$$$$$$$..............."
180 +
	"...............$$$$$$$$$$$$$$$$$$$$$$......................$$$$$$$$$$$$$$$$$$$$$$..............."
181 +
	"...............$$$$$$$$$$$$$$$$$$$$$........................$$$$$$$$$$$$$$$$$$$$$..............."
182 +
	"...............$$$$$$$$$$$$$$$$$$$$..........................$$$$$$$$$$$$$$$$$$$$..............."
183 +
	"...............$$$$$$$$$$$$$$$$$$$............................$$$$$$$$$$$$$$$$$$$..............."
184 +
	"...............$$$$$$$$$$$$$$$$$$..............................$$$$$$$$$$$$$$$$$$..............."
185 +
	"...............$$$$$$$$$$$$$$$$$................................$$$$$$$$$$$$$$$$$..............."
186 +
	"............+++++++++++++++++++..................................+++++++++++++++++++............"
187 +
	"............++++++++++++++++++....................................++++++++++++++++++............"
188 +
	"............+++++++++++++++++......................................+++++++++++++++++............"
189 +
	"............++++++++++++++++........................................++++++++++++++++............"
190 +
	"............+++++++++++++++..........................................+++++++++++++++............"
191 +
	"............++++++++++++++............................................++++++++++++++............"
192 +
	"............+++++++++++++..............................................+++++++++++++............"
193 +
	"............++++++++++++................................................++++++++++++............"
194 +
	"............+++++++++++..................................................+++++++++++............"
195 +
	"............++++++++++....................................................++++++++++............"
196 +
	"............+++++++++......................................................+++++++++............"
197 +
	"............++++++++........................................................++++++++............"
198 +
	"............+++++++..........................................................+++++++............"
199 +
	"................................................................................................"
200 +
	"................................................................................................"
201 +
	"................................................................................................"
202 +
	"................................................................................................"
203 +
	"................................................................................................";
204 +
#endif
205 +
206 +
SDL_Surface *CommanderX16Icon()
207 +
{
208 +
	const SDL_Color color[8] = {
209 +
		{ 0x00, 0x00, 0xAA, 0x00 },
210 +
		{ 0x88, 0x00, 0x00, 0xFF },
211 +
		{ 0xCC, 0x44, 0xCC, 0xFF },
212 +
		{ 0x00, 0x88, 0xFF, 0xFF },
213 +
		{ 0xDD, 0x88, 0x55, 0xFF },
214 +
		{ 0x00, 0xCC, 0x55, 0xFF },
215 +
		{ 0xEE, 0xEE, 0x77, 0xFF },
216 +
		{ 0xAA, 0xFF, 0xEE, 0xFF },
217 +
	};
218 +
219 +
	SDL_Surface *surface;
220 +
	surface = SDL_CreateRGBSurfaceFrom(pixels, 96, 96, 8, 96, 0, 0, 0, 0);
221 +
	SDL_SetPaletteColors(surface->format->palette, color+0, '.', 1);
222 +
	SDL_SetPaletteColors(surface->format->palette, color+1, '+', 1);
223 +
	SDL_SetPaletteColors(surface->format->palette, color+2, '@', 1);
224 +
	SDL_SetPaletteColors(surface->format->palette, color+3, '#', 1);
225 +
	SDL_SetPaletteColors(surface->format->palette, color+4, '$', 1);
226 +
	SDL_SetPaletteColors(surface->format->palette, color+5, '%', 1);
227 +
	SDL_SetPaletteColors(surface->format->palette, color+6, '&', 1);
228 +
	SDL_SetPaletteColors(surface->format->palette, color+7, '*', 1);
229 +
	return surface;
230 +
}
added src/icon.h +12 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _ICON_H_
6 +
#define _ICON_H_
7 +
8 +
#include <SDL.h>
9 +
10 +
SDL_Surface *CommanderX16Icon();
11 +
12 +
#endif
added src/ieee.c +2211 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2022, 2023 Michael Steil, et al
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
// Commodore Bus emulation
6 +
// * L2: TALK/LISTEN layer: https://www.pagetable.com/?p=1031
7 +
// * L3: Commodore DOS: https://www.pagetable.com/?p=1038
8 +
// This is used from
9 +
// * serial.c: L1: Serial Bus emulation (low level)
10 +
// * main.c: IEEE KERNAL call hooks (high level)
11 +
12 +
#ifndef __APPLE__
13 +
#define _XOPEN_SOURCE   600
14 +
#define _POSIX_C_SOURCE 1
15 +
#endif
16 +
#include <string.h>
17 +
#include <stdio.h>
18 +
#include <stdbool.h>
19 +
#include <sys/stat.h>
20 +
#include <dirent.h>
21 +
#include <unistd.h>
22 +
#include <stdlib.h>
23 +
#include <SDL.h>
24 +
#include <errno.h>
25 +
#include <time.h>
26 +
#include <limits.h>
27 +
#include "memory.h"
28 +
#include "ieee.h"
29 +
#include "glue.h"
30 +
#include "utf8_encode.h"
31 +
#include "utf8.h"
32 +
#include "iso_8859_15.h"
33 +
#ifdef __MINGW32__
34 +
#include <direct.h>
35 +
// Windows just has to be different
36 +
#define localtime_r(S,D) !localtime_s(D,S)
37 +
#include <io.h>
38 +
#define F_OK 0
39 +
#define access _access
40 +
#endif
41 +
42 +
extern SDL_RWops *prg_file;
43 +
44 +
#define WILDCARD_ALL 0
45 +
#define WILDCARD_PRG 1
46 +
#define WILDCARD_DIR 2
47 +
48 +
// Globals
49 +
50 +
//bool log_ieee = true;
51 +
bool log_ieee = false;
52 +
53 +
bool ieee_initialized_once = false;
54 +
55 +
uint8_t error[256];
56 +
int error_len = 0;
57 +
int error_pos = 0;
58 +
uint8_t cmd[256];
59 +
int cmdlen = 0;
60 +
int namelen = 0;
61 +
int channel = 0;
62 +
bool listening = false;
63 +
bool talking = false;
64 +
bool opening = false;
65 +
bool overwrite = false;
66 +
bool path_exists = false;
67 +
bool prg_consumed = false;
68 +
69 +
int ieee_unit = 8;
70 +
71 +
uint8_t *hostfscwd = NULL;
72 +
73 +
uint8_t dirlist[1024]; // Plenty large to hold a single entry
74 +
int dirlist_len = 0;
75 +
int dirlist_pos = 0;
76 +
bool dirlist_cwd = false; // whether we're doing a cwd dirlist or a normal one
77 +
bool dirlist_eof = true;
78 +
bool dirlist_timestmaps = false;
79 +
bool dirlist_long = false;
80 +
DIR *dirlist_dirp;
81 +
uint8_t dirlist_wildcard[256];
82 +
uint8_t dirlist_type_filter;
83 +
84 +
uint16_t cbdos_flags = 0;
85 +
86 +
const char *blocks_free = "BLOCKS FREE.";
87 +
88 +
typedef struct {
89 +
	uint8_t name[80];
90 +
	bool read;
91 +
	bool write;
92 +
	SDL_RWops *f;
93 +
} channel_t;
94 +
95 +
channel_t channels[16];
96 +
97 +
#ifdef __MINGW32__
98 +
// realpath doesn't exist on Windows. This function implements its behavior.
99 +
static char *
100 +
realpath(const char *path, char *resolved_path) {
101 +
	char *ret = _fullpath(resolved_path, path, PATH_MAX);
102 +
103 +
	if (ret && _access(ret,0) && errno == ENOENT) {
104 +
		if (resolved_path == NULL)
105 +
			free(ret);
106 +
		return NULL;
107 +
	}
108 +
109 +
	return ret;
110 +
}
111 +
#endif
112 +
113 +
#define u8strchr(A,B) (uint8_t *)strchr((char *)A,B)
114 +
#define u8strrchr(A,B) (uint8_t *)strrchr((char *)A,B)
115 +
#define u8strcpy(A,B) strcpy((char *)A,(char *)B)
116 +
#define u8strcmp(A,B) strcmp((char *)A,(char *)B)
117 +
#define u8strncmp(A,B,C) strncmp((char *)A,(char *)B,C)
118 +
#define u8strncpy(A,B,C) strncpy((char *)A,(char *)B,C)
119 +
#define u8strlen(A) strlen((char *)A)
120 +
#define u8stat(A,B) stat((char *)A,B)
121 +
#define u8realpath(A,B) (uint8_t *)realpath((char *)A,(char *)B)
122 +
#define u8getcwd(A,B) (uint8_t *)getcwd((char *)A,B)
123 +
124 +
// Prototypes for some of the static functions
125 +
126 +
static void clear_error();
127 +
static void set_error(int e, int t, int s);
128 +
static void cchdir(uint8_t *dir);
129 +
static int cgetcwd(uint8_t *buf, size_t len);
130 +
static void cseek(int channel, uint32_t pos);
131 +
static void ctell(int channel);
132 +
static void cmkdir(uint8_t *dir);
133 +
static void crmdir(uint8_t *dir);
134 +
static void cunlink(uint8_t *f);
135 +
static void crename(uint8_t *f);
136 +
137 +
// Functions
138 +
139 +
static void
140 +
set_kernal_cbdos_flags(uint8_t flags)
141 +
{
142 +
	if (cbdos_flags)
143 +
		write6502(cbdos_flags, 0, flags);
144 +
}
145 +
146 +
static uint8_t
147 +
get_kernal_cbdos_flags(void)
148 +
{
149 +
	if (cbdos_flags) {
150 +
		return read6502(cbdos_flags, 0);
151 +
	} else {
152 +
		return 0;
153 +
	}
154 +
}
155 +
156 +
157 +
static void
158 +
utf8_to_iso_string(uint8_t *dst, const uint8_t *src)
159 +
{
160 +
	int i;
161 +
	int e;
162 +
	uint32_t cp;
163 +
164 +
	// utf8_decode requires the source string be at least 4 bytes
165 +
	// longer than the string (IOW, 4 terminating nulls)
166 +
	uint8_t *s = malloc(u8strlen(src)+4);
167 +
	memset(s, 0, u8strlen(src)+4);
168 +
	u8strcpy(s, src);
169 +
170 +
	uint8_t *so = s;
171 +
172 +
	for (i = 0; *so; i++) {
173 +
		so = utf8_decode(so, &cp, &e);
174 +
		if (e) {
175 +
			dst[i] = '?';
176 +
		} else {
177 +
			switch (cp) {
178 +
				case ',':
179 +
				case '"':
180 +
				case '*':
181 +
				case ':':
182 +
				case '\\':
183 +
				case '<':
184 +
				case '>':
185 +
				case '|':
186 +
					// these are not valid in filenames on FAT32 but might be encountered on hostfs
187 +
					dst[i] = '?';
188 +
					break;
189 +
				default:
190 +
					dst[i] = iso8859_15_from_unicode(cp);
191 +
					break;
192 +
			}
193 +
		}
194 +
	}
195 +
196 +
	dst[i] = 0;
197 +
	free(s);
198 +
}
199 +
200 +
// Puts the emulated cwd in buf, up to the maximum length specified by len
201 +
// Turn null termination into a space
202 +
// This is for displaying in the directory header
203 +
static int
204 +
cgetcwd(uint8_t *buf, size_t len)
205 +
{
206 +
	int o = 0;
207 +
	uint8_t l = fsroot_path[u8strlen(fsroot_path)-1];
208 +
	if (l == '/' || l == '\\')
209 +
		o--;
210 +
	if (u8strlen(fsroot_path) == u8strlen(hostfscwd))
211 +
		u8strncpy(buf, "/", len);
212 +
	else 
213 +
		u8strncpy(buf, hostfscwd+u8strlen(fsroot_path)+o, len);
214 +
	// Turn backslashes into slashes
215 +
	for (o = 0; o < len; o++) {
216 +
		if (buf[o] == 0) buf[o] = ' ';
217 +
		if (buf[o] == '\\') buf[o] = '/';
218 +
	}
219 +
220 +
	return 0;
221 +
}
222 +
223 +
static uint32_t
224 +
case_fold_unicode(uint32_t cp)
225 +
{
226 +
	// ASCII letters, and most of the ISO letters
227 +
	if ((cp >= 0x41 && cp <= 0x5a) || (cp >= 0xc0 && cp <= 0xde)) cp += 0x20;
228 +
	// fold the Š
229 +
	else if (cp == 0x0160) cp = 0x0161;
230 +
	// fold the Ž
231 +
	else if (cp == 0x017d) cp = 0x017e;
232 +
	// fold the Œ
233 +
	else if (cp == 0x0152) cp = 0x0153;
234 +
	// fold the Ÿ
235 +
	else if (cp == 0x0178) cp = 0xff;
236 +
237 +
	return cp;
238 +
}
239 +
240 +
static uint8_t
241 +
case_fold_iso(uint8_t c) {
242 +
	uint8_t f = c;
243 +
	// ASCII letters, and most of the ISO letters
244 +
	if ((f >= 0x41 && f <= 0x5a) || (f >= 0xc0 && f <= 0xde)) f += 0x20;
245 +
	// fold the Š
246 +
	else if (f == 0xa6) f = 0xa8;
247 +
	// fold the Ž
248 +
	else if (f == 0xb4) f = 0xb8;
249 +
	// fold the Œ
250 +
	else if (f == 0xbc) f = 0xbd;
251 +
	// fold the Ÿ
252 +
	else if (f == 0xbe) f = 0xff;
253 +
254 +
	return f;
255 +
}
256 +
257 +
258 +
static uint32_t
259 +
utf8_to_codepoint(uint8_t *str, int *off)
260 +
{
261 +
	uint32_t cp;
262 +
	int e;
263 +
264 +
	// utf8_decode requires the source string be at least 4 bytes
265 +
	// longer than the string (IOW, 4 terminating nulls)
266 +
	uint8_t *s = malloc(u8strlen(str+(*off))+4);
267 +
	memset(s, 0, u8strlen(str+(*off))+4);
268 +
	u8strcpy(s, str+(*off));
269 +
	
270 +
	uint8_t *so = utf8_decode(s, &cp, &e);
271 +
272 +
	*off += (so - s) - 1;
273 +
	free(s);
274 +
275 +
	if (e) {
276 +
		cp = '?';
277 +
	}
278 +
279 +
	return cp;
280 +
}
281 +
282 +
283 +
// compare two characters in a UTF-8 string
284 +
// in a case-insensitive manner,
285 +
// return value is cp2 - cp1 after case folding
286 +
// hence will be zero on match
287 +
static int
288 +
u8compare_utf8_char_i(uint8_t *str1, uint8_t *str2, int *off1, int *off2)
289 +
{
290 +
	uint32_t cp1, cp2;
291 +
292 +
	// these functions will increment *off1/*off2 to the
293 +
	// byte before the next character if the UTF-8
294 +
	// strings are multi-byte
295 +
	cp1 = utf8_to_codepoint(str1, off1);
296 +
	cp2 = utf8_to_codepoint(str2, off2);
297 +
298 +
	return case_fold_unicode(cp2) - case_fold_unicode(cp1);
299 +
}
300 +
301 +
302 +
static uint8_t *
303 +
parse_dos_filename(const uint8_t *name, bool dirhandling)
304 +
{
305 +
	// in case the name starts with something with special meaning,
306 +
	// such as @0:
307 +
	uint8_t *name_ptr = NULL;
308 +
	uint8_t *newname = malloc(u8strlen(name)+1);
309 +
	int i, j;
310 +
311 +
	newname[u8strlen(name)] = 0;
312 +
313 +
	overwrite = false;
314 +
315 +
	// [[@][<media 0-9>][</relative_path/> | <//absolute_path/>]:]<file_path>[*]
316 +
	// Examples of valid dos filenames
317 +
	//   ":FILE.PRG"  (same as "FILE.PRG")
318 +
	//   "@:FILE.PRG"  (same as "FILE.PRG" but overwrite okay)
319 +
	//   "@0:FILE.PRG"  (same as above)
320 +
	//   "//DIR/:FILE.PRG"  (same as "/DIR/FILE.PRG")
321 +
	//   "/DIR/:FILE.PRG"  (same as "./DIR/FILE.PRG")
322 +
	//   "FILE*" (matches the first file in the directory which starts with "FILE")
323 +
324 +
	// This routine only parses the bits before the ':'
325 +
	// and normalizes directory parts by attaching them to the name part
326 +
327 +
	// resolve_path*() is responsible for resolving absolute and relative
328 +
	// paths, and for processing the wildcard option
329 +
330 +
	name_ptr = u8strchr(name,':');
331 +
	if (dirhandling && !name_ptr)
332 +
		name_ptr = (uint8_t *)(name+u8strlen(name));
333 +
334 +
	if (name_ptr) {
335 +
		name_ptr++;
336 +
		i = 0;
337 +
		j = 0;
338 +
339 +
340 +
		if (!dirhandling) {
341 +
			// @ is overwrite flag
342 +
			if (name[i] == '@') {
343 +
				overwrite = true;
344 +
				i++;
345 +
			}
346 +
347 +
			// Medium, we don't care what this number is
348 +
			while (name[i] >= '0' && name[i] <= '9')
349 +
				i++;
350 +
		}
351 +
352 +
		// Directory name
353 +
		if (name[i] == '/') {
354 +
			i++;
355 +
			for (; name+i+1 < name_ptr; i++, j++) {
356 +
				newname[j] = name[i];
357 +
			}
358 +
359 +
			// mangled directory name (single slash?)
360 +
			if (j == 0) {
361 +
				free(newname);
362 +
				return NULL;
363 +
			}
364 +
365 +
			// Directory portion must end with /
366 +
			if (newname[j-1] != '/') {
367 +
				free(newname);
368 +
				return NULL;
369 +
			}
370 +
		}
371 +
372 +
		u8strcpy(newname+j,name_ptr);
373 +
374 +
	} else {
375 +
		u8strcpy(newname,name);
376 +
	}
377 +
378 +
	return newname;
379 +
}
380 +
381 +
// Returns a ptr to malloc()ed space which must be free()d later, or NULL
382 +
static uint8_t *
383 +
resolve_path_utf8(const uint8_t *name, bool must_exist, int wildcard_filetype)
384 +
{
385 +
	path_exists = false;
386 +
	clear_error();
387 +
	// Resolve the filename in the context of the emulated cwd
388 +
	// allocate plenty of string space
389 +
	uint8_t *tmp = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
390 +
	uint8_t *tmp2 = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
391 +
	uint8_t *c;
392 +
	uint8_t *d;
393 +
	uint8_t *ret;
394 +
	DIR *dirp;
395 +
	struct dirent *dp;
396 +
	struct stat st;
397 +
	int i;
398 +
	int j;
399 +
	bool has_wildcard_chars = false;
400 +
401 +
	if (tmp == NULL || tmp2 == NULL) {
402 +
		if (tmp) free(tmp);
403 +
		if (tmp2) free(tmp2);
404 +
		set_error(0x70, 0, 0);
405 +
		return NULL;
406 +
	}
407 +
408 +
	// If the filename begins with /, simply append it to the fsroot_path,
409 +
	// slash(es) and all, otherwise append it to the cwd, but with a /
410 +
	// in between
411 +
	if (name[0] == '/' || name[0] == '\\') { // absolute
412 +
		u8strcpy(tmp, fsroot_path);
413 +
		u8strcpy(tmp+u8strlen(fsroot_path), name);
414 +
	} else { // relative
415 +
		u8strcpy(tmp, hostfscwd);
416 +
		tmp[u8strlen(hostfscwd)] = '/';
417 +
		u8strcpy(tmp+u8strlen(hostfscwd)+1, name);
418 +
	}
419 +
420 +
	// keep the original parsed name
421 +
	u8strcpy(tmp2, tmp);
422 +
423 +
	if (u8strchr(tmp,'*') || u8strchr(tmp,'?')) { // oh goodie, a wildcard
424 +
		has_wildcard_chars = true;
425 +
	}
426 +
	// We have to search the directory for the first occurrence
427 +
	// in directory order.
428 +
429 +
	c = u8strrchr(tmp,'\\');
430 +
	d = u8strrchr(tmp,'/');
431 +
432 +
	if (c == NULL && d == NULL) { // This should never happen
433 +
		free(tmp);
434 +
		free(tmp2);
435 +
		set_error(0x62, 0, 0);
436 +
		return NULL;
437 +
	}
438 +
439 +
	// Chop off ret at the last path separator
440 +
	// and set c to point at the element with the wildcard
441 +
	// pattern
442 +
	if (c > d) {
443 +
		*c = 0;
444 +
		c++;
445 +
	} else {
446 +
		*d = 0;
447 +
		c = d+1;
448 +
	}
449 +
450 +
	// XXX
451 +
	//
452 +
	// tmp could have been populated with path parts that have not been checked
453 +
	// this will fail on case-sensitive filesystems
454 +
	// but if the user did not specify a path part in their filename
455 +
	// we should be able to resolve the new part of the name
456 +
	// we'll probably want to fix this at some point
457 +
	// but it requires some thought
458 +
459 +
	if (!(dirp = opendir((char *)tmp))) { // Directory couldn't be opened
460 +
		free(tmp);
461 +
		free(tmp2);
462 +
		set_error(0x62, 0, 0);
463 +
		return NULL;
464 +
	}
465 +
466 +
	ret = NULL;
467 +
468 +
	bool found = false;
469 +
470 +
	while ((dp = readdir(dirp))) {
471 +
		// in a wildcard match that starts at first position, leading dot filenames are not considered
472 +
		if ((*c == '*' || *c == '?') && *(dp->d_name) == '.')
473 +
			continue;
474 +
		for (i = 0, j = 0; j < u8strlen(c) && i < u8strlen(dp->d_name); i++, j++) {
475 +
			if (c[j] == '*') {
476 +
				found = true;
477 +
				break;
478 +
			} else if (c[j] == '?') {
479 +
				// '?' needs to eat the extra UTF-8 bytes if applicable
480 +
				if (((dp->d_name)[i] & 0xe0) == 0xc0) i++;
481 +
				else if (((dp->d_name)[i] & 0xf0) == 0xe0) i+=2;
482 +
				else if (((dp->d_name)[i] & 0xf8) == 0xf0) i+=3;
483 +
				continue;
484 +
			} else if (u8compare_utf8_char_i(c, (uint8_t *)(dp->d_name), &j, &i)) {
485 +
				break;
486 +
			}
487 +
		}
488 +
489 +
		// If we reach the end of both strings, it's a match
490 +
		if (i == u8strlen(dp->d_name) && j == u8strlen(c))
491 +
			found = true;
492 +
493 +
		// If we reach the end of the filename, but the next char
494 +
		// in the search string is *, then it's also a match
495 +
		else if (i == u8strlen(dp->d_name) && c[j] == '*')
496 +
			found = true;
497 +
498 +
		if (found) { // simple wildcard match
499 +
			ret = malloc(u8strlen(tmp)+u8strlen(dp->d_name)+2);
500 +
			if (ret == NULL) { // memory allocation error
501 +
				free(tmp);
502 +
				free(tmp2);
503 +
				closedir(dirp);
504 +
				set_error(0x70, 0, 0);
505 +
				return NULL;
506 +
			}
507 +
			u8strcpy(ret, tmp);
508 +
			ret[u8strlen(tmp)] = '/';
509 +
			u8strcpy(ret+u8strlen(tmp)+1, dp->d_name);
510 +
			if (wildcard_filetype) {
511 +
				u8stat(ret, &st);
512 +
				// in a wildcard match where the filetype is wrong, mark as not found
513 +
				// and continue
514 +
				if (wildcard_filetype == WILDCARD_DIR && !S_ISDIR(st.st_mode)) {
515 +
					free(ret);
516 +
					ret = NULL;
517 +
					found = false;
518 +
					continue;
519 +
				} else if (wildcard_filetype == WILDCARD_PRG && !S_ISREG(st.st_mode)) {
520 +
					free(ret);
521 +
					ret = NULL;
522 +
					found = false;
523 +
					continue;
524 +
				}
525 +
			}
526 +
			break;
527 +
		}
528 +
	}
529 +
	closedir(dirp);
530 +
531 +
	free(tmp);
532 +
533 +
	if (ret) { // We had a match
534 +
		free(tmp2);
535 +
		tmp = ret;
536 +
	} else if (has_wildcard_chars) {
537 +
		// our original query had * or ? and we didn't find it
538 +
		free(tmp2);
539 +
		set_error(0x62, 0, 0);
540 +
		return NULL;
541 +
	} else { // reset tmp to the original name
542 +
		tmp = tmp2;
543 +
	}
544 +
545 +
	// now resolve the path using OS routines
546 +
	ret = u8realpath(tmp, NULL);
547 +
	free(tmp);
548 +
549 +
	if (ret == NULL) {
550 +
		if (must_exist) {
551 +
			// path wasn't found or had another error in construction
552 +
			set_error(0x62, 0, 0);
553 +
		} else {
554 +
			// path does not exist, but as long as everything but the final
555 +
			// path element exists, we're still okay.
556 +
			tmp = malloc(u8strlen(name)+1);
557 +
			if (tmp == NULL) {
558 +
				set_error(0x70, 0, 0);
559 +
				return NULL;
560 +
			}
561 +
			u8strcpy(tmp, name);
562 +
			c = u8strrchr(tmp, '/');
563 +
			d = u8strrchr(tmp, '\\');
564 +
			if (c > d) {
565 +
				*c = 0;
566 +
			} else if (d != NULL) {
567 +
				*d = 0;
568 +
				c = d;
569 +
			}
570 +
571 +
			// assemble a path with what we have left
572 +
			ret = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
573 +
			if (ret == NULL) {
574 +
				free(tmp);
575 +
				set_error(0x70, 0, 0);
576 +
				return NULL;
577 +
			}
578 +
579 +
			if (name[0] == '/' || name[0] == '\\') { // absolute
580 +
				u8strcpy(ret, fsroot_path);
581 +
				if (c == tmp) { // leading slash was the only slash
582 +
					*tmp = name[0];
583 +
					c = NULL;
584 +
				}
585 +
				u8strcpy(ret+u8strlen(fsroot_path), tmp);
586 +
			} else { // relative
587 +
				u8strcpy(ret, hostfscwd);
588 +
				*(ret+u8strlen(hostfscwd)) = '/';
589 +
				u8strcpy(ret+u8strlen(hostfscwd)+1, tmp);
590 +
			}
591 +
592 +
			free(tmp);
593 +
594 +
			// if we found a path separator in the name string (non-leading)
595 +
			// we check everything up to that final separator
596 +
			if (c != NULL) {
597 +
				tmp = u8realpath(ret, NULL);
598 +
				free(ret);
599 +
				if (tmp == NULL) {
600 +
					// missing parent path element too
601 +
					set_error(0x62, 0, 0);
602 +
					ret = NULL;
603 +
				} else {
604 +
					free(tmp);
605 +
					// found everything up to the parent path element
606 +
					// restore ret to original case
607 +
					ret = malloc(u8strlen(name)+u8strlen(hostfscwd)+2);
608 +
					if (ret == NULL) {
609 +
						set_error(0x70, 0, 0);
610 +
						return NULL;
611 +
					}
612 +
					u8strcpy(ret, hostfscwd);
613 +
					ret[u8strlen(hostfscwd)] = '/';
614 +
					u8strcpy(ret+u8strlen(hostfscwd)+1, name);
615 +
				}
616 +
			}
617 +
		}
618 +
	} else {
619 +
		path_exists = true;
620 +
	}
621 +
622 +
	if (ret == NULL)
623 +
		return ret;
624 +
625 +
	// Prevent resolving outside the fsroot_path
626 +
	if (u8strlen(fsroot_path) > u8strlen(ret)) {
627 +
		free(ret);
628 +
		set_error(0x62, 0, 0);
629 +
		return NULL;
630 +
	} else if (u8strncmp(fsroot_path, ret, u8strlen(fsroot_path))) {
631 +
		free(ret);
632 +
		set_error(0x62, 0, 0);
633 +
		return NULL;
634 +
	} else if (u8strlen(fsroot_path) < u8strlen(ret) &&
635 +
	           fsroot_path[u8strlen(fsroot_path)-1] != '/' &&
636 +
			   fsroot_path[u8strlen(fsroot_path)-1] != '\\' &&
637 +
	           ret[u8strlen(fsroot_path)] != '/' &&
638 +
	           ret[u8strlen(fsroot_path)] != '\\')
639 +
	{
640 +
		// ret matches beginning of fsroot_path,
641 +
		// end of fsroot_path is not a path-separator,
642 +
		// and next char in ret is not a path-separator
643 +
		// This could happen if
644 +
		//   fsroot_path == "/home/user/ba" and
645 +
		//   ret == "/home/user/bah".
646 +
		// This condition should be considered a jailbreak and we fail out
647 +
		free(ret);
648 +
		set_error(0x62, 0, 0);
649 +
		return NULL;
650 +
	}
651 +
652 +
	return ret;
653 +
}
654 +
655 +
656 +
static uint8_t *
657 +
resolve_path_iso(const uint8_t *name, bool must_exist, int wildcard_filetype)
658 +
{
659 +
	uint8_t *ret;
660 +
	uint8_t *buf;
661 +
	int i, j;
662 +
	uint32_t cp;
663 +
664 +
	buf = malloc(u8strlen(name)*3+1);
665 +
666 +
	for (i = 0, j = 0; name[i]; i++) {
667 +
		cp = unicode_from_iso8859_15(name[i]);
668 +
		j += utf8_encode((char *)buf+j, cp);
669 +
	}
670 +
671 +
	ret = resolve_path_utf8(buf, must_exist, wildcard_filetype);
672 +
	free(buf);
673 +
	return ret;
674 +
}
675 +
676 +
static int
677 +
create_directory_listing(uint8_t *data, uint8_t *dirstring)
678 +
{
679 +
	uint8_t *data_start = data;
680 +
681 +
	dirlist_eof = true;
682 +
	dirlist_cwd = false;
683 +
	int i = 0;
684 +
	int j;
685 +
686 +
	
687 +
	dirlist_timestmaps = false;
688 +
	dirlist_long = false;
689 +
	dirlist_type_filter = 0;
690 +
	dirlist_wildcard[0] = 0;
691 +
692 +
	// Here's where we parse out directory listing options
693 +
	// Such as "$=T:MATCH*=P"
694 +
695 +
	while (i < u8strlen(dirstring)) {
696 +
		if (dirstring[i] == ':' || i == 0) {
697 +
			i++;
698 +
			j = 0;
699 +
			while (i < u8strlen(dirstring)) {
700 +
				if (i == 1 && dirstring[i] == ':')
701 +
					i++;
702 +
				if (dirstring[i] == '=' || dirstring[i] == 0) {
703 +
					if (dirstring[++i] == 'D')
704 +
						dirlist_type_filter = 'D';
705 +
					else if (dirstring[i] == 'P' && i != 2)
706 +
						dirlist_type_filter = 'P';
707 +
					else if (dirstring[i] == 'T' && i == 2)
708 +
						dirlist_timestmaps = true;
709 +
					else if (dirstring[i] == 'L' && i == 2) {
710 +
						dirlist_timestmaps = true;
711 +
						dirlist_long = true;
712 +
					}
713 +
					break;
714 +
				} else {
715 +
					dirlist_wildcard[j++] = dirstring[i++];
716 +
					dirlist_wildcard[j] = 0;
717 +
				}
718 +
			}
719 +
		}
720 +
		i++;
721 +
	}
722 +
723 +
	// load address
724 +
	*data++ = 1;
725 +
	*data++ = 8;
726 +
	// link
727 +
	*data++ = 1;
728 +
	*data++ = 1;
729 +
	// line number
730 +
	*data++ = 0;
731 +
	*data++ = 0;
732 +
	*data++ = 0x12; // REVERSE ON
733 +
	*data++ = '"';
734 +
	for (int i = 0; i < 16; i++) {
735 +
		*data++ = ' ';
736 +
	}
737 +
	if (cgetcwd(data - 16, 16)) {
738 +
		return false;
739 +
	}
740 +
	*data++ = '"';
741 +
	*data++ = ' ';
742 +
	*data++ = 'H';
743 +
	*data++ = 'O';
744 +
	*data++ = 'S';
745 +
	*data++ = 'T';
746 +
	*data++ = ' ';
747 +
	*data++ = 0;
748 +
749 +
	if (!(dirlist_dirp = opendir((char *)hostfscwd))) {
750 +
		return 0;
751 +
	}
752 +
	dirlist_eof = false;
753 +
	return data - data_start;
754 +
}
755 +
756 +
static int
757 +
continue_directory_listing(uint8_t *data)
758 +
{
759 +
	uint8_t *data_start = data;
760 +
	struct stat st;
761 +
	struct dirent *dp;
762 +
	size_t file_size;
763 +
	uint8_t *tmpnam;
764 +
	bool found;
765 +
	int i;
766 +
767 +
	while ((dp = readdir(dirlist_dirp))) {
768 +
		// Type match
769 +
		if (dirlist_type_filter) {
770 +
			// Because resolving the path within the hostfs
771 +
			// logic is expensive, and could keep us here for multiple seconds
772 +
			// when directories are very large, it is much faster and gets us
773 +
			// past the negative matches of the filter ASAP if we do a quick
774 +
			// stat().
775 +
			//
776 +
			// The later logic can validate if the path resolves to something
777 +
			// within the hostfs root or not.
778 +
			size_t nl = u8strlen(dp->d_name);
779 +
			size_t pl = u8strlen(hostfscwd);
780 +
			uint8_t *tn = malloc(nl+pl+3);
781 +
			u8strcpy(tn, hostfscwd);
782 +
			*(tn+pl) = '/';
783 +
			u8strcpy(tn+pl+1, dp->d_name);
784 +
			u8stat(tn, &st);
785 +
			free(tn);
786 +
787 +
			switch (dirlist_type_filter) {
788 +
				case 'D':
789 +
					if (!S_ISDIR(st.st_mode))
790 +
						continue;
791 +
					break;
792 +
				case 'P':
793 +
					if (!S_ISREG(st.st_mode))
794 +
						continue;
795 +
					break;
796 +
			}
797 +
		}
798 +
799 +
		size_t namlen = u8strlen(dp->d_name);
800 +
		tmpnam = resolve_path_utf8((uint8_t *)dp->d_name, true, WILDCARD_ALL);
801 +
		if (tmpnam == NULL) continue;
802 +
		u8stat(tmpnam, &st);
803 +
		free(tmpnam);
804 +
805 +
		// don't show the . or .. in the root directory
806 +
		// this behaves like SD card/FAT32
807 +
		if (!u8strcmp("..",dp->d_name) || !u8strcmp(".",dp->d_name)) {
808 +
			if (!u8strcmp(hostfscwd,fsroot_path)) {
809 +
				continue;
810 +
			}
811 +
		}
812 +
813 +
		tmpnam = malloc(namlen+1);
814 +
		utf8_to_iso_string(tmpnam, (uint8_t *)dp->d_name);
815 +
816 +
		if (dirlist_wildcard[0]) { // wildcard match selected
817 +
			// in a wildcard match that starts at first position, leading dot filenames are not considered
818 +
			if ((dirlist_wildcard[0] == '*' || dirlist_wildcard[0] == '?') && *(tmpnam) == '.')
819 +
				continue;
820 +
821 +
			found = false;
822 +
			for (i = 0; i < u8strlen(dirlist_wildcard) && i < u8strlen(tmpnam); i++) {
823 +
				if (dirlist_wildcard[i] == '*') {
824 +
					found = true;
825 +
					break;
826 +
				} else if (dirlist_wildcard[i] == '?') {
827 +
					continue;
828 +
				} else if (case_fold_iso(dirlist_wildcard[i]) != case_fold_iso((tmpnam)[i])) {
829 +
					break;
830 +
				}
831 +
			}
832 +
833 +
			// If we reach the end of both strings, it's a match
834 +
			if (i == u8strlen(tmpnam) && i == u8strlen(dirlist_wildcard))
835 +
				found = true;
836 +
837 +
			// If we reach the end of the filename and the following character in
838 +
			// the wildcard string is *, it's also a match
839 +
			else if (i == u8strlen(tmpnam) && dirlist_wildcard[i] == '*')
840 +
				found = true;
841 +
842 +
			if (!found) continue;
843 +
		}
844 +
845 +
		// link
846 +
		*data++ = 1;
847 +
		*data++ = 1;
848 +
849 +
		if (dirlist_long) {
850 +
			unsigned char unit = 'K';
851 +
			file_size = (st.st_size + 1023)/1024;
852 +
			if (file_size > 0xFFFF) {
853 +
				file_size /= 1024;
854 +
				unit = 'M';
855 +
			}
856 +
			if (file_size > 0xFFFF) {
857 +
				file_size /= 1024;
858 +
				unit = 'G';
859 +
			}
860 +
			if (file_size > 0xFFFF) {
861 +
				file_size = 0xFFFF;
862 +
			}
863 +
864 +
			*data++ = file_size & 0xFF;
865 +
			*data++ = file_size >> 8;
866 +
			*data++ = unit;
867 +
			*data++ = 'B';
868 +
		} else {
869 +
			file_size = (st.st_size + 255)/256;
870 +
			if (file_size > 0xFFFF) {
871 +
				file_size = 0xFFFF;
872 +
			}
873 +
			*data++ = file_size & 0xFF;
874 +
			*data++ = file_size >> 8;
875 +
		}
876 +
877 +
		if (file_size < 1000) {
878 +
			*data++ = ' ';
879 +
			if (file_size < 100) {
880 +
				*data++ = ' ';
881 +
				if (file_size < 10) {
882 +
					*data++ = ' ';
883 +
				}
884 +
			}
885 +
		}
886 +
		*data++ = '"';
887 +
		//if (namlen > 16) {
888 +
		//	namlen = 16; // TODO hack
889 +
		//}
890 +
		namlen = u8strlen(tmpnam);
891 +
		memcpy(data, tmpnam, namlen);
892 +
		data += namlen;
893 +
		*data++ = '"';
894 +
		for (int i = namlen; i < 16; i++) {
895 +
			*data++ = ' ';
896 +
		}
897 +
		*data++ = ' ';
898 +
		if (S_ISDIR(st.st_mode)) {
899 +
			*data++ = 'D';
900 +
			*data++ = 'I';
901 +
			*data++ = 'R';
902 +
		} else {
903 +
			*data++ = 'P';
904 +
			*data++ = 'R';
905 +
			*data++ = 'G';
906 +
		}
907 +
		// This would be a '<' if file were protected, but it's a space instead
908 +
		*data++ = ' ';
909 +
910 +
		if (dirlist_timestmaps) {
911 +
			// ISO-8601 date+time
912 +
			struct tm mtime;
913 +
			if (localtime_r(&st.st_mtime, &mtime)) {
914 +
				*data++ = ' '; // space before the date
915 +
				data += strftime((char *)data, 20, "%Y-%m-%d %H:%M:%S", &mtime);
916 +
				*data++ = ' '; // space after the date
917 +
			}
918 +
		}
919 +
920 +
		if (dirlist_long) {
921 +
			// attribute and size go at the end if dirlist_long is active
922 +
			uint8_t attrbyte = 0;
923 +
			if (S_ISDIR(st.st_mode)) attrbyte |= 0x10;
924 +
			if (S_ISREG(st.st_mode)) attrbyte |= 0x20;
925 +
			if (!(st.st_mode & 0200)) attrbyte |= 0x01; // read-only
926 +
			size_t fullsize = st.st_size;
927 +
			if (fullsize > 0xffffffff) {
928 +
				fullsize = 0xffffffff;
929 +
			}
930 +
931 +
			data += sprintf((char *)data, "%02X %08X ", attrbyte, (unsigned int)fullsize);
932 +
		}
933 +
934 +
		free(tmpnam);
935 +
936 +
		*data++ = 0;
937 +
		return data - data_start;
938 +
	}
939 +
940 +
	// link
941 +
	*data++ = 1;
942 +
	*data++ = 1;
943 +
944 +
	*data++ = 255; // "65535"
945 +
	*data++ = 255;
946 +
947 +
	memcpy(data, blocks_free, u8strlen(blocks_free));
948 +
	data += u8strlen(blocks_free);
949 +
	*data++ = 0;
950 +
951 +
	// link
952 +
	*data++ = 0;
953 +
	*data++ = 0;
954 +
	(void)closedir(dirlist_dirp);
955 +
	dirlist_eof = true;
956 +
	return data - data_start;
957 +
}
958 +
959 +
960 +
static int
961 +
create_cwd_listing(uint8_t *data)
962 +
{
963 +
	uint8_t *data_start = data;
964 +
	int file_size;
965 +
966 +
	// load address
967 +
	*data++ = 1;
968 +
	*data++ = 8;
969 +
	// link
970 +
	*data++ = 1;
971 +
	*data++ = 1;
972 +
	// line number
973 +
	*data++ = 0;
974 +
	*data++ = 0;
975 +
	*data++ = 0x12; // REVERSE ON
976 +
	*data++ = '"';
977 +
	for (int i = 0; i < 16; i++) {
978 +
		*data++ = ' ';
979 +
	}
980 +
	if (cgetcwd(data - 16, 16)) {
981 +
		dirlist_eof = true;
982 +
		return 0;
983 +
	}
984 +
	*data++ = '"';
985 +
	*data++ = ' ';
986 +
	*data++ = 'H';
987 +
	*data++ = 'O';
988 +
	*data++ = 'S';
989 +
	*data++ = 'T';
990 +
	*data++ = ' ';
991 +
	*data++ = 0;
992 +
993 +
	uint8_t *tmp = malloc(u8strlen(hostfscwd)+1);
994 +
	if (tmp == NULL) {
995 +
		set_error(0x70, 0, 0);
996 +
		return 0;
997 +
	}
998 +
	int i = u8strlen(hostfscwd);
999 +
	int j = u8strlen(fsroot_path);
1000 +
	u8strcpy(tmp,hostfscwd);
1001 +
1002 +
	for(; i>= j-1; --i) {
1003 +
		// find the beginning of a path element
1004 +
		if (i >= j && tmp[i-1] != '/' && tmp[i-1] != '\\')
1005 +
			continue;
1006 +
		
1007 +
		tmp[i-1]=0;
1008 +
1009 +
		if (i < j) {
1010 +
			u8strcpy(tmp+i,"/");
1011 +
		}
1012 +
1013 +
		file_size = 0;
1014 +
		size_t namlen = u8strlen(tmp+i);
1015 +
1016 +
		if (!namlen) continue; // there was a doubled path separator
1017 +
1018 +
		// link
1019 +
		*data++ = 1;
1020 +
		*data++ = 1;
1021 +
1022 +
		*data++ = file_size & 0xFF;
1023 +
		*data++ = file_size >> 8;
1024 +
		if (file_size < 1000) {
1025 +
			*data++ = ' ';
1026 +
			if (file_size < 100) {
1027 +
				*data++ = ' ';
1028 +
				if (file_size < 10) {
1029 +
					*data++ = ' ';
1030 +
				}
1031 +
			}
1032 +
		}
1033 +
		*data++ = '"';
1034 +
1035 +
		memcpy(data, tmp+i, namlen);
1036 +
		data += namlen;
1037 +
		*data++ = '"';
1038 +
		for (int i = namlen; i < 16; i++) {
1039 +
			*data++ = ' ';
1040 +
		}
1041 +
		*data++ = ' ';
1042 +
		*data++ = 'D';
1043 +
		*data++ = 'I';
1044 +
		*data++ = 'R';
1045 +
		*data++ = 0;
1046 +
	}
1047 +
1048 +
	free(tmp);
1049 +
1050 +
	// link
1051 +
	*data++ = 1;
1052 +
	*data++ = 1;
1053 +
1054 +
	*data++ = 255; // "65535"
1055 +
	*data++ = 255;
1056 +
1057 +
	memcpy(data, blocks_free, u8strlen(blocks_free));
1058 +
	data += u8strlen(blocks_free);
1059 +
	*data++ = 0;
1060 +
1061 +
	// link
1062 +
	*data++ = 0;
1063 +
	*data++ = 0;
1064 +
1065 +
	dirlist_eof = true;
1066 +
	dirlist_cwd = true;
1067 +
1068 +
	return data - data_start;
1069 +
}
1070 +
1071 +
1072 +
1073 +
static const char*
1074 +
error_string(int e)
1075 +
{
1076 +
	switch(e) {
1077 +
		case 0x00:
1078 +
			return " OK";
1079 +
		case 0x01:
1080 +
			return " FILES SCRATCHED";
1081 +
		case 0x02:
1082 +
			return "PARTITION SELECTED";
1083 +
		// 0x2x: Physical disk error
1084 +
		case 0x20:
1085 +
			return "READ ERROR"; // generic read error
1086 +
		case 0x25:
1087 +
			return "WRITE ERROR"; // generic write error
1088 +
		case 0x26:
1089 +
			return "WRITE PROTECT ON";
1090 +
		// 0x3x: Error parsing the command
1091 +
		case 0x30: // generic
1092 +
		case 0x31: // invalid command
1093 +
		case 0x32: // command buffer overflow
1094 +
			return "SYNTAX ERROR";
1095 +
		case 0x33: // illegal filename
1096 +
			return "ILLEGAL FILENAME";
1097 +
		case 0x34: // empty file name
1098 +
			return "EMPTY FILENAME";
1099 +
		case 0x39: // subdirectory not found
1100 +
			return "SUBDIRECTORY NOT FOUND";
1101 +
		// 0x4x: Controller error (CMD addition)
1102 +
		case 0x49:
1103 +
			return "INVALID FORMAT"; // partition present, but not FAT32
1104 +
		// 0x5x: Relative file related error
1105 +
		// unsupported
1106 +
		// 0x6x: File error
1107 +
		case 0x62:
1108 +
			return " FILE NOT FOUND";
1109 +
		case 0x63:
1110 +
			return "FILE EXISTS";
1111 +
		// 0x7x: Generic disk or device error
1112 +
		case 0x70:
1113 +
			return "NO CHANNEL"; // error allocating context
1114 +
		case 0x71:
1115 +
			return "DIRECTORY ERROR"; // FAT error
1116 +
		case 0x72:
1117 +
			return "PARTITION FULL"; // filesystem full
1118 +
		case 0x73:
1119 +
			return "HOST FS V1.0 X16";
1120 +
		case 0x74:
1121 +
			return "DRIVE NOT READY"; // illegal partition for any command but "CP"
1122 +
		case 0x75:
1123 +
			return "FORMAT ERROR";
1124 +
		case 0x77:
1125 +
			return "SELECTED PARTITION ILLEGAL";
1126 +
		default:
1127 +
			return "";
1128 +
	}
1129 +
}
1130 +
1131 +
static void
1132 +
set_activity(bool active)
1133 +
{
1134 +
	uint8_t cbdos_flags = get_kernal_cbdos_flags();
1135 +
	if (active) {
1136 +
		cbdos_flags |= 0x10; // set activity flag
1137 +
	} else {
1138 +
		cbdos_flags &= ~0x10; // clear activity flag
1139 +
	}
1140 +
	set_kernal_cbdos_flags(cbdos_flags);
1141 +
}
1142 +
1143 +
static void
1144 +
set_error_text(int e, const char *text, int t, int s)
1145 +
{
1146 +
	snprintf((char *)error, sizeof(error), "%02x,%s,%02d,%02d\r", e, text, t, s);
1147 +
	error_len = u8strlen(error);
1148 +
	error_pos = 0;
1149 +
	uint8_t cbdos_flags = get_kernal_cbdos_flags();
1150 +
	if (e < 0x10 || e == 0x73) {
1151 +
		cbdos_flags &= ~0x20; // clear error
1152 +
	} else {
1153 +
		cbdos_flags |= 0x20; // set error flag
1154 +
	}
1155 +
	set_kernal_cbdos_flags(cbdos_flags);
1156 +
}
1157 +
1158 +
static void
1159 +
set_error(int e, int t, int s)
1160 +
{
1161 +
	set_error_text(e, error_string(e), t, s);
1162 +
}
1163 +
1164 +
static void
1165 +
clear_error()
1166 +
{
1167 +
	set_error(0, 0, 0);
1168 +
}
1169 +
1170 +
1171 +
static void
1172 +
command(uint8_t *cmd)
1173 +
{
1174 +
	if (!cmd[0]) {
1175 +
		return;
1176 +
	}
1177 +
	if (log_ieee) {
1178 +
		if (cmd[0] == 'P') {
1179 +
			printf("  COMMAND \"%c\" [binary parameters suppressed]\n", cmd[0]);
1180 +
		} else {
1181 +
			printf("  COMMAND \"%s\"\n", cmd);
1182 +
		}
1183 +
	}
1184 +
	switch(cmd[0]) {
1185 +
		case 'C': // C (copy), CD (change directory), CP (change partition)
1186 +
			switch(cmd[1]) {
1187 +
				case 'D': // Change directory
1188 +
					cchdir(cmd+2);
1189 +
					return;
1190 +
				case 'P': // Change partition
1191 +
					set_error(0x02, 0, 0);
1192 +
					return;
1193 +
				default: // Copy
1194 +
					// NYI
1195 +
					set_error(0x30, 0, 0);
1196 +
					return;
1197 +
			}
1198 +
		case 'I': // Initialize
1199 +
			clear_error();
1200 +
			return;
1201 +
		case 'M': // MD
1202 +
			switch(cmd[1]) {
1203 +
				case 'D': // Make directory
1204 +
					cmkdir(cmd+2);
1205 +
					return;
1206 +
				default: // Memory (not implemented)
1207 +
					set_error(0x31, 0, 0);
1208 +
					return;
1209 +
			}
1210 +
		case 'P': // Seek
1211 +
			cseek(cmd[1],
1212 +
				((uint8_t)cmd[2])
1213 +
				| ((uint8_t)cmd[3] << 8)
1214 +
				| ((uint8_t)cmd[4] << 16)
1215 +
				| ((uint8_t)cmd[5] << 24));
1216 +
			return;
1217 +
		case 'R': // RD
1218 +
			switch(cmd[1]) {
1219 +
				case 'D': // Remove directory
1220 +
					crmdir(cmd+2);
1221 +
					return;
1222 +
				default: // Rename 
1223 +
					crename(cmd); // Need to parse out the arg in this function
1224 +
					return;
1225 +
			}
1226 +
		case 'S':
1227 +
			switch(cmd[1]) {
1228 +
				case '-': // Swap
1229 +
					if (cmd[2] == '8' || cmd[2] == 'D') {
1230 +
						ieee_unit = 8;
1231 +
						clear_error();
1232 +
					} else if (cmd[2] == '9') {
1233 +
						ieee_unit = 9;
1234 +
						clear_error();
1235 +
					} else {
1236 +
						set_error(0x31, 0, 0);
1237 +
					}
1238 +
					return;
1239 +
				default: // Scratch
1240 +
					cunlink(cmd); // Need to parse out the arg in this function
1241 +
					return;
1242 +
			}	
1243 +
		case 'T': // Tell
1244 +
			ctell(cmd[1]);
1245 +
			return;
1246 +
		case 'U':
1247 +
			switch(cmd[1]) {
1248 +
				case 'I': // UI: Reset
1249 +
					set_error(0x73, 0, 0);
1250 +
					return;
1251 +
				case '0': // U0
1252 +
					if (cmd[2] == '>') {
1253 +
						if (cmd[3] >= 8 && cmd[3] <= 15) {
1254 +
							ieee_unit = cmd[3];
1255 +
							clear_error();
1256 +
							return;
1257 +
						}
1258 +
					}
1259 +
			}
1260 +
1261 +
		default:
1262 +
			if (log_ieee) {
1263 +
				printf("    (unsupported command ignored)\n");
1264 +
			}
1265 +
	}
1266 +
	set_error(0x30, 0, 0);
1267 +
}
1268 +
1269 +
static void
1270 +
cchdir(uint8_t *dir)
1271 +
{
1272 +
	// The directory name is in dir, coming from the command channel
1273 +
	// with the CD portion stripped off
1274 +
	uint8_t *parsed;
1275 +
	uint8_t *resolved;
1276 +
	struct stat st;
1277 +
1278 +
	if (!u8strcmp(dir,":_")) { // turn CD:← into CD:..
1279 +
		parsed = parse_dos_filename((uint8_t *)":..", true);
1280 +
	} else {
1281 +
		parsed = parse_dos_filename(dir, true);
1282 +
	}
1283 +
1284 +
	if (parsed == NULL) {
1285 +
		set_error(0x32, 0, 0);
1286 +
		return;
1287 +
	}
1288 +
	
1289 +
	if ((resolved = resolve_path_iso(parsed, true, WILDCARD_DIR)) == NULL) {
1290 +
		free(parsed);
1291 +
		// error already set
1292 +
		return;
1293 +
	}
1294 +
1295 +
	free(parsed);
1296 +
1297 +
	// Is it a directory?
1298 +
	if (u8stat(resolved, &st)) {
1299 +
		// FNF
1300 +
		free(resolved);
1301 +
		set_error(0x62, 0, 0);
1302 +
	} else if (!S_ISDIR(st.st_mode)) {
1303 +
		// Not a directory
1304 +
		free(resolved);
1305 +
		set_error(0x39, 0, 0);
1306 +
	} else {
1307 +
		// cwd has been changed
1308 +
		free(hostfscwd);
1309 +
		hostfscwd = resolved;
1310 +
	}
1311 +
1312 +
	return;
1313 +
}
1314 +
1315 +
static void
1316 +
cmkdir(uint8_t *dir)
1317 +
{
1318 +
	// The directory name is in dir, coming from the command channel
1319 +
	// with the MD portion stripped off
1320 +
	uint8_t *resolved;
1321 +
	uint8_t *parsed;
1322 +
1323 +
	parsed = parse_dos_filename(dir, true);
1324 +
1325 +
	if (parsed == NULL) {
1326 +
		set_error(0x32, 0, 0);
1327 +
		return;
1328 +
	}
1329 +
1330 +
	clear_error();
1331 +
	if ((resolved = resolve_path_iso(parsed, false, WILDCARD_DIR)) == NULL) {
1332 +
		free(parsed);
1333 +
		// error already set
1334 +
		return;
1335 +
	}
1336 +
1337 +
	free(parsed);
1338 +
#ifdef __MINGW32__
1339 +
	if (_mkdir((char *)resolved))
1340 +
#else
1341 +
	if (mkdir((char *)resolved,0777))
1342 +
#endif
1343 +
	{
1344 +
		if (errno == EEXIST) {
1345 +
			set_error(0x63, 0, 0);
1346 +
		} else {
1347 +
			set_error(0x62, 0, 0);
1348 +
		}
1349 +
	}
1350 +
1351 +
	free(resolved);
1352 +
1353 +
	return;
1354 +
}
1355 +
1356 +
static void
1357 +
crename(uint8_t *f)
1358 +
{
1359 +
	// This function receives the whole R command, which could be
1360 +
	// "R:NEW=OLD" or "RENAME:NEW=OLD" or anything in between
1361 +
	// let's simply find the first colon and chop it there
1362 +
	uint8_t *tmp = malloc(u8strlen(f)+1);
1363 +
	if (tmp == NULL) {
1364 +
		set_error(0x70, 0, 0);
1365 +
		return;
1366 +
	}
1367 +
	u8strcpy(tmp,f);
1368 +
	uint8_t *d = u8strchr(tmp,':');
1369 +
1370 +
	if (d == NULL) {
1371 +
		// No colon, not a valid rename command
1372 +
		free(tmp);
1373 +
		set_error(0x34, 0, 0);
1374 +
		return;
1375 +
	}
1376 +
1377 +
	d++; // one character after the colon
1378 +
1379 +
	// Now split on the = sign to find
1380 +
	uint8_t *s = u8strchr(d,'=');
1381 +
1382 +
	if (s == NULL) {
1383 +
		// No equals sign, not a valid rename command
1384 +
		free(tmp);
1385 +
		set_error(0x34, 0, 0);
1386 +
		return;
1387 +
	}
1388 +
1389 +
	*(s++) = 0; // null-terminate d and advance s
1390 +
	
1391 +
	uint8_t *src;
1392 +
	uint8_t *dst;
1393 +
1394 +
	clear_error();
1395 +
	if ((src = resolve_path_iso(s, true, WILDCARD_ALL)) == NULL) {
1396 +
		// source not found
1397 +
		free(tmp);
1398 +
		set_error(0x62, 0, 0);
1399 +
		return;
1400 +
	}
1401 +
1402 +
	if ((dst = resolve_path_iso(d, false, WILDCARD_ALL)) == NULL) {
1403 +
		// dest not found
1404 +
		free(tmp);
1405 +
		free(src);
1406 +
		set_error(0x39, 0, 0);
1407 +
		return;
1408 +
	}
1409 +
1410 +
	free(tmp); // we're now done with d and s (part of tmp)
1411 +
1412 +
	if (rename((char *)src, (char *)dst)) {
1413 +
		if (errno == EACCES) {
1414 +
			set_error(0x63, 0, 0);
1415 +
		} else if (errno == EINVAL) {
1416 +
			set_error(0x33, 0, 0);
1417 +
		} else {
1418 +
			set_error(0x62, 0, 0);
1419 +
		}
1420 +
	}
1421 +
1422 +
	free(src);
1423 +
	free(dst);
1424 +
1425 +
	return;
1426 +
}
1427 +
1428 +
1429 +
static void
1430 +
crmdir(uint8_t *dir)
1431 +
{
1432 +
	// The directory name is in dir, coming from the command channel
1433 +
	// with the RD: portion stripped off
1434 +
	uint8_t *resolved;
1435 +
	uint8_t *parsed;
1436 +
1437 +
	parsed = parse_dos_filename(dir, true);
1438 +
1439 +
	if (parsed == NULL) {
1440 +
		set_error(0x32, 0, 0);
1441 +
		return;
1442 +
	}
1443 +
1444 +
	clear_error();
1445 +
	if ((resolved = resolve_path_iso(parsed, true, WILDCARD_DIR)) == NULL) {
1446 +
		free(parsed);
1447 +
		set_error(0x39, 0, 0);
1448 +
		return;
1449 +
	}
1450 +
1451 +
	free(parsed);
1452 +
1453 +
	if (rmdir((char *)resolved)) {
1454 +
		if (errno == ENOTEMPTY || errno == EACCES) {
1455 +
			set_error(0x63, 0, 0);
1456 +
		} else {
1457 +
			set_error(0x62, 0, 0);
1458 +
		}
1459 +
	}
1460 +
1461 +
	free(resolved);
1462 +
1463 +
	return;
1464 +
}
1465 +
1466 +
1467 +
static void
1468 +
cunlink(uint8_t *f)
1469 +
{
1470 +
	// This function receives the whole S command, which could be
1471 +
	// "S:FILENAME" or "SCRATCH:FILENAME" or anything in between
1472 +
	// let's simply find the first colon and chop it there
1473 +
	// TODO path syntax and multiple files
1474 +
	uint8_t *tmp = malloc(u8strlen(f)+1);
1475 +
	if (tmp == NULL) {
1476 +
		set_error(0x70, 0, 0);
1477 +
		return;
1478 +
	}
1479 +
	u8strcpy(tmp,f);
1480 +
	uint8_t *fn = u8strchr(tmp,':');
1481 +
1482 +
	if (fn == NULL) {
1483 +
		// No colon, not a valid scratch command
1484 +
		free(tmp);
1485 +
		set_error(0x34, 0, 0);
1486 +
		return;
1487 +
	}
1488 +
1489 +
	fn++; // one character after the colon
1490 +
	uint8_t *resolved;
1491 +
1492 +
	clear_error();
1493 +
	if ((resolved = resolve_path_iso(fn, true, WILDCARD_PRG)) == NULL) {
1494 +
		free(tmp);
1495 +
		set_error(0x62, 0, 0);
1496 +
		return;
1497 +
	}
1498 +
1499 +
	free(tmp); // we're now done with fn (part of tmp)
1500 +
1501 +
	if (unlink((char *)resolved)) {
1502 +
		if (errno == EACCES) {
1503 +
			set_error(0x63, 0, 0);
1504 +
		} else {
1505 +
			set_error(0x62, 0, 0);
1506 +
		}
1507 +
	} else {
1508 +
		set_error(0x01, 0, 0); // 1 file scratched
1509 +
	}
1510 +
1511 +
	free(resolved);
1512 +
1513 +
	return;
1514 +
}
1515 +
1516 +
1517 +
static int
1518 +
copen(int channel)
1519 +
{
1520 +
	if (channel == 15) {
1521 +
		command(channels[channel].name);
1522 +
		return -1;
1523 +
	}
1524 +
1525 +
	uint8_t *resolved_filename = NULL;
1526 +
	uint8_t *parsed_filename = NULL;
1527 +
	int ret = -1;
1528 +
1529 +
	// decode ",P,W"-like suffix to know whether we're writing
1530 +
	bool append = false;
1531 +
	channels[channel].read = true;
1532 +
	channels[channel].write = false;
1533 +
	uint8_t *first = u8strchr(channels[channel].name, ',');
1534 +
	if (first) {
1535 +
		*first = 0; // truncate name here
1536 +
		uint8_t *second = u8strchr(first+1, ',');
1537 +
		if (second) {
1538 +
			switch(second[1]) {
1539 +
				case 'A':
1540 +
					append = true;
1541 +
					// fallthrough
1542 +
				case 'W':
1543 +
					channels[channel].read = false;
1544 +
					channels[channel].write = true;
1545 +
					break;
1546 +
				case 'M':
1547 +
					channels[channel].read = true;
1548 +
					channels[channel].write = true;
1549 +
					break;
1550 +
			}
1551 +
		}
1552 +
	}
1553 +
	if (channel <= 1) {
1554 +
		// channels 0 and 1 are magic
1555 +
		channels[channel].write = channel;
1556 +
		channels[channel].read = !channel;
1557 +
	}
1558 +
	if (log_ieee) {
1559 +
		printf("  OPEN \"%s\",%d (%s%s)\n", channels[channel].name, channel,
1560 +
			channels[channel].read ? "R" : "",
1561 +
			channels[channel].write ? "W" : "");
1562 +
	}
1563 +
	
1564 +
	if (channels[channel].name[0] == 0) { // empty filename
1565 +
		set_error(0x34, 0, 0);
1566 +
		return -2;
1567 +
	}
1568 +
1569 +
	if (!channels[channel].write && channels[channel].name[0] == '$') {	
1570 +
		dirlist_pos = 0;
1571 +
		if (!u8strncmp(channels[channel].name,"$=C",3)) {
1572 +
			// This emulates the behavior in the ROM code in
1573 +
			// https://github.com/X16Community/x16-rom/pull/5
1574 +
			dirlist_len = create_cwd_listing(dirlist);
1575 +
		} else {
1576 +
			dirlist_len = create_directory_listing(dirlist, channels[channel].name);
1577 +
		}
1578 +
	} else {
1579 +
		if (!u8strcmp(channels[channel].name, ":*") && prg_file && !prg_consumed) {
1580 +
			channels[channel].f = prg_file; // special case
1581 +
			prg_consumed = true;
1582 +
		} else {
1583 +
			if ((parsed_filename = parse_dos_filename(channels[channel].name, false)) == NULL) {
1584 +
				set_error(0x32, 0, 0); // Didn't parse out properly
1585 +
				return -2; 
1586 +
			}
1587 +
1588 +
			resolved_filename = resolve_path_iso(parsed_filename, false, WILDCARD_PRG);
1589 +
			free(parsed_filename);
1590 +
1591 +
			if (resolved_filename == NULL) {
1592 +
				// Resolve the path, if we get a null ptr back, error is already set.
1593 +
				return -2;
1594 +
			}
1595 +
1596 +
			if (path_exists && !overwrite && !append && !channels[channel].read) {
1597 +
				free(resolved_filename);
1598 +
				set_error(0x63, 0, 0); // forbid overwrite unless requested
1599 +
				return -1;
1600 +
			}
1601 +
		
1602 +
			if (append) {
1603 +
				channels[channel].f = SDL_RWFromFile((char *)resolved_filename, "ab+");
1604 +
			} else if (channels[channel].read && channels[channel].write) {
1605 +
				if (access((char *)resolved_filename, F_OK) == 0) {
1606 +
					channels[channel].f = SDL_RWFromFile((char *)resolved_filename, "rb+");
1607 +
				} else {
1608 +
					channels[channel].f = SDL_RWFromFile((char *)resolved_filename, "wb+");
1609 +
				}
1610 +
			} else {
1611 +
				channels[channel].f = SDL_RWFromFile((char *)resolved_filename, channels[channel].write ? "wb" : "rb");
1612 +
			}
1613 +
1614 +
			free(resolved_filename);
1615 +
		}
1616 +
1617 +
		if (!channels[channel].f) {
1618 +
			if (log_ieee) {
1619 +
				printf("  FILE NOT FOUND\n");
1620 +
			}
1621 +
			set_error(0x62, 0, 0);
1622 +
			ret = -2; // FNF
1623 +
		} else {
1624 +
			clear_error();
1625 +
		}
1626 +
	}
1627 +
	return ret;
1628 +
}
1629 +
1630 +
static void
1631 +
cclose(int channel)
1632 +
{
1633 +
	if (log_ieee) {
1634 +
		printf("  CLOSE %d\n", channel);
1635 +
	}
1636 +
	channels[channel].name[0] = 0;
1637 +
	if (channels[channel].f) {
1638 +
		SDL_RWclose(channels[channel].f);
1639 +
		channels[channel].f = NULL;
1640 +
	}
1641 +
}
1642 +
1643 +
static void
1644 +
cseek(int channel, uint32_t pos)
1645 +
{
1646 +
	if (channel == 15) {
1647 +
		set_error(0x30, 0, 0);
1648 +
		return;
1649 +
	}
1650 +
1651 +
	if (channels[channel].f) {
1652 +
		SDL_RWseek(channels[channel].f, pos, RW_SEEK_SET);
1653 +
	} else {
1654 +
		set_error(0x70, 0, 0);
1655 +
	}
1656 +
}
1657 +
1658 +
static void
1659 +
ctell(int channel)
1660 +
{
1661 +
	char buf[32];
1662 +
1663 +
	if (channel == 15) {
1664 +
		set_error(0x30, 0, 0);
1665 +
		return;
1666 +
	}
1667 +
1668 +
	if (channels[channel].f) {
1669 +
		uint64_t pos = SDL_RWtell(channels[channel].f);
1670 +
		uint64_t siz = SDL_RWsize(channels[channel].f);
1671 +
		if (pos > 0xffffffffULL) {
1672 +
			pos = 0xffffffff;
1673 +
		}
1674 +
		if (siz > 0xffffffffULL) {
1675 +
			siz = 0xffffffff;
1676 +
		}
1677 +
		snprintf((char *)buf, sizeof(buf), "%08X %08X", (uint32_t)pos, (uint32_t)siz);
1678 +
		set_error_text(0x07, buf, 0, 0);
1679 +
	} else {
1680 +
		set_error(0x70, 0, 0);
1681 +
	}
1682 +
}
1683 +
1684 +
void
1685 +
ieee_init()
1686 +
{
1687 +
1688 +
	int ch;
1689 +
1690 +
	if (!ieee_initialized_once) {
1691 +
		// Init the hostfs "jail" and cwd
1692 +
		if (fsroot_path == NULL) { // if null, default to cwd
1693 +
			// We hold this for the lifetime of the program, and we don't have
1694 +
			// any sort of destructor, so we rely on the OS teardown to free() it.
1695 +
			fsroot_path = u8getcwd(NULL, 0);
1696 +
		} else {
1697 +
			// Normalize it
1698 +
			fsroot_path = u8realpath(fsroot_path, NULL);
1699 +
		}
1700 +
1701 +
		if (startin_path == NULL) {
1702 +
			// same as above
1703 +
			startin_path = u8getcwd(NULL, 0);
1704 +
		} else {
1705 +
			// Normalize it
1706 +
			startin_path = u8realpath(startin_path, NULL);
1707 +
		}
1708 +
		// Quick error checks
1709 +
		if (fsroot_path == NULL) {
1710 +
			fprintf(stderr, "Failed to resolve argument to -fsroot\n");
1711 +
			exit(1);
1712 +
		}
1713 +
1714 +
		if (startin_path == NULL) {
1715 +
			fprintf(stderr, "Failed to resolve argument to -startin\n");
1716 +
			exit(1);
1717 +
		}
1718 +
1719 +
		// Now we verify that startin_path is within fsroot_path
1720 +
		// In other words, if fsroot_path is a left-justified substring of startin_path
1721 +
1722 +
		// If startin_path is not reachable, we instead default to setting it
1723 +
		// back to fsroot_path
1724 +
		if (u8strncmp(fsroot_path, startin_path, u8strlen(fsroot_path))) { // not equal
1725 +
			free(startin_path);
1726 +
			startin_path = fsroot_path;
1727 +
		}
1728 +
1729 +
		for (ch = 0; ch < 16; ch++) {
1730 +
			channels[ch].f = NULL;
1731 +
			channels[ch].name[0] = 0;
1732 +
		}
1733 +
1734 +
		ieee_initialized_once = true;
1735 +
	} else {
1736 +
		for (ch = 0; ch < 16; ch++) {
1737 +
			cclose(ch);
1738 +
		}
1739 +
1740 +
		listening = false;
1741 +
		talking = false;
1742 +
		opening = false;
1743 +
1744 +
		free(hostfscwd);
1745 +
	}
1746 +
1747 +
	// Now initialize our emulated cwd.
1748 +
	hostfscwd = malloc(u8strlen(startin_path)+1);
1749 +
	if (hostfscwd == NULL) {
1750 +
		fprintf(stderr, "Failed to allocate memory for hostfscwd\n");
1751 +
		exit(1);
1752 +
	}
1753 +
	u8strcpy(hostfscwd, startin_path);
1754 +
1755 +
	// Locate and remember cbdos_flags variable address in KERNAL vars
1756 +
	{
1757 +
		// check JMP instruction at ACPTR API
1758 +
		if (debug_read6502(0xffa5, 0, 0) != 0x4c) goto fail;
1759 +
1760 +
		// get address of ACPTR routine
1761 +
		uint16_t kacptr = debug_read6502(0xffa6, 0, 0) | debug_read6502(0xffa7, 0, 0) << 8;
1762 +
		if (kacptr < 0xc000) goto fail;
1763 +
1764 +
		// first instruction is BIT cbdos_flags
1765 +
		if (debug_read6502(kacptr, 0, 0) != 0x2c) goto fail;
1766 +
1767 +
		// get the address of cbdos_flags
1768 +
		cbdos_flags = debug_read6502(kacptr+1, 0, 0) | debug_read6502(kacptr+2, 0, 0) << 8;
1769 +
1770 +
		if (cbdos_flags < 0x0200 || cbdos_flags >= 0x0400) goto fail;
1771 +
		goto success;
1772 +
fail:
1773 +
		printf("Unable to find KERNAL cbdos_flags\n");
1774 +
		cbdos_flags = 0;
1775 +
1776 +
	}
1777 +
success:
1778 +
1779 +
	set_error(0x73, 0, 0);
1780 +
}
1781 +
1782 +
int
1783 +
SECOND(uint8_t a)
1784 +
{
1785 +
	int ret = -1;
1786 +
	if (listening) {
1787 +
		if (log_ieee) {
1788 +
			printf("%s $%02x\n", __func__, a);
1789 +
		}
1790 +
		channel = a & 0xf;
1791 +
		opening = false;
1792 +
		if (channel == 15)
1793 +
			ret = 0;
1794 +
		switch(a & 0xf0) {
1795 +
			case 0x60:
1796 +
				if (log_ieee) {
1797 +
					printf("  WRITE %d...\n", channel);
1798 +
				}
1799 +
				break;
1800 +
			case 0xe0:
1801 +
				cclose(channel);
1802 +
				break;
1803 +
			case 0xf0:
1804 +
				if (log_ieee) {
1805 +
					printf("  OPEN %d...\n", channel);
1806 +
				}
1807 +
				opening = true;
1808 +
				namelen = 0;
1809 +
				break;
1810 +
		}
1811 +
	} else {
1812 +
		ret = -2;	// Not listening, do not handle.
1813 +
	}
1814 +
	return ret;
1815 +
}
1816 +
1817 +
int
1818 +
TKSA(uint8_t a)
1819 +
{
1820 +
	int ret = -1;
1821 +
	if (talking) {
1822 +
		if (log_ieee) {
1823 +
			printf("%s $%02x\n", __func__, a);
1824 +
		}
1825 +
		channel = a & 0xf;
1826 +
	} else {
1827 +
		ret = -2;	// Not talking, do not handle.
1828 +
	}
1829 +
	return ret;
1830 +
}
1831 +
1832 +
1833 +
int
1834 +
ACPTR(uint8_t *a)
1835 +
{
1836 +
	int ret = 0;
1837 +
	if (talking) {
1838 +
		if (log_ieee) {
1839 +
			printf("%s-> $%02x\n", __func__, *a);
1840 +
		}
1841 +
		if (channel == 15) {
1842 +
			*a = error[error_pos++];
1843 +
			if (error_pos >= error_len) {
1844 +
				clear_error();
1845 +
				ret = 0x40; // EOI
1846 +
			}
1847 +
		} else if (channels[channel].read) {
1848 +
			if (channels[channel].name[0] == '$') {
1849 +
				if (dirlist_pos < dirlist_len) {
1850 +
					*a = dirlist[dirlist_pos++];
1851 +
				} else {
1852 +
					*a = 0;
1853 +
				}
1854 +
				if (dirlist_pos == dirlist_len) {
1855 +
					if (dirlist_eof) {
1856 +
						ret = 0x40;
1857 +
					} else {
1858 +
						dirlist_pos = 0;
1859 +
						dirlist_len = continue_directory_listing(dirlist);
1860 +
					}
1861 +
				}
1862 +
			} else if (channels[channel].f) {
1863 +
				if (SDL_RWread(channels[channel].f, a, 1, 1) != 1) {
1864 +
					ret = 0x42;
1865 +
					*a = 0;
1866 +
				} else {
1867 +
					// We need to send EOI on the last byte of the file.
1868 +
					// We have to check every time since CMDR-DOS
1869 +
					// supports random access R/W mode
1870 +
1871 +
					Sint64 curpos = SDL_RWtell(channels[channel].f);
1872 +
					if (curpos == SDL_RWseek(channels[channel].f, 0, RW_SEEK_END)) {
1873 +
						ret = 0x40;
1874 +
						channels[channel].read = false;
1875 +
						cclose(channel);
1876 +
					} else {
1877 +
						SDL_RWseek(channels[channel].f, curpos, RW_SEEK_SET);
1878 +
					}
1879 +
				}
1880 +
			} else {
1881 +
				ret = 0x42;
1882 +
			}
1883 +
		} else {
1884 +
			ret = 0x42; // FNF
1885 +
		}
1886 +
	} else {
1887 +
		ret = -2;	// Not talking, do not handle.
1888 +
	}
1889 +
	return ret;
1890 +
}
1891 +
1892 +
int
1893 +
CIOUT(uint8_t a)
1894 +
{
1895 +
	int ret = 0;
1896 +
	if (listening) {
1897 +
		if (log_ieee) {
1898 +
			printf("%s $%02x\n", __func__, a);
1899 +
		}
1900 +
		if (opening) {
1901 +
			if (namelen < sizeof(channels[channel].name) - 1) {
1902 +
				channels[channel].name[namelen++] = a;
1903 +
			}
1904 +
		} else {
1905 +
			if (channel == 15) {
1906 +
				// P/T commands take binary parameters, so we can't terminate
1907 +
				// the command on CR.
1908 +
				if ((a == 13) && (cmd[0] != 'P' && cmd[0] != 'T')) {
1909 +
					cmd[cmdlen] = 0;
1910 +
					command(cmd);
1911 +
					cmdlen = 0;
1912 +
				} else {
1913 +
					if (cmdlen < sizeof(cmd) - 1) {
1914 +
						cmd[cmdlen++] = a;
1915 +
					}
1916 +
				}
1917 +
			} else if (channels[channel].write && channels[channel].f) {
1918 +
				if (!SDL_WriteU8(channels[channel].f, a))
1919 +
					ret = 0x40;
1920 +
			} else {
1921 +
				ret = 2; // FNF
1922 +
			}
1923 +
		}
1924 +
	} else {
1925 +
		ret = -2;	// Not listening, do not handle.
1926 +
	}
1927 +
	return ret;
1928 +
}
1929 +
1930 +
int
1931 +
UNTLK() {
1932 +
	if (talking) {
1933 +
		if (log_ieee) {
1934 +
			printf("%s\n", __func__);
1935 +
		}
1936 +
		talking = false;
1937 +
		set_activity(false);
1938 +
		return -1;
1939 +
	} else {
1940 +
		return -2;
1941 +
	}
1942 +
}
1943 +
1944 +
int
1945 +
UNLSN() {
1946 +
	if (listening) {
1947 +
		if (log_ieee) {
1948 +
			printf("%s\n", __func__);
1949 +
		}
1950 +
		listening = false;
1951 +
		set_activity(false);
1952 +
		if (opening) {
1953 +
			channels[channel].name[namelen] = 0; // term
1954 +
			opening = false;
1955 +
			copen(channel);
1956 +
		} else if (channel == 15) {
1957 +
			cmd[cmdlen] = 0;
1958 +
			command(cmd);
1959 +
			cmdlen = 0;
1960 +
		}
1961 +
		return -1;
1962 +
	} else {
1963 +
		return -2;
1964 +
	}
1965 +
}
1966 +
1967 +
int
1968 +
LISTEN(uint8_t a)
1969 +
{
1970 +
	int ret = -1;
1971 +
	if ((a & 0x1f) == ieee_unit) {
1972 +
		if (log_ieee) {
1973 +
			printf("%s $%02x\n", __func__, a);
1974 +
		}
1975 +
		listening = true;
1976 +
		set_activity(true);
1977 +
	} else {
1978 +
		ret = -2;	// Not us, do not handle.
1979 +
	}
1980 +
	return ret;
1981 +
}
1982 +
1983 +
int
1984 +
TALK(uint8_t a)
1985 +
{
1986 +
	int ret = -1;
1987 +
	if ((a & 0x1f) == ieee_unit) {
1988 +
		if (log_ieee) {
1989 +
			printf("%s $%02x\n", __func__, a);
1990 +
		}
1991 +
		talking = true;
1992 +
		set_activity(true);
1993 +
	} else {
1994 +
		ret = -2;	// Not us, do not handle.
1995 +
	}
1996 +
	return ret;
1997 +
}
1998 +
1999 +
int
2000 +
MACPTR(uint16_t addr, uint16_t *c, uint8_t stream_mode)
2001 +
{
2002 +
	if (talking) {
2003 +
		int ret = 0;
2004 +
		int count = *c ?: 256;
2005 +
		uint8_t ram_bank = read6502(0, 0);
2006 +
		int i = 0;
2007 +
2008 +
		if (channel != 15 && channels[channel].read && channels[channel].name[0] != '$' && channels[channel].f) {
2009 +
			// find the end of the file so we know our EOF condition ahead of time
2010 +
			Sint64 curpos = SDL_RWtell(channels[channel].f);
2011 +
			Sint64 endpos = SDL_RWseek(channels[channel].f, 0, RW_SEEK_END);
2012 +
			SDL_RWseek(channels[channel].f, curpos, RW_SEEK_SET);
2013 +
2014 +
			do {
2015 +
				uint8_t byte = 0;
2016 +
				if (SDL_RWread(channels[channel].f, &byte, 1, 1) != 1) {
2017 +
					ret = 0x42;
2018 +
					byte = 0;
2019 +
				} else {
2020 +
					curpos++;
2021 +
					if (curpos == endpos) {
2022 +
						ret = 0x40;
2023 +
						channels[channel].read = false;
2024 +
						cclose(channel);
2025 +
					}
2026 +
2027 +
					write6502(addr, 0, byte);
2028 +
					i++;
2029 +
					if (!stream_mode) {
2030 +
						addr++;
2031 +
						if (addr == 0xc000) {
2032 +
							addr = 0xa000;
2033 +
							ram_bank++;
2034 +
							write6502(0, 0, ram_bank);
2035 +
						}
2036 +
					}
2037 +
				}
2038 +
				if (ret > 0) {
2039 +
					break;
2040 +
				}
2041 +
			} while(i < count);
2042 +
		} else {
2043 +
			ret = -3; // unsupported
2044 +
		}
2045 +
		*c = i;
2046 +
		return ret;
2047 +
	} else {
2048 +
		return -2; // not us, do not handle
2049 +
	}
2050 +
}
2051 +
2052 +
int
2053 +
MCIOUT(uint16_t addr, uint16_t *c, uint8_t stream_mode)
2054 +
{
2055 +
	if (listening) {
2056 +
		int ret = 0;
2057 +
		int count = *c ?: 256;
2058 +
		uint8_t ram_bank = read6502(0, 0);
2059 +
		int i = 0;
2060 +
		if (channels[channel].f && channels[channel].write) {
2061 +
			do {
2062 +
				uint8_t byte;
2063 +
				byte = read6502(addr, 0);
2064 +
				i++;
2065 +
				if (!stream_mode) {
2066 +
					addr++;
2067 +
					if (addr == 0xc000) {
2068 +
						addr = 0xa000;
2069 +
						ram_bank++;
2070 +
						write6502(0, 0, ram_bank);
2071 +
					}
2072 +
				}
2073 +
				ret = CIOUT(byte);
2074 +
				if (ret) {
2075 +
					break;
2076 +
				}
2077 +
			} while(i < count);
2078 +
		} else {
2079 +
			ret = -3; // unsupported
2080 +
		}
2081 +
		*c = i;
2082 +
		return ret;
2083 +
	} else {
2084 +
		return -2; // not us, do not handle
2085 +
	}
2086 +
}
2087 +
2088 +
int
2089 +
XMACPTR(uint8_t stream_mode) // stream_mode is only for passing into MACPTR fallback
2090 +
{
2091 +
	if (talking) {
2092 +
		int ret = 0;
2093 +
		int count = read6502(X16_REG_R2L, 0) | (read6502(X16_REG_R2H, 0) << 8);
2094 +
		uint16_t destaddr = read6502(X16_REG_R0L, 0) | (read6502(X16_REG_R0H, 0) << 8);
2095 +
		uint8_t destbnk = read6502(X16_REG_R1L, 0);
2096 +
2097 +
		if (count == 0) {
2098 +
			count = 65536;
2099 +
		}
2100 +
2101 +
		if (destbnk == 0) {
2102 +
			int s;
2103 +
			uint16_t cnt = count;
2104 +
			s = MACPTR(destaddr, &cnt, stream_mode);
2105 +
			if (s < 0) {
2106 +
				return s;
2107 +
			}
2108 +
			write6502(X16_REG_R2L, 0, cnt & 0xff);
2109 +
			write6502(X16_REG_R2H, 0, cnt >> 8);
2110 +
			return s;
2111 +
		}
2112 +
2113 +
		int i = 0;
2114 +
		if (channel != 15 && channels[channel].read && channels[channel].name[0] != '$' && channels[channel].f) {
2115 +
			// find the end of the file so we know our EOF condition ahead of time
2116 +
			Sint64 curpos = SDL_RWtell(channels[channel].f);
2117 +
			Sint64 endpos = SDL_RWseek(channels[channel].f, 0, RW_SEEK_END);
2118 +
			SDL_RWseek(channels[channel].f, curpos, RW_SEEK_SET);
2119 +
2120 +
			do {
2121 +
				uint8_t byte = 0;
2122 +
				if (SDL_RWread(channels[channel].f, &byte, 1, 1) != 1) {
2123 +
					ret = 0x42;
2124 +
					byte = 0;
2125 +
				} else {
2126 +
					curpos++;
2127 +
					if (curpos == endpos) {
2128 +
						ret = 0x40;
2129 +
						channels[channel].read = false;
2130 +
						cclose(channel);
2131 +
					}
2132 +
2133 +
					write6502(destaddr, destbnk, byte);
2134 +
					i++;
2135 +
					destaddr++;
2136 +
					if (destaddr == 0) {
2137 +
						destbnk++;
2138 +
						if (destbnk == 0) {
2139 +
							break;
2140 +
						}
2141 +
					}
2142 +
				}
2143 +
				if (ret > 0) {
2144 +
					break;
2145 +
				}
2146 +
			} while(i < count);
2147 +
		} else {
2148 +
			ret = -3; // unsupported
2149 +
		}
2150 +
		write6502(X16_REG_R2L, 0, i & 0xff);
2151 +
		write6502(X16_REG_R2H, 0, i >> 8);
2152 +
		return ret;
2153 +
	} else {
2154 +
		return -2; // not us, do not handle
2155 +
	}
2156 +
}
2157 +
2158 +
int
2159 +
XMCIOUT(uint8_t stream_mode) // stream_mode is only for passing into MCIOUT fallback
2160 +
{
2161 +
	if (listening) {
2162 +
		int ret = 0;
2163 +
		int count = read6502(X16_REG_R2L, 0) | (read6502(X16_REG_R2H, 0) << 8);
2164 +
		uint16_t srcaddr = read6502(X16_REG_R0L, 0) | (read6502(X16_REG_R0H, 0) << 8);
2165 +
		uint8_t srcbnk = read6502(X16_REG_R1L, 0);
2166 +
2167 +
		if (count == 0) {
2168 +
			count = 65536;
2169 +
		}
2170 +
2171 +
		if (srcbnk == 0) {
2172 +
			int s;
2173 +
			uint16_t cnt = count;
2174 +
			s = MCIOUT(srcaddr, &cnt, stream_mode);
2175 +
			if (s < 0) {
2176 +
				return s;
2177 +
			}
2178 +
			write6502(X16_REG_R2L, 0, cnt & 0xff);
2179 +
			write6502(X16_REG_R2H, 0, cnt >> 8);
2180 +
			return s;
2181 +
		}
2182 +
2183 +
		int i = 0;
2184 +
		if (channels[channel].f && channels[channel].write) {
2185 +
			do {
2186 +
				uint8_t byte;
2187 +
				byte = read6502(srcaddr, srcbnk);
2188 +
				ret = CIOUT(byte);
2189 +
				i++;
2190 +
				srcaddr++;
2191 +
				if (srcaddr == 0) {
2192 +
					srcbnk++;
2193 +
					if (srcbnk == 0) {
2194 +
						break;
2195 +
					}
2196 +
				}
2197 +
				if (ret) {
2198 +
					break;
2199 +
				}
2200 +
			} while(i < count);
2201 +
		} else {
2202 +
			ret = -3; // unsupported
2203 +
		}
2204 +
2205 +
		write6502(X16_REG_R2L, 0, i & 0xff);
2206 +
		write6502(X16_REG_R2H, 0, i >> 8);
2207 +
		return ret;
2208 +
	} else {
2209 +
		return -2; // not us, do not handle
2210 +
	}
2211 +
}
added src/ieee.h +17 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2022 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
void ieee_init();
6 +
int SECOND(uint8_t a);
7 +
int TKSA(uint8_t a);
8 +
int ACPTR(uint8_t *a);
9 +
int CIOUT(uint8_t a);
10 +
int UNTLK(void);
11 +
int UNLSN(void);
12 +
int LISTEN(uint8_t a);
13 +
int TALK(uint8_t a);
14 +
int MACPTR(uint16_t addr, uint16_t *count, uint8_t stream_mode);
15 +
int MCIOUT(uint16_t addr, uint16_t *count, uint8_t stream_mode);
16 +
int XMACPTR(uint8_t stream_mode);
17 +
int XMCIOUT(uint8_t stream_mode);
added src/iso_8859_15.c +92 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019,2022 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdint.h>
6 +
#include <stdio.h>
7 +
#include "utf8_encode.h"
8 +
9 +
uint8_t
10 +
iso8859_15_from_unicode(uint32_t c)
11 +
{
12 +
	// line feed -> carriage return
13 +
	if (c == '\n') {
14 +
		return '\r';
15 +
	}
16 +
17 +
	// translate Unicode characters not part of Latin-1 but part of Latin-15
18 +
	switch (c) {
19 +
		case 0x20ac: // '€'
20 +
			return 0xa4;
21 +
		case 0x160: // 'Š'
22 +
			return 0xa6;
23 +
		case 0x161: // 'š'
24 +
			return 0xa8;
25 +
		case 0x17d: // 'Ž'
26 +
			return 0xb4;
27 +
		case 0x17e: // 'ž'
28 +
			return 0xb8;
29 +
		case 0x152: // 'Œ'
30 +
			return 0xbc;
31 +
		case 0x153: // 'œ'
32 +
			return 0xbd;
33 +
		case 0x178: // 'Ÿ'
34 +
			return 0xbe;
35 +
	}
36 +
37 +
	// remove Unicode characters part of Latin-1 but not part of Latin-15
38 +
	switch (c) {
39 +
		case 0xa4: // '¤'
40 +
		case 0xa6: // '¦'
41 +
		case 0xa8: // '¨'
42 +
		case 0xb4: // '´'
43 +
		case 0xb8: // '¸'
44 +
		case 0xbc: // '¼'
45 +
		case 0xbd: // '½'
46 +
		case 0xbe: // '¾'
47 +
			return '?';
48 +
	}
49 +
50 +
	// all other Unicode characters are also unsupported
51 +
	if (c >= 256) {
52 +
		return '?';
53 +
	}
54 +
55 +
	// everything else is Latin-15 already
56 +
	return c;
57 +
}
58 +
59 +
uint32_t
60 +
unicode_from_iso8859_15(uint8_t c)
61 +
{
62 +
	// translate Latin-15 characters not part of Latin-1
63 +
	switch (c) {
64 +
		case 0xa4:
65 +
			return 0x20ac; // '€'
66 +
		case 0xa6:
67 +
			return 0x160; // 'Š'
68 +
		case 0xa8:
69 +
			return 0x161; // 'š'
70 +
		case 0xb4:
71 +
			return 0x17d; // 'Ž'
72 +
		case 0xb8:
73 +
			return 0x17e; // 'ž'
74 +
		case 0xbc:
75 +
			return 0x152; // 'Œ'
76 +
		case 0xbd:
77 +
			return 0x153; // 'œ'
78 +
		case 0xbe:
79 +
			return 0x178; // 'Ÿ'
80 +
		default:
81 +
			return c;
82 +
	}
83 +
}
84 +
85 +
// converts the character to UTF-8 and prints it
86 +
void
87 +
print_iso8859_15_char(char c)
88 +
{
89 +
	char utf8[5];
90 +
	utf8_encode(utf8, unicode_from_iso8859_15(c));
91 +
	printf("%s", utf8);
92 +
}
added src/iso_8859_15.h +9 −0
1 +
#ifndef ISO_8859_15_H
2 +
#define ISO_8859_15_H
3 +
4 +
#include <stdint.h>
5 +
6 +
uint8_t iso8859_15_from_unicode(uint32_t c);
7 +
uint32_t unicode_from_iso8859_15(uint8_t c);
8 +
void print_iso8859_15_char(char c);
9 +
#endif
added src/javascript_interface.c +34 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdio.h>
6 +
#include <stdlib.h>
7 +
#include <string.h>
8 +
#include "glue.h"
9 +
#include "audio.h"
10 +
11 +
char javascript_text_data[65536];
12 +
13 +
void
14 +
j2c_reset()
15 +
{
16 +
	machine_reset();
17 +
}
18 +
19 +
void
20 +
j2c_paste(char * buffer)
21 +
{
22 +
	memset(javascript_text_data, 0, 65536);
23 +
	strcpy(javascript_text_data, buffer);
24 +
	machine_paste(javascript_text_data, false);
25 +
}
26 +
27 +
void
28 +
j2c_start_audio(bool start)
29 +
{
30 +
	if (start)
31 +
		audio_init(NULL, 8);
32 +
	else
33 +
		audio_close();
34 +
}
added src/joystick.c +338 −0
1 +
#include "joystick.h"
2 +
3 +
#include <SDL.h>
4 +
#include <stdio.h>
5 +
6 +
struct joystick_info {
7 +
	int                 instance_id;
8 +
	SDL_GameController *controller;
9 +
	uint16_t            button_mask;
10 +
	uint16_t            shift_mask;
11 +
};
12 +
13 +
static const uint16_t button_map[SDL_CONTROLLER_BUTTON_MAX] = {
14 +
    1 << 0,  //SDL_CONTROLLER_BUTTON_A,
15 +
    1 << 8,  //SDL_CONTROLLER_BUTTON_B,
16 +
    1 << 1,  //SDL_CONTROLLER_BUTTON_X,
17 +
    1 << 9,  //SDL_CONTROLLER_BUTTON_Y,
18 +
    1 << 2,  //SDL_CONTROLLER_BUTTON_BACK,
19 +
    0,       //SDL_CONTROLLER_BUTTON_GUIDE,
20 +
    1 << 3,  //SDL_CONTROLLER_BUTTON_START,
21 +
    0,       //SDL_CONTROLLER_BUTTON_LEFTSTICK,
22 +
    0,       //SDL_CONTROLLER_BUTTON_RIGHTSTICK,
23 +
    1 << 10, //SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
24 +
    1 << 11, //SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
25 +
    1 << 4,  //SDL_CONTROLLER_BUTTON_DPAD_UP,
26 +
    1 << 5,  //SDL_CONTROLLER_BUTTON_DPAD_DOWN,
27 +
    1 << 6,  //SDL_CONTROLLER_BUTTON_DPAD_LEFT,
28 +
    1 << 7,  //SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
29 +
};
30 +
31 +
static struct joystick_info *Joystick_controllers     = NULL;
32 +
static int                   Num_joystick_controllers = 0;
33 +
34 +
static void
35 +
resize_joystick_controllers(int new_size)
36 +
{
37 +
	if (new_size == 0) {
38 +
		free(Joystick_controllers);
39 +
		Joystick_controllers     = NULL;
40 +
		Num_joystick_controllers = 0;
41 +
		return;
42 +
	}
43 +
44 +
	struct joystick_info *old_controllers = Joystick_controllers;
45 +
	Joystick_controllers                  = (struct joystick_info *)malloc(sizeof(struct joystick_info) * new_size);
46 +
47 +
	int min_size = new_size < Num_joystick_controllers ? new_size : Num_joystick_controllers;
48 +
	if (min_size > 0) {
49 +
		memcpy(Joystick_controllers, old_controllers, sizeof(struct joystick_info) * min_size);
50 +
		free(old_controllers);
51 +
	}
52 +
53 +
	for (int i = min_size; i < new_size; ++i) {
54 +
		Joystick_controllers[i].instance_id = -1;
55 +
		Joystick_controllers[i].controller  = NULL;
56 +
		Joystick_controllers[i].button_mask = 0xffff;
57 +
		Joystick_controllers[i].shift_mask  = 0;
58 +
	}
59 +
}
60 +
61 +
static void
62 +
add_joystick_controller(struct joystick_info *info)
63 +
{
64 +
	int i;
65 +
	for (i = 0; i < Num_joystick_controllers; ++i) {
66 +
		if (Joystick_controllers[i].instance_id == -1) {
67 +
			memcpy(&Joystick_controllers[i], info, sizeof(struct joystick_info));
68 +
			return;
69 +
		}
70 +
	}
71 +
72 +
	i = Num_joystick_controllers;
73 +
	resize_joystick_controllers(Num_joystick_controllers << 1);
74 +
75 +
	memcpy(&Joystick_controllers[i], info, sizeof(struct joystick_info));
76 +
}
77 +
78 +
static void
79 +
remove_joystick_controller(int instance_id)
80 +
{
81 +
	for (int i = 0; i < Num_joystick_controllers; ++i) {
82 +
		if (Joystick_controllers[i].instance_id == instance_id) {
83 +
			Joystick_controllers[i].instance_id = -1;
84 +
			Joystick_controllers[i].controller  = NULL;
85 +
			return;
86 +
		}
87 +
	}
88 +
}
89 +
90 +
static struct joystick_info *
91 +
find_joystick_controller(int instance_id)
92 +
{
93 +
	for (int i = 0; i < Num_joystick_controllers; ++i) {
94 +
		if (Joystick_controllers[i].instance_id == instance_id) {
95 +
			return &Joystick_controllers[i];
96 +
		}
97 +
	}
98 +
99 +
	return NULL;
100 +
}
101 +
102 +
bool Joystick_slots_enabled[NUM_JOYSTICKS] = {false, false, false, false};
103 +
static int Joystick_slots[NUM_JOYSTICKS];
104 +
105 +
static bool Joystick_latch = false;
106 +
uint8_t Joystick_data  = 0;
107 +
108 +
bool
109 +
joystick_init(void)
110 +
{
111 +
	for (int i = 0; i < NUM_JOYSTICKS; ++i) {
112 +
		Joystick_slots[i] = -1;
113 +
	}
114 +
115 +
	const int num_joysticks = SDL_NumJoysticks();
116 +
117 +
	Num_joystick_controllers = num_joysticks > 16 ? num_joysticks : 16;
118 +
	Joystick_controllers     = malloc(sizeof(struct joystick_info) * Num_joystick_controllers);
119 +
120 +
	for (int i = 0; i < Num_joystick_controllers; ++i) {
121 +
		Joystick_controllers[i].instance_id = -1;
122 +
		Joystick_controllers[i].controller  = NULL;
123 +
		Joystick_controllers[i].button_mask = 0xffff;
124 +
		Joystick_controllers[i].shift_mask  = 0;
125 +
	}
126 +
127 +
	for (int i = 0; i < num_joysticks; ++i) {
128 +
		joystick_add(i);
129 +
	}
130 +
131 +
	return true;
132 +
}
133 +
134 +
void
135 +
joystick_add(int index)
136 +
{
137 +
	if (!SDL_IsGameController(index)) {
138 +
		return;
139 +
	}
140 +
141 +
	SDL_GameController *controller = SDL_GameControllerOpen(index);
142 +
	if (controller == NULL) {
143 +
		fprintf(stderr, "Could not open controller %d: %s\n", index, SDL_GetError());
144 +
		return;
145 +
	}
146 +
147 +
	SDL_JoystickID instance_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
148 +
	bool           exists      = false;
149 +
	for (int i = 0; i < NUM_JOYSTICKS; ++i) {
150 +
		if (!Joystick_slots_enabled[i]) {
151 +
			continue;
152 +
		}
153 +
154 +
		if (Joystick_slots[i] == instance_id) {
155 +
			exists = true;
156 +
			break;
157 +
		}
158 +
	}
159 +
160 +
	if (!exists) {
161 +
		for (int i = 0; i < NUM_JOYSTICKS; ++i) {
162 +
			if (!Joystick_slots_enabled[i]) {
163 +
				continue;
164 +
			}
165 +
166 +
			if (Joystick_slots[i] == -1) {
167 +
				Joystick_slots[i] = instance_id;
168 +
				break;
169 +
			}
170 +
		}
171 +
172 +
		struct joystick_info new_info;
173 +
		new_info.instance_id = instance_id;
174 +
		new_info.controller  = controller;
175 +
		new_info.button_mask = 0xffff;
176 +
		new_info.shift_mask  = 0;
177 +
		add_joystick_controller(&new_info);
178 +
	}
179 +
}
180 +
181 +
void
182 +
joystick_remove(int instance_id)
183 +
{
184 +
	for (int i = 0; i < NUM_JOYSTICKS; ++i) {
185 +
		if (Joystick_slots[i] == instance_id) {
186 +
			Joystick_slots[i] = -1;
187 +
			break;
188 +
		}
189 +
	}
190 +
191 +
	SDL_GameController *controller = SDL_GameControllerFromInstanceID(instance_id);
192 +
	if (controller == NULL) {
193 +
		fprintf(stderr, "Could not find controller from instance_id %d: %s\n", instance_id, SDL_GetError());
194 +
	} else {
195 +
		SDL_GameControllerClose(controller);
196 +
		remove_joystick_controller(instance_id);
197 +
	}
198 +
}
199 +
200 +
void
201 +
joystick_button_down(int instance_id, uint8_t button)
202 +
{
203 +
	struct joystick_info *joy = find_joystick_controller(instance_id);
204 +
	if (joy != NULL) {
205 +
		joy->button_mask &= ~(button_map[button]);
206 +
	}
207 +
}
208 +
209 +
void
210 +
joystick_button_up(int instance_id, uint8_t button)
211 +
{
212 +
	struct joystick_info *joy = find_joystick_controller(instance_id);
213 +
	if (joy != NULL) {
214 +
		joy->button_mask |= button_map[button];
215 +
	}
216 +
}
217 +
218 +
static void
219 +
do_shift()
220 +
{
221 +
	for (int i = 0; i < NUM_JOYSTICKS; ++i) {
222 +
		if (Joystick_slots[i] >= 0) {
223 +
			struct joystick_info *joy = find_joystick_controller(Joystick_slots[i]);
224 +
			if (joy != NULL) {
225 +
				Joystick_data |= ((joy->shift_mask & 1) ? (0x80 >> i) : 0);
226 +
				joy->shift_mask >>= 1;
227 +
			} else {
228 +
				Joystick_data |= 0x80 >> i;
229 +
			}
230 +
		} else {
231 +
			Joystick_data |= 0x80 >> i;
232 +
		}
233 +
	}
234 +
}
235 +
236 +
void
237 +
joystick_set_latch(bool value)
238 +
{
239 +
	Joystick_latch = value;
240 +
	if (value) {
241 +
		for (int i = 0; i < Num_joystick_controllers; ++i) {
242 +
			if (Joystick_controllers[i].instance_id != -1) {
243 +
				Joystick_controllers[i].shift_mask = Joystick_controllers[i].button_mask | 0xF000;
244 +
			}
245 +
		}
246 +
		do_shift();
247 +
	}
248 +
}
249 +
250 +
void
251 +
joystick_set_clock(bool value)
252 +
{
253 +
	if (!Joystick_latch && value) {
254 +
		Joystick_data = 0;
255 +
		do_shift();
256 +
	}
257 +
}
258 +
259 +
/* --- Virtual pads, for the RRDC control port (contract 0.5) ----------
260 +
 *
261 +
 * A harness driving the machine over HTTP has no SDL controller to
262 +
 * plug in, so it asks for one to be synthesised. The synthetic pad is
263 +
 * an ordinary entry in Joystick_controllers with no SDL device behind
264 +
 * it: everything downstream — latch, shift, Joystick_data — treats it
265 +
 * exactly like real hardware, which is the point. Nothing in the
266 +
 * read path learns that a pad is virtual.
267 +
 *
268 +
 * Instance ids come from a range SDL cannot produce (SDL hands out
269 +
 * small ascending ints), so a virtual pad and a real one can coexist
270 +
 * without ever colliding in find_joystick_controller().
271 +
 * ------------------------------------------------------------------ */
272 +
273 +
#define JOY_VIRTUAL_ID_BASE (1 << 24)
274 +
275 +
bool
276 +
joystick_virtual_set(int slot, bool connected, bool set_buttons, uint16_t mask)
277 +
{
278 +
	if (slot < 0 || slot >= NUM_JOYSTICKS) {
279 +
		return false;
280 +
	}
281 +
282 +
	const int vid = JOY_VIRTUAL_ID_BASE + slot;
283 +
284 +
	if (!connected) {
285 +
		if (Joystick_slots[slot] == vid) {
286 +
			Joystick_slots[slot] = -1;
287 +
		}
288 +
		remove_joystick_controller(vid);
289 +
		return true;
290 +
	}
291 +
292 +
	struct joystick_info *joy = find_joystick_controller(vid);
293 +
	if (joy == NULL) {
294 +
		struct joystick_info new_info;
295 +
		new_info.instance_id = vid;
296 +
		new_info.controller  = NULL;    /* no SDL device behind it */
297 +
		new_info.button_mask = 0xffff;  /* active low: nothing held */
298 +
		new_info.shift_mask  = 0;
299 +
		add_joystick_controller(&new_info);
300 +
		joy = find_joystick_controller(vid);
301 +
		if (joy == NULL) {
302 +
			return false;
303 +
		}
304 +
	}
305 +
306 +
	if (set_buttons) {
307 +
		joy->button_mask = mask;
308 +
	}
309 +
310 +
	/* Presenting a pad implies its slot is live: a harness that had to
311 +
	 * remember -joy1 on the command line as well would be a trap. */
312 +
	Joystick_slots_enabled[slot] = true;
313 +
	Joystick_slots[slot]         = vid;
314 +
	return true;
315 +
}
316 +
317 +
bool
318 +
joystick_virtual_get(int slot, bool *connected, uint16_t *mask)
319 +
{
320 +
	if (slot < 0 || slot >= NUM_JOYSTICKS) {
321 +
		return false;
322 +
	}
323 +
324 +
	const int id = Joystick_slots[slot];
325 +
	if (id < 0) {
326 +
		*connected = false;
327 +
		*mask      = 0xffff;
328 +
		return true;
329 +
	}
330 +
331 +
	/* Deliberately not restricted to virtual ids: a real controller
332 +
	 * bound to this slot reads back the same way, so a test can assert
333 +
	 * on input regardless of how it got there. */
334 +
	struct joystick_info *joy = find_joystick_controller(id);
335 +
	*connected = (joy != NULL);
336 +
	*mask      = (joy != NULL) ? joy->button_mask : 0xffff;
337 +
	return true;
338 +
}
added src/joystick.h +44 −0
1 +
#pragma once
2 +
#ifndef JOYSTICK_H
3 +
#define JOYSTICK_H
4 +
5 +
#include <stdint.h>
6 +
#include <stdbool.h>
7 +
8 +
#define JOY_LATCH_MASK 0x04
9 +
#define JOY_CLK_MASK 0x08
10 +
11 +
#define NUM_JOYSTICKS 4
12 +
13 +
extern uint8_t Joystick_data;
14 +
extern bool Joystick_slots_enabled[NUM_JOYSTICKS];
15 +
16 +
bool joystick_init(void); //initialize SDL controllers
17 +
void joystick_add(int index);
18 +
void joystick_remove(int index);
19 +
20 +
void joystick_button_down(int instance_id, uint8_t button);
21 +
void joystick_button_up(int instance_id, uint8_t button);
22 +
23 +
void joystick_set_latch(bool value);
24 +
void joystick_set_clock(bool value);
25 +
26 +
/* --- Virtual pads, for the RRDC control port (contract 0.5) ----------
27 +
 *
28 +
 * A harness has no SDL controller to plug in, so it asks for one to be
29 +
 * synthesised in a joystick slot. `mask` is the X16's own SNES bit
30 +
 * order and is ACTIVE LOW, exactly like joystick_info::button_mask —
31 +
 * converting from RRDC's canonical layout is the caller's job, not
32 +
 * this layer's.
33 +
 *
34 +
 * A virtual pad is a LEVEL, not an event: what it holds persists across
35 +
 * frames until set again, which is what makes "hold RIGHT for 60
36 +
 * frames" expressible. Returns false if `slot` is out of range. */
37 +
bool joystick_virtual_set(int slot, bool connected, bool set_buttons, uint16_t mask);
38 +
39 +
/* Read back what is present in `slot` — a virtual pad OR a real one, so
40 +
 * a harness can tell "no controller at all" from "controller present,
41 +
 * nothing held". Returns false if `slot` is out of range. */
42 +
bool joystick_virtual_get(int slot, bool *connected, uint16_t *mask);
43 +
44 +
#endif
added src/keyboard.c +265 −0
1 +
#include <SDL.h>
2 +
#include <stdio.h>
3 +
#include <stdbool.h>
4 +
#include "glue.h"
5 +
#include "i2c.h"
6 +
#include "keyboard.h"
7 +
8 +
#define EXTENDED_FLAG 0x100
9 +
// #define ESC_IS_BREAK /* if enabled, Esc sends Break/Pause key instead of Esc */
10 +
11 +
int
12 +
keynum_from_SDL_Scancode(SDL_Scancode scancode)
13 +
{
14 +
	switch (scancode) {
15 +
		case SDL_SCANCODE_GRAVE:
16 +
			return 1;
17 +
		case SDL_SCANCODE_BACKSPACE:
18 +
			return 15;
19 +
		case SDL_SCANCODE_TAB:
20 +
			return 16;
21 +
		case SDL_SCANCODE_CLEAR:
22 +
			return 0;
23 +
		case SDL_SCANCODE_RETURN:
24 +
			return 43;
25 +
		case SDL_SCANCODE_PAUSE:
26 +
			return 126;
27 +
		case SDL_SCANCODE_ESCAPE:
28 +
#ifdef ESC_IS_BREAK
29 +
			return 126;
30 +
#else
31 +
			return 110;
32 +
#endif
33 +
		case SDL_SCANCODE_SPACE:
34 +
			return 61;
35 +
		case SDL_SCANCODE_APOSTROPHE:
36 +
			return 41;
37 +
		case SDL_SCANCODE_COMMA:
38 +
			return 53;
39 +
		case SDL_SCANCODE_MINUS:
40 +
			return 12;
41 +
		case SDL_SCANCODE_PERIOD:
42 +
			return 54;
43 +
		case SDL_SCANCODE_SLASH:
44 +
			return 55;
45 +
		case SDL_SCANCODE_0:
46 +
			return 11;
47 +
		case SDL_SCANCODE_1:
48 +
			return 2;
49 +
		case SDL_SCANCODE_2:
50 +
			return 3;
51 +
		case SDL_SCANCODE_3:
52 +
			return 4;
53 +
		case SDL_SCANCODE_4:
54 +
			return 5;
55 +
		case SDL_SCANCODE_5:
56 +
			return 6;
57 +
		case SDL_SCANCODE_6:
58 +
			return 7;
59 +
		case SDL_SCANCODE_7:
60 +
			return 8;
61 +
		case SDL_SCANCODE_8:
62 +
			return 9;
63 +
		case SDL_SCANCODE_9:
64 +
			return 10;
65 +
		case SDL_SCANCODE_SEMICOLON:
66 +
			return 40;
67 +
		case SDL_SCANCODE_EQUALS:
68 +
			return 13;
69 +
		case SDL_SCANCODE_LEFTBRACKET:
70 +
			return 27;
71 +
		case SDL_SCANCODE_BACKSLASH:
72 +
			return 29;
73 +
		case SDL_SCANCODE_RIGHTBRACKET:
74 +
			return 28;
75 +
		case SDL_SCANCODE_A:
76 +
			return 31;
77 +
		case SDL_SCANCODE_B:
78 +
			return 50;
79 +
		case SDL_SCANCODE_C:
80 +
			return 48;
81 +
		case SDL_SCANCODE_D:
82 +
			return 33;
83 +
		case SDL_SCANCODE_E:
84 +
			return 19;
85 +
		case SDL_SCANCODE_F:
86 +
			return 34;
87 +
		case SDL_SCANCODE_G:
88 +
			return 35;
89 +
		case SDL_SCANCODE_H:
90 +
			return 36;
91 +
		case SDL_SCANCODE_I:
92 +
			return 24;
93 +
		case SDL_SCANCODE_J:
94 +
			return 37;
95 +
		case SDL_SCANCODE_K:
96 +
			return 38;
97 +
		case SDL_SCANCODE_L:
98 +
			return 39;
99 +
		case SDL_SCANCODE_M:
100 +
			return 52;
101 +
		case SDL_SCANCODE_N:
102 +
			return 51;
103 +
		case SDL_SCANCODE_O:
104 +
			return 25;
105 +
		case SDL_SCANCODE_P:
106 +
			return 26;
107 +
		case SDL_SCANCODE_Q:
108 +
			return 17;
109 +
		case SDL_SCANCODE_R:
110 +
			return 20;
111 +
		case SDL_SCANCODE_S:
112 +
			return 32;
113 +
		case SDL_SCANCODE_T:
114 +
			return 21;
115 +
		case SDL_SCANCODE_U:
116 +
			return 23;
117 +
		case SDL_SCANCODE_V:
118 +
			return 49;
119 +
		case SDL_SCANCODE_W:
120 +
			return 18;
121 +
		case SDL_SCANCODE_X:
122 +
			return 47;
123 +
		case SDL_SCANCODE_Y:
124 +
			return 22;
125 +
		case SDL_SCANCODE_Z:
126 +
			return 46;
127 +
		case SDL_SCANCODE_DELETE:
128 +
			return 76;
129 +
		case SDL_SCANCODE_UP:
130 +
			return 83;
131 +
		case SDL_SCANCODE_DOWN:
132 +
			return 84;
133 +
		case SDL_SCANCODE_RIGHT:
134 +
			return 89;
135 +
		case SDL_SCANCODE_LEFT:
136 +
			return 79;
137 +
		case SDL_SCANCODE_INSERT:
138 +
			return 75;
139 +
		case SDL_SCANCODE_HOME:
140 +
			return 80;
141 +
		case SDL_SCANCODE_END:
142 +
			return 81;
143 +
		case SDL_SCANCODE_PAGEUP:
144 +
			return 85;
145 +
		case SDL_SCANCODE_PAGEDOWN:
146 +
			return 86;
147 +
		case SDL_SCANCODE_F1:
148 +
			return 112;
149 +
		case SDL_SCANCODE_F2:
150 +
			return 113;
151 +
		case SDL_SCANCODE_F3:
152 +
			return 114;
153 +
		case SDL_SCANCODE_F4:
154 +
			return 115;
155 +
		case SDL_SCANCODE_F5:
156 +
			return 116;
157 +
		case SDL_SCANCODE_F6:
158 +
			return 117;
159 +
		case SDL_SCANCODE_F7:
160 +
			return 118;
161 +
		case SDL_SCANCODE_F8:
162 +
			return 119;
163 +
		case SDL_SCANCODE_F9:
164 +
			return 120;
165 +
		case SDL_SCANCODE_F10:
166 +
			return 121;
167 +
		case SDL_SCANCODE_F11:
168 +
			return 122;
169 +
		case SDL_SCANCODE_F12:
170 +
			return 123;
171 +
		case SDL_SCANCODE_SCROLLLOCK:
172 +
			return 125;
173 +
		case SDL_SCANCODE_RSHIFT:
174 +
			return 57;
175 +
		case SDL_SCANCODE_LSHIFT:
176 +
			return 44;
177 +
		case SDL_SCANCODE_CAPSLOCK:
178 +
			return 30;
179 +
		case SDL_SCANCODE_LCTRL:
180 +
			return 58;
181 +
		case SDL_SCANCODE_RCTRL:
182 +
			return 64;
183 +
		case SDL_SCANCODE_LALT:
184 +
			return 60;
185 +
		case SDL_SCANCODE_RALT:
186 +
			return 62;
187 +
		case SDL_SCANCODE_LGUI:
188 +
			return 59;
189 +
		case SDL_SCANCODE_RGUI:
190 +
			return 63;
191 +
		case SDL_SCANCODE_APPLICATION: // Menu
192 +
			return 65;
193 +
		case SDL_SCANCODE_NONUSBACKSLASH:
194 +
			return 45;
195 +
		case SDL_SCANCODE_KP_ENTER:
196 +
			return 108;
197 +
		case SDL_SCANCODE_KP_0:
198 +
			return 99;
199 +
		case SDL_SCANCODE_KP_1:
200 +
			return 93;
201 +
		case SDL_SCANCODE_KP_2:
202 +
			return 98;
203 +
		case SDL_SCANCODE_KP_3:
204 +
			return 103;
205 +
		case SDL_SCANCODE_KP_4:
206 +
			return 92;
207 +
		case SDL_SCANCODE_KP_5:
208 +
			return 97;
209 +
		case SDL_SCANCODE_KP_6:
210 +
			return 102;
211 +
		case SDL_SCANCODE_KP_7:
212 +
			return 91;
213 +
		case SDL_SCANCODE_KP_8:
214 +
			return 96;
215 +
		case SDL_SCANCODE_KP_9:
216 +
			return 101;
217 +
		case SDL_SCANCODE_KP_PERIOD:
218 +
			return 104;
219 +
		case SDL_SCANCODE_KP_PLUS:
220 +
			return 106;
221 +
		case SDL_SCANCODE_KP_MINUS:
222 +
			return 105;
223 +
		case SDL_SCANCODE_KP_MULTIPLY:
224 +
			return 100;
225 +
		case SDL_SCANCODE_KP_DIVIDE:
226 +
			return 95;
227 +
		case SDL_SCANCODE_NUMLOCKCLEAR:
228 +
			return 90;
229 +
		case SDL_SCANCODE_INTERNATIONAL1:
230 +
			return 56;
231 +
		default:
232 +
			return 0;
233 +
	}
234 +
}
235 +
236 +
void
237 +
handle_keyboard(bool down, SDL_Keycode sym, SDL_Scancode scancode)
238 +
{
239 +
	int keynum = keynum_from_SDL_Scancode(scancode);
240 +
	
241 +
	if (keynum == 0) return;
242 +
243 +
	if (down) {
244 +
		if (log_keyboard) {
245 +
			printf("DOWN 0x%02X\n", scancode);
246 +
			fflush(stdout);
247 +
		}
248 +
249 +
		if (keynum & EXTENDED_FLAG) {
250 +
			i2c_kbd_buffer_add(0x7f);
251 +
		}
252 +
		i2c_kbd_buffer_add(keynum & 0xff);
253 +
	} else {
254 +
		if (log_keyboard) {
255 +
			printf("UP   0x%02X\n", scancode);
256 +
			fflush(stdout);
257 +
		}
258 +
259 +
		keynum = keynum | 0b10000000;
260 +
		if (keynum & EXTENDED_FLAG) {
261 +
			i2c_kbd_buffer_add(0xff);
262 +
		}
263 +
		i2c_kbd_buffer_add(keynum & 0xff);
264 +
	}
265 +
}
added src/keyboard.h +2 −0
1 +
void handle_keyboard(bool down, SDL_Keycode sym, SDL_Scancode scancode);
2 +
added src/main.c +1960 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019,2022 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef __APPLE__
6 +
#define _XOPEN_SOURCE   600
7 +
#define _POSIX_C_SOURCE 1
8 +
#endif
9 +
#include <stdlib.h>
10 +
#include <stdbool.h>
11 +
#include <stdint.h>
12 +
#include <stdio.h>
13 +
#include <string.h>
14 +
#include <unistd.h>
15 +
#include <limits.h>
16 +
#ifdef __MINGW32__
17 +
#include <ctype.h>
18 +
#endif
19 +
#include "cpu/fake6502.h"
20 +
#include "timing.h"
21 +
#include "disasm.h"
22 +
#include "files.h"
23 +
#include "memory.h"
24 +
#include "video.h"
25 +
#include "via.h"
26 +
#include "serial.h"
27 +
#include "i2c.h"
28 +
#include "rtc.h"
29 +
#include "smc.h"
30 +
#include "vera_spi.h"
31 +
#include "sdcard.h"
32 +
#include "ieee.h"
33 +
#include "glue.h"
34 +
#include "debugger.h"
35 +
#include "utf8.h"
36 +
#include "iso_8859_15.h"
37 +
#include "joystick.h"
38 +
#include "rom_symbols.h"
39 +
#include "ymglue.h"
40 +
#include "audio.h"
41 +
#include "version.h"
42 +
#include "wav_recorder.h"
43 +
#include "testbench.h"
44 +
#include "coverage.h"
45 +
#include "cartridge.h"
46 +
#include "midi.h"
47 +
#include "netcard.h"
48 +
#include "retro_control.h"
49 +
50 +
int x16_control_start(int port);   // control_backend.c
51 +
52 +
#ifdef __EMSCRIPTEN__
53 +
#include <emscripten.h>
54 +
#include <pthread.h>
55 +
#endif
56 +
57 +
void *emulator_loop(void *param);
58 +
void emscripten_main_loop(void);
59 +
60 +
// This must match the KERNAL's set!
61 +
char *keymaps[] = {
62 +
	"en-us",
63 +
	"en-us-int",
64 +
	"en-gb",
65 +
	"sv",
66 +
	"de",
67 +
	"da",
68 +
	"it",
69 +
	"pl",
70 +
	"nb",
71 +
	"hu",
72 +
	"es",
73 +
	"fi",
74 +
	"pt-br",
75 +
	"cz",
76 +
	"jp",
77 +
	"fr",
78 +
	"de-ch",
79 +
	"en-us-dvo",
80 +
	"et",
81 +
	"fr-be",
82 +
	"fr-ca",
83 +
	"is",
84 +
	"pt",
85 +
	"hr",
86 +
	"sk",
87 +
	"sl",
88 +
	"lv",
89 +
	"lt",
90 +
};
91 +
92 +
#ifdef PERFSTAT
93 +
uint32_t stat[65536];
94 +
#endif
95 +
96 +
bool debugger_enabled = false;
97 +
char *paste_text = NULL;
98 +
char *clipboard_buffer = NULL;
99 +
char paste_text_data[65536];
100 +
bool pasting_bas = false;
101 +
102 +
uint16_t num_banks = 1;
103 +
uint16_t num_ram_banks = 64; // 512 KB default
104 +
105 +
bool log_video = false;
106 +
bool log_speed = false;
107 +
bool log_keyboard = false;
108 +
bool dump_cpu = false;
109 +
bool dump_ram = true;
110 +
bool dump_bank = true;
111 +
bool dump_vram = false;
112 +
int control_port = 0;   // retro-remote-debug-controller HTTP port (0 = off)
113 +
bool warp_mode = false;
114 +
bool warp_pastes = false;
115 +
bool grab_mouse = false;
116 +
echo_mode_t echo_mode;
117 +
bool save_on_exit = true;
118 +
bool disable_emu_cmd_keys = false;
119 +
bool set_system_time = false;
120 +
bool has_serial = false;
121 +
bool no_ieee_intercept = false;
122 +
bool has_via2 = false;
123 +
gif_recorder_state_t record_gif = RECORD_GIF_DISABLED;
124 +
char *gif_path = NULL;
125 +
char *wav_path = NULL;
126 +
uint8_t *fsroot_path = NULL;
127 +
uint8_t *startin_path = NULL;
128 +
uint8_t keymap = 0; // KERNAL's default
129 +
int window_scale = 1;
130 +
float screen_x_scale = 1.0;
131 +
float window_opacity = 1.0;
132 +
char *scale_quality = "best";
133 +
bool test_init_complete=false;
134 +
bool headless = false;
135 +
bool fullscreen = false;
136 +
bool testbench = false;
137 +
bool enable_midline = false;
138 +
bool ym2151_irq_support = false;
139 +
char *cartridge_path = NULL;
140 +
141 +
bool has_midi_card = false;
142 +
uint16_t midi_card_addr;
143 +
144 +
bool using_hostfs = true;
145 +
146 +
uint8_t MHZ = 8;
147 +
148 +
#ifdef TRACE
149 +
bool trace_mode = false;
150 +
uint16_t trace_address = 0;
151 +
#endif
152 +
153 +
int instruction_counter;
154 +
SDL_RWops *prg_file;
155 +
bool prg_finished_loading;
156 +
int prg_override_start = -1;
157 +
bool run_after_load = false;
158 +
159 +
char *nvram_path = NULL;
160 +
161 +
bool pwr_long_press=false;
162 +
bool is_gen2 = false;
163 +
164 +
#ifdef TRACE
165 +
#include "rom_labels.h"
166 +
#include "rom_lst.h"
167 +
char *
168 +
label_for_address(uint16_t address)
169 +
{
170 +
	uint16_t *addresses;
171 +
	char **labels;
172 +
	int count;
173 +
	switch (memory_get_rom_bank()) {
174 +
		case 0:
175 +
			addresses = addresses_bank0;
176 +
			labels = labels_bank0;
177 +
			count = sizeof(addresses_bank0) / sizeof(uint16_t);
178 +
			break;
179 +
		case 1:
180 +
			addresses = addresses_bank1;
181 +
			labels = labels_bank1;
182 +
			count = sizeof(addresses_bank1) / sizeof(uint16_t);
183 +
			break;
184 +
		case 2:
185 +
			addresses = addresses_bank2;
186 +
			labels = labels_bank2;
187 +
			count = sizeof(addresses_bank2) / sizeof(uint16_t);
188 +
			break;
189 +
		case 3:
190 +
			addresses = addresses_bank3;
191 +
			labels = labels_bank3;
192 +
			count = sizeof(addresses_bank3) / sizeof(uint16_t);
193 +
			break;
194 +
		case 4:
195 +
			addresses = addresses_bank4;
196 +
			labels = labels_bank4;
197 +
			count = sizeof(addresses_bank4) / sizeof(uint16_t);
198 +
			break;
199 +
		case 5:
200 +
			addresses = addresses_bank5;
201 +
			labels = labels_bank5;
202 +
			count = sizeof(addresses_bank5) / sizeof(uint16_t);
203 +
			break;
204 +
		case 6:
205 +
			addresses = addresses_bank6;
206 +
			labels = labels_bank6;
207 +
			count = sizeof(addresses_bank6) / sizeof(uint16_t);
208 +
			break;
209 +
		case 10:
210 +
			addresses = addresses_bankA;
211 +
			labels = labels_bankA;
212 +
			count = sizeof(addresses_bankA) / sizeof(uint16_t);
213 +
			break;
214 +
		case 11:
215 +
			addresses = addresses_bankB;
216 +
			labels = labels_bankB;
217 +
			count = sizeof(addresses_bankB) / sizeof(uint16_t);
218 +
			break;
219 +
		case 12:
220 +
			addresses = addresses_bankC;
221 +
			labels = labels_bankC;
222 +
			count = sizeof(addresses_bankC) / sizeof(uint16_t);
223 +
			break;
224 +
		case 13:
225 +
			addresses = addresses_bankD;
226 +
			labels = labels_bankD;
227 +
			count = sizeof(addresses_bankD) / sizeof(uint16_t);
228 +
			break;
229 +
		case 14:
230 +
			addresses = addresses_bankE;
231 +
			labels = labels_bankE;
232 +
			count = sizeof(addresses_bankE) / sizeof(uint16_t);
233 +
			break;
234 +
		case 15:
235 +
			addresses = addresses_bankF;
236 +
			labels = labels_bankF;
237 +
			count = sizeof(addresses_bankF) / sizeof(uint16_t);
238 +
			break;
239 +
		default:
240 +
			addresses = NULL;
241 +
			labels = NULL;
242 +
	}
243 +
244 +
	if (!addresses) {
245 +
		return NULL;
246 +
	}
247 +
248 +
	for (int i = 0; i < count; i++) {
249 +
		if (address == addresses[i]) {
250 +
			return labels[i];
251 +
		}
252 +
	}
253 +
	return NULL;
254 +
}
255 +
256 +
char *
257 +
lst_for_address(uint16_t address)
258 +
{
259 +
	if (address < 0xc000) {
260 +
		return NULL;
261 +
	}
262 +
263 +
	char **lst;
264 +
	switch (memory_get_rom_bank()) {
265 +
		case 0: lst = lst_bank0; break;
266 +
		case 2: lst = lst_bank2; break;
267 +
		case 3: lst = lst_bank3; break;
268 +
		case 4: lst = lst_bank4; break;
269 +
		case 5: lst = lst_bank5; break;
270 +
		case 7: lst = lst_bank7; break;
271 +
		case 8: lst = lst_bank8; break;
272 +
		case 9: lst = lst_bank9; break;
273 +
		case 10: lst = lst_bankA; break;
274 +
		case 11: lst = lst_bankB; break;
275 +
		case 12: lst = lst_bankC; break;
276 +
		case 13: lst = lst_bankD; break;
277 +
		case 14: lst = lst_bankE; break;
278 +
		case 15: lst = lst_bankF; break;
279 +
		default:
280 +
			return NULL;
281 +
	}
282 +
	return lst[address - 0xc000];
283 +
}
284 +
#endif
285 +
286 +
void
287 +
machine_dump(const char* reason)
288 +
{
289 +
	printf("Dumping system memory. Reason: %s\n", reason);
290 +
	int index = 0;
291 +
	char filename[22];
292 +
	for (;;) {
293 +
		if (!index) {
294 +
			strcpy(filename, "dump.bin");
295 +
		} else {
296 +
			sprintf(filename, "dump-%i.bin", index);
297 +
		}
298 +
		if (access(filename, F_OK) == -1) {
299 +
			break;
300 +
		}
301 +
		index++;
302 +
	}
303 +
	SDL_RWops *f = SDL_RWFromFile(filename, "wb");
304 +
	if (!f) {
305 +
		printf("Cannot write to %s!\n", filename);
306 +
		return;
307 +
	}
308 +
309 +
	if (dump_cpu) {
310 +
		SDL_RWwrite(f, &regs.a, sizeof(uint8_t), 1);
311 +
		SDL_RWwrite(f, &regs.xl, sizeof(uint8_t), 1);
312 +
		SDL_RWwrite(f, &regs.yl, sizeof(uint8_t), 1);
313 +
		SDL_RWwrite(f, &regs.sp, sizeof(uint8_t), 1);
314 +
		SDL_RWwrite(f, &regs.status, sizeof(uint8_t), 1);
315 +
		SDL_RWwrite(f, &regs.pc, sizeof(uint16_t), 1);
316 +
	}
317 +
	memory_save(f, dump_ram, dump_bank);
318 +
319 +
	if (dump_vram) {
320 +
		video_save(f);
321 +
	}
322 +
323 +
	SDL_RWclose(f);
324 +
	printf("Dumped system to %s.\n", filename);
325 +
}
326 +
327 +
void mouse_state_init(void)
328 +
{
329 +
	kernal_mouse_enabled = 0;
330 +
	SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
331 +
}
332 +
333 +
void
334 +
machine_reset()
335 +
{
336 +
	i2c_reset_state();
337 +
	ieee_init();
338 +
	memory_reset();
339 +
	vera_spi_init();
340 +
	via1_init();
341 +
	if (has_via2) {
342 +
		via2_init();
343 +
	}
344 +
	video_reset();
345 +
	mouse_state_init();
346 +
	reset6502(regs.is65c816);
347 +
	midi_serial_init();
348 +
	netcard_init();
349 +
}
350 +
351 +
void
352 +
machine_nmi()
353 +
{
354 +
	nmi6502();
355 +
}
356 +
357 +
void
358 +
machine_paste(char *s, bool handle_free)
359 +
{
360 +
	if (s) {
361 +
		paste_text = s;
362 +
		if (handle_free) {
363 +
			clipboard_buffer = s; // so that we can free this later
364 +
		}
365 +
		pasting_bas = true;
366 +
		if (warp_pastes) warp_mode = true;
367 +
	}
368 +
}
369 +
370 +
void
371 +
machine_toggle_warp()
372 +
{
373 +
	warp_mode = !warp_mode;
374 +
	timing_init();
375 +
}
376 +
377 +
378 +
static bool
379 +
is_kernal()
380 +
{
381 +
	// only for KERNAL
382 +
	return (debug_read6502(0xfff6, 0, USE_CURRENT_X16_BANK) == 'M' &&
383 +
			debug_read6502(0xfff7, 0, USE_CURRENT_X16_BANK) == 'I' &&
384 +
			debug_read6502(0xfff8, 0, USE_CURRENT_X16_BANK) == 'S' &&
385 +
			debug_read6502(0xfff9, 0, USE_CURRENT_X16_BANK) == 'T')
386 +
		|| (debug_read6502(0xc008, 0, USE_CURRENT_X16_BANK) == 'M' &&
387 +
			debug_read6502(0xc009, 0, USE_CURRENT_X16_BANK) == 'I' &&
388 +
			debug_read6502(0xc00a, 0, USE_CURRENT_X16_BANK) == 'S' &&
389 +
			debug_read6502(0xc00b, 0, USE_CURRENT_X16_BANK) == 'T');
390 +
}
391 +
392 +
static void
393 +
usage()
394 +
{
395 +
	printf("\nCommander X16 Emulator r%s (%s)", VER, VER_NAME);
396 +
#ifdef GIT_REV
397 +
	printf(", "GIT_REV);
398 +
#endif
399 +
	printf("\n(C)2019, 2023 Michael Steil et al.\n");
400 +
	printf("All rights reserved. License: 2-clause BSD\n\n");
401 +
	printf("Usage: x16emu [option] ...\n\n");
402 +
	printf("-rom <rom.bin>\n");
403 +
	printf("\tOverride KERNAL/BASIC/* ROM file.\n");
404 +
	printf("-ram <ramsize>\n");
405 +
	printf("\tSpecify banked RAM size in KB (8, 16, 32, ..., 2048).\n");
406 +
	printf("\tThe default is 512.\n");
407 +
	printf("-nvram <nvram.bin>\n");
408 +
	printf("\tSpecify NVRAM image. By default, the machine starts with\n");
409 +
	printf("\tempty NVRAM and does not save it to disk.\n");
410 +
	printf("-keymap <keymap>\n");
411 +
	printf("\tEnable a specific keyboard layout decode table.\n");
412 +
	printf("-sdcard <sdcard.img>\n");
413 +
	printf("\tSpecify SD card image (partition map + FAT32)\n");
414 +
	printf("-cart <crtfile.crt>\n");
415 +
	printf("\tLoads a specially-formatted cartridge file.\n");
416 +
	printf("-cartbin <romfile.bin>\n");
417 +
	printf("\tLoads a raw cartridge file starting at ROM bank 32. After\n");
418 +
	printf("\tloading, all of the affected banks will function as RAM.\n");
419 +
	printf("-serial\n");
420 +
	printf("\tConnect host fs through Serial Bus [experimental]\n");
421 +
	printf("-nohostieee / -nohostfs\n");
422 +
	printf("\tDisable HostFS through IEEE API interception.\n");
423 +
	printf("\tIEEE API HostFS is normally enabled unless -sdcard or\n");
424 +
	printf("\t-serial is specified.\n");
425 +
	printf("-hostfsdev <unit>\n");
426 +
	printf("\tSet the HostFS IEEE device number. Range 8-31. Default: %d.\n", ieee_unit);
427 +
	printf("-fsroot <directory>\n");
428 +
	printf("\tSpecify the host filesystem directory path which is to\n");
429 +
	printf("\tact as the emulated root directory of the Commander X16.\n");
430 +
	printf("\tDefault is the current working directory.\n");
431 +
	printf("-startin <directory>\n");
432 +
	printf("\tSpecify the host filesystem directory path that the\n");
433 +
	printf("\temulated filesystem starts in. Default is the current\n");
434 +
	printf("\tworking directory if it lies within the hierarchy of fsroot,\n");
435 +
	printf("\totherwise it defaults to fsroot itself.\n");
436 +
	printf("-noemucmdkeys\n");
437 +
	printf("\tDisable emulator command keys.\n");
438 +
	printf("-capture\n");
439 +
	printf("\tStart emulator with mouse/keyboard captured.\n");
440 +
	printf("-nokeyboardcapture\n");
441 +
	printf("\tWhile in capture mode, causes the emulator not to intercept\n");
442 +
	printf("\tkeyboard combinations which are used by the operating system,\n");
443 +
	printf("\tsuch as Alt+Tab.\n");
444 +
	printf("-prg <app.prg>[,<load_addr>]\n");
445 +
	printf("\tLoad application from the *host filesystem* into RAM,\n");
446 +
	printf("\teven if an SD card is attached.\n");
447 +
	printf("\tThe override load address is hex without a prefix.\n");
448 +
	printf("-bas <app.txt>\n");
449 +
	printf("\tInject a BASIC program in ASCII encoding through the\n");
450 +
	printf("\tkeyboard.\n");
451 +
	printf("-run\n");
452 +
	printf("\tStart the -prg/-bas program using RUN\n");
453 +
	printf("-warp\n");
454 +
	printf("\tEnable warp mode, run emulator as fast as possible.\n");
455 +
	printf("-pastewarp\n");
456 +
	printf("\tEnable warp mode during pastes and during loading via -bas.\n");
457 +
	printf("-echo [{iso|raw}]\n");
458 +
	printf("\tPrint all KERNAL output to the host's stdout.\n");
459 +
	printf("\tBy default, everything but printable ASCII characters get\n");
460 +
	printf("\tescaped. \"iso\" will escape everything but non-printable\n");
461 +
	printf("\tISO-8859-15 characters and convert the output to UTF-8.\n");
462 +
	printf("\t\"raw\" will not do any substitutions.\n");
463 +
	printf("\tWith the BASIC statement \"LIST\", this can be used\n");
464 +
	printf("\tto detokenize a BASIC program.\n");
465 +
	printf("-log {K|S|V}...\n");
466 +
	printf("\tEnable logging of (K)eyboard, (S)peed, (V)ideo.\n");
467 +
	printf("\tMultiple characters are possible, e.g. -log KS\n");
468 +
	printf("-gif <file.gif>[,wait]\n");
469 +
	printf("\tRecord a gif for the video output.\n");
470 +
	printf("\tUse ,wait to start paused.\n");
471 +
	printf("\tPOKE $9FB5,2 to start recording.\n");
472 +
	printf("\tPOKE $9FB5,1 to capture a single frame.\n");
473 +
	printf("\tPOKE $9FB5,0 to pause.\n");
474 +
	printf("-wav <file.wav>[{,wait|,auto}]\n");
475 +
	printf("\tRecord a wav for the audio output.\n");
476 +
	printf("\tUse ,wait to start paused, or ,auto to start paused and automatically begin recording on the first non-zero audio signal.\n");
477 +
	printf("\tPOKE $9FB6,2 to automatically begin recording on the first non-zero audio signal.\n");
478 +
	printf("\tPOKE $9FB6,1 to begin recording immediately.\n");
479 +
	printf("\tPOKE $9FB6,0 to pause.\n");
480 +
	printf("-scale {1|2|3|4}\n");
481 +
	printf("\tScale output to an integer multiple of 640x480\n");
482 +
	printf("-quality {nearest|linear|best}\n");
483 +
	printf("\tScaling algorithm quality\n");
484 +
	printf("-widescreen\n");
485 +
	printf("\tStretch output to 16:9 resolution to mimic display of a widescreen monitor.\n");
486 +
	printf("-fullscreen\n");
487 +
	printf("\tStart up in fullscreen mode instead of in a window.\n");
488 +
	printf("-opacity (0.0,...,1.0)\n");
489 +
	printf("\tSet the opacity value (0.0 for transparent, 1.0 for opaque) of the window. (default: %.1f)\n", window_opacity);
490 +
	printf("-debug [<address>]\n");
491 +
	printf("\tEnable debugger. Optionally, set a breakpoint\n");
492 +
	printf("-randram\n");
493 +
	printf("\t(deprecated, no effect)\n");
494 +
	printf("-zeroram\n");
495 +
	printf("\tSet all RAM to zero instead of uninitialized random values\n");
496 +
	printf("-wuninit\n");
497 +
	printf("\tPrints warning to stdout if uninitialized RAM is accessed\n");
498 +
	printf("-memorystats <file.txt>\n");
499 +
	printf("\tSaves memory access statistics to the given file when emulator exits\n");
500 +
	printf("-coverage <file>\n");
501 +
	printf("\tRecord every address an instruction runs from, with its bank,\n");
502 +
	printf("\tand write them to the given file on exit or SIGTERM\n");
503 +
	printf("-dump {C|R|B|V}...\n");
504 +
	printf("\tConfigure system dump: (C)PU, (R)AM, (B)anked-RAM, (V)RAM\n");
505 +
	printf("\tMultiple characters are possible, e.g. -dump CV ; Default: RB\n");
506 +
	printf("-joy1\n");
507 +
	printf("\tEnable binding a gamepad to SNES controller port 1\n");
508 +
	printf("-joy2\n");
509 +
	printf("\tEnable binding a gamepad to SNES controller port 2\n");
510 +
	printf("-joy3\n");
511 +
	printf("\tEnable binding a gamepad to SNES controller port 3\n");
512 +
	printf("-joy4\n");
513 +
	printf("\tEnable binding a gamepad to SNES controller port 4\n");
514 +
	printf("-sound <output device>\n");
515 +
	printf("\tSet the output device used for audio emulation\n");
516 +
	printf("\tIf output device is 'none', no audio is generated\n");
517 +
	printf("-abufs <number of audio buffers>\n");
518 +
	printf("\tSet the number of audio buffers used for playback.\n");
519 +
	printf("\tIf using HostFS, the default is 32, otherwise 8.\n");
520 +
	printf("\tIncreasing this will reduce stutter on slower computers,\n");
521 +
	printf("\tbut will increase audio latency.\n");
522 +
	printf("-rtc\n");
523 +
	printf("\tSet the real-time-clock to the current system time and date.\n");
524 +
	printf("-via2\n");
525 +
	printf("\tInstall the second VIA chip expansion at $9F10\n");
526 +
	printf("-testbench\n");
527 +
	printf("\tHeadless mode for unit testing with an external test runner\n");
528 +
	printf("-mhz <integer>\n");
529 +
	printf("\tRun the emulator with a system clock speed other than the default of\n");
530 +
	printf("\t8 MHz. Valid values are in the range of 1-40, inclusive. This option\n");
531 +
	printf("\tis meant mainly for benchmarking, and may not reflect accurate\n");
532 +
	printf("\thardware behavior.\n");
533 +
	printf("-midline-effects\n");
534 +
	printf("\tApproximate mid-line raster effects when changing tile, sprite,\n");
535 +
	printf("\tand palette data. Requires a fast host CPU.\n");
536 +
	printf("-enable-ym2151-irq\n");
537 +
	printf("\tConnect the YM2151 IRQ source to the emulated CPU. This option increases\n");
538 +
	printf("\tCPU usage as audio render is triggered for every CPU instruction.\n");
539 +
	printf("-c02\n");
540 +
	printf("\tRun the emulator under an emulated 65C02 (default)\n");
541 +
	printf("-c816\n");
542 +
	printf("\tRun the emulator under an emulated 65C816\n");
543 +
	printf("\tThis option is experimental.\n");
544 +
	printf("-rockwell\n");
545 +
	printf("\tSuppress warning emitted when encountering a Rockwell extension on the 65C02\n");
546 +
	printf("-longpwron\n");
547 +
	printf("\tSimulate a long press of the power button at system power-on.\n");
548 +
	printf("-netcard [<address>]\n");
549 +
	printf("\tInstall a virtual serial/network card at the specified address, or at $9F60\n");
550 +
	printf("\tby default: a 16450 UART with a Hayes modem behind it whose dial opens a\n");
551 +
	printf("\treal TCP connection from the emulator (ATD\"host:port\"). For testing\n");
552 +
	printf("\tprograms written against the TexElec serial/ESP32 card.\n");
553 +
	printf("-midicard [<address>]\n");
554 +
	printf("\tInstall a serial MIDI card at the specified address, or at $9F60 by default.\n");
555 +
	printf("\tThe -sf2 option must be specified along with this option.\n");
556 +
	printf("-sf2 <SoundFont filename>\n");
557 +
	printf("\tInitialize MIDI synth with the specified SoundFont.\n");
558 +
	printf("\tThe -midicard option must be specified along with this option.\n");
559 +
	printf("-midi-in\n");
560 +
	printf("\tConnect the system MIDI input devices to the input of the first UART\n");
561 +
	printf("\tof the emulated MIDI card. The -midicard option is required for this\n");
562 +
	printf("\toption to have any effect.\n");
563 +
#ifdef TRACE
564 +
	printf("-trace [<address>]\n");
565 +
	printf("\tPrint instruction trace. Optionally, a trigger address\n");
566 +
	printf("\tcan be specified.\n");
567 +
#endif
568 +
	printf("-version\n");
569 +
	printf("\tPrint additional version information of the emulator and ROM.\n");
570 +
	printf("\n");
571 +
	exit(1);
572 +
}
573 +
574 +
void
575 +
usage_keymap()
576 +
{
577 +
	printf("The following keymaps are supported:\n");
578 +
	for (int i = 0; i < sizeof(keymaps)/sizeof(*keymaps); i++) {
579 +
		printf("\t%s\n", keymaps[i]);
580 +
	}
581 +
	exit(1);
582 +
}
583 +
584 +
void no_fluidsynth_warning(void)
585 +
{
586 +
	static bool already_warned;
587 +
588 +
	if (!already_warned) {
589 +
		fprintf(stderr, "\nWarning: x16emu was built without FluidSynth support,\n");
590 +
		fprintf(stderr, "so the MIDI synth will be inoperative.\n\n");
591 +
#if defined(__linux__)
592 +
		fprintf(stderr, "To build x16emu with fluidsynth support, you distro may\n");
593 +
		fprintf(stderr, "have a libfluidsynth-dev or fluidsynth-devel package that\n");
594 +
		fprintf(stderr, "needs to be installed before building x16emu.\n\n");
595 +
#elif defined(__APPLE__)
596 +
		fprintf(stderr, "To build x16emu with fluidsynth support,\n");
597 +
		fprintf(stderr, "install the homebrew package fluid-synth before\n");
598 +
		fprintf(stderr, "building x16emu.\n\n");
599 +
#elif defined(_WIN64)
600 +
		fprintf(stderr, "To build x16emu with fluidsynth support under MSYS2,\n");
601 +
		fprintf(stderr, "install the mingw-w64-x86_64-fluidsynth package before\n");
602 +
		fprintf(stderr, "building x16emu.\n\n");
603 +
#elif defined(_WIN32)
604 +
		fprintf(stderr, "To build x16emu with fluidsynth support under MSYS2,\n");
605 +
		fprintf(stderr, "install the mingw-w64-i686-fluidsynth package before\n");
606 +
		fprintf(stderr, "building x16emu.\n\n");
607 +
#endif
608 +
		fprintf(stderr, "Then build x16emu with FLUIDSYNTH=1. For example:\n");
609 +
		fprintf(stderr, "FLUIDSYNTH=1 make\n");
610 +
		already_warned = true;
611 +
	}
612 +
}
613 +
614 +
int
615 +
main(int argc, char **argv)
616 +
{
617 +
	char *rom_filename = "rom.bin";
618 +
	char rom_path_data[PATH_MAX];
619 +
620 +
	char *rom_path = rom_path_data;
621 +
	char *prg_path = NULL;
622 +
	char *bas_path = NULL;
623 +
	char *sf2_path = NULL;
624 +
	char *sdcard_path = NULL;
625 +
	bool run_test = false;
626 +
	int test_number = 0;
627 +
	int audio_buffers = 8;
628 +
	bool zeroram = false;
629 +
	bool audio_buffers_set = false;
630 +
	bool hostfs_set = false;
631 +
632 +
	const char *audio_dev_name = NULL;
633 +
634 +
	run_after_load = false;
635 +
636 +
	char *base_path = SDL_GetBasePath();
637 +
638 +
	// This causes the emulator to load ROM data from the executable's directory when
639 +
	// no ROM file is specified on the command line.
640 +
	memcpy(rom_path, base_path, strlen(base_path) + 1);
641 +
	strncpy(rom_path + strlen(rom_path), rom_filename, PATH_MAX - strlen(rom_path));
642 +
	memory_randomize_ram(true);
643 +
644 +
	argc--;
645 +
	argv++;
646 +
647 +
	while (argc > 0) {
648 +
		if (!strcmp(argv[0], "-rom")) {
649 +
			argc--;
650 +
			argv++;
651 +
			if (!argc || argv[0][0] == '-') {
652 +
				usage();
653 +
			}
654 +
			rom_path = argv[0];
655 +
			argc--;
656 +
			argv++;
657 +
		} else if (!strcmp(argv[0], "-ram")) {
658 +
			argc--;
659 +
			argv++;
660 +
			if (!argc || argv[0][0] == '-') {
661 +
				usage();
662 +
			}
663 +
			int kb = atoi(argv[0]);
664 +
			if (!((kb & 7)==0) || kb < 8 || kb > 2048) {
665 +
				printf("-ram value must be a multiple of 8 in the range of 8-2048.\n");
666 +
				exit(1);
667 +
			}
668 +
			num_ram_banks = kb /8;
669 +
			argc--;
670 +
			argv++;
671 +
		} else if (!strcmp(argv[0], "-keymap")) {
672 +
			argc--;
673 +
			argv++;
674 +
			if (!argc || argv[0][0] == '-') {
675 +
				usage_keymap();
676 +
			}
677 +
			bool found = false;
678 +
			for (int i = 0; i < sizeof(keymaps)/sizeof(*keymaps); i++) {
679 +
				if (!strcmp(argv[0], keymaps[i])) {
680 +
					found = true;
681 +
					keymap = i;
682 +
				}
683 +
			}
684 +
			if (!found) {
685 +
				usage_keymap();
686 +
			}
687 +
			argc--;
688 +
			argv++;
689 +
		} else if (!strcmp(argv[0], "-prg")) {
690 +
			argc--;
691 +
			argv++;
692 +
			if (!argc || argv[0][0] == '-') {
693 +
				usage();
694 +
			}
695 +
			prg_path = argv[0];
696 +
			argc--;
697 +
			argv++;
698 +
		} else if (!strcmp(argv[0], "-netcard")) {
699 +
			argc--;
700 +
			argv++;
701 +
			has_netcard = true;
702 +
			if (argc && argv[0][0] != '-') {
703 +
				netcard_addr = 0x9f00 | ((uint16_t)strtol(argv[0], NULL, 16) & 0xff);
704 +
				netcard_addr &= 0xfff0;
705 +
				argc--;
706 +
				argv++;
707 +
			} else {
708 +
				netcard_addr = 0x9f60;
709 +
			}
710 +
		} else if (!strcmp(argv[0], "-midicard")) {
711 +
#ifndef HAS_FLUIDSYNTH
712 +
			no_fluidsynth_warning();
713 +
#endif
714 +
			argc--;
715 +
			argv++;
716 +
			has_midi_card = true;
717 +
			if (argc && argv[0][0] != '-') {
718 +
				midi_card_addr = 0x9f00 | ((uint16_t)strtol(argv[0], NULL, 16) & 0xff);
719 +
				midi_card_addr &= 0xfff0;
720 +
				argc--;
721 +
				argv++;
722 +
			} else {
723 +
				midi_card_addr = 0x9f60;
724 +
			}
725 +
		} else if (!strcmp(argv[0], "-sf2")) {
726 +
#ifndef HAS_FLUIDSYNTH
727 +
			no_fluidsynth_warning();
728 +
#endif
729 +
			argc--;
730 +
			argv++;
731 +
			if (!argc || argv[0][0] == '-') {
732 +
				usage();
733 +
			}
734 +
			sf2_path = argv[0];
735 +
			argc--;
736 +
			argv++;
737 +
		} else if (!strcmp(argv[0], "-midi-in")) {
738 +
#ifndef HAS_FLUIDSYNTH
739 +
			no_fluidsynth_warning();
740 +
#endif
741 +
			argc--;
742 +
			argv++;
743 +
			fs_midi_in_connect = true;
744 +
		} else if (!strcmp(argv[0], "-controlport")) {
745 +
			argc--;
746 +
			argv++;
747 +
			if (!argc || argv[0][0] == '-') {
748 +
				usage();
749 +
			}
750 +
			control_port = (int)strtol(argv[0], NULL, 0);
751 +
			argc--;
752 +
			argv++;
753 +
		} else if (!strcmp(argv[0], "-run")) {
754 +
			argc--;
755 +
			argv++;
756 +
			run_after_load = true;
757 +
		} else if (!strcmp(argv[0], "-bas")) {
758 +
			argc--;
759 +
			argv++;
760 +
			if (!argc || argv[0][0] == '-') {
761 +
				usage();
762 +
			}
763 +
			bas_path = argv[0];
764 +
			argc--;
765 +
			argv++;
766 +
		} else if (!strcmp(argv[0], "-test")) {
767 +
			argc--;
768 +
			argv++;
769 +
			if (!argc || argv[0][0] == '-') {
770 +
				usage();
771 +
			}
772 +
			test_number = atoi(argv[0]);
773 +
			run_test = true;
774 +
			argc--;
775 +
			argv++;
776 +
		} else if (!strcmp(argv[0], "-nvram")) {
777 +
			argc--;
778 +
			argv++;
779 +
			if (!argc || argv[0][0] == '-') {
780 +
				usage();
781 +
			}
782 +
			nvram_path = argv[0];
783 +
			argc--;
784 +
			argv++;
785 +
		} else if (!strcmp(argv[0], "-sdcard")) {
786 +
			argc--;
787 +
			argv++;
788 +
			if (!argc || argv[0][0] == '-') {
789 +
				usage();
790 +
			}
791 +
			sdcard_path = argv[0];
792 +
			argc--;
793 +
			argv++;
794 +
		} else if (!strcmp(argv[0], "-cart")) {
795 +
			argc--;
796 +
			argv++;
797 +
			if (!argc || argv[0][0] == '-') {
798 +
				usage();
799 +
			}
800 +
			cartridge_path = argv[0];
801 +
			argc--;
802 +
			argv++;
803 +
		} else if (!strcmp(argv[0], "-cartbin")) {
804 +
			argc--;
805 +
			argv++;
806 +
			if (!argc || argv[0][0] == '-') {
807 +
				usage();
808 +
			}
809 +
			cartridge_new();
810 +
			cartridge_define_bank_range(32, 255, CART_BANK_UNINITIALIZED_RAM);
811 +
			cartridge_import_files(argv, 1, 32, CART_BANK_INITIALIZED_RAM, 0);
812 +
			argc--;
813 +
			argv++;
814 +
		} else if (!strcmp(argv[0], "-warp")) {
815 +
			argc--;
816 +
			argv++;
817 +
			warp_mode = true;
818 +
		} else if (!strcmp(argv[0], "-pastewarp")) {
819 +
			argc--;
820 +
			argv++;
821 +
			warp_pastes = true;
822 +
		} else if (!strcmp(argv[0], "-echo")) {
823 +
			argc--;
824 +
			argv++;
825 +
			if (argc && argv[0][0] != '-') {
826 +
				if (!strcmp(argv[0], "raw")) {
827 +
					echo_mode = ECHO_MODE_RAW;
828 +
				} else if (!strcmp(argv[0], "iso")) {
829 +
						echo_mode = ECHO_MODE_ISO;
830 +
				} else {
831 +
					usage();
832 +
				}
833 +
				argc--;
834 +
				argv++;
835 +
			} else {
836 +
				echo_mode = ECHO_MODE_COOKED;
837 +
			}
838 +
		} else if (!strcmp(argv[0], "-log")) {
839 +
			argc--;
840 +
			argv++;
841 +
			if (!argc || argv[0][0] == '-') {
842 +
				usage();
843 +
			}
844 +
			for (char *p = argv[0]; *p; p++) {
845 +
				switch (tolower(*p)) {
846 +
					case 'k':
847 +
						log_keyboard = true;
848 +
						break;
849 +
					case 's':
850 +
						log_speed = true;
851 +
						break;
852 +
					case 'v':
853 +
						log_video = true;
854 +
						break;
855 +
					default:
856 +
						usage();
857 +
				}
858 +
			}
859 +
			argc--;
860 +
			argv++;
861 +
		} else if (!strcmp(argv[0], "-dump")) {
862 +
			argc--;
863 +
			argv++;
864 +
			if (!argc || argv[0][0] == '-') {
865 +
				usage();
866 +
			}
867 +
			dump_cpu = false;
868 +
			dump_ram = false;
869 +
			dump_bank = false;
870 +
			dump_vram = false;
871 +
			for (char *p = argv[0]; *p; p++) {
872 +
				switch (tolower(*p)) {
873 +
					case 'c':
874 +
						dump_cpu = true;
875 +
						break;
876 +
					case 'r':
877 +
						dump_ram = true;
878 +
						break;
879 +
					case 'b':
880 +
						dump_bank = true;
881 +
						break;
882 +
					case 'v':
883 +
						dump_vram = true;
884 +
						break;
885 +
					default:
886 +
						usage();
887 +
				}
888 +
			}
889 +
			argc--;
890 +
			argv++;
891 +
		} else if (!strcmp(argv[0], "-gif")) {
892 +
			argc--;
893 +
			argv++;
894 +
			// set up for recording
895 +
			record_gif = RECORD_GIF_PAUSED;
896 +
			if (!argc || argv[0][0] == '-') {
897 +
				usage();
898 +
			}
899 +
			gif_path = argv[0];
900 +
			argv++;
901 +
			argc--;
902 +
		} else if (!strcmp(argv[0], "-wav")) {
903 +
			argc--;
904 +
			argv++;
905 +
			// set up for recording
906 +
			if (!argc || argv[0][0] == '-') {
907 +
				usage();
908 +
			}
909 +
			wav_path = argv[0];
910 +
			argv++;
911 +
			argc--;
912 +
		} else if (!strcmp(argv[0], "-debug")) {
913 +
			argc--;
914 +
			argv++;
915 +
			debugger_enabled = true;
916 +
			if (argc && argv[0][0] != '-') {
917 +
				uint32_t bpVal = (uint32_t)strtol(argv[0], NULL, 16);
918 +
				struct breakpoint bp;
919 +
				if (bpVal < 0xA000) {
920 +
					bp.pc = bpVal;
921 +
					bp.bank = 0;
922 +
					bp.x16Bank = -1;
923 +
				} else {
924 +
					bp.pc = bpVal & 0xffff;
925 +
					bp.bank = 0;
926 +
					bp.x16Bank = bpVal >> 16;
927 +
				}
928 +
				DEBUGSetBreakPoint(bp);
929 +
				argc--;
930 +
				argv++;
931 +
			}
932 +
		} else if (!strcmp(argv[0], "-randram")) {
933 +
			/* this operation has no effect anymore, randomizing the Ram is now default */
934 +
			argc--;
935 +
			argv++;
936 +
		} else if (!strcmp(argv[0], "-zeroram")) {
937 +
			argc--;
938 +
			argv++;
939 +
			memory_randomize_ram(false);
940 +
			zeroram = true;
941 +
		} else if (!strcmp(argv[0], "-wuninit")) {
942 +
			argc--;
943 +
			argv++;
944 +
			memory_report_uninitialized_access(true);
945 +
		} else if (!strcmp(argv[0], "-memorystats")) {
946 +
			argc--;
947 +
			argv++;
948 +
			if (!argc || argv[0][0] == '-') {
949 +
				usage();
950 +
			}
951 +
			memory_report_usage_statistics(argv[0]);
952 +
			argv++;
953 +
			argc--;
954 +
		} else if (!strcmp(argv[0], "-joy1")) {
955 +
			argc--;
956 +
			argv++;
957 +
			Joystick_slots_enabled[0] = true;
958 +
		} else if (!strcmp(argv[0], "-joy2")){
959 +
			argc--;
960 +
			argv++;
961 +
			Joystick_slots_enabled[1] = true;
962 +
		} else if (!strcmp(argv[0], "-joy3")) {
963 +
			argc--;
964 +
			argv++;
965 +
			Joystick_slots_enabled[2] = true;
966 +
		} else if (!strcmp(argv[0], "-joy4")) {
967 +
			argc--;
968 +
			argv++;
969 +
			Joystick_slots_enabled[3] = true;
970 +
#ifdef TRACE
971 +
		} else if (!strcmp(argv[0], "-trace")) {
972 +
			argc--;
973 +
			argv++;
974 +
			if (argc && argv[0][0] != '-') {
975 +
				trace_mode = false;
976 +
				trace_address = (uint16_t)strtol(argv[0], NULL, 16);
977 +
				argc--;
978 +
				argv++;
979 +
			} else {
980 +
				trace_mode = true;
981 +
				trace_address = 0;
982 +
			}
983 +
#endif
984 +
		} else if (!strcmp(argv[0], "-scale")) {
985 +
			argc--;
986 +
			argv++;
987 +
			if(!argc || argv[0][0] == '-') {
988 +
				usage();
989 +
			}
990 +
			for(char *p = argv[0]; *p; p++) {
991 +
				switch(tolower(*p)) {
992 +
				case '1':
993 +
					window_scale = 1;
994 +
					break;
995 +
				case '2':
996 +
					window_scale = 2;
997 +
					break;
998 +
				case '3':
999 +
					window_scale = 3;
1000 +
					break;
1001 +
				case '4':
1002 +
					window_scale = 4;
1003 +
					break;
1004 +
				default:
1005 +
					usage();
1006 +
				}
1007 +
			}
1008 +
			argc--;
1009 +
			argv++;
1010 +
		} else if (!strcmp(argv[0], "-quality")) {
1011 +
			argc--;
1012 +
			argv++;
1013 +
			if(!argc || argv[0][0] == '-') {
1014 +
				usage();
1015 +
			}
1016 +
			if (!strcmp(argv[0], "nearest") ||
1017 +
				!strcmp(argv[0], "linear") ||
1018 +
				!strcmp(argv[0], "best")) {
1019 +
				scale_quality = argv[0];
1020 +
			} else {
1021 +
				usage();
1022 +
			}
1023 +
			argc--;
1024 +
			argv++;
1025 +
		} else if (!strcmp(argv[0], "-widescreen")) {
1026 +
			argc--;
1027 +
			argv++;
1028 +
			screen_x_scale = 4.0/3;
1029 +
		} else if (!strcmp(argv[0], "-fullscreen")) {
1030 +
			argc--;
1031 +
			argv++;
1032 +
			fullscreen = true;
1033 +
		} else if (!strcmp(argv[0], "-opacity")) {
1034 +
			argc--;
1035 +
			argv++;
1036 +
			if (!argc || argv[0][0] == '-') {
1037 +
				usage();
1038 +
			}
1039 +
			window_opacity = strtof(argv[0], NULL);
1040 +
			argc--;
1041 +
			argv++;
1042 +
		} else if (!strcmp(argv[0], "-sound")) {
1043 +
			argc--;
1044 +
			argv++;
1045 +
			if (!argc || argv[0][0] == '-') {
1046 +
				audio_usage();
1047 +
			}
1048 +
			audio_dev_name = argv[0];
1049 +
			argc--;
1050 +
			argv++;
1051 +
		} else if (!strcmp(argv[0], "-abufs")) {
1052 +
			argc--;
1053 +
			argv++;
1054 +
			if (!argc || argv[0][0] == '-') {
1055 +
				usage();
1056 +
			}
1057 +
			audio_buffers = (int)strtol(argv[0], NULL, 10);
1058 +
			audio_buffers_set = true;
1059 +
			argc--;
1060 +
			argv++;
1061 +
		} else if (!strcmp(argv[0], "-rtc")) {
1062 +
			argc--;
1063 +
			argv++;
1064 +
			set_system_time = true;
1065 +
		} else if (!strcmp(argv[0], "-serial")) {
1066 +
			argc--;
1067 +
			argv++;
1068 +
			has_serial = true;
1069 +
		} else if (!strcmp(argv[0], "-nohostieee") || !strcmp(argv[0], "-nohostfs")) {
1070 +
			argc--;
1071 +
			argv++;
1072 +
			no_ieee_intercept = true;
1073 +
			hostfs_set = false;
1074 +
			using_hostfs = false;
1075 +
		} else if (!strcmp(argv[0], "-hostfsdev")){
1076 +
			argc--;
1077 +
			argv++;
1078 +
			if (!argc || argv[0][0] == '-') {
1079 +
				usage();
1080 +
			}
1081 +
			ieee_unit = (uint8_t)strtol(argv[0], NULL, 10);
1082 +
			if (ieee_unit < 8 || ieee_unit > 31) {
1083 +
				usage();
1084 +
			}
1085 +
			hostfs_set = true;
1086 +
			using_hostfs = true;
1087 +
			argc--;
1088 +
			argv++;
1089 +
		} else if (!strcmp(argv[0], "-fsroot")) {
1090 +
			argc--;
1091 +
			argv++;
1092 +
			if (!argc || argv[0][0] == '-') {
1093 +
				usage();
1094 +
			}
1095 +
			fsroot_path = (uint8_t *)argv[0];
1096 +
			argc--;
1097 +
			argv++;
1098 +
		} else if (!strcmp(argv[0], "-startin")) {
1099 +
			argc--;
1100 +
			argv++;
1101 +
			if (!argc || argv[0][0] == '-') {
1102 +
				usage();
1103 +
			}
1104 +
			startin_path = (uint8_t *)argv[0];
1105 +
			argc--;
1106 +
			argv++;
1107 +
		} else if (!strcmp(argv[0], "-noemucmdkeys")) {
1108 +
			argc--;
1109 +
			argv++;
1110 +
			disable_emu_cmd_keys = true;
1111 +
		} else if (!strcmp(argv[0], "-capture")) {
1112 +
			argc--;
1113 +
			argv++;
1114 +
			grab_mouse = true;
1115 +
		} else if (!strcmp(argv[0], "-longpwron")) {
1116 +
			argc--;
1117 +
			argv++;
1118 +
			pwr_long_press = true;
1119 +
		} else if (!strcmp(argv[0], "-nokeyboardcapture")) {
1120 +
			argc--;
1121 +
			argv++;
1122 +
			no_keyboard_capture = true;
1123 +
		} else if (!strcmp(argv[0], "-via2")) {
1124 +
			argc--;
1125 +
			argv++;
1126 +
			has_via2 = true;
1127 +
		} else if (!strcmp(argv[0], "-version")){
1128 +
			printf("%s", VER_INFO);
1129 +
#ifdef GIT_REV
1130 +
			printf(" "GIT_REV"\n");
1131 +
#else
1132 +
			printf("\n");
1133 +
#endif
1134 +
			argc--;
1135 +
			argv++;
1136 +
			exit(0);
1137 +
		} else if (!strcmp(argv[0], "-coverage")) {
1138 +
			argc--;
1139 +
			argv++;
1140 +
			if (!argc || argv[0][0] == '-') {
1141 +
				usage();
1142 +
			}
1143 +
			if (!coverage_init(argv[0])) {
1144 +
				printf("Cannot set up coverage for %s!\n", argv[0]);
1145 +
				exit(1);
1146 +
			}
1147 +
			argc--;
1148 +
			argv++;
1149 +
		} else if (!strcmp(argv[0], "-testbench")){
1150 +
			printf("Testbench mode...\n");
1151 +
			fflush(stdout);
1152 +
			argc--;
1153 +
			argv++;
1154 +
			testbench=true;
1155 +
			headless=true;
1156 +
		} else if (!strcmp(argv[0], "-mhz")){
1157 +
			argc--;
1158 +
			argv++;
1159 +
			if (!argc || argv[0][0] == '-') {
1160 +
				usage();
1161 +
			}
1162 +
			MHZ = (uint8_t)strtol(argv[0], NULL, 10);
1163 +
			if (MHZ < 1 || MHZ > 40) {
1164 +
				usage();
1165 +
			}
1166 +
			argc--;
1167 +
			argv++;
1168 +
		} else if (!strcmp(argv[0], "-midline-effects")){
1169 +
			argc--;
1170 +
			argv++;
1171 +
			enable_midline = true;
1172 +
		} else if (!strcmp(argv[0], "-enable-ym2151-irq")){
1173 +
			argc--;
1174 +
			argv++;
1175 +
			ym2151_irq_support = true;
1176 +
		} else if (!strcmp(argv[0], "-c816")){
1177 +
			argc--;
1178 +
			argv++;
1179 +
			regs.is65c816 = true;
1180 +
			is_gen2 = false;
1181 +
		} else if (!strcmp(argv[0], "-c02")){
1182 +
			argc--;
1183 +
			argv++;
1184 +
			regs.is65c816 = false;
1185 +
			is_gen2 = false;
1186 +
		} else if (!strcmp(argv[0], "-gs")) {
1187 +
			argc--;
1188 +
			argv++;
1189 +
			regs.is65c816 = true;
1190 +
			is_gen2 = true;
1191 +
		}
1192 +
		else if (!strcmp(argv[0], "-rockwell")){
1193 +
			argc--;
1194 +
			argv++;
1195 +
			warn_rockwell = false;
1196 +
		} else {
1197 +
			usage();
1198 +
		}
1199 +
	}
1200 +
1201 +
	if (is_gen2) {
1202 +
		num_banks = NUM_MAX_BANKS;
1203 +
		num_ram_banks = NUM_MAX_RAM_BANKS;
1204 +
	}
1205 +
1206 +
	SDL_RWops *f = SDL_RWFromFile(rom_path, "rb");
1207 +
	if (!f) {
1208 +
		printf("Cannot open %s!\n", rom_path);
1209 +
		exit(1);
1210 +
	}
1211 +
	size_t rom_size = SDL_RWread(f, ROM, ROM_SIZE, 1);
1212 +
	(void)rom_size;
1213 +
	SDL_RWclose(f);
1214 +
1215 +
	if (nvram_path) {
1216 +
		SDL_RWops *f = SDL_RWFromFile(nvram_path, "rb");
1217 +
		if (f) {
1218 +
			SDL_RWread(f, nvram, 1, sizeof(nvram));
1219 +
			SDL_RWclose(f);
1220 +
		}
1221 +
	}
1222 +
1223 +
	if (sdcard_path) {
1224 +
		sdcard_set_path(sdcard_path);
1225 +
		if (!hostfs_set) {
1226 +
			using_hostfs = false;
1227 +
		}
1228 +
	}
1229 +
1230 +
	if (using_hostfs && !audio_buffers_set) {
1231 +
#ifdef __EMSCRIPTEN__
1232 +
		audio_buffers = 8; // wasm has larger buffers in audio.c, so we keep it 8 even w/ HostFS
1233 +
#else
1234 +
		audio_buffers = 32;
1235 +
#endif
1236 +
	}
1237 +
1238 +
	if (sf2_path && has_midi_card) {
1239 +
		if (midi_card_addr < 0x9f60) {
1240 +
			fprintf(stderr, "Warning: Serial MIDI card address must be in the range of 9F60-9FF0\n");
1241 +
		} else {
1242 +
			midi_init();
1243 +
			midi_load_sf2((uint8_t *)sf2_path);
1244 +
		}
1245 +
	} else if (sf2_path || has_midi_card) {
1246 +
		fprintf(stderr, "Warning: -sf2 and -midicard must be specified together in order to enable the MIDI synth.\n");
1247 +
		has_midi_card = false;
1248 +
	}
1249 +
1250 +
	if (cartridge_path) {
1251 +
		if (!cartridge_load(cartridge_path, !zeroram)) {
1252 +
			printf("Cannot open %s!\n", cartridge_path);
1253 +
			exit(1);
1254 +
		}
1255 +
	}
1256 +
1257 +
	prg_override_start = -1;
1258 +
	if (prg_path) {
1259 +
		char *comma = strchr(prg_path, ',');
1260 +
		if (comma) {
1261 +
			prg_override_start = (uint16_t)strtol(comma + 1, NULL, 16);
1262 +
			*comma = 0;
1263 +
		}
1264 +
1265 +
		prg_file = SDL_RWFromFile(prg_path, "rb");
1266 +
		if (!prg_file) {
1267 +
			printf("Cannot open %s!\n", prg_path);
1268 +
			exit(1);
1269 +
		}
1270 +
	}
1271 +
1272 +
	if (bas_path) {
1273 +
		SDL_RWops *bas_file = SDL_RWFromFile(bas_path, "r");
1274 +
		if (!bas_file) {
1275 +
			printf("Cannot open %s!\n", bas_path);
1276 +
			exit(1);
1277 +
		}
1278 +
		paste_text = paste_text_data;
1279 +
		size_t paste_size = SDL_RWread(bas_file, paste_text, 1, sizeof(paste_text_data) - 1);
1280 +
		if (run_after_load) {
1281 +
			strncpy(paste_text + paste_size, "\rRUN\r", sizeof(paste_text_data) - paste_size);
1282 +
		} else {
1283 +
			paste_text[paste_size] = 0;
1284 +
		}
1285 +
		SDL_RWclose(bas_file);
1286 +
	}
1287 +
1288 +
	if (run_test) {
1289 +
		paste_text = paste_text_data;
1290 +
		snprintf(paste_text, sizeof(paste_text_data), "TEST %d\r", test_number);
1291 +
	}
1292 +
1293 +
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
1294 +
	// Don't disable compositing (on KDE for example)
1295 +
	// Available since SDL 2.0.8
1296 +
	SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
1297 +
#endif
1298 +
#ifdef __EMSCRIPTEN__
1299 +
	emscripten_set_main_loop(emscripten_main_loop, 0, 0);
1300 +
#endif
1301 +
	if (!headless) {
1302 +
		// Shows up in the power management area of Linux desktops of applications inhibiting the screensaver
1303 +
		// As well as the audio mixer
1304 +
		// Unless hinted, defaults are "My SDL application" and "Playing a game"
1305 +
#ifdef SDL_HINT_AUDIO_DEVICE_APP_NAME
1306 +
		SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Commander X16 Emulator");
1307 +
#endif
1308 +
#ifdef SDL_HINT_APP_NAME
1309 +
		SDL_SetHint(SDL_HINT_APP_NAME, "Commander X16 Emulator");
1310 +
#endif
1311 +
#ifdef SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME
1312 +
		SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, "Emulating modern retro awesomeness");
1313 +
#endif
1314 +
		int e = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO | SDL_INIT_TIMER);
1315 +
		if (e < 0) {
1316 +
			fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
1317 +
			exit(-1);
1318 +
		}
1319 +
		audio_init(audio_dev_name, audio_buffers);
1320 +
		video_init(window_scale, screen_x_scale, scale_quality, fullscreen, window_opacity);
1321 +
	}
1322 +
1323 +
	wav_recorder_set_path(wav_path);
1324 +
1325 +
	memory_init();
1326 +
1327 +
	joystick_init();
1328 +
1329 +
	rtc_init(set_system_time);
1330 +
1331 +
	machine_reset();
1332 +
1333 +
	timing_init();
1334 +
1335 +
	instruction_counter = 0;
1336 +
1337 +
	if (control_port > 0) {
1338 +
		x16_control_start(control_port);
1339 +
	}
1340 +
1341 +
#ifdef __EMSCRIPTEN__
1342 +
	emscripten_cancel_main_loop();
1343 +
	emscripten_set_main_loop(emscripten_main_loop, 0, 1);
1344 +
#else
1345 +
	emulator_loop(NULL);
1346 +
#endif
1347 +
1348 +
	main_shutdown();
1349 +
	memory_dump_usage_counts();
1350 +
	return 0;
1351 +
}
1352 +
1353 +
void main_shutdown() {
1354 +
	coverage_write();
1355 +
	if (!headless){
1356 +
		wav_recorder_shutdown();
1357 +
		audio_close();
1358 +
		video_end();
1359 +
		SDL_Quit();
1360 +
	}
1361 +
	if(cartridge_path) {
1362 +
		cartridge_save_nvram();
1363 +
		cartridge_unload();
1364 +
	}
1365 +
	files_shutdown();
1366 +
1367 +
#ifdef PERFSTAT
1368 +
	for (int pc = 0xc000; pc < sizeof(stat)/sizeof(*stat); pc++) {
1369 +
		if (stat[pc] == 0) {
1370 +
			continue;
1371 +
		}
1372 +
		char *label = label_for_address(pc);
1373 +
		if (!label) {
1374 +
			continue;
1375 +
		}
1376 +
		char *original_label = label;
1377 +
		uint16_t pc2 = pc;
1378 +
		if (label[0] == '@') {
1379 +
			label = NULL;
1380 +
			while (!label || label[0] == '@') {
1381 +
				pc2--;
1382 +
				label = label_for_address(pc2);
1383 +
			}
1384 +
		}
1385 +
		printf("%d\t $%04X %s+%d", stat[pc], pc, label, pc-pc2);
1386 +
		if (pc-pc2 != 0) {
1387 +
			printf(" (%s)", original_label);
1388 +
		}
1389 +
		printf("\n");
1390 +
	}
1391 +
#endif
1392 +
1393 +
}
1394 +
1395 +
bool
1396 +
set_kernal_status(uint8_t s)
1397 +
{
1398 +
	// There is no KERNAL API to write the STATUS variable.
1399 +
	// But there is code to read it, READST, which should
1400 +
	// always look like this:
1401 +
	// 00:.,d6a0 ad 89 02 lda $0289
1402 +
	// 00:.,d6a3 0d 89 02 ora $0289
1403 +
	// 00:.,d6a6 8d 89 02 sta $0289
1404 +
	// We can extract the location of the STATUS variable
1405 +
	// from it.
1406 +
1407 +
	// JMP in the KERNAL API vectors
1408 +
	if (debug_read6502(0xffb7, 0, 0) != 0x4c) {
1409 +
		return false;
1410 +
	}
1411 +
	// target of KERNAL API vector JMP
1412 +
	uint16_t readst = debug_read6502(0xffb8, 0, 0) | debug_read6502(0xffb9, 0, 0) << 8;
1413 +
	if (readst < 0xc000) {
1414 +
		return false;
1415 +
	}
1416 +
	// ad 89 02 lda $0289
1417 +
	if (debug_read6502(readst, 0, 0) != 0xad) {
1418 +
		return false;
1419 +
	}
1420 +
	// ad 89 02 lda $0289
1421 +
	if (debug_read6502(readst + 3, 0, 0) != 0x0d) {
1422 +
		return false;
1423 +
	}
1424 +
	// ad 89 02 lda $0289
1425 +
	if (debug_read6502(readst + 6, 0, 0) != 0x8d) {
1426 +
		return false;
1427 +
	}
1428 +
	uint16_t status0 = debug_read6502(readst+1, 0, 0) | debug_read6502(readst+2, 0, 0) << 8;
1429 +
	uint16_t status1 = debug_read6502(readst+4, 0, 0) | debug_read6502(readst+5, 0, 0) << 8;
1430 +
	uint16_t status2 = debug_read6502(readst+7, 0, 0) | debug_read6502(readst+8, 0, 0) << 8;
1431 +
	// all three addresses must be the same
1432 +
	if (status0 != status1 || status0 != status2) {
1433 +
		return false;
1434 +
	}
1435 +
1436 +
	// everything okay, write the status!
1437 +
	RAM[status0] = s;
1438 +
	return true;
1439 +
}
1440 +
1441 +
bool
1442 +
handle_ieee_intercept()
1443 +
{
1444 +
	if (no_ieee_intercept) {
1445 +
		return false;
1446 +
	}
1447 +
1448 +
	if (regs.pc < 0xFEA8 || (is_gen2 && regs.k != 0) || !is_kernal()) {
1449 +
		return false;
1450 +
	}
1451 +
1452 +
	if (has_serial) {
1453 +
		// if we do bit-level serial bus emulation, we don't
1454 +
		// do high-level KERNAL IEEE API interception
1455 +
		return false;
1456 +
	}
1457 +
1458 +
	if (sdcard_attached && !prg_file && !using_hostfs) {
1459 +
		// if should emulate an SD card (and don't need to
1460 +
		// hack a PRG into RAM), we skip HostFS if it uses unit 8
1461 +
		return false;
1462 +
	}
1463 +
1464 +
	if (sdcard_attached && prg_file && prg_finished_loading && !using_hostfs) {
1465 +
		// also skip if we should do SD card and we're done
1466 +
		// with the PRG hack if HostFS uses unit 8
1467 +
		return false;
1468 +
	}
1469 +
1470 +
	uint64_t base_ticks = SDL_GetPerformanceCounter();
1471 +
1472 +
	static int count_unlistn = 0;
1473 +
	bool handled = true;
1474 +
	int s = -1;
1475 +
	switch(regs.pc) {
1476 +
		case 0xFEA8: { // EXTAPI16
1477 +
			if (!regs.is65c816 || (regs.status & 0x20)) { // don't handle if not '816 or if m=1
1478 +
				handled = false; // punt to kernal's 65C816 check and will break if m=1
1479 +
				break;
1480 +
			}
1481 +
			switch(regs.c) {
1482 +
				case 5: { // XMACPTR
1483 +
					s=XMACPTR(regs.status & 0x01);
1484 +
					if (s == -2) {
1485 +
						handled = false;
1486 +
					} else if (s == -3) {
1487 +
						regs.status |= 1; // SEC (unsupported, or in this case, no open context)
1488 +
						regs.status &= ~0x30; // REP #$30
1489 +
					} else {
1490 +
						regs.status &= ~0x31; // REP #$30 and CLC -> supported
1491 +
					}
1492 +
					break;
1493 +
				}
1494 +
				case 6: { // XMCIOUT
1495 +
					s=XMCIOUT(regs.status & 0x01);
1496 +
					if (s == -2) {
1497 +
						handled = false;
1498 +
					} else if (s == -3) {
1499 +
						regs.status |= 1; // SEC (unsupported, or in this case, no open context)
1500 +
						regs.status &= ~0x30; // REP #$30
1501 +
					} else {
1502 +
						regs.status &= ~0x31; // REP #$30 and CLC -> supported
1503 +
					}
1504 +
					break;
1505 +
				}
1506 +
				default:
1507 +
					handled = false;
1508 +
					break;
1509 +
			}
1510 +
			break;
1511 +
		}
1512 +
		case 0xFEB1: {
1513 +
			uint16_t count = regs.a;
1514 +
			s=MCIOUT(regs.yl << 8 | regs.xl, &count, regs.status & 0x01);
1515 +
			if (s == -2) {
1516 +
				handled = false;
1517 +
			} else if (s == -3) {
1518 +
				regs.status = (regs.status | 1); // SEC (unsupported, or in this case, no open context)
1519 +
			} else {
1520 +
				regs.x = count & 0xff;
1521 +
				regs.y = count >> 8;
1522 +
				regs.status &= 0xfe; // clear C -> supported
1523 +
			}
1524 +
			break;
1525 +
		}
1526 +
		case 0xFF44: {
1527 +
			uint16_t count = regs.a;
1528 +
			s=MACPTR(regs.yl << 8 | regs.xl, &count, regs.status & 0x01);
1529 +
			if (s == -2) {
1530 +
				handled = false;
1531 +
			} else if (s == -3) {
1532 +
				regs.status = (regs.status | 1); // SEC (unsupported, or in this case, no open context)
1533 +
			} else {
1534 +
				regs.x = count & 0xff;
1535 +
				regs.y = count >> 8;
1536 +
				regs.status &= 0xfe; // clear C -> supported
1537 +
			}
1538 +
			break;
1539 +
		}
1540 +
		case 0xFF93:
1541 +
			s=SECOND(regs.a);
1542 +
			if (s == -2) {
1543 +
				handled = false;
1544 +
			}
1545 +
			break;
1546 +
		case 0xFF96:
1547 +
			s=TKSA(regs.a);
1548 +
			if (s == -2) {
1549 +
				handled = false;
1550 +
			}
1551 +
			break;
1552 +
		case 0xFFA5:
1553 +
			s=ACPTR(&regs.a);
1554 +
			if (s == -2) {
1555 +
				handled = false;
1556 +
			} else {
1557 +
				regs.status = (regs.status & ~3) | (!regs.a << 1); // unconditional CLC, and set zero flag based on byte read
1558 +
			}
1559 +
			break;
1560 +
		case 0xFFA8:
1561 +
			s=CIOUT(regs.a);
1562 +
			if (s == -2) {
1563 +
				handled = false;
1564 +
			} else {
1565 +
				regs.status = (regs.status & ~1); // unconditonal CLC
1566 +
			}
1567 +
			break;
1568 +
		case 0xFFAB:
1569 +
			s=UNTLK();
1570 +
			if (s == -2) {
1571 +
				handled = false;
1572 +
			}
1573 +
			break;
1574 +
		case 0xFFAE:
1575 +
			s=UNLSN();
1576 +
			if (s == -2) {
1577 +
				handled = false;
1578 +
			}
1579 +
			if (prg_file && sdcard_path_is_set() && ++count_unlistn == 4) {
1580 +
				// after auto-loading a PRG from the host fs,
1581 +
				// switch to the SD card if requested
1582 +
				// 4x UNLISTEN:
1583 +
				//    2x for LOAD"AUTOBOOT.X16*"
1584 +
				//    2x for LOAD":*"
1585 +
				prg_finished_loading = true;
1586 +
				sdcard_attach();
1587 +
			}
1588 +
			break;
1589 +
		case 0xFFB1:
1590 +
			s=LISTEN(regs.a);
1591 +
			if (s == -2) {
1592 +
				handled = false;
1593 +
			} else {
1594 +
				regs.status = (regs.status & ~1); // unconditonal CLC
1595 +
			}
1596 +
			break;
1597 +
		case 0xFFB4:
1598 +
			s=TALK(regs.a);
1599 +
			if (s == -2) {
1600 +
				handled = false;
1601 +
			} else {
1602 +
				regs.status = (regs.status & ~1); // unconditonal CLC
1603 +
			}
1604 +
			break;
1605 +
		default:
1606 +
			handled = false;
1607 +
			break;
1608 +
	}
1609 +
1610 +
	if (handled) {
1611 +
		// Add the number CPU cycles equivalent to the amount of time that the operation actually took
1612 +
		// to prevent the emu from warping after a hostfs load
1613 +
		uint64_t perf_diff = SDL_GetPerformanceCounter() - base_ticks;
1614 +
		uint32_t missed_ticks = (uint64_t)(perf_diff * 1000000ULL * MHZ) / SDL_GetPerformanceFrequency();
1615 +
		clockticks6502 += missed_ticks;
1616 +
		if (s >= 0) {
1617 +
			if (!set_kernal_status(s)) {
1618 +
				printf("Warning: Could not set STATUS!\n");
1619 +
			}
1620 +
		}
1621 +
1622 +
		increment_wrap_at_page_boundary(&regs.sp);
1623 +
		uint8_t low = debug_read6502(regs.sp, 0, USE_CURRENT_X16_BANK);
1624 +
		increment_wrap_at_page_boundary(&regs.sp);
1625 +
		regs.pc = ((debug_read6502(regs.sp, 0, USE_CURRENT_X16_BANK) << 8) | low) + 1;
1626 +
	}
1627 +
	return handled;
1628 +
}
1629 +
1630 +
void
1631 +
emscripten_main_loop(void) {
1632 +
	emulator_loop(NULL);
1633 +
}
1634 +
1635 +
1636 +
void *
1637 +
emulator_loop(void *param)
1638 +
{
1639 +
	uint32_t old_clockticks6502 = clockticks6502;
1640 +
	for (;;) {
1641 +
		if (coverage_stop) break;
1642 +
		if (smc_requested_reset) machine_reset();
1643 +
1644 +
		retro_control_service();
1645 +
		if (!retro_control_running()) {
1646 +
			SDL_Delay(1);
1647 +
			continue;
1648 +
		}
1649 +
1650 +
		if (testbench && regs.pc == 0xfffd){
1651 +
			testbench_init();
1652 +
		}
1653 +
1654 +
		if (debugger_enabled) {
1655 +
			int dbgCmd = DEBUGGetCurrentStatus();
1656 +
			if (dbgCmd > 0) continue;
1657 +
			if (dbgCmd < 0) break;
1658 +
		}
1659 +
1660 +
#ifdef PERFSTAT
1661 +
1662 +
//		if (memory_get_rom_bank() == 3) {
1663 +
//			stat[pc]++;
1664 +
//		}
1665 +
		if (memory_get_rom_bank() == 3) {
1666 +
			static uint8_t old_sp;
1667 +
			static uint16_t base_pc;
1668 +
			if (regs.sp < old_sp) {
1669 +
				base_pc = pc;
1670 +
			}
1671 +
			old_sp = regs.sp;
1672 +
			stat[base_pc]++;
1673 +
		}
1674 +
#endif
1675 +
1676 +
#ifdef TRACE
1677 +
		if (regs.pc == trace_address && trace_address != 0) {
1678 +
			trace_mode = true;
1679 +
		}
1680 +
		if (trace_mode && !waiting) {
1681 +
			char *lst = lst_for_address(regs.pc);
1682 +
			if (lst) {
1683 +
				char *lf;
1684 +
				while ((lf = strchr(lst, '\n'))) {
1685 +
					for (int i = 0; i < 120; i++) {
1686 +
						printf(" ");
1687 +
					}
1688 +
					if (regs.is65c816) {
1689 +
						printf("        "); // 8 extra width
1690 +
					}
1691 +
					for (char *c = lst; c < lf; c++) {
1692 +
						printf("%c", *c);
1693 +
					}
1694 +
					printf("\n");
1695 +
					lst = lf + 1;
1696 +
				}
1697 +
			}
1698 +
1699 +
			printf("[%8d] ", instruction_counter);
1700 +
1701 +
			int32_t eff_addr;
1702 +
1703 +
			char *label = label_for_address(regs.pc);
1704 +
			int label_len = label ? strlen(label) : 0;
1705 +
			if (label) {
1706 +
				printf("%s", label);
1707 +
			}
1708 +
			for (int i = 0; i < 20 - label_len; i++) {
1709 +
				printf(" ");
1710 +
			}
1711 +
1712 +
			if (regs.pc >= 0xc000 && regs.k == 0) {
1713 +
				printf (" %02x", memory_get_rom_bank());
1714 +
			} else if (regs.pc >= 0xa000 && regs.k == 0) {
1715 +
				printf (" %02x", memory_get_ram_bank());
1716 +
			} else {
1717 +
				printf (" --");
1718 +
			}
1719 +
1720 +
			printf(":.,%04x ", regs.pc);
1721 +
1722 +
			char disasm_line[15];
1723 +
			int len = disasm(regs.pc, regs.k, RAM, disasm_line, sizeof(disasm_line), -1, regs.status, &eff_addr);
1724 +
			for (int i = 0; i < len; i++) {
1725 +
				printf("%02x ", debug_read6502(regs.pc + i, regs.k, USE_CURRENT_X16_BANK));
1726 +
			}
1727 +
			for (int i = 0; i < 9 - 3 * len; i++) {
1728 +
				printf(" ");
1729 +
			}
1730 +
			printf("%s", disasm_line);
1731 +
			for (int i = 0; i < 15 - strlen(disasm_line); i++) {
1732 +
				printf(" ");
1733 +
			}
1734 +
			if (regs.is65c816) {
1735 +
				printf("C=$%04x X=$%04x Y=$%04x S=$%04x P=", regs.c, regs.x, regs.y, regs.sp);
1736 +
				for (int i = 7; i >= 0; i--) {
1737 +
					printf("%c", (regs.status & (1 << i)) ? "czidxmvn"[i] : '-');
1738 +
				}
1739 +
1740 +
				putchar(regs.e ? 'e' : '-');
1741 +
			} else {
1742 +
				printf("A=$%02x X=$%02x Y=$%02x S=$%02x P=", regs.a, regs.xl, regs.yl, regs.sp & 0xff);
1743 +
				for (int i = 7; i >= 0; i--) {
1744 +
					printf("%c", (regs.status & (1 << i)) ? "czidb-vn"[i] : '-');
1745 +
				}
1746 +
			}
1747 +
1748 +
			if (eff_addr == 0x9f23 && regs.k == 0) {
1749 +
				printf(" VRAM=$%05x ", video_get_address(0));
1750 +
			} else if (eff_addr == 0x9f24 && regs.k == 0) {
1751 +
				printf(" VRAM=$%05x ", video_get_address(1));
1752 +
			} else if (eff_addr >= 0xc000 && regs.k == 0) {
1753 +
				printf(" EA=$%02x:%04x ", memory_get_rom_bank(), eff_addr);
1754 +
			} else if (eff_addr >= 0xa000 && regs.k == 0) {
1755 +
				printf(" EA=$%02x:%04x ", memory_get_ram_bank(), eff_addr);
1756 +
			} else if (eff_addr >= 0) {
1757 +
				printf(" EA=$--:%04x ", eff_addr);
1758 +
			} else {
1759 +
				printf("             ");
1760 +
			}
1761 +
1762 +
			if (lst) {
1763 +
				printf("%s      %s", regs.is65c816 ? "" : " ", lst);
1764 +
			}
1765 +
1766 +
			printf("\n");
1767 +
		}
1768 +
#endif
1769 +
1770 +
		if (handle_ieee_intercept()) {
1771 +
			continue;
1772 +
		}
1773 +
1774 +
		instruction_counter += waiting ^ 0x1;
1775 +
1776 +
		if (coverage_enabled && !waiting) {
1777 +
			coverage_mark(regs.pc);
1778 +
		}
1779 +
		step6502();
1780 +
		uint32_t clocks = clockticks6502 - old_clockticks6502;
1781 +
		old_clockticks6502 = clockticks6502;
1782 +
		bool new_frame = false;
1783 +
		via1_step(clocks);
1784 +
		vera_spi_step(MHZ, clocks);
1785 +
		if (has_serial) {
1786 +
			serial_step(clocks);
1787 +
		}
1788 +
		if (has_via2) {
1789 +
			via2_step(clocks);
1790 +
		}
1791 +
		if (!headless) {
1792 +
			new_frame |= video_step(MHZ, clocks, false);
1793 +
		}
1794 +
1795 +
		if (new_frame) {
1796 +
			retro_control_on_frame();
1797 +
			if (has_netcard) {
1798 +
				netcard_step();
1799 +
			}
1800 +
		}
1801 +
1802 +
		for (uint32_t i = 0; i < clocks; i++) {
1803 +
			i2c_step();
1804 +
		}
1805 +
		rtc_step(clocks);
1806 +
1807 +
		if (!headless) {
1808 +
			audio_step(clocks);
1809 +
		}
1810 +
1811 +
		midi_serial_step(clocks);
1812 +
1813 +
		if (!headless && new_frame) {
1814 +
			if (nvram_dirty && nvram_path) {
1815 +
				SDL_RWops *f = SDL_RWFromFile(nvram_path, "wb");
1816 +
				if (f) {
1817 +
					SDL_RWwrite(f, nvram, 1, sizeof(nvram));
1818 +
					SDL_RWclose(f);
1819 +
				}
1820 +
				nvram_dirty = false;
1821 +
			}
1822 +
1823 +
			if (!video_update()) {
1824 +
				break;
1825 +
			}
1826 +
1827 +
			timing_update();
1828 +
#ifdef __EMSCRIPTEN__
1829 +
			// After completing a frame we yield back control to the browser to stay responsive
1830 +
			return 0;
1831 +
#endif
1832 +
		}
1833 +
1834 +
		// The optimization from the opportunistic batching of audio rendering
1835 +
		// is lost if we need to track the YM2151 IRQ, so it has been made a
1836 +
		// command-line switch that's disabled by default.
1837 +
		if (ym2151_irq_support) {
1838 +
			audio_render();
1839 +
		}
1840 +
1841 +
		if (video_get_irq_out() || via1_irq() || (has_via2 && via2_irq()) || (ym2151_irq_support && YM_irq()) || (has_midi_card && midi_serial_irq())) {
1842 +
//			printf("IRQ!\n");
1843 +
			irq6502();
1844 +
		}
1845 +
1846 +
		if (regs.pc == 0xffff) {
1847 +
			if (save_on_exit) {
1848 +
				machine_dump("CPU program counter reached $ffff");
1849 +
			}
1850 +
			break;
1851 +
		}
1852 +
1853 +
		// Change this comparison value if ever additional KERNAL
1854 +
		// API calls are snooped in this routine.
1855 +
1856 +
		if (regs.pc >= 0xff68 && (!is_gen2 || regs.k == 0) && is_kernal()) {
1857 +
			if (regs.pc == 0xff68) {
1858 +
				kernal_mouse_enabled = !!regs.a;
1859 +
				SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
1860 +
			}
1861 +
1862 +
			if (echo_mode != ECHO_MODE_NONE && regs.pc == 0xffd2) {
1863 +
				uint8_t c = regs.a;
1864 +
				if (echo_mode == ECHO_MODE_COOKED) {
1865 +
					if (c == 0x0d) {
1866 +
						printf("\n");
1867 +
					} else if (c == 0x0a) {
1868 +
						// skip
1869 +
					} else if (c < 0x20 || c >= 0x80) {
1870 +
						printf("\\X%02X", c);
1871 +
					} else {
1872 +
						printf("%c", c);
1873 +
					}
1874 +
				} else if (echo_mode == ECHO_MODE_ISO) {
1875 +
					if (c == 0x0d) {
1876 +
						printf("\n");
1877 +
					} else if (c == 0x0a) {
1878 +
						// skip
1879 +
					} else if (c < 0x20 || (c >= 0x80 && c < 0xa0)) {
1880 +
						printf("\\X%02X", c);
1881 +
					} else {
1882 +
						print_iso8859_15_char(c);
1883 +
					}
1884 +
				} else {
1885 +
					printf("%c", c);
1886 +
				}
1887 +
				fflush(stdout);
1888 +
			}
1889 +
1890 +
			if (regs.pc == 0xffcf) {
1891 +
				// as soon as BASIC starts reading a line...
1892 +
				static bool prg_done = false;
1893 +
1894 +
				if (prg_file && !prg_done) {
1895 +
					int loadlen = 0;
1896 +
					// LOAD":*" will cause the IEEE library
1897 +
					// to load from "prg_file"
1898 +
					if (prg_override_start >= 0) {
1899 +
						loadlen = snprintf(paste_text_data, sizeof(paste_text_data), "LOAD\":*\",%d,1,$%04X\r", ieee_unit, prg_override_start);
1900 +
					} else {
1901 +
						loadlen = snprintf(paste_text_data, sizeof(paste_text_data), "LOAD\":*\",%d,1\r", ieee_unit);
1902 +
					}
1903 +
					paste_text = paste_text_data;
1904 +
					prg_done = true;
1905 +
1906 +
					if (run_after_load) {
1907 +
						if (prg_override_start >= 0) {
1908 +
							snprintf(paste_text_data + loadlen, sizeof(paste_text_data) - loadlen, "SYS$%04X\r", prg_override_start);
1909 +
						} else {
1910 +
							snprintf(paste_text_data + loadlen, sizeof(paste_text_data) - loadlen, "RUN\r");
1911 +
						}
1912 +
					}
1913 +
				}
1914 +
				else if (testbench && !test_init_complete){
1915 +
					snprintf(paste_text_data, sizeof(paste_text_data), "SYS65533\r");
1916 +
					paste_text = paste_text_data;
1917 +
					test_init_complete=true;
1918 +
				}
1919 +
1920 +
				if (paste_text) {
1921 +
					// ...paste BASIC code into the keyboard buffer
1922 +
					pasting_bas = true;
1923 +
					if (warp_pastes) warp_mode = true;
1924 +
				}
1925 +
			}
1926 +
1927 +
		}
1928 +
#if 0 // enable this for slow pasting
1929 +
		if (!(instruction_counter % 100000))
1930 +
#endif
1931 +
		while (pasting_bas && BRAM[NDX - 0xa000] < 10 && !(regs.status & 0x04)) {
1932 +
			uint32_t c;
1933 +
			int e = 0;
1934 +
1935 +
			if (paste_text[0] == '\\' && paste_text[1] == 'X' && paste_text[2] && paste_text[3]) {
1936 +
				uint8_t hi = strtol((char[]){paste_text[2], 0}, NULL, 16);
1937 +
				uint8_t lo = strtol((char[]){paste_text[3], 0}, NULL, 16);
1938 +
				c = hi << 4 | lo;
1939 +
				paste_text += 4;
1940 +
			} else {
1941 +
				paste_text = utf8_decode(paste_text, &c, &e);
1942 +
				c = iso8859_15_from_unicode(c);
1943 +
			}
1944 +
			if (c && !e) {
1945 +
				BRAM[KEYD - 0xa000 + BRAM[NDX - 0xa000]] = c;
1946 +
				BRAM[NDX - 0xa000]++;
1947 +
			} else {
1948 +
				pasting_bas = false;
1949 +
				if (warp_pastes) warp_mode = false;
1950 +
				paste_text = NULL;
1951 +
				if (clipboard_buffer) {
1952 +
					SDL_free(clipboard_buffer);
1953 +
					clipboard_buffer = NULL;
1954 +
				}
1955 +
			}
1956 +
		}
1957 +
	}
1958 +
1959 +
	return 0;
1960 +
}
added src/makecart.c +665 −0
1 +
#define _POSIX_C_SOURCE 200809L
2 +
3 +
#include "cartridge.h"
4 +
#include "files.h"
5 +
6 +
#include <stdlib.h>
7 +
#include <stdio.h>
8 +
#include <string.h>
9 +
#include <ctype.h>
10 +
11 +
extern uint8_t *CART;
12 +
13 +
uint8_t fill_value = 0;
14 +
15 +
static void parse_cmdline(int argc, char **argv);
16 +
17 +
static int x16_getline(char **line, size_t *n, FILE *f)
18 +
{
19 +
	if(line == NULL) {
20 +
		return -1;
21 +
	}
22 +
	if(n == NULL) {
23 +
		return -1;
24 +
	}
25 +
	if(f == NULL) {
26 +
		return -1;
27 +
	}
28 +
	if(*line == NULL) {
29 +
		*n = 64;
30 +
		*line = malloc(*n);
31 +
	} else if(*n == 0) {
32 +
		*n = 64;
33 +
		*line = realloc(*line, *n);
34 +
	}
35 +
36 +
	int i=EOF;
37 +
	int c;
38 +
	while((c = fgetc(f)) > EOF) {
39 +
		++i;
40 +
		if(i >= *n) {
41 +
			*n <<= 1;
42 +
			*line = realloc(*line, *n);
43 +
		}
44 +
		(*line)[i] = c;
45 +
		if(c == '\n') {
46 +
			break;
47 +
		}
48 +
	}
49 +
	if(c < 0 && i >= 0) {
50 +
		++i;
51 +
		if(i >= *n) {
52 +
			*n <<= 1;
53 +
			*line = realloc(*line, *n);
54 +
		}
55 +
		(*line)[i] = '\0';
56 +
	}
57 +
	if(i > 0) {
58 +
		if((*line)[i-1] == '\r') {
59 +
			(*line)[i-1] = '\0';
60 +
		} else {
61 +
			(*line)[i] = '\0';
62 +
		}
63 +
	} else if(i == 0) {
64 +
		(*line)[i] = '\0';
65 +
	}
66 +
	return i;
67 +
}
68 +
69 +
void
70 +
usage()
71 +
{
72 +
	printf("-help\n");
73 +
	printf("\tPrint this message and exit.\n");
74 +
	printf("\n");
75 +
	printf("-cfg <filename.cfg>\n");
76 +
	printf("\tUse this file to pack the cartridge data. Config file is simply the\n");
77 +
	printf("\tcommand line switches, one per line.\n");
78 +
	printf("\n");
79 +
	printf("-desc \"Name/Description\"\n");
80 +
	printf("\tSet the description field of the cartridge file. This is limited to 32\n");
81 +
	printf("\tbytes and padded with null characters ('\\0').\n");
82 +
	printf("\n");
83 +
	printf("-author \"Author Information\"\n");
84 +
	printf("\tSet the author field of the cartridge file. This is limited to 32 bytes\n");
85 +
	printf("\tand padded with null characters ('\\0').\n");
86 +
	printf("\n");
87 +
	printf("-copyright \"Copyright Information\"\n");
88 +
	printf("\tSet the copyright field of the cartridge file. This is limited to 32\n");
89 +
	printf("\tbytes and padded with null characters ('\\0').\n");
90 +
	printf("\n");
91 +
	printf("-version \"Version Information\"\n");
92 +
	printf("\tSet the version field of the cartridge file. This is limited to 32 bytes\n");
93 +
	printf("\tand padded with null characters ('\\0').\n");
94 +
	printf("\n");
95 +
	printf("-fill <value>\n");
96 +
	printf("\tSet the fill value to use with any partially-filled banks of cartridge\n");
97 +
	printf("\tmemory. Value can be defined in decimal, in hexadecimal with a '$' or\n");
98 +
	printf("\t'0x' prefix, in octal with a leading 0, or in binary with a leading 0b or %%.\n");
99 +
	printf("\n");
100 +
	printf("-rom_file <start_bank> [<filenames.bin>...]\n");
101 +
	printf("\tDefine rom banks from the specified list of files. File data is tightly\n");
102 +
	printf("\tpacked -- if a file does not end on a 16KB interval, the next file will be inserted\n");
103 +
	printf("\timmediately after it within the same bank. If the last file does not end on a 16KB\n");
104 +
	printf("\tinterval, the remainder of the rom will be filled with the value set by '-fill'.\n");
105 +
	printf("\n");
106 +
	printf("-ram <start_bank> [<end bank>]\n");
107 +
	printf("\tDefine one or more banks of uninitialized RAM.\n");
108 +
	printf("\n");
109 +
	printf("-ram_file <start_bank> [<filenames.bin>...]\n");
110 +
	printf("\tDefine pre-initialized ram banks from the specified list of files. File\n");
111 +
	printf("\tdata is tightly packed like with -rom. If the last file does not end on a 16KB\n");
112 +
	printf("\tinterval, the remainder of the rom will be filled with the value set by '-fill'.\n");
113 +
	printf("\n");
114 +
	printf("-nvram <start_bank> [<end_bank>]\n");
115 +
	printf("\tDefine one or more uninitialized nvram banks.\n");
116 +
	printf("\n");
117 +
	printf("-nvram_file <start_bank> [<filenames.bin>...]\n");
118 +
	printf("\tDefine pre-initialized nvram banks from the specified list of files. File\n");
119 +
	printf("\tdata is tightly packed like with -rom. If the last file does not end on a 16KB\n");
120 +
	printf("\tinterval, the remainder of the rom will be filled with the value set by '-fill'.\n");
121 +
	printf("\n");
122 +
	printf("-nvram_value <start_bank> <end_bank>\n");
123 +
	printf("\tDefine pre-initialized nvram banks with the value set by '-fill'.\n");
124 +
	printf("\n");
125 +
	printf("-none <start_bank> [<end_bank>]\n");
126 +
	printf("\tDefine one or more unpopulated banks of the cartridge. By default, all\n");
127 +
	printf("\tbanks are unpopulated unless specified by a previous command-line option.\n");
128 +
	printf("\n");
129 +
	printf("-o <output.crt>\n");
130 +
	printf("\tSet the filename of the output cartridge file.\n");
131 +
	printf("\n");
132 +
	printf("-unpack <input.crt> [<rom_size>]\n");
133 +
	printf("\tUnpacks the binary data from the cartridge file into <rom_size> slices.\n");
134 +
	printf("\t(for use with an EPROM programmer.) The ouput files will be the same\n");
135 +
	printf("\tfilename as the input file, with _### appended. This will also create a\n");
136 +
	printf("\t.cfg file that can be used to re-pack the files into a new CRT if needed.\n");
137 +
	printf("\n");
138 +
	printf("All options can be specified multiple times, and are applied  in-order from\n");
139 +
	printf("\tleft to right.\n");
140 +
141 +
	exit(1);
142 +
}
143 +
144 +
static void
145 +
unpack_cart(const char *path, uint32_t bank_size)
146 +
{
147 +
	if(bank_size & 0x3fff) {
148 +
		printf("Warning: rom_size specified to -unpack was not 16KB-aligned, the resulting config file will not be trustworthy.\n");
149 +
	}
150 +
151 +
	const char *extension = file_find_extension(path, NULL);
152 +
	size_t extlen = strlen(extension);
153 +
154 +
	size_t root_pathlen = strlen(path) - extlen;
155 +
	char *root_path = malloc(root_pathlen + 1);
156 +
	memcpy(root_path, path, root_pathlen);
157 +
	root_path[root_pathlen] = '\0';
158 +
159 +
	size_t outpath_len = root_pathlen + 8; // strlen("_000.bin");
160 +
	char *outpath = malloc(outpath_len + 1);
161 +
162 +
	size_t cfgpath_len = root_pathlen + 4; // strlen(".cfg");
163 +
	char *cfgpath = malloc(cfgpath_len + 1);
164 +
	sprintf(cfgpath, "%s.cfg", root_path);
165 +
166 +
	FILE *cfg = fopen(cfgpath, "wb");
167 +
	if(cfg == NULL) {
168 +
		printf("Error: Could not open config file for writing: %s\n", cfgpath);
169 +
	} 
170 +
171 +
	cartridge_new();
172 +
	if(cartridge_load(path, true)) {
173 +
		if(cfg != NULL) {
174 +
			char buffer[65];
175 +
176 +
			cartridge_get_desc(buffer, 65);
177 +
			fprintf(cfg, "-desc \"%s\"\n", buffer);
178 +
179 +
			cartridge_get_author(buffer, 65);
180 +
			fprintf(cfg, "-author \"%s\"\n", buffer);
181 +
182 +
			cartridge_get_copyright(buffer, 65);
183 +
			fprintf(cfg, "-copyright \"%s\"\n", buffer);
184 +
185 +
			cartridge_get_program_version(buffer, 65);
186 +
			fprintf(cfg, "-prg_version \"%s\"\n", buffer);
187 +
		}
188 +
		
189 +
		uint32_t bank_idx = 0;
190 +
		for(uint32_t offset = 0; offset < CART_MAX_SIZE; offset += bank_size) {
191 +
			sprintf(outpath, "%s_%03d.bin", root_path, bank_idx);
192 +
			++bank_idx;
193 +
194 +
			struct x16file *f = x16open(outpath, "wb");
195 +
			if(f == NULL) {
196 +
				printf("Error opening file for write: %s\n", outpath);
197 +
				continue;
198 +
			}
199 +
			size_t avail = CART_MAX_SIZE - offset;
200 +
			if(!x16write(f, CART + offset, bank_size > avail ? avail : bank_size, 1)) {
201 +
				printf("Error writing to file: %s\n", outpath);
202 +
			}
203 +
			x16close(f);
204 +
205 +
			uint32_t start_bank = offset >> 14;
206 +
			uint32_t end_bank = ((offset + bank_size) >> 14) - 1;
207 +
208 +
			uint8_t start_bank_type = cartridge_get_bank_type(start_bank + 32);
209 +
			bool conflicted = false;
210 +
			for(uint32_t bank = start_bank + 1; bank <= end_bank; ++bank) {
211 +
				uint8_t bank_type = cartridge_get_bank_type(bank + 32);
212 +
				if(bank_type != start_bank_type) {
213 +
					if(!conflicted) {
214 +
						printf("Warning: Discovered bank type mismatch while writing file: %s\n", outpath);
215 +
						printf("\tFile starts at offset $%08x, which is banks %d through %d\n", offset, start_bank, end_bank);
216 +
						conflicted = true;
217 +
					}
218 +
					if(start_bank == bank - 1) {
219 +
						printf("\tBank %d is type %d\n", start_bank + 32, start_bank_type);
220 +
					} else {
221 +
						printf("\tBanks %d through %d are type %d\n", start_bank + 32, bank + 32 - 1, start_bank_type);
222 +
					}
223 +
					start_bank_type = bank_type;
224 +
					start_bank = bank;
225 +
				}
226 +
			}
227 +
			if(conflicted) {
228 +
				if(start_bank == end_bank) {
229 +
					printf("\tBank %d is type %d\n", start_bank + 32, start_bank_type);
230 +
				} else {
231 +
					printf("\tBanks %d through %d are type %d\n", start_bank + 32, end_bank + 32, start_bank_type);
232 +
				}
233 +
				printf("\tThe resulting config file will not be trustworthy.");
234 +
			}
235 +
236 +
			if(cfg != NULL) {
237 +
				start_bank = offset >> 14;
238 +
				switch(cartridge_get_bank_type(offset >> 14)) {
239 +
					case CART_BANK_NONE: /* skip */ break;
240 +
					case CART_BANK_ROM: fprintf(cfg, "-rom_file %d \"%s\"\n", start_bank, outpath); break;
241 +
					case CART_BANK_UNINITIALIZED_RAM: fprintf(cfg, "-ram %d %d\n", start_bank, end_bank); break;
242 +
					case CART_BANK_INITIALIZED_RAM: fprintf(cfg, "-ram_file %d \"%s\"\n", start_bank, outpath); break;
243 +
					case CART_BANK_UNINITIALIZED_NVRAM: fprintf(cfg, "-nvram %d %d\n", start_bank, end_bank); break;
244 +
					case CART_BANK_INITIALIZED_NVRAM: fprintf(cfg, "-nvram_file %d \"%s\"\n", start_bank, outpath); break;
245 +
					default: /* error */ break;
246 +
				}
247 +
			}
248 +
		}
249 +
	}
250 +
251 +
	fclose(cfg);
252 +
253 +
	free(cfgpath);
254 +
	free(outpath);
255 +
	free(root_path);
256 +
}
257 +
258 +
static char *
259 +
next_token(char **token)
260 +
{
261 +
	if(**token == '\0' || **token == '\r' || **token == '\n') {
262 +
		return NULL;
263 +
	}
264 +
	char *start = *token;
265 +
	while(!isgraph(*start) && *start) {
266 +
		++start;
267 +
	}
268 +
269 +
	char *next = start;
270 +
	if(*next == '"') {
271 +
		++start;
272 +
		++next;
273 +
		while(*next != '"' && *next) {
274 +
			if(*next == '\\') {
275 +
				++next;
276 +
			}
277 +
			++next;
278 +
		}
279 +
	} else {
280 +
		while(isgraph(*next) && *next) {
281 +
			if(*next == '\\') {
282 +
				++next;
283 +
			}
284 +
			++next;
285 +
		}
286 +
	}
287 +
	if(*next) {
288 +
		*next = '\0';
289 +
		*token = next+1;
290 +
	} else {
291 +
		*token = next;
292 +
	}
293 +
	
294 +
	return start;
295 +
}
296 +
297 +
static void
298 +
parse_config(const char *path)
299 +
{
300 +
	FILE *cfg = fopen(path, "rb");
301 +
	if(cfg == NULL) {
302 +
		printf("Error opening file for read: %s", path);
303 +
		return;
304 +
	}
305 +
306 +
	size_t argslen = 32;
307 +
	char **argv = malloc(argslen * sizeof(char **));
308 +
309 +
	char *line = NULL;
310 +
	size_t n = 0;
311 +
	while(x16_getline(&line, &n, cfg) >= 0) {
312 +
		char *token = line;
313 +
		int argc = 0;
314 +
		char *cmd;
315 +
316 +
		while((cmd = next_token(&token))) {
317 +
			if(strlen(cmd) == 0) {
318 +
				continue;
319 +
			}
320 +
			if(argc >= argslen) {
321 +
				argslen <<= 1;
322 +
				argv = realloc(argv, argslen * sizeof(char **));
323 +
			}
324 +
			argv[argc] = cmd;
325 +
			++argc;
326 +
		}
327 +
328 +
		if(argc > 0) {
329 +
			parse_cmdline(argc, argv);
330 +
		}
331 +
	}
332 +
333 +
	free(line);
334 +
	free(argv);
335 +
}
336 +
337 +
static void 
338 +
parse_cmdline(int argc, char **argv)
339 +
{
340 +
	while(argc > 0) {
341 +
		if (!strcmp(argv[0], "-help")) {
342 +
			usage();
343 +
344 +
		} else if (!strcmp(argv[0], "-cfg")) {
345 +
			++argv;
346 +
			--argc;
347 +
348 +
			if (!argc || argv[0][0] == '-') {
349 +
				usage();
350 +
			}
351 +
352 +
			parse_config(argv[0]);
353 +
			++argv;
354 +
			--argc;
355 +
356 +
		} else if (!strcmp(argv[0], "-desc")) {
357 +
			++argv;
358 +
			--argc;
359 +
360 +
			if (!argc || argv[0][0] == '-') {
361 +
				usage();
362 +
			}
363 +
364 +
			cartridge_set_desc(argv[0]);
365 +
			++argv;
366 +
			--argc;
367 +
368 +
		} else if (!strcmp(argv[0], "-author")) {
369 +
			++argv;
370 +
			--argc;
371 +
372 +
			if (!argc || argv[0][0] == '-') {
373 +
				usage();
374 +
			}
375 +
376 +
			cartridge_set_author(argv[0]);
377 +
			++argv;
378 +
			--argc;
379 +
380 +
		} else if (!strcmp(argv[0], "-copyright")) {
381 +
			++argv;
382 +
			--argc;
383 +
384 +
			if (!argc || argv[0][0] == '-') {
385 +
				usage();
386 +
			}
387 +
388 +
			cartridge_set_copyright(argv[0]);
389 +
			++argv;
390 +
			--argc;
391 +
392 +
		} else if (!strcmp(argv[0], "-version")) {
393 +
			++argv;
394 +
			--argc;
395 +
396 +
			if (!argc || argv[0][0] == '-') {
397 +
				usage();
398 +
			}
399 +
400 +
			cartridge_set_program_version(argv[0]);
401 +
			++argv;
402 +
			--argc;
403 +
404 +
		} else if(!strcmp(argv[0], "-fill")) {
405 +
			++argv;
406 +
			--argc;
407 +
408 +
			if (!argc || argv[0][0] == '-') {
409 +
				usage();
410 +
			}
411 +
412 +
			if(argv[0][0] == '$') {
413 +
				fill_value = strtol(argv[0] + 1, NULL, 16);
414 +
			} else if(argv[0][0] == '%') {
415 +
				fill_value = strtol(argv[0] + 1, NULL, 2);
416 +
			} else if(argv[0][0] == '0') {
417 +
				if(argv[0][1] == 'x') {
418 +
					fill_value = strtol(argv[0] + 2, NULL, 16);
419 +
				} else if(argv[0][1] == 'b') {
420 +
					fill_value = strtol(argv[0] + 2, NULL, 2);
421 +
				} else {
422 +
					fill_value = strtol(argv[0] + 1, NULL, 8);
423 +
				}
424 +
			} else {
425 +
				fill_value = strtol(argv[0], NULL, 10);
426 +
			}
427 +
			++argv;
428 +
			--argc;
429 +
430 +
		} else if(!strcmp(argv[0], "-rom_file")) {
431 +
			++argv;
432 +
			--argc;
433 +
434 +
			if (!argc || argv[0][0] == '-') {
435 +
				usage();
436 +
			}
437 +
438 +
			uint8_t start_bank = atoi(argv[0]);
439 +
			++argv;
440 +
			--argc;
441 +
442 +
			if (!argc || argv[0][0] == '-') {
443 +
				usage();
444 +
			}
445 +
446 +
			char **files = &argv[0];
447 +
			int num_files = 1;
448 +
			while (argc && argv[0][0] != '-') {
449 +
				++argv;
450 +
				--argc;
451 +
			}
452 +
453 +
			cartridge_import_files(files, num_files, start_bank, CART_BANK_ROM, fill_value);
454 +
455 +
		} else if(!strcmp(argv[0], "-ram")) {
456 +
			++argv;
457 +
			--argc;
458 +
459 +
			if (!argc || argv[0][0] == '-') {
460 +
				usage();
461 +
			}
462 +
463 +
			uint8_t start_bank = atoi(argv[0]);
464 +
			++argv;
465 +
			--argc;
466 +
467 +
			if (!argc || argv[0][0] == '-') {
468 +
				cartridge_define_bank_range(start_bank, start_bank, CART_BANK_UNINITIALIZED_RAM);
469 +
				continue;
470 +
			}
471 +
472 +
			uint8_t end_bank = atoi(argv[0]);
473 +
			++argv;
474 +
			--argc;
475 +
476 +
			cartridge_define_bank_range(start_bank, end_bank, CART_BANK_UNINITIALIZED_RAM);
477 +
478 +
		} else if(!strcmp(argv[0], "-ram_file")) {
479 +
			++argv;
480 +
			--argc;
481 +
482 +
			if (!argc || argv[0][0] == '-') {
483 +
				usage();
484 +
			}
485 +
486 +
			uint8_t start_bank = atoi(argv[0]);
487 +
			++argv;
488 +
			--argc;
489 +
490 +
			if (!argc || argv[0][0] == '-') {
491 +
				usage();
492 +
			}
493 +
494 +
			char **files = &argv[0];
495 +
			int num_files = 1;
496 +
			while (argc && argv[0][0] != '-') {
497 +
				++argv;
498 +
				--argc;
499 +
			}
500 +
501 +
			cartridge_import_files(files, num_files, start_bank, CART_BANK_INITIALIZED_RAM, fill_value);
502 +
503 +
		} else if(!strcmp(argv[0], "-nvram")) {
504 +
			++argv;
505 +
			--argc;
506 +
507 +
			if (!argc || argv[0][0] == '-') {
508 +
				usage();
509 +
			}
510 +
511 +
			uint8_t start_bank = atoi(argv[0]);
512 +
			++argv;
513 +
			--argc;
514 +
515 +
			if (!argc || argv[0][0] == '-') {
516 +
				cartridge_define_bank_range(start_bank, start_bank, CART_BANK_UNINITIALIZED_NVRAM);
517 +
				continue;
518 +
			}
519 +
520 +
			uint8_t end_bank = atoi(argv[0]);
521 +
			++argv;
522 +
			--argc;
523 +
524 +
			cartridge_define_bank_range(start_bank, end_bank, CART_BANK_UNINITIALIZED_NVRAM);
525 +
526 +
		} else if(!strcmp(argv[0], "-nvram_file")) {
527 +
			++argv;
528 +
			--argc;
529 +
530 +
			if (!argc || argv[0][0] == '-') {
531 +
				usage();
532 +
			}
533 +
534 +
			uint8_t start_bank = atoi(argv[0]);
535 +
			++argv;
536 +
			--argc;
537 +
538 +
			if (!argc || argv[0][0] == '-') {
539 +
				usage();
540 +
			}
541 +
542 +
			char **files = &argv[0];
543 +
			int num_files = 1;
544 +
			while (argc && argv[0][0] != '-') {
545 +
				++argv;
546 +
				--argc;
547 +
			}
548 +
549 +
			cartridge_import_files(files, num_files, start_bank, CART_BANK_INITIALIZED_NVRAM, fill_value);
550 +
551 +
		} else if(!strcmp(argv[0], "-nvram_value")) {
552 +
			++argv;
553 +
			--argc;
554 +
555 +
			if (!argc || argv[0][0] == '-') {
556 +
				usage();
557 +
			}
558 +
559 +
			uint8_t start_bank = atoi(argv[0]);
560 +
			++argv;
561 +
			--argc;
562 +
563 +
			if (!argc || argv[0][0] == '-') {
564 +
				cartridge_fill(start_bank, start_bank, CART_BANK_INITIALIZED_NVRAM, fill_value);
565 +
				continue;
566 +
			}
567 +
568 +
			uint8_t end_bank = atoi(argv[0]);
569 +
			++argv;
570 +
			--argc;
571 +
572 +
			cartridge_fill(start_bank, end_bank, CART_BANK_INITIALIZED_NVRAM, fill_value);
573 +
574 +
		} else if(!strcmp(argv[0], "-none")) {
575 +
			++argv;
576 +
			--argc;
577 +
578 +
			if (!argc || argv[0][0] == '-') {
579 +
				usage();
580 +
			}
581 +
582 +
			uint8_t start_bank = atoi(argv[0]);
583 +
			++argv;
584 +
			--argc;
585 +
586 +
			if (!argc || argv[0][0] == '-') {
587 +
				cartridge_define_bank_range(start_bank, start_bank, CART_BANK_NONE);
588 +
				continue;
589 +
			}
590 +
591 +
			uint8_t end_bank = atoi(argv[0]);
592 +
			++argv;
593 +
			--argc;
594 +
595 +
			cartridge_define_bank_range(start_bank, end_bank, CART_BANK_NONE);
596 +
597 +
		} else if(!strcmp(argv[0], "-o")) {
598 +
			++argv;
599 +
			--argc;
600 +
601 +
			if (!argc || argv[0][0] == '-') {
602 +
				usage();
603 +
			}
604 +
605 +
			cartridge_save(argv[0]);
606 +
			++argv;
607 +
			--argc;
608 +
609 +
		} else if(!strcmp(argv[0], "-unpack")) {
610 +
			++argv;
611 +
			--argc;
612 +
613 +
			if (!argc || argv[0][0] == '-') {
614 +
				usage();
615 +
			}
616 +
617 +
			char *unpack_file = argv[0];
618 +
			++argv;
619 +
			--argc;
620 +
621 +
			if (!argc || argv[0][0] == '-') {
622 +
				unpack_cart(unpack_file, CART_BANK_SIZE);
623 +
				continue;
624 +
			}
625 +
626 +
			uint32_t unpack_size = atoi(argv[0]);
627 +
			++argv;
628 +
			--argc;
629 +
630 +
			unpack_cart(unpack_file, unpack_size);
631 +
632 +
		} else if(!strcmp(argv[0], "-testbins")) {
633 +
			++argv;
634 +
			--argc;
635 +
636 +
			char test_path[256];
637 +
			for(uint8_t b=0; b<CART_MAX_BANKS; ++b) {
638 +
				sprintf(test_path, "test-%03d.bin", b);
639 +
				struct x16file *f = x16open(test_path, "wb");
640 +
				if(f == NULL) {
641 +
					printf("Error opening file for write: %s\n", test_path);
642 +
					continue;
643 +
				}
644 +
				for(int i=0; i<CART_BANK_SIZE; ++i) {
645 +
					x16write(f, &b, 1, 1);
646 +
				}
647 +
				x16close(f);
648 +
			}
649 +
		} else {
650 +
			return usage();
651 +
		}
652 +
	}
653 +
}
654 +
655 +
int 
656 +
main(int argc, char **argv)
657 +
{
658 +
	argc--;
659 +
	argv++;
660 +
661 +
	cartridge_new();
662 +
	parse_cmdline(argc, argv);
663 +
664 +
	return 0;
665 +
}
added src/makecart_javascript_interface.c +22 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdbool.h>
6 +
7 +
char javascript_text_data[65536];
8 +
9 +
void
10 +
j2c_reset()
11 +
{
12 +
}
13 +
14 +
void
15 +
j2c_paste(char *buffer)
16 +
{
17 +
}
18 +
19 +
void
20 +
j2c_start_audio(bool start)
21 +
{
22 +
}
added src/memory.c +596 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <sys/types.h>
6 +
#include <string.h>
7 +
#include <stdlib.h>
8 +
#include <unistd.h>
9 +
#include <time.h>
10 +
#include <inttypes.h>
11 +
#include "glue.h"
12 +
#include "via.h"
13 +
#include "memory.h"
14 +
#include "video.h"
15 +
#include "ymglue.h"
16 +
#include "cpu/fake6502.h"
17 +
#include "wav_recorder.h"
18 +
#include "audio.h"
19 +
#include "cartridge.h"
20 +
#include "iso_8859_15.h"
21 +
#include "midi.h"
22 +
#include "netcard.h"
23 +
24 +
uint8_t ram_bank;
25 +
uint8_t rom_bank;
26 +
27 +
uint8_t *RAM, *BRAM;
28 +
uint8_t ROM[ROM_SIZE];
29 +
extern uint8_t *CART;
30 +
31 +
static uint8_t addr_ym = 0;
32 +
33 +
bool randomizeRAM = false;
34 +
bool reportUninitializedAccess = false;
35 +
const char *reportUsageStatisticsFilename = NULL;
36 +
bool *RAM_access_flags, *BRAM_access_flags;
37 +
uint64_t *RAM_system_reads;
38 +
uint64_t *RAM_system_writes;
39 +
uint64_t *RAM_banked_reads[256];
40 +
uint64_t *RAM_banked_writes[256];
41 +
uint64_t *ROM_banked_reads[256];
42 +
uint64_t *ROM_banked_writes[256];	// shouldn't occur for obvious reasons unless Bonk RAM is installed in a cart
43 +
44 +
45 +
static uint32_t clock_snap = 0UL;
46 +
static uint32_t clock_base = 0UL;
47 +
48 +
#define DEVICE_EMULATOR (0x9fb0)
49 +
50 +
void cpuio_write(uint8_t reg, uint8_t value);
51 +
52 +
void
53 +
memory_init()
54 +
{
55 +
	// Initialize RAM array
56 +
	RAM = calloc(RAM_SIZE, sizeof(uint8_t));
57 +
	BRAM = calloc(BRAM_SIZE, sizeof(uint8_t));
58 +
59 +
	if(reportUsageStatisticsFilename!=NULL) {
60 +
		RAM_system_reads = calloc(num_banks * BANK_SIZE, sizeof(uint64_t));
61 +
		RAM_system_writes = calloc(num_banks * BANK_SIZE, sizeof(uint64_t));
62 +
		for(int bank=0; bank<256; ++bank) {
63 +
			RAM_banked_reads[bank] = calloc(8192, sizeof(uint64_t));
64 +
			RAM_banked_writes[bank] = calloc(8192, sizeof(uint64_t));
65 +
			ROM_banked_reads[bank] = calloc(16384, sizeof(uint64_t));
66 +
			ROM_banked_writes[bank] = calloc(16384, sizeof(uint64_t));
67 +
		}
68 +
	}
69 +
70 +
71 +
	// Randomize all RAM (if option selected)
72 +
	if (randomizeRAM) {
73 +
		time_t t;
74 +
		srand((unsigned)time(&t));
75 +
		for (int i = 0; i < RAM_SIZE; i++) {
76 +
			if (i >= 0x9f00 && i < 0x10000) {
77 +
				// Leave the unused hole in the address space at 0
78 +
				// Memory dumps will likely confuse people less often
79 +
				RAM[i] = 0;
80 +
			} else {
81 +
				RAM[i] = rand();
82 +
			}
83 +
		}
84 +
		for (int i = 0; i < BRAM_SIZE; i++) {
85 +
			BRAM[i] = rand();
86 +
		}
87 +
	}
88 +
89 +
	// Initialize RAM access flag array (if option selected)
90 +
	if (reportUninitializedAccess) {
91 +
		RAM_access_flags = (bool*) malloc(RAM_SIZE * sizeof(bool));
92 +
		for (int i = 0; i < RAM_SIZE; i++) {
93 +
			RAM_access_flags[i] = false;
94 +
		}
95 +
		BRAM_access_flags = (bool*) malloc(BRAM_SIZE * sizeof(bool));
96 +
		for (int i = 0; i < BRAM_SIZE; i++) {
97 +
			BRAM_access_flags[i] = false;
98 +
		}
99 +
	}
100 +
101 +
	memory_reset();
102 +
}
103 +
104 +
void
105 +
memory_reset()
106 +
{
107 +
	// default banks are 0
108 +
	memory_set_ram_bank(0);
109 +
	memory_set_rom_bank(0);
110 +
}
111 +
112 +
void
113 +
memory_report_uninitialized_access(bool value)
114 +
{
115 +
	reportUninitializedAccess = value;
116 +
}
117 +
118 +
void
119 +
memory_report_usage_statistics(const char *filename) {
120 +
	reportUsageStatisticsFilename = filename;
121 +
}
122 +
123 +
void
124 +
memory_randomize_ram(bool value)
125 +
{
126 +
	randomizeRAM = value;
127 +
}
128 +
129 +
void
130 +
memory_initialize_cart(uint8_t *mem)
131 +
{
132 +
	if(randomizeRAM) {
133 +
		for(int i=0; i<0x4000; ++i) {
134 +
			mem[i] = rand();
135 +
		}
136 +
	} else {
137 +
		memset(mem, 0, 0x4000);
138 +
	}
139 +
}
140 +
141 +
//
142 +
// interface for fake6502
143 +
//
144 +
// if debugOn then reads memory only for debugger; no I/O, no side effects whatsoever
145 +
146 +
static const char *format_addr(uint16_t address, uint8_t bank, uint8_t x16Bank) {
147 +
	static char buffer[2 + 1 + 4 + 1];
148 +
149 +
	if (bank == 0 && address >= 0xA000) {
150 +
		if (address >= 0xA000) {
151 +
			snprintf(buffer, sizeof(buffer), "%02X:%04X", x16Bank, address);
152 +
		}
153 +
	}
154 +
	else if (is_gen2) {
155 +
		snprintf(buffer, sizeof(buffer), "%02X %04X", bank, address);
156 +
	} else {
157 +
		snprintf(buffer, sizeof(buffer), "%04X", address);
158 +
	}
159 +
160 +
	return buffer;
161 +
}
162 +
163 +
uint8_t
164 +
read6502(uint16_t address, uint8_t bank) {
165 +
	if (!is_gen2) bank = 0;
166 +
	// Report access to uninitialized RAM (if option selected)
167 +
	if (reportUninitializedAccess) {
168 +
		if (bank == 0) {
169 +
			uint8_t pc_x16_bank;
170 +
171 +
			if (opcode_addr < 0xa000) {
172 +
				pc_x16_bank = 0;
173 +
			} else if (opcode_addr < 0xc000) {
174 +
				pc_x16_bank = memory_get_ram_bank();
175 +
			} else {
176 +
				pc_x16_bank = memory_get_rom_bank();
177 +
			}
178 +
179 +
			if (address >= 0xa000 && address < 0xc000) {
180 +
				if (memory_get_ram_bank() < num_ram_banks && BRAM_access_flags[(memory_get_ram_bank() << 13) + address - 0xa000] == false){
181 +
					printf("Warning: %02X:%04X accessed uninitialized RAM address %02X:%04X\n", pc_x16_bank, opcode_addr, memory_get_ram_bank(), address);
182 +
				}
183 +
			}
184 +
		}
185 +
186 +
		if (bank != 0 || address < 0x9f00) {
187 +
			if (RAM_access_flags[bank * BANK_SIZE + address] == false) {
188 +
				printf("Warning: %s accessed uninitialized RAM address %02X %04X\n", format_addr(opcode_addr, regs.k, 0), bank, address);
189 +
			}
190 +
		}
191 +
	}
192 +
193 +
    if (reportUsageStatisticsFilename!=NULL) {
194 +
      if (bank != 0 || address < 0xa000) {
195 +
        RAM_system_reads[bank * BANK_SIZE + address]++;
196 +
      } else if (address < 0xc000) {
197 +
        RAM_banked_reads[memory_get_ram_bank()][address-0xa000]++;
198 +
      } else {
199 +
		ROM_banked_reads[rom_bank][address-0xc000]++;
200 +
      }
201 +
    }
202 +
203 +
	return real_read6502(address, bank, false, USE_CURRENT_X16_BANK);
204 +
}
205 +
206 +
uint8_t
207 +
real_read6502(uint16_t address, uint8_t bank, bool debugOn, int16_t x16Bank)
208 +
{
209 +
	if (is_gen2 && bank != 0) { // RAM
210 +
		if (bank < num_banks) {
211 +
			return RAM[bank * BANK_SIZE + address];
212 +
		} else {
213 +
			return (address >> 8) & 0xff; // open bus read
214 +
		}
215 +
	}
216 +
217 +
	if (address < 0x9f00) { // RAM
218 +
		return RAM[address];
219 +
	} else if (address < 0xa000) { // I/O
220 +
		if (!debugOn && address >= 0x9fa0) {
221 +
			// slow IO5-7 range
222 +
			clockticks6502 += 3;
223 +
		}
224 +
		if (address >= 0x9f00 && address < 0x9f10) {
225 +
			return via1_read(address & 0xf, debugOn);
226 +
		} else if (has_via2 && (address >= 0x9f10 && address < 0x9f20)) {
227 +
			return via2_read(address & 0xf, debugOn);
228 +
		} else if (address >= 0x9f20 && address < 0x9f40) {
229 +
			return video_read(address & 0x1f, debugOn);
230 +
		} else if (address >= 0x9f40 && address < 0x9f60) {
231 +
			// slow IO2 range
232 +
			if (!debugOn) {
233 +
				clockticks6502 += 3;
234 +
			}
235 +
			if ((address & 0x01) != 0) { // partial decoding in this range
236 +
				audio_render();
237 +
				return YM_read_status();
238 +
			}
239 +
			return 0x9f; // open bus read
240 +
		} else if (address >= 0x9fb0 && address < 0x9fc0) {
241 +
			// emulator state
242 +
			return emu_read(address & 0xf, debugOn);
243 +
		} else if (has_netcard && (address & 0xfff0) == netcard_addr) {
244 +
			// virtual serial/network card
245 +
			return netcard_read(address & 0xf, debugOn);
246 +
		} else if (has_midi_card && (address & 0xfff0) == midi_card_addr) {
247 +
			// midi card
248 +
			return midi_serial_read(address & 0xf, debugOn);
249 +
		} else {
250 +
			// future expansion
251 +
			return 0x9f; // open bus read
252 +
		}
253 +
	} else if (address < 0xc000) { // banked RAM
254 +
		int ramBank = x16Bank >= 0 ? (uint8_t)x16Bank : memory_get_ram_bank();
255 +
		if (ramBank < num_ram_banks) {
256 +
			return BRAM[(ramBank << 13) + address - 0xa000];
257 +
		} else {
258 +
			return (address >> 8) & 0xff; // open bus read
259 +
		}
260 +
	} else { // banked ROM
261 +
		int romBank = x16Bank >= 0 ? (uint8_t)x16Bank : rom_bank;
262 +
		if (romBank < 32) {
263 +
			return ROM[(romBank << 14) + address - 0xc000];
264 +
		} else {
265 +
			if (!CART) {
266 +
				return (address >> 8) & 0xff; // open bus read
267 +
			}
268 +
			return cartridge_read(address, romBank);
269 +
		}
270 +
	}
271 +
}
272 +
273 +
void
274 +
write6502(uint16_t address, uint8_t bank, uint8_t value)
275 +
{
276 +
	if (!is_gen2) bank = 0;
277 +
278 +
	if(reportUsageStatisticsFilename!=NULL) {
279 +
		if (bank != 0 || address < 0xa000) {
280 +
			RAM_system_writes[bank * BANK_SIZE + address]++;
281 +
		} else if (address < 0xc000) {
282 +
			RAM_banked_writes[memory_get_ram_bank()][address-0xa000]++;
283 +
		} else {
284 +
			// this is weird, but it does occur. And cartridges can install "Bonk RAM" in place of ROM.
285 +
			ROM_banked_writes[rom_bank][address-0xc000]++;
286 +
		}
287 +
	}
288 +
289 +
	// Update RAM access flag
290 +
	if (reportUninitializedAccess) {
291 +
		if (bank != 0 || address < 0xa000) {
292 +
			RAM_access_flags[bank * BANK_SIZE + address] = true;
293 +
		} else if (address < 0xc000) {
294 +
			if (memory_get_ram_bank() < num_ram_banks)
295 +
				BRAM_access_flags[(memory_get_ram_bank() << 13) + address - 0xa000] = true;
296 +
		}
297 +
	}
298 +
299 +
	// Write to memory
300 +
	if (is_gen2 && bank != 0) {
301 +
		if (bank < num_banks) {
302 +
			RAM[bank * BANK_SIZE + address] = value;
303 +
		}
304 +
		return;
305 +
	}
306 +
307 +
	// Write to CPU I/O ports
308 +
	if (address < 2) {
309 +
		cpuio_write(address, value);
310 +
	}
311 +
	// Write to memory
312 +
	if (address < 0x9f00) { // RAM
313 +
		RAM[address] = value;
314 +
	} else if (address < 0xa000) { // I/O
315 +
		if (address >= 0x9fa0) {
316 +
			// slow IO5-7 range
317 +
			clockticks6502 += 3;
318 +
		}
319 +
		if (address >= 0x9f00 && address < 0x9f10) {
320 +
			via1_write(address & 0xf, value);
321 +
		} else if (has_via2 && (address >= 0x9f10 && address < 0x9f20)) {
322 +
			via2_write(address & 0xf, value);
323 +
		} else if (address >= 0x9f20 && address < 0x9f40) {
324 +
			video_write(address & 0x1f, value);
325 +
		} else if (address >= 0x9f40 && address < 0x9f60) {
326 +
			// slow IO2 range
327 +
			clockticks6502 += 3;
328 +
			if ((address & 0x01) == 0) {   // YM reg (partially decoded)
329 +
				addr_ym = value;
330 +
			} else {                       // YM data (partially decoded)
331 +
				audio_render();
332 +
				YM_write_reg(addr_ym, value);
333 +
			}
334 +
		} else if (address >= 0x9fb0 && address < 0x9fc0) {
335 +
			// emulator state
336 +
			emu_write(address & 0xf, value);
337 +
		} else if (has_netcard && (address & 0xfff0) == netcard_addr) {
338 +
			netcard_write(address & 0xf, value);
339 +
		} else if (has_midi_card && (address & 0xfff0) == midi_card_addr) {
340 +
			midi_serial_write(address & 0xf, value);
341 +
		} else {
342 +
			// future expansion
343 +
		}
344 +
	} else if (address < 0xc000) { // banked RAM
345 +
		if (memory_get_ram_bank() < num_ram_banks) {
346 +
			BRAM[(memory_get_ram_bank() << 13) + address - 0xa000] = value;
347 +
		}
348 +
	} else { // ROM
349 +
		if (rom_bank >= 32) { // Cartridge ROM/RAM
350 +
			cartridge_write(address, rom_bank, value);
351 +
		}
352 +
		// ignore if base ROM (banks 0-31)
353 +
	}
354 +
}
355 +
356 +
void
357 +
vp6502()
358 +
{
359 +
	memory_set_rom_bank(0);
360 +
}
361 +
362 +
//
363 +
// saves the memory content into a file
364 +
//
365 +
366 +
void
367 +
memory_save(SDL_RWops *f, bool dump_ram, bool dump_bank)
368 +
{
369 +
	if (dump_ram) {
370 +
		SDL_RWwrite(f, &RAM[0], sizeof(uint8_t), is_gen2 ? num_banks * BANK_SIZE : 0xa000);
371 +
	}
372 +
	if (dump_bank) {
373 +
		SDL_RWwrite(f, &BRAM[0], sizeof(uint8_t), (num_ram_banks * 8192));
374 +
	}
375 +
}
376 +
377 +
378 +
void writestring(SDL_RWops *f, const char *string) {
379 +
	SDL_RWwrite(f, string, strlen(string), 1);
380 +
}
381 +
382 +
void memory_dump_usage_counts() {
383 +
	if(reportUsageStatisticsFilename==NULL)
384 +
		return;
385 +
386 +
	SDL_RWops *f = SDL_RWFromFile(reportUsageStatisticsFilename, "w");
387 +
	if (!f) {
388 +
		printf("Cannot write to %s!\n", reportUsageStatisticsFilename);
389 +
		return;
390 +
	}
391 +
392 +
	writestring(f, "Usage counts of all memory locations. Locations not printed have count zero.\n");
393 +
	writestring(f, "Tip: use 'sort -r -n -k 3' to sort it so it shows the most used at the top.\n");
394 +
	writestring(f, "\nsystem RAM reads:\n");
395 +
	int addr;
396 +
	char buf[100];
397 +
	for(addr=0; addr<num_banks * BANK_SIZE; ++addr) {
398 +
		if(RAM_system_reads[addr]>0) {
399 +
			SDL_RWwrite(f, buf, snprintf(buf, sizeof(buf), "r %04x %" PRIu64 "\n", addr, RAM_system_reads[addr]), 1);
400 +
		}
401 +
	}
402 +
	writestring(f, "\nsystem RAM writes:\n");
403 +
	for(addr=0; addr<65536; ++addr) {
404 +
		if(RAM_system_writes[addr]>0) {
405 +
			SDL_RWwrite(f, buf, snprintf(buf, sizeof(buf), "w %04x %" PRIu64 "\n", addr, RAM_system_writes[addr]), 1);
406 +
		}
407 +
	}
408 +
	writestring(f, "\nbanked RAM reads:\n");
409 +
	int bank;
410 +
	for(bank=0; bank<256; ++bank) {
411 +
		for(addr=0; addr<8192; ++addr) {
412 +
			if(RAM_banked_reads[bank][addr]>0) {
413 +
				SDL_RWwrite(f, buf, snprintf(buf, sizeof(buf), "r %02x:%04x %" PRIu64 "\n", bank, addr+0xa000, RAM_banked_reads[bank][addr]), 1);
414 +
			}
415 +
		}
416 +
	}
417 +
	writestring(f, "\nbanked RAM writes:\n");
418 +
	for(bank=0; bank<256; ++bank) {
419 +
		for(addr=0; addr<8192; ++addr) {
420 +
			if(RAM_banked_writes[bank][addr]>0) {
421 +
				SDL_RWwrite(f, buf, snprintf(buf, sizeof(buf), "w %02x:%04x %" PRIu64 "\n", bank, addr+0xa000, RAM_banked_writes[bank][addr]), 1);
422 +
			}
423 +
		}
424 +
	}
425 +
	writestring(f, "\nbanked ROM reads:\n");
426 +
	for(bank=0; bank<256; ++bank) {
427 +
		for(addr=0; addr<16384; ++addr) {
428 +
			if(ROM_banked_reads[bank][addr]>0) {
429 +
				SDL_RWwrite(f, buf, snprintf(buf, sizeof(buf), "r %02x:%04x %" PRIu64 "\n", bank, addr+0xc000, ROM_banked_reads[bank][addr]), 1);
430 +
			}
431 +
		}
432 +
	}
433 +
	writestring(f, "\nbanked ROM / 'Bonk RAM' writes:\n");
434 +
	for(bank=0; bank<256; ++bank) {
435 +
		for(addr=0; addr<16384; ++addr) {
436 +
			if(ROM_banked_writes[bank][addr]>0) {
437 +
				SDL_RWwrite(f, buf, snprintf(buf, sizeof(buf), "w %02x:%04x %" PRIu64 "\n", bank, addr+0xc000, ROM_banked_writes[bank][addr]), 1);
438 +
			}
439 +
		}
440 +
	}
441 +
442 +
	SDL_RWclose(f);
443 +
}
444 +
445 +
446 +
///
447 +
///
448 +
///
449 +
450 +
inline void
451 +
memory_set_ram_bank(uint8_t bank)
452 +
{
453 +
	ram_bank = bank;
454 +
}
455 +
456 +
inline uint8_t
457 +
memory_get_ram_bank()
458 +
{
459 +
	return ram_bank;
460 +
}
461 +
462 +
inline void
463 +
memory_set_rom_bank(uint8_t bank)
464 +
{
465 +
	rom_bank = bank;
466 +
}
467 +
468 +
inline uint8_t
469 +
memory_get_rom_bank()
470 +
{
471 +
	return rom_bank;
472 +
}
473 +
474 +
void
475 +
cpuio_write(uint8_t reg, uint8_t value)
476 +
{
477 +
	switch (reg) {
478 +
		case 0:
479 +
			memory_set_ram_bank(value);
480 +
			break;
481 +
		case 1:
482 +
			memory_set_rom_bank(value);
483 +
			break;
484 +
	}
485 +
}
486 +
487 +
// Control the GIF recorder
488 +
void
489 +
emu_recorder_set(gif_recorder_command_t command)
490 +
{
491 +
	// turning off while recording is enabled
492 +
	if (command == RECORD_GIF_PAUSE && record_gif != RECORD_GIF_DISABLED) {
493 +
		record_gif = RECORD_GIF_PAUSED; // need to save
494 +
	}
495 +
	// turning on continuous recording
496 +
	if (command == RECORD_GIF_RESUME && record_gif != RECORD_GIF_DISABLED) {
497 +
		record_gif = RECORD_GIF_ACTIVE;		// activate recording
498 +
	}
499 +
	// capture one frame
500 +
	if (command == RECORD_GIF_SNAP && record_gif != RECORD_GIF_DISABLED) {
501 +
		record_gif = RECORD_GIF_SINGLE;		// single-shot
502 +
	}
503 +
}
504 +
505 +
//
506 +
// read/write emulator state (feature flags)
507 +
//
508 +
// 0: debugger_enabled
509 +
// 1: log_video
510 +
// 2: log_keyboard
511 +
// 3: echo_mode
512 +
// 4: save_on_exit
513 +
// 5: record_gif
514 +
// 6: record_wav
515 +
// 7: cmd key toggle
516 +
// 8: write: reset cpu clock counter
517 +
// 8: read: snapshots cpu clock counter and reads the LSB bits 0-7
518 +
// 9: write: output debug byte 1
519 +
// 9: read: cpu clock bits 8-15
520 +
// 10: write: output debug byte 2
521 +
// 10: read: cpu clock bits 16-23
522 +
// 11: write: write character to STDOUT of console
523 +
// 11: read: cpu clock MSB bits 24-31
524 +
// POKE $9FB3,1:PRINT"ECHO MODE IS ON":POKE $9FB3,0
525 +
void
526 +
emu_write(uint8_t reg, uint8_t value)
527 +
{
528 +
	bool v = value != 0;
529 +
	switch (reg) {
530 +
		case 0: debugger_enabled = v; break;
531 +
		case 1: log_video = v; break;
532 +
		case 2: log_keyboard = v; break;
533 +
		case 3: echo_mode = value; break;
534 +
		case 4: save_on_exit = v; break;
535 +
		case 5: emu_recorder_set((gif_recorder_command_t) value); break;
536 +
		case 6: wav_recorder_set((wav_recorder_command_t) value); break;
537 +
		case 7: disable_emu_cmd_keys = v; break;
538 +
		case 8: clock_base = clockticks6502; break;
539 +
		case 9: printf("User debug 1: $%02x\n", value); fflush(stdout); break;
540 +
		case 10: printf("User debug 2: $%02x\n", value); fflush(stdout); break;
541 +
		case 11: {
542 +
			if (value == 0x09 || value == 0x0a || value == 0x0d || (value >= 0x20 && value < 0x7f)) {
543 +
				printf("%c", value);
544 +
			} else if (value >= 0xa1) {
545 +
				print_iso8859_15_char((char) value);
546 +
			} else {
547 +
				printf("\xef\xbf\xbd"); // �
548 +
			}
549 +
			fflush(stdout);
550 +
			break;
551 +
		}
552 +
		default: printf("WARN: Invalid register %x\n", DEVICE_EMULATOR + reg);
553 +
	}
554 +
}
555 +
556 +
uint8_t
557 +
emu_read(uint8_t reg, bool debugOn)
558 +
{
559 +
	if (reg == 0) {
560 +
		return debugger_enabled ? 1 : 0;
561 +
	} else if (reg == 1) {
562 +
		return log_video ? 1 : 0;
563 +
	} else if (reg == 2) {
564 +
		return log_keyboard ? 1 : 0;
565 +
	} else if (reg == 3) {
566 +
		return echo_mode;
567 +
	} else if (reg == 4) {
568 +
		return save_on_exit ? 1 : 0;
569 +
	} else if (reg == 5) {
570 +
		return record_gif;
571 +
	} else if (reg == 6) {
572 +
		return wav_recorder_get_state();
573 +
	} else if (reg == 7) {
574 +
		return disable_emu_cmd_keys ? 1 : 0;
575 +
576 +
	} else if (reg == 8) {
577 +
		if (!debugOn)
578 +
			clock_snap = clockticks6502 - clock_base;
579 +
		return (clock_snap >> 0) & 0xff;
580 +
	} else if (reg == 9) {
581 +
		return (clock_snap >> 8) & 0xff;
582 +
	} else if (reg == 10) {
583 +
		return (clock_snap >> 16) & 0xff;
584 +
	} else if (reg == 11) {
585 +
		return (clock_snap >> 24) & 0xff;
586 +
587 +
	} else if (reg == 13) {
588 +
		return keymap;
589 +
	} else if (reg == 14) {
590 +
		return '1'; // emulator detection
591 +
	} else if (reg == 15) {
592 +
		return '6'; // emulator detection
593 +
	}
594 +
	if (!debugOn) printf("WARN: Invalid register %x\n", DEVICE_EMULATOR + reg);
595 +
	return -1;
596 +
}
added src/memory.h +75 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _MEMORY_H_
6 +
#define _MEMORY_H_
7 +
8 +
#include <stdbool.h>
9 +
#include <stdint.h>
10 +
#include <stdio.h>
11 +
#include <SDL.h>
12 +
13 +
#define BANK_SIZE 65536
14 +
15 +
#define USE_CURRENT_X16_BANK (-1)
16 +
#define debug_read6502(a, b, x) real_read6502((a), (b), true, (x))
17 +
18 +
// X16 r0-r15
19 +
#define X16_REG_R0L (direct_page_add(2))
20 +
#define X16_REG_R0H (direct_page_add(3))
21 +
#define X16_REG_R1L (direct_page_add(4))
22 +
#define X16_REG_R1H (direct_page_add(5))
23 +
#define X16_REG_R2L (direct_page_add(6))
24 +
#define X16_REG_R2H (direct_page_add(7))
25 +
#define X16_REG_R3L (direct_page_add(8))
26 +
#define X16_REG_R3H (direct_page_add(9))
27 +
#define X16_REG_R4L (direct_page_add(10))
28 +
#define X16_REG_R4H (direct_page_add(11))
29 +
#define X16_REG_R5L (direct_page_add(12))
30 +
#define X16_REG_R5H (direct_page_add(13))
31 +
#define X16_REG_R6L (direct_page_add(14))
32 +
#define X16_REG_R6H (direct_page_add(15))
33 +
#define X16_REG_R7L (direct_page_add(16))
34 +
#define X16_REG_R7H (direct_page_add(17))
35 +
#define X16_REG_R8L (direct_page_add(18))
36 +
#define X16_REG_R8H (direct_page_add(19))
37 +
#define X16_REG_R9L (direct_page_add(20))
38 +
#define X16_REG_R9H (direct_page_add(21))
39 +
#define X16_REG_R10L (direct_page_add(22))
40 +
#define X16_REG_R10H (direct_page_add(23))
41 +
#define X16_REG_R11L (direct_page_add(24))
42 +
#define X16_REG_R11H (direct_page_add(25))
43 +
#define X16_REG_R12L (direct_page_add(26))
44 +
#define X16_REG_R12H (direct_page_add(27))
45 +
#define X16_REG_R13L (direct_page_add(28))
46 +
#define X16_REG_R13H (direct_page_add(29))
47 +
#define X16_REG_R14L (direct_page_add(30))
48 +
#define X16_REG_R14H (direct_page_add(31))
49 +
#define X16_REG_R15L (direct_page_add(32))
50 +
#define X16_REG_R15H (direct_page_add(33))
51 +
52 +
uint8_t read6502(uint16_t address, uint8_t bank);
53 +
uint8_t real_read6502(uint16_t address, uint8_t bank, bool debugOn, int16_t x16Bank);
54 +
void write6502(uint16_t address, uint8_t bank, uint8_t value);
55 +
void vp6502();
56 +
57 +
void memory_init();
58 +
void memory_reset();
59 +
void memory_report_uninitialized_access(bool);
60 +
void memory_report_usage_statistics(const char *filename);
61 +
void memory_randomize_ram(bool);
62 +
63 +
void memory_save(SDL_RWops *f, bool dump_ram, bool dump_bank);
64 +
void memory_dump_usage_counts();
65 +
66 +
void memory_set_ram_bank(uint8_t bank);
67 +
void memory_set_rom_bank(uint8_t bank);
68 +
69 +
uint8_t memory_get_ram_bank();
70 +
uint8_t memory_get_rom_bank();
71 +
72 +
uint8_t emu_read(uint8_t reg, bool debugOn);
73 +
void emu_write(uint8_t reg, uint8_t value);
74 +
75 +
#endif
added src/midi.c +997 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2024 MooingLemur
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <pthread.h>
6 +
#include <stdio.h>
7 +
#include "glue.h"
8 +
#include "midi.h"
9 +
#include "audio.h"
10 +
#include "endian.h"
11 +
12 +
#ifdef _WIN32
13 +
    #include <windows.h>
14 +
    #define LIBRARY_TYPE HMODULE
15 +
    #define LOAD_LIBRARY(name) LoadLibrary(name)
16 +
    #define GET_FUNCTION(lib, name) (void *)GetProcAddress(lib, name)
17 +
    #define CLOSE_LIBRARY(lib) FreeLibrary(lib)
18 +
#else
19 +
    #include <dlfcn.h>
20 +
    #define LIBRARY_TYPE void*
21 +
    #define LOAD_LIBRARY(name) dlopen(name, RTLD_LAZY)
22 +
    #define GET_FUNCTION(lib, name) dlsym(lib, name)
23 +
    #define CLOSE_LIBRARY(lib) dlclose(lib)
24 +
#endif
25 +
26 +
#define ASSIGN_FUNCTION(lib, var, name) {\
27 +
    var = GET_FUNCTION(lib, name);\
28 +
    if (!var) { fprintf(stderr, "Unable to find symbol for '%s'\n", name); CLOSE_LIBRARY(handle); return; }\
29 +
}
30 +
31 +
enum MIDI_states {
32 +
    MSTATE_Normal,
33 +
    MSTATE_Param,
34 +
    MSTATE_SysEx,
35 +
};
36 +
37 +
struct midi_serial_regs
38 +
{
39 +
    LOW_HIGH_UNION(dl, dll, dlm);
40 +
41 +
    bool ier_erbi;
42 +
    bool ier_etbei;
43 +
    bool ier_elsi;
44 +
    bool ier_edssi;
45 +
46 +
    uint8_t iir;
47 +
48 +
    bool fcr_fifo_enable;
49 +
    uint8_t fcr_ififo_trigger_level_bytes;
50 +
51 +
    uint8_t lcr_word_length_bits;
52 +
    bool lcr_stb;
53 +
    bool lcr_pen;
54 +
    bool lcr_eps;
55 +
    bool lcr_stick;
56 +
    bool lcr_break;
57 +
    bool lcr_dlab;
58 +
59 +
    bool mcr_dtr;
60 +
    bool mcr_rts;
61 +
    bool mcr_out1;
62 +
    bool mcr_out2;
63 +
    bool mcr_loop;
64 +
    bool mcr_afe;
65 +
66 +
    bool lsr_oe;
67 +
    bool lsr_pe;
68 +
    bool lsr_fe;
69 +
    bool lsr_bi;
70 +
    bool lsr_eif;
71 +
72 +
    bool msr_dcts;
73 +
    bool msr_ddsr;
74 +
    bool msr_teri;
75 +
    bool msr_ddcd;
76 +
    bool msr_cts;
77 +
    bool msr_dsr;
78 +
    bool msr_ri;
79 +
    bool msr_dcd;
80 +
81 +
    uint8_t obyte_bits_remain;
82 +
    uint8_t ibyte_bits_remain;
83 +
    uint8_t rx_timeout;
84 +
    bool rx_timeout_enabled;
85 +
86 +
    bool thre_intr;
87 +
    uint8_t thre_bits_remain;
88 +
89 +
    uint8_t scratch;
90 +
    uint8_t ififo[16];
91 +
    uint8_t ifsz;
92 +
    uint8_t ofifo[16];
93 +
    uint8_t ofsz;
94 +
95 +
    int64_t clock; // 40.24 fixed point
96 +
    int32_t clockdec; // 8.24 fixed point
97 +
98 +
    time_t last_warning;
99 +
100 +
    uint8_t midi_event_fifo[256];
101 +
    int mfsz;
102 +
    uint8_t in_midi_last_command;
103 +
104 +
    pthread_mutex_t fifo_mutex;
105 +
    pthread_mutexattr_t fifo_mutex_attr;
106 +
};
107 +
108 +
struct midi_serial_regs mregs[2];
109 +
static bool serial_midi_mutexes_initialized = false;
110 +
111 +
void midi_serial_iir_check(uint8_t sel);
112 +
bool fs_midi_in_connect = false;
113 +
114 +
#ifdef HAS_FLUIDSYNTH
115 +
116 +
int handle_midi_event(void* data, fluid_midi_event_t* event);
117 +
118 +
static uint8_t sysex_buffer[1024];
119 +
static int sysex_bufptr;
120 +
121 +
static enum MIDI_states midi_state[2] = {MSTATE_Normal, MSTATE_Normal};
122 +
static uint8_t out_midi_last_command[2] = {0, 0};
123 +
static uint8_t out_midi_first_param[2];
124 +
static uint16_t out_midi_nrpn[2][16];
125 +
static uint16_t out_midi_rpn[2][16];
126 +
static bool out_midi_nrpn_active[2][16];
127 +
128 +
static bool midi_initialized = false;
129 +
130 +
static fluid_settings_t* fl_settings;
131 +
static fluid_midi_driver_t* fl_mdriver;
132 +
static fluid_synth_t* fl_synth;
133 +
static int fl_sf2id;
134 +
135 +
typedef fluid_settings_t* (*new_fluid_settings_f_t)(void);
136 +
typedef fluid_synth_t* (*new_fluid_synth_f_t)(fluid_settings_t*);
137 +
typedef fluid_audio_driver_t* (*new_fluid_audio_driver_f_t)(fluid_settings_t*, fluid_synth_t*);
138 +
typedef fluid_midi_driver_t* (*new_fluid_midi_driver_f_t)(fluid_settings_t*, handle_midi_event_func_t, void*);
139 +
typedef int (*fluid_settings_setnum_f_t)(fluid_settings_t*,const char*, double);
140 +
typedef int (*fluid_settings_setint_f_t)(fluid_settings_t*,const char*, int);
141 +
typedef int (*fluid_settings_setstr_f_t)(fluid_settings_t*,const char*, const char*);
142 +
typedef int (*fluid_synth_sfload_f_t)(fluid_synth_t*, const char *, int);
143 +
typedef int (*fluid_synth_program_change_f_t)(fluid_synth_t*, int, int);
144 +
typedef int (*fluid_synth_channel_pressure_f_t)(fluid_synth_t*, int, int);
145 +
typedef int (*fluid_synth_system_reset_f_t)(fluid_synth_t*);
146 +
typedef int (*fluid_synth_noteoff_f_t)(fluid_synth_t*, int, int);
147 +
typedef int (*fluid_synth_noteon_f_t)(fluid_synth_t*, int, int, int);
148 +
typedef int (*fluid_synth_key_pressure_f_t)(fluid_synth_t*, int, int, int);
149 +
typedef int (*fluid_synth_cc_f_t)(fluid_synth_t*, int, int, int);
150 +
typedef int (*fluid_synth_pitch_bend_f_t)(fluid_synth_t*, int, int);
151 +
typedef int (*fluid_synth_sysex_f_t)(fluid_synth_t*, const char*, int, char*, int*, int*, int);
152 +
typedef int (*fluid_synth_write_s16_f_t)(fluid_synth_t*, int, void*, int, int, void*, int, int);
153 +
typedef int (*fluid_midi_event_get_channel_f_t)(const fluid_midi_event_t*);
154 +
typedef int (*fluid_midi_event_get_control_f_t)(const fluid_midi_event_t*);
155 +
typedef int (*fluid_midi_event_get_key_f_t)(const fluid_midi_event_t*);
156 +
typedef int (*fluid_midi_event_get_lyrics_f_t)(const fluid_midi_event_t*, void**, int*);
157 +
typedef int (*fluid_midi_event_get_pitch_f_t)(const fluid_midi_event_t*);
158 +
typedef int (*fluid_midi_event_get_program_f_t)(const fluid_midi_event_t*);
159 +
typedef int (*fluid_midi_event_get_text_f_t)(const fluid_midi_event_t*, void**, int*);
160 +
typedef int (*fluid_midi_event_get_type_f_t)(const fluid_midi_event_t*);
161 +
typedef int (*fluid_midi_event_get_value_f_t)(const fluid_midi_event_t*);
162 +
typedef int (*fluid_midi_event_get_velocity_f_t)(const fluid_midi_event_t*);
163 +
typedef int (*fluid_midi_event_set_type_f_t)(fluid_midi_event_t*, int);
164 +
165 +
static new_fluid_settings_f_t dl_new_fluid_settings;
166 +
static new_fluid_synth_f_t dl_new_fluid_synth;
167 +
static new_fluid_audio_driver_f_t dl_new_fluid_audio_driver;
168 +
static new_fluid_midi_driver_f_t dl_new_fluid_midi_driver;
169 +
static fluid_settings_setnum_f_t dl_fluid_settings_setnum;
170 +
static fluid_settings_setint_f_t dl_fluid_settings_setint;
171 +
static fluid_settings_setstr_f_t dl_fluid_settings_setstr;
172 +
static fluid_synth_sfload_f_t dl_fs_sfload;
173 +
static fluid_synth_program_change_f_t dl_fs_program_change;
174 +
static fluid_synth_channel_pressure_f_t dl_fs_channel_pressure;
175 +
static fluid_synth_system_reset_f_t dl_fs_system_reset;
176 +
static fluid_synth_noteoff_f_t dl_fs_noteoff;
177 +
static fluid_synth_noteon_f_t dl_fs_noteon;
178 +
static fluid_synth_key_pressure_f_t dl_fs_key_pressure;
179 +
static fluid_synth_cc_f_t dl_fs_cc;
180 +
static fluid_synth_pitch_bend_f_t dl_fs_pitch_bend;
181 +
static fluid_synth_sysex_f_t dl_fs_sysex;
182 +
static fluid_synth_write_s16_f_t dl_fs_write_s16;
183 +
static fluid_midi_event_get_channel_f_t dl_fluid_midi_event_get_channel;
184 +
static fluid_midi_event_get_control_f_t dl_fluid_midi_event_get_control;
185 +
static fluid_midi_event_get_key_f_t dl_fluid_midi_event_get_key;
186 +
static fluid_midi_event_get_lyrics_f_t dl_fluid_midi_event_get_lyrics;
187 +
static fluid_midi_event_get_pitch_f_t dl_fluid_midi_event_get_pitch;
188 +
static fluid_midi_event_get_program_f_t dl_fluid_midi_event_get_program;
189 +
static fluid_midi_event_get_text_f_t dl_fluid_midi_event_get_text;
190 +
static fluid_midi_event_get_type_f_t dl_fluid_midi_event_get_type;
191 +
static fluid_midi_event_get_value_f_t dl_fluid_midi_event_get_value;
192 +
static fluid_midi_event_get_velocity_f_t dl_fluid_midi_event_get_velocity;
193 +
static fluid_midi_event_set_type_f_t dl_fluid_midi_event_set_type;
194 +
195 +
196 +
void midi_init()
197 +
{
198 +
    if (midi_initialized) {
199 +
        return;
200 +
    }
201 +
202 +
#ifdef _WIN32
203 +
    LIBRARY_TYPE handle = LOAD_LIBRARY("libfluidsynth-3.dll");
204 +
#elif __APPLE__
205 +
    LIBRARY_TYPE handle = LOAD_LIBRARY("libfluidsynth.dylib");
206 +
#else
207 +
    LIBRARY_TYPE handle = LOAD_LIBRARY("libfluidsynth.so.3");
208 +
#endif
209 +
210 +
    if (!handle) {
211 +
        // Handle the error on both platforms
212 +
#ifdef _WIN32
213 +
        fprintf(stderr, "Could not load MIDI synth library: error code %lu\n", GetLastError());
214 +
#else
215 +
        fprintf(stderr, "Could not load MIDI synth library: %s\n", dlerror());
216 +
#endif
217 +
        return;
218 +
    }
219 +
220 +
#ifndef _WIN32
221 +
    dlerror();
222 +
#endif
223 +
224 +
    ASSIGN_FUNCTION(handle, dl_new_fluid_settings, "new_fluid_settings");
225 +
    ASSIGN_FUNCTION(handle, dl_new_fluid_synth, "new_fluid_synth");
226 +
    ASSIGN_FUNCTION(handle, dl_new_fluid_audio_driver, "new_fluid_audio_driver");
227 +
    ASSIGN_FUNCTION(handle, dl_new_fluid_midi_driver, "new_fluid_midi_driver");
228 +
    ASSIGN_FUNCTION(handle, dl_fluid_settings_setnum, "fluid_settings_setnum");
229 +
    ASSIGN_FUNCTION(handle, dl_fluid_settings_setint, "fluid_settings_setint");
230 +
    ASSIGN_FUNCTION(handle, dl_fluid_settings_setstr, "fluid_settings_setstr");
231 +
    ASSIGN_FUNCTION(handle, dl_fs_sfload, "fluid_synth_sfload");
232 +
    ASSIGN_FUNCTION(handle, dl_fs_program_change, "fluid_synth_program_change");
233 +
    ASSIGN_FUNCTION(handle, dl_fs_channel_pressure, "fluid_synth_channel_pressure");
234 +
    ASSIGN_FUNCTION(handle, dl_fs_system_reset, "fluid_synth_system_reset");
235 +
    ASSIGN_FUNCTION(handle, dl_fs_noteoff, "fluid_synth_noteoff");
236 +
    ASSIGN_FUNCTION(handle, dl_fs_noteon, "fluid_synth_noteon");
237 +
    ASSIGN_FUNCTION(handle, dl_fs_key_pressure, "fluid_synth_key_pressure");
238 +
    ASSIGN_FUNCTION(handle, dl_fs_cc, "fluid_synth_cc");
239 +
    ASSIGN_FUNCTION(handle, dl_fs_pitch_bend, "fluid_synth_pitch_bend");
240 +
    ASSIGN_FUNCTION(handle, dl_fs_sysex, "fluid_synth_sysex");
241 +
    ASSIGN_FUNCTION(handle, dl_fs_write_s16, "fluid_synth_write_s16");
242 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_channel, "fluid_midi_event_get_channel");
243 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_control, "fluid_midi_event_get_control");
244 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_key, "fluid_midi_event_get_key");
245 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_lyrics, "fluid_midi_event_get_lyrics");
246 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_pitch, "fluid_midi_event_get_pitch");
247 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_program, "fluid_midi_event_get_program");
248 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_text, "fluid_midi_event_get_text");
249 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_type, "fluid_midi_event_get_type");
250 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_value, "fluid_midi_event_get_value");
251 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_get_velocity, "fluid_midi_event_get_velocity");
252 +
    ASSIGN_FUNCTION(handle, dl_fluid_midi_event_set_type, "fluid_midi_event_set_type");
253 +
254 +
    fl_settings = dl_new_fluid_settings();
255 +
    dl_fluid_settings_setnum(fl_settings, "synth.sample-rate", 
256 +
    AUDIO_SAMPLERATE);
257 +
    dl_fluid_settings_setnum(fl_settings, "synth.gain", FL_DEFAULT_GAIN);
258 +
    dl_fluid_settings_setstr(fl_settings, "midi.portname", "Commander X16 Emulator");
259 +
    dl_fluid_settings_setint(fl_settings, "midi.autoconnect", fs_midi_in_connect);
260 +
    fl_synth = dl_new_fluid_synth(fl_settings);
261 +
    if (fs_midi_in_connect) {
262 +
        fl_mdriver = dl_new_fluid_midi_driver(fl_settings, handle_midi_event, &mregs[0]);
263 +
    }
264 +
265 +
    midi_initialized = true;
266 +
    fprintf(stderr, "Initialized MIDI synth at $%04X.\n", midi_card_addr);
267 +
}
268 +
269 +
void midi_load_sf2(uint8_t* filename)
270 +
{
271 +
    if (!midi_initialized) return;
272 +
    fl_sf2id = dl_fs_sfload(fl_synth, (const char *)filename, true);
273 +
    if (fl_sf2id == FLUID_FAILED) {
274 +
        fprintf(stderr, "Unable to load soundfont.\n");
275 +
    }
276 +
}
277 +
278 +
// Receive a byte from client
279 +
// Store state, or dispatch event
280 +
void midi_byte_out(uint8_t sel, uint8_t b)
281 +
{
282 +
    uint8_t chan = out_midi_last_command[sel] & 0xf;
283 +
284 +
    if (!midi_initialized) return;
285 +
    switch (midi_state[sel]) {
286 +
        case MSTATE_Normal:
287 +
            if (b < 0x80) {
288 +
                if ((out_midi_last_command[sel] & 0xf0) == 0xc0) { // patch change
289 +
                    dl_fs_program_change(fl_synth, out_midi_last_command[sel] & 0xf, b);
290 +
                } else if ((out_midi_last_command[sel] & 0xf0) == 0xd0) { // channel pressure
291 +
                    dl_fs_channel_pressure(fl_synth, out_midi_last_command[sel] & 0xf, b);
292 +
                } else if (out_midi_last_command[sel] >= 0x80) { // two-param command
293 +
                    out_midi_first_param[sel] = b;
294 +
                    midi_state[sel] = MSTATE_Param;
295 +
                }
296 +
            } else {
297 +
                if (b < 0xf0) {
298 +
                    out_midi_last_command[sel] = b;
299 +
                } else if (b == 0xf0) {
300 +
                    sysex_bufptr = 0;
301 +
                    midi_state[sel] = MSTATE_SysEx;
302 +
                    out_midi_last_command[sel] = 0;
303 +
                } else if (b == 0xff) {
304 +
                    dl_fs_system_reset(fl_synth);
305 +
                    out_midi_last_command[sel] = 0;
306 +
                } else if (b < 0xf8) {
307 +
                    out_midi_last_command[sel] = 0;
308 +
                }
309 +
            }
310 +
            break;
311 +
        case MSTATE_Param:
312 +
            switch (out_midi_last_command[sel] & 0xf0) {
313 +
                case 0x80: // note off
314 +
                    dl_fs_noteoff(fl_synth, chan, out_midi_first_param[sel]); // no release velocity
315 +
                    break;
316 +
                case 0x90: // note on
317 +
                    if (b == 0) {
318 +
                        dl_fs_noteoff(fl_synth, chan, out_midi_first_param[sel]);
319 +
                    } else {
320 +
                        dl_fs_noteon(fl_synth, chan, out_midi_first_param[sel], b);
321 +
                    }
322 +
                    break;
323 +
                case 0xa0: // aftertouch
324 +
                    dl_fs_key_pressure(fl_synth, chan, out_midi_first_param[sel], b);
325 +
                    break;
326 +
                case 0xb0: // controller
327 +
                    switch (out_midi_first_param[sel]) {
328 +
                        case 98:
329 +
                            out_midi_nrpn_active[sel][chan] = true;
330 +
                            out_midi_nrpn[sel][chan] = (out_midi_nrpn[sel][chan] & 0x00ff) | (b << 8);
331 +
                            break;
332 +
                        case 99:
333 +
                            out_midi_nrpn_active[sel][chan] = true;
334 +
                            out_midi_nrpn[sel][chan] = (out_midi_nrpn[sel][chan] & 0xff00) | b;
335 +
                            break;
336 +
                        case 100:
337 +
                            out_midi_nrpn_active[sel][chan] = false;
338 +
                            out_midi_rpn[sel][chan] = (out_midi_rpn[sel][chan] & 0x00ff) | (b << 8);
339 +
                            break;
340 +
                        case 101:
341 +
                            out_midi_nrpn_active[sel][chan] = false;
342 +
                            out_midi_rpn[sel][chan] = (out_midi_rpn[sel][chan] & 0xff00) | b;
343 +
                            break;
344 +
                    }
345 +
                    dl_fs_cc(fl_synth, chan, out_midi_first_param[sel], b);
346 +
                    if (out_midi_nrpn_active[sel][chan] && out_midi_nrpn[sel][chan] == 0x0121 && out_midi_first_param[sel] == 6) {
347 +
                        dl_fs_cc(fl_synth, chan, 71, b); // Translate NRPN 0x0121 -> Controller 71 (Timbre/Resonance)
348 +
                    }
349 +
                    break;
350 +
                case 0xe0: // pitch bend
351 +
                    dl_fs_pitch_bend(fl_synth, chan, ((uint16_t)out_midi_first_param[sel]) | (uint16_t)b << 7);
352 +
                    break;
353 +
            }
354 +
            midi_state[sel] = MSTATE_Normal;
355 +
            break;
356 +
        case MSTATE_SysEx:
357 +
            if (b & 0x80) { // any command byte can terminate a SYSEX, not just 0xf7
358 +
                if (sysex_bufptr < (sizeof(sysex_buffer) / sizeof(sysex_buffer[0]))-1) { // only if buffer didn't fill
359 +
                    sysex_buffer[sysex_bufptr] = 0;
360 +
                    if (!strncmp((char *)sysex_buffer, "\x7F\x7F\x04\x01\x00", sysex_bufptr-1)) {
361 +
                        // Handle Master Volume SysEx
362 +
                        dl_fluid_settings_setnum(fl_settings, "synth.gain", FL_DEFAULT_GAIN*((float)sysex_buffer[sysex_bufptr-1]/127));
363 +
                    } else if (!strncmp((char *)sysex_buffer, "\x7E\x7F\x09\x01", sysex_bufptr)) {
364 +
                        // Handle General MIDI reset SysEx
365 +
                        dl_fs_system_reset(fl_synth);
366 +
                    } else {
367 +
                        dl_fs_sysex(fl_synth, (char *)sysex_buffer, sysex_bufptr, NULL, NULL, NULL, 0);
368 +
                    }
369 +
                }
370 +
                midi_state[sel] = MSTATE_Normal;
371 +
                return midi_byte_out(sel, b);
372 +
            } else {
373 +
                sysex_buffer[sysex_bufptr] = b;
374 +
                // we can't do much about a runaway sysex other than continue to absorb it
375 +
                // but we throw it all away later if the buffer filled
376 +
                if (sysex_bufptr < (sizeof(sysex_buffer) / sizeof(sysex_buffer[0]))-1) {
377 +
                    sysex_bufptr++;
378 +
                }
379 +
            }
380 +
            break;
381 +
    }
382 +
}
383 +
384 +
void midi_synth_render(int16_t* buf, int len)
385 +
{
386 +
    if (midi_initialized) {
387 +
        dl_fs_write_s16(fl_synth, len, buf, 0, 2, buf, 1, 2);
388 +
    } else {
389 +
        memset(buf, 0, len * 2 * sizeof(int16_t));
390 +
    }
391 +
}
392 +
393 +
void midi_event_enqueue_byte(struct midi_serial_regs* mrp, uint8_t val)
394 +
{
395 +
    if (mrp->mfsz >= 256) return;
396 +
    mrp->midi_event_fifo[mrp->mfsz++] = val;
397 +
}
398 +
399 +
void midi_event_enqueue_short(struct midi_serial_regs* mrp, uint8_t cmd, uint8_t val)
400 +
{
401 +
    if (mrp->mfsz >= 255) {
402 +
        mrp->in_midi_last_command = 0;
403 +
        return;
404 +
    }
405 +
    if (mrp->in_midi_last_command != cmd) {
406 +
        mrp->midi_event_fifo[mrp->mfsz++] = cmd;
407 +
        mrp->in_midi_last_command = cmd;
408 +
    }
409 +
    mrp->midi_event_fifo[mrp->mfsz++] = val;
410 +
}
411 +
412 +
void midi_event_enqueue_normal(struct midi_serial_regs* mrp, uint8_t cmd, uint8_t key, uint8_t val)
413 +
{
414 +
    if (mrp->mfsz >= 254) {
415 +
        mrp->in_midi_last_command = 0;
416 +
        return;
417 +
    }
418 +
    if (mrp->in_midi_last_command != cmd) {
419 +
        mrp->midi_event_fifo[mrp->mfsz++] = cmd;
420 +
        mrp->in_midi_last_command = cmd;
421 +
    }
422 +
    mrp->midi_event_fifo[mrp->mfsz++] = key;
423 +
    mrp->midi_event_fifo[mrp->mfsz++] = val;
424 +
}
425 +
426 +
void midi_event_enqueue_sysex(struct midi_serial_regs* mrp, uint8_t *bufptr, int buflen)
427 +
{
428 +
    int i;
429 +
430 +
    mrp->in_midi_last_command = 0;
431 +
    if (mrp->mfsz >= 255L - (buflen + 2)) { // too full
432 +
        return;
433 +
    }
434 +
435 +
    mrp->midi_event_fifo[mrp->mfsz++] = 0xf0;
436 +
    for (i = 0; i < buflen; i++) {
437 +
        mrp->midi_event_fifo[mrp->mfsz++] = bufptr[i];
438 +
    }
439 +
    mrp->midi_event_fifo[mrp->mfsz++] = 0xf7;
440 +
}
441 +
442 +
int handle_midi_event(void* data, fluid_midi_event_t* event)
443 +
{
444 +
    struct midi_serial_regs* mrp = (struct midi_serial_regs*)data;
445 +
    pthread_mutex_lock(&mrp->fifo_mutex);
446 +
447 +
    uint8_t type = dl_fluid_midi_event_get_type(event);
448 +
    uint8_t chan = dl_fluid_midi_event_get_channel(event);
449 +
    uint8_t cmd = (type < 0x80 || type >= 0xf0) ? type : (type | (chan & 0xf));
450 +
    uint8_t key, val;
451 +
    uint8_t *bufptr;
452 +
    int buflen;
453 +
454 +
    switch (type) {
455 +
        case FS_NOTE_OFF:
456 +
        case FS_NOTE_ON:
457 +
            key = dl_fluid_midi_event_get_key(event);
458 +
            val = dl_fluid_midi_event_get_velocity(event);
459 +
            midi_event_enqueue_normal(mrp, cmd, key, val);
460 +
            break;
461 +
        case FS_KEY_PRESSURE:
462 +
            key = dl_fluid_midi_event_get_key(event);
463 +
            val = dl_fluid_midi_event_get_value(event);
464 +
            midi_event_enqueue_normal(mrp, cmd, key, val);
465 +
            break;
466 +
        case FS_CONTROL_CHANGE:
467 +
            key = dl_fluid_midi_event_get_control(event);
468 +
            val = dl_fluid_midi_event_get_value(event);
469 +
            midi_event_enqueue_normal(mrp, cmd, key, val);
470 +
            break;
471 +
        case FS_PITCH_BEND:
472 +
            key = dl_fluid_midi_event_get_pitch(event) & 0x7f;
473 +
            val = (dl_fluid_midi_event_get_pitch(event) >> 7) & 0x7f;
474 +
            midi_event_enqueue_normal(mrp, cmd, key, val);
475 +
            break;
476 +
        case FS_PROGRAM_CHANGE:
477 +
        case FS_CHANNEL_PRESSURE:
478 +
            val = dl_fluid_midi_event_get_program(event);
479 +
            midi_event_enqueue_short(mrp, cmd, val);
480 +
            break;
481 +
        case FS_MIDI_TIME_CODE:
482 +
            val = dl_fluid_midi_event_get_value(event);
483 +
            midi_event_enqueue_short(mrp, type, val);
484 +
            mrp->in_midi_last_command = 0;
485 +
            break;
486 +
        case FS_MIDI_TUNE_REQUEST:
487 +
        case 0xF4:
488 +
        case 0xF5:
489 +
            midi_event_enqueue_byte(mrp, type);
490 +
            mrp->in_midi_last_command = 0;
491 +
            break;
492 +
        case FS_MIDI_SYNC:
493 +
        case FS_MIDI_TICK:
494 +
        case FS_MIDI_START:
495 +
        case FS_MIDI_CONTINUE:
496 +
        case FS_MIDI_STOP:
497 +
        case FS_MIDI_ACTIVE_SENSING:
498 +
        case FS_MIDI_SYSTEM_RESET:
499 +
            midi_event_enqueue_byte(mrp, type);
500 +
            break;
501 +
        case FS_MIDI_SYSEX:
502 +
            // FluidSynth doesn't offer a get_sysex function, but internally
503 +
            // a text event is equivalent to a sysex event, in terms of what
504 +
            // parts of the event structure are populated
505 +
            //
506 +
            // Unfortunately that means we have to fool it in order to
507 +
            // access the data.
508 +
            dl_fluid_midi_event_set_type(event, FS_MIDI_TEXT);
509 +
            if (dl_fluid_midi_event_get_text(event, (void **)&bufptr, &buflen) == FLUID_OK && bufptr != NULL) {
510 +
                midi_event_enqueue_sysex(mrp, bufptr, buflen);
511 +
            }
512 +
            break;
513 +
    }
514 +
515 +
    //fprintf(stderr, "Debug: MIDI IN: Type: %02X Chan: %02X\n", type, chan);
516 +
517 +
    pthread_mutex_unlock(&mrp->fifo_mutex);
518 +
    return FLUID_OK;
519 +
}
520 +
521 +
522 +
#else
523 +
void midi_load_sf2(uint8_t* filename)
524 +
{
525 +
    // no-op
526 +
}
527 +
528 +
void midi_init()
529 +
{
530 +
    fprintf(stderr, "No FluidSynth support.\n");
531 +
}
532 +
533 +
void midi_byte_out(uint8_t sel, uint8_t b)
534 +
{
535 +
    // no-op
536 +
}
537 +
538 +
void midi_synth_render(int16_t* buf, int len)
539 +
{
540 +
    // no synth, return zeroed buffer
541 +
    memset(buf, 0, len * 2 * sizeof(int16_t));
542 +
}
543 +
544 +
#endif
545 +
546 +
void midi_serial_init()
547 +
{
548 +
    uint8_t sel;
549 +
550 +
    for (sel=0; sel<2; sel++) {
551 +
        mregs[sel].ier_erbi = false;
552 +
        mregs[sel].ier_etbei = false;
553 +
        mregs[sel].ier_elsi = false;
554 +
        mregs[sel].ier_edssi = false;
555 +
556 +
        mregs[sel].iir = 0x01;
557 +
558 +
        mregs[sel].fcr_fifo_enable = false;
559 +
        mregs[sel].fcr_ififo_trigger_level_bytes = 1;
560 +
561 +
        mregs[sel].lcr_word_length_bits = 5;
562 +
        mregs[sel].lcr_stb = false;
563 +
        mregs[sel].lcr_pen = false;
564 +
        mregs[sel].lcr_eps = false;
565 +
        mregs[sel].lcr_stick = false;
566 +
        mregs[sel].lcr_break = false;
567 +
        mregs[sel].lcr_dlab = false;
568 +
569 +
        mregs[sel].mcr_dtr = false;
570 +
        mregs[sel].mcr_rts = false;
571 +
        mregs[sel].mcr_out1 = false;
572 +
        mregs[sel].mcr_out2 = false;
573 +
        mregs[sel].mcr_loop = false;
574 +
        mregs[sel].mcr_afe = false;
575 +
576 +
        mregs[sel].lsr_oe = false;
577 +
        mregs[sel].lsr_pe = false;
578 +
        mregs[sel].lsr_fe = false;
579 +
        mregs[sel].lsr_bi = false;
580 +
        mregs[sel].lsr_eif = false;
581 +
582 +
        mregs[sel].msr_dcts = false;
583 +
        mregs[sel].msr_ddsr = false;
584 +
        mregs[sel].msr_teri = false;
585 +
        mregs[sel].msr_ddcd = false;
586 +
587 +
        mregs[sel].msr_cts = false;
588 +
        mregs[sel].msr_dsr = false;
589 +
        mregs[sel].msr_ri = false;
590 +
        mregs[sel].msr_dcd = false;
591 +
592 +
        mregs[sel].obyte_bits_remain = 0;
593 +
        mregs[sel].rx_timeout = 0;
594 +
        mregs[sel].rx_timeout_enabled = false;
595 +
        mregs[sel].thre_intr = false;
596 +
        mregs[sel].thre_bits_remain = 0;
597 +
598 +
        mregs[sel].ibyte_bits_remain = 7;
599 +
600 +
        mregs[sel].ifsz = 0;
601 +
        mregs[sel].ififo[0] = 0;
602 +
        mregs[sel].ofsz = 0;
603 +
        mregs[sel].clock = 0;
604 +
        mregs[sel].clockdec = 0;
605 +
        mregs[sel].last_warning = 0;
606 +
607 +
        mregs[sel].mfsz = 0;
608 +
        mregs[sel].in_midi_last_command = 0;
609 +
    }
610 +
611 +
    if (!serial_midi_mutexes_initialized) {
612 +
        for (sel=0; sel<2; sel++) {
613 +
            pthread_mutexattr_init(&mregs[sel].fifo_mutex_attr);
614 +
            pthread_mutex_init(&mregs[sel].fifo_mutex, &mregs[sel].fifo_mutex_attr);
615 +
        }
616 +
        serial_midi_mutexes_initialized = true;
617 +
    }
618 +
}
619 +
620 +
void midi_serial_iir_check(uint8_t sel)
621 +
{
622 +
    uint8_t fifoen = (uint8_t)mregs[sel].fcr_fifo_enable << 6 | (uint8_t)mregs[sel].fcr_fifo_enable << 7;
623 +
    if (mregs[sel].ier_elsi && (mregs[sel].lsr_oe || mregs[sel].lsr_pe || mregs[sel].lsr_fe || mregs[sel].lsr_bi)) {
624 +
        mregs[sel].iir = (0x06 | fifoen); // Receiver line status interrupt
625 +
    } else if (mregs[sel].ier_erbi && !mregs[sel].fcr_fifo_enable && mregs[sel].ifsz > 0) {
626 +
        mregs[sel].iir = (0x04 | fifoen); // Received data available interrupt (16450 mode)
627 +
    } else if (mregs[sel].ier_erbi && mregs[sel].fcr_fifo_enable && mregs[sel].ifsz >= mregs[sel].fcr_ififo_trigger_level_bytes) {
628 +
        mregs[sel].iir = (0x04 | fifoen); // Received data available interrupt (16550 mode)
629 +
    } else if (mregs[sel].ier_erbi && mregs[sel].fcr_fifo_enable && mregs[sel].ifsz > 0 && mregs[sel].rx_timeout_enabled && mregs[sel].rx_timeout == 0) {
630 +
        mregs[sel].iir = (0x0c | fifoen); // Received data available interrupt (timeout, 16550 mode)
631 +
    } else if (mregs[sel].ier_etbei && mregs[sel].thre_intr) {
632 +
        mregs[sel].iir = (0x02 | fifoen); // Transmitter holding register empty interrupt
633 +
    } else if (mregs[sel].ier_edssi && ((!mregs[sel].mcr_afe && mregs[sel].msr_dcts) || mregs[sel].msr_ddcd || mregs[sel].msr_ddsr || mregs[sel].msr_teri)) {
634 +
        mregs[sel].iir = (0x00 | fifoen); // Modem status register interrupt
635 +
    } else {
636 +
        mregs[sel].iir = (0x01 | fifoen); // no interrupt waiting
637 +
    }
638 +
}
639 +
640 +
void midi_serial_step(int clocks)
641 +
{
642 +
    uint8_t sel, i;
643 +
    for (sel=0; sel<2; sel++) {
644 +
        mregs[sel].clock -= (int64_t)mregs[sel].clockdec * clocks;
645 +
        while (mregs[sel].clock < 0) {
646 +
            // process uart
647 +
            pthread_mutex_lock(&mregs[sel].fifo_mutex);
648 +
649 +
            if (mregs[sel].obyte_bits_remain > 0) {
650 +
                mregs[sel].obyte_bits_remain--;
651 +
            }
652 +
            if (mregs[sel].ofsz > 0) {
653 +
                if (mregs[sel].obyte_bits_remain == 0) {
654 +
                    midi_byte_out(sel, mregs[sel].ofifo[0]);
655 +
                    mregs[sel].ofsz--;
656 +
                    if (mregs[sel].ofsz > 0) {
657 +
                        for (i=0; i<mregs[sel].ofsz; i++) {
658 +
                            mregs[sel].ofifo[i] = mregs[sel].ofifo[i+1];
659 +
                        }
660 +
                    } else if (mregs[sel].thre_bits_remain == 0) {
661 +
                        mregs[sel].thre_intr = true;
662 +
                    }
663 +
                    mregs[sel].obyte_bits_remain = 2 + mregs[sel].lcr_word_length_bits + mregs[sel].lcr_stb + mregs[sel].lcr_pen;
664 +
                }
665 +
            } else {
666 +
                if (mregs[sel].thre_bits_remain > 0) {
667 +
                    mregs[sel].thre_bits_remain--;
668 +
                    if (mregs[sel].thre_bits_remain == 0) {
669 +
                        mregs[sel].thre_intr = true;
670 +
                    }
671 +
                }
672 +
            }
673 +
674 +
            if (mregs[sel].rx_timeout > 0) {
675 +
                mregs[sel].rx_timeout--;
676 +
            }
677 +
678 +
            if (mregs[sel].ibyte_bits_remain > 0 && mregs[sel].mfsz > 0) {
679 +
                mregs[sel].ibyte_bits_remain--;
680 +
            }
681 +
            if (mregs[sel].ibyte_bits_remain == 0 && mregs[sel].mfsz > 0) {
682 +
                if (mregs[sel].ifsz < (mregs[sel].fcr_fifo_enable ? 16 : 1)) {
683 +
                    mregs[sel].ififo[mregs[sel].ifsz++] = mregs[sel].midi_event_fifo[0];
684 +
                    mregs[sel].rx_timeout_enabled = true;
685 +
                    mregs[sel].rx_timeout = 4 * (2 + mregs[sel].lcr_word_length_bits + mregs[sel].lcr_stb + mregs[sel].lcr_pen);
686 +
                } else {
687 +
                    mregs[sel].lsr_oe = true; // inbound FIFO overflow
688 +
                    if (!mregs[sel].fcr_fifo_enable) {
689 +
                        // RBR is overwritten by RSR in this mode
690 +
                        // whenever overflow happens
691 +
                        mregs[sel].ififo[mregs[sel].ifsz-1] = mregs[sel].midi_event_fifo[0];
692 +
                    }
693 +
                }
694 +
                mregs[sel].mfsz--;
695 +
                if (mregs[sel].mfsz > 0) {
696 +
                    for (i=0; i<mregs[sel].mfsz; i++) {
697 +
                        mregs[sel].midi_event_fifo[i] = mregs[sel].midi_event_fifo[i+1];
698 +
                    }
699 +
                }
700 +
                mregs[sel].ibyte_bits_remain = 2 + mregs[sel].lcr_word_length_bits + mregs[sel].lcr_stb + mregs[sel].lcr_pen;
701 +
            }
702 +
703 +
            pthread_mutex_unlock(&mregs[sel].fifo_mutex);
704 +
            mregs[sel].clock += 0x1000000LL;
705 +
            midi_serial_iir_check(sel);
706 +
        }
707 +
    }
708 +
}
709 +
710 +
uint8_t midi_serial_dequeue_ibyte(uint8_t sel)
711 +
{
712 +
    pthread_mutex_lock(&mregs[sel].fifo_mutex);
713 +
    uint8_t ret = mregs[sel].ififo[0];
714 +
    uint8_t i;
715 +
716 +
    if (mregs[sel].ifsz > 0) {
717 +
        mregs[sel].ifsz--;
718 +
        if (mregs[sel].ifsz == 0) {
719 +
            mregs[sel].rx_timeout_enabled = false;
720 +
        } else {
721 +
            for (i=0; i<mregs[sel].ifsz; i++) {
722 +
                mregs[sel].ififo[i] = mregs[sel].ififo[i+1];
723 +
            }
724 +
            mregs[sel].rx_timeout_enabled = true;
725 +
            mregs[sel].rx_timeout = 4 * (2 + mregs[sel].lcr_word_length_bits + mregs[sel].lcr_stb + mregs[sel].lcr_pen);
726 +
        }
727 +
        midi_serial_iir_check(sel);
728 +
    }
729 +
730 +
    pthread_mutex_unlock(&mregs[sel].fifo_mutex);
731 +
    return ret;
732 +
}
733 +
734 +
void midi_serial_enqueue_obyte(uint8_t sel, uint8_t val)
735 +
{
736 +
    pthread_mutex_lock(&mregs[sel].fifo_mutex);
737 +
    if (mregs[sel].ofsz < (mregs[sel].fcr_fifo_enable ? 16 : 1)) {
738 +
        mregs[sel].ofifo[mregs[sel].ofsz] = val;
739 +
        mregs[sel].thre_intr = false;
740 +
        mregs[sel].ofsz++;
741 +
        if (mregs[sel].ofsz == 1) { // We were empty
742 +
            if (mregs[sel].fcr_fifo_enable) {
743 +
                // We delay a bit longer to reraise the THRE interrupt if we're coming from 0
744 +
                mregs[sel].thre_bits_remain = 1 + mregs[sel].lcr_word_length_bits + mregs[sel].lcr_stb + mregs[sel].lcr_pen;
745 +
            }
746 +
        } else {
747 +
            mregs[sel].thre_bits_remain = 0;
748 +
        }
749 +
    } else {
750 +
        fprintf(stderr, "Serial MIDI: Warning: UART %d TX Overflow\n", sel);
751 +
    }
752 +
    midi_serial_iir_check(sel);
753 +
    pthread_mutex_unlock(&mregs[sel].fifo_mutex);
754 +
}
755 +
756 +
uint8_t midi_serial_read(uint8_t reg, bool debugOn)
757 +
{
758 +
    //printf("midi_serial_read %d\n", reg);
759 +
    uint8_t sel = (reg & 8) >> 3;
760 +
    switch (reg & 7) {
761 +
        case 0x0:
762 +
            if (mregs[sel].lcr_dlab) {
763 +
                return mregs[sel].dll;
764 +
            } else {
765 +
                if (debugOn) {
766 +
                    return mregs[sel].ififo[0];
767 +
                } else {
768 +
                    return midi_serial_dequeue_ibyte(sel);
769 +
                }
770 +
            }
771 +
            break;
772 +
        case 0x1:
773 +
            if (mregs[sel].lcr_dlab) {
774 +
                return mregs[sel].dlm;
775 +
            } else {
776 +
                return (((uint8_t)mregs[sel].ier_edssi << 3) |
777 +
                        ((uint8_t)mregs[sel].ier_elsi << 2) |
778 +
                        ((uint8_t)mregs[sel].ier_etbei << 1) |
779 +
                        ((uint8_t)mregs[sel].ier_erbi));
780 +
            }
781 +
            break;
782 +
        case 0x2: {
783 +
            uint8_t ret = mregs[sel].iir;
784 +
            if (!debugOn) {
785 +
                mregs[sel].thre_intr = false;
786 +
                midi_serial_iir_check(sel);
787 +
            }
788 +
            return ret;
789 +
            break;
790 +
        }
791 +
        case 0x3:
792 +
            return (((uint8_t)mregs[sel].lcr_dlab << 7) |
793 +
                    ((uint8_t)mregs[sel].lcr_break << 6) |
794 +
                    ((uint8_t)mregs[sel].lcr_stick << 5) |
795 +
                    ((uint8_t)mregs[sel].lcr_eps << 4) |
796 +
                    ((uint8_t)mregs[sel].lcr_pen << 3) |
797 +
                    ((uint8_t)mregs[sel].lcr_stb << 2) |
798 +
                    ((mregs[sel].lcr_word_length_bits - 5) & 0x3));
799 +
            break;
800 +
        case 0x4:
801 +
            return (((uint8_t)mregs[sel].mcr_afe << 5) |
802 +
                    ((uint8_t)mregs[sel].mcr_loop << 4) |
803 +
                    ((uint8_t)mregs[sel].mcr_out2 << 3) |
804 +
                    ((uint8_t)mregs[sel].mcr_out1 << 2) |
805 +
                    ((uint8_t)mregs[sel].mcr_rts << 1) |
806 +
                    ((uint8_t)mregs[sel].mcr_dtr));
807 +
            break;
808 +
        case 0x5: {
809 +
            uint8_t ret = (((uint8_t)mregs[sel].lsr_eif << 7) |
810 +
                    ((uint8_t)(mregs[sel].obyte_bits_remain == 0 && mregs[sel].ofsz == 0) << 6) |
811 +
                    ((uint8_t)(mregs[sel].ofsz == 0) << 5) |
812 +
                    ((uint8_t)mregs[sel].lsr_bi << 4) |
813 +
                    ((uint8_t)mregs[sel].lsr_fe << 3) |
814 +
                    ((uint8_t)mregs[sel].lsr_pe << 2) |
815 +
                    ((uint8_t)mregs[sel].lsr_oe << 1) |
816 +
                    ((uint8_t)(mregs[sel].ifsz > 0)));
817 +
            if (!debugOn) {
818 +
                mregs[sel].lsr_oe = false;
819 +
                mregs[sel].lsr_pe = false;
820 +
                mregs[sel].lsr_fe = false;
821 +
                mregs[sel].lsr_bi = false;
822 +
            }
823 +
            return ret;
824 +
            break;
825 +
        }
826 +
        case 0x6:
827 +
            return (((uint8_t)mregs[sel].msr_dcd << 7) |
828 +
                    ((uint8_t)mregs[sel].msr_ri << 6) |
829 +
                    ((uint8_t)mregs[sel].msr_dsr << 5) |
830 +
                    ((uint8_t)mregs[sel].msr_cts << 4) |
831 +
                    ((uint8_t)mregs[sel].msr_ddcd << 3) |
832 +
                    ((uint8_t)mregs[sel].msr_teri << 2) |
833 +
                    ((uint8_t)mregs[sel].msr_ddsr << 1) |
834 +
                    ((uint8_t)mregs[sel].msr_dcts));
835 +
            break;
836 +
        case 0x7:
837 +
            return mregs[sel].scratch;
838 +
            break;
839 +
    }
840 +
    return 0x00;
841 +
}
842 +
843 +
void midi_serial_calculate_clk(uint8_t sel)
844 +
{
845 +
    double uart_clks_per_cpu = (MIDI_UART_OSC_RATE_MHZ / MIDI_UART_PRIMARY_DIVIDER) / MHZ;
846 +
    if (mregs[sel].dl > 0) {
847 +
        uart_clks_per_cpu /= mregs[sel].dl;
848 +
        mregs[sel].clockdec = uart_clks_per_cpu * 0x1000000L; // convert to 8.24 fixed point
849 +
    } else {
850 +
        mregs[sel].clockdec = 0;
851 +
    }
852 +
}
853 +
854 +
void midi_serial_write(uint8_t reg, uint8_t val)
855 +
{
856 +
    time_t now = time(NULL);
857 +
858 +
    //printf("midi_serial_write %d %d\n", reg, val);
859 +
    uint8_t sel = (reg & 8) >> 3;
860 +
    switch (reg & 7) {
861 +
        case 0x0:
862 +
            if (mregs[sel].lcr_dlab) {
863 +
                mregs[sel].dll = val;
864 +
                midi_serial_calculate_clk(sel);
865 +
            } else {
866 +
                if (mregs[sel].lcr_word_length_bits != 8 || mregs[sel].lcr_stb || mregs[sel].lcr_pen) {
867 +
                    if (mregs[sel].last_warning + 60 < now) {
868 +
                        unsigned char par = 'N';
869 +
                        if (mregs[sel].lcr_pen) {
870 +
                            switch ((uint8_t)(mregs[sel].lcr_eps << 1) | (uint8_t)mregs[sel].lcr_stick) {
871 +
                                case 0:
872 +
                                    par = 'O';
873 +
                                    break;
874 +
                                case 1:
875 +
                                    par = 'M';
876 +
                                    break;
877 +
                                case 2:
878 +
                                    par = 'E';
879 +
                                    break;
880 +
                                case 3:
881 +
                                    par = 'S';
882 +
                                    break;
883 +
                            }
884 +
                        }
885 +
                        fprintf(stderr, "Serial MIDI: Warning: improper LCR %d%c%d for UART %d, must be set to 8N1.\n", mregs[sel].lcr_word_length_bits, par, 1+(uint8_t)mregs[sel].lcr_stb, sel);
886 +
                        mregs[sel].last_warning = now;
887 +
                    }
888 +
                } else if (mregs[sel].lcr_break && mregs[sel].last_warning + 60 < now) {
889 +
                    fprintf(stderr, "Serial MIDI: Warning: break improperly set for UART %d.\n", sel);
890 +
                    mregs[sel].last_warning = now;
891 +
                } else if (mregs[sel].dl != 32 && mregs[sel].last_warning + 60 < now) {
892 +
                    fprintf(stderr, "Serial MIDI: Warning: improper divisor %d for UART %d, must be set to 32 for standard MIDI bitrate.\n", mregs[sel].dl, sel);
893 +
                    mregs[sel].last_warning = now;
894 +
                } else {
895 +
                    midi_serial_enqueue_obyte(sel, val);
896 +
                }
897 +
            }
898 +
            break;
899 +
        case 0x1:
900 +
            if (mregs[sel].lcr_dlab) {
901 +
                mregs[sel].dlm = val;
902 +
                midi_serial_calculate_clk(sel);
903 +
            } else {
904 +
                mregs[sel].ier_erbi = !!(val & 1);
905 +
                mregs[sel].ier_etbei = !!(val & 2);
906 +
                mregs[sel].ier_elsi = !!(val & 4);
907 +
                mregs[sel].ier_edssi = !!(val & 8);
908 +
            }
909 +
            break;
910 +
        case 0x2:
911 +
            if (val & 1) {
912 +
                mregs[sel].fcr_fifo_enable = true;
913 +
                switch ((val & 0xc0) >> 6) {
914 +
                    case 0:
915 +
                        mregs[sel].fcr_ififo_trigger_level_bytes = 1;
916 +
                        break;
917 +
                    case 1:
918 +
                        mregs[sel].fcr_ififo_trigger_level_bytes = 4;
919 +
                        break;
920 +
                    case 2:
921 +
                        mregs[sel].fcr_ififo_trigger_level_bytes = 8;
922 +
                        break;
923 +
                    case 3:
924 +
                        mregs[sel].fcr_ififo_trigger_level_bytes = 14;
925 +
                        break;
926 +
                }
927 +
            } else {
928 +
                mregs[sel].fcr_fifo_enable = false;
929 +
                mregs[sel].fcr_ififo_trigger_level_bytes = 1;
930 +
            }
931 +
            if (val & 2) {
932 +
                mregs[sel].ifsz = 0;
933 +
            }
934 +
            if (val & 4) {
935 +
                mregs[sel].ofsz = 0;
936 +
            }
937 +
            if (mregs[sel].thre_bits_remain == 0) { 
938 +
                mregs[sel].thre_intr = true;
939 +
            }
940 +
            midi_serial_iir_check(sel);
941 +
            break;
942 +
        case 0x3:
943 +
            mregs[sel].lcr_word_length_bits = (val & 0x03)+5;
944 +
            mregs[sel].lcr_stb = !!(val & 0x04);
945 +
            mregs[sel].lcr_pen = !!(val & 0x08);
946 +
            mregs[sel].lcr_eps = !!(val & 0x10);
947 +
            mregs[sel].lcr_stick = !!(val & 0x20);
948 +
            mregs[sel].lcr_break = !!(val & 0x40);
949 +
            mregs[sel].lcr_dlab = !!(val & 0x80);
950 +
            if (mregs[sel].mfsz == 0) {
951 +
                mregs[sel].ibyte_bits_remain = 2 + mregs[sel].lcr_word_length_bits + mregs[sel].lcr_stb + mregs[sel].lcr_pen;
952 +
            }
953 +
            break;
954 +
        case 0x4:
955 +
            mregs[sel].mcr_dtr = !!(val & 0x01);
956 +
            mregs[sel].mcr_rts = !!(val & 0x02);
957 +
            mregs[sel].mcr_out1 = !!(val & 0x04);
958 +
            mregs[sel].mcr_out2 = !!(val & 0x08);
959 +
            mregs[sel].mcr_loop = !!(val & 0x10);
960 +
            mregs[sel].mcr_afe = !!(val & 0x20);
961 +
962 +
            if (mregs[sel].mcr_loop) {
963 +
                mregs[sel].msr_dcts |= mregs[sel].msr_cts ^ mregs[sel].mcr_dtr;
964 +
                mregs[sel].msr_cts = mregs[sel].mcr_dtr;
965 +
966 +
                mregs[sel].msr_ddsr |= mregs[sel].msr_dsr ^ mregs[sel].mcr_rts;
967 +
                mregs[sel].msr_dsr = mregs[sel].mcr_rts;
968 +
969 +
                if (mregs[sel].msr_ri) {
970 +
                    mregs[sel].msr_teri |= mregs[sel].msr_ri ^ mregs[sel].mcr_out1;
971 +
                }
972 +
                mregs[sel].msr_ri = mregs[sel].mcr_out1;
973 +
974 +
                mregs[sel].msr_ddcd |= mregs[sel].msr_dcd ^ mregs[sel].mcr_out2;
975 +
                mregs[sel].msr_dcd = mregs[sel].mcr_out2;
976 +
977 +
            } else {
978 +
                // DTR tied to RI on opposite UART, wired this way on the official X16 MIDI card
979 +
                if (mregs[sel ^ 1].msr_ri && !mregs[sel].mcr_dtr) {
980 +
                    mregs[sel ^ 1].msr_teri = true;
981 +
                }
982 +
                mregs[sel ^ 1].msr_ri = mregs[sel].mcr_dtr;
983 +
            }
984 +
985 +
            break;
986 +
        case 0x7:
987 +
            mregs[sel].scratch = val;
988 +
            break;
989 +
    }
990 +
}
991 +
992 +
bool midi_serial_irq(void)
993 +
{
994 +
    bool uart0int = (mregs[0].iir & 1) == 0 && mregs[0].mcr_out2;
995 +
    bool uart1int = (mregs[1].iir & 1) == 0 && mregs[1].mcr_out2;
996 +
    return (uart0int | uart1int);
997 +
}
added src/midi.h +47 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2024 MooingLemur
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#pragma once
6 +
7 +
#ifdef HAS_FLUIDSYNTH
8 +
#include <fluidsynth.h>
9 +
#endif
10 +
11 +
#define MIDI_UART_OSC_RATE_MHZ 16.0f
12 +
#define MIDI_UART_PRIMARY_DIVIDER 16
13 +
14 +
#define FS_MIDI_TEXT 0x01
15 +
#define FS_NOTE_OFF 0x80
16 +
#define FS_NOTE_ON 0x90
17 +
#define FS_KEY_PRESSURE 0xa0
18 +
#define FS_CONTROL_CHANGE 0xb0
19 +
#define FS_PROGRAM_CHANGE 0xc0
20 +
#define FS_CHANNEL_PRESSURE 0xd0
21 +
#define FS_PITCH_BEND 0xe0
22 +
#define FS_MIDI_SYSEX 0xf0
23 +
#define FS_MIDI_TIME_CODE 0xf1
24 +
#define FS_MIDI_SONG_POSITION 0xf2
25 +
#define FS_MIDI_SONG_SELECT 0xf3
26 +
#define FS_MIDI_TUNE_REQUEST 0xf6
27 +
#define FS_MIDI_EOX 0xf7
28 +
#define FS_MIDI_SYNC 0xf8
29 +
#define FS_MIDI_TICK 0xf9
30 +
#define FS_MIDI_START 0xfa
31 +
#define FS_MIDI_CONTINUE 0xfb
32 +
#define FS_MIDI_STOP 0xfc
33 +
#define FS_MIDI_ACTIVE_SENSING 0xfe
34 +
#define FS_MIDI_SYSTEM_RESET 0xff
35 +
36 +
#define FL_DEFAULT_GAIN 0.2f
37 +
38 +
void midi_init(void);
39 +
void midi_serial_init(void);
40 +
void midi_serial_step(int clocks);
41 +
uint8_t midi_serial_read(uint8_t reg, bool debugOn);
42 +
void midi_serial_write(uint8_t reg, uint8_t val);
43 +
void midi_load_sf2(uint8_t* filename);
44 +
void midi_synth_render(int16_t* buf, int len);
45 +
bool midi_serial_irq(void);
46 +
47 +
extern bool fs_midi_in_connect;
added src/netcard.c +452 −0
1 +
// Commander X16 Emulator — a virtual serial/network card. See netcard.h.
2 +
//
3 +
// THE MODEM'S SIDE OF THE CONVERSATION, as the X16 floor of C*'s CLM expects
4 +
// it, because that floor is what this exists to test:
5 +
//
6 +
//   +++            in data mode, back to command mode with the call kept
7 +
//   ATH0<CR>       hang up; OK if there was a call, ERROR if not
8 +
//   ATD"host:port"<CR>   dial; CONNECT, or NO CARRIER when nothing answers
9 +
//   ATI<CR>        a Zimodem-shaped report: CONNECTED TO <ssid> (<address>)
10 +
//   ATE0/ATE1      echo off/on (on, as Zimodem ships)
11 +
//   AT<CR>         OK
12 +
//
13 +
// Everything else is answered ERROR, which is also what Zimodem does.
14 +
// Commands are echoed while echo is on, as the card's firmware does, so a
15 +
// program that has to skip its own echo gets to practise here.
16 +
17 +
#ifndef _WIN32
18 +
#define _POSIX_C_SOURCE 200809L   // getaddrinfo under -std=c11
19 +
#define _DEFAULT_SOURCE
20 +
#endif
21 +
22 +
#include "netcard.h"
23 +
24 +
#include <ctype.h>
25 +
#include <stdio.h>
26 +
#include <stdlib.h>
27 +
#include <string.h>
28 +
29 +
#ifndef _WIN32
30 +
#include <errno.h>
31 +
#include <fcntl.h>
32 +
#include <netdb.h>
33 +
#include <netinet/in.h>
34 +
#include <netinet/tcp.h>
35 +
#include <sys/select.h>
36 +
#include <sys/socket.h>
37 +
#include <unistd.h>
38 +
#define NETCARD_SOCKETS 1
39 +
#else
40 +
#define NETCARD_SOCKETS 0
41 +
#endif
42 +
43 +
bool has_netcard = false;
44 +
uint16_t netcard_addr = 0x9f60;
45 +
46 +
// 16450 registers
47 +
#define LCR_DLAB   0x80
48 +
#define LSR_DR     0x01
49 +
#define LSR_OE     0x02
50 +
#define LSR_THRE   0x20
51 +
#define LSR_TEMT   0x40
52 +
#define MSR_CTS    0x10
53 +
#define MSR_DSR    0x20
54 +
#define MSR_DCD    0x80
55 +
56 +
#define RX_CAP 4096
57 +
#define LINE_CAP 256
58 +
59 +
static struct {
60 +
	uint8_t dll, dlm, ier, lcr, mcr, scr, fcr;
61 +
	uint8_t rx[RX_CAP];
62 +
	int rx_head, rx_tail;
63 +
	bool overrun;
64 +
	// the modem
65 +
	bool echo;
66 +
	bool online;            // a call is up (the socket is open)
67 +
	bool data_mode;         // bytes go to the wire rather than the parser
68 +
	char line[LINE_CAP];
69 +
	int line_len;
70 +
	int plus_count;         // +++ seen in data mode
71 +
	int fd;
72 +
	bool connecting;
73 +
	char host[128];
74 +
	int port;
75 +
} card;
76 +
77 +
static void rx_put(uint8_t b)
78 +
{
79 +
	int next = (card.rx_tail + 1) % RX_CAP;
80 +
	if (next == card.rx_head) {
81 +
		card.overrun = true;
82 +
		return;
83 +
	}
84 +
	card.rx[card.rx_tail] = b;
85 +
	card.rx_tail = next;
86 +
}
87 +
88 +
static void rx_str(const char *s)
89 +
{
90 +
	while (*s) {
91 +
		rx_put((uint8_t)*s++);
92 +
	}
93 +
}
94 +
95 +
static void reply(const char *s)
96 +
{
97 +
	rx_str("\r\n");
98 +
	rx_str(s);
99 +
	rx_str("\r\n");
100 +
}
101 +
102 +
static void hangup(void)
103 +
{
104 +
#if NETCARD_SOCKETS
105 +
	if (card.fd >= 0) {
106 +
		close(card.fd);
107 +
	}
108 +
#endif
109 +
	card.fd = -1;
110 +
	card.online = false;
111 +
	card.connecting = false;
112 +
	card.data_mode = false;
113 +
}
114 +
115 +
void netcard_init(void)
116 +
{
117 +
	memset(&card, 0, sizeof card);
118 +
	card.fd = -1;
119 +
	card.echo = true;
120 +
	card.lcr = 0x03;
121 +
}
122 +
123 +
// ATD"host:port" — begin a connection; the answer comes from netcard_step.
124 +
static void dial(const char *arg)
125 +
{
126 +
	char buf[160];
127 +
	char *colon;
128 +
	size_t n;
129 +
130 +
	while (*arg == '"' || *arg == 'T' || *arg == 'P' || *arg == ' ') {
131 +
		arg++;
132 +
	}
133 +
	n = strlen(arg);
134 +
	if (n >= sizeof buf) {
135 +
		reply("ERROR");
136 +
		return;
137 +
	}
138 +
	memcpy(buf, arg, n + 1);
139 +
	while (n > 0 && (buf[n - 1] == '"' || buf[n - 1] == ' ')) {
140 +
		buf[--n] = '\0';
141 +
	}
142 +
	colon = strrchr(buf, ':');
143 +
	if (!colon || colon == buf) {
144 +
		reply("ERROR");
145 +
		return;
146 +
	}
147 +
	*colon = '\0';
148 +
	snprintf(card.host, sizeof card.host, "%s", buf);
149 +
	card.port = atoi(colon + 1);
150 +
	if (card.port <= 0 || card.port > 65535) {
151 +
		reply("ERROR");
152 +
		return;
153 +
	}
154 +
155 +
#if NETCARD_SOCKETS
156 +
	{
157 +
		struct addrinfo hints, *res = NULL;
158 +
		char portstr[8];
159 +
		snprintf(portstr, sizeof portstr, "%d", card.port);
160 +
		memset(&hints, 0, sizeof hints);
161 +
		hints.ai_family = AF_UNSPEC;
162 +
		hints.ai_socktype = SOCK_STREAM;
163 +
		if (getaddrinfo(card.host, portstr, &hints, &res) != 0 || !res) {
164 +
			reply("NO CARRIER");
165 +
			return;
166 +
		}
167 +
		card.fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
168 +
		if (card.fd < 0) {
169 +
			freeaddrinfo(res);
170 +
			reply("NO CARRIER");
171 +
			return;
172 +
		}
173 +
		fcntl(card.fd, F_SETFL, fcntl(card.fd, F_GETFL, 0) | O_NONBLOCK);
174 +
		{
175 +
			int one = 1;
176 +
			(void)setsockopt(card.fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
177 +
		}
178 +
		if (connect(card.fd, res->ai_addr, res->ai_addrlen) < 0 && errno != EINPROGRESS) {
179 +
			freeaddrinfo(res);
180 +
			hangup();
181 +
			reply("NO CARRIER");
182 +
			return;
183 +
		}
184 +
		freeaddrinfo(res);
185 +
		card.connecting = true;
186 +
	}
187 +
#else
188 +
	reply("NO CARRIER");
189 +
#endif
190 +
}
191 +
192 +
// One AT command line, without its CR.
193 +
static void command(char *cmd)
194 +
{
195 +
	char *p = cmd;
196 +
	while (*p == '+') {           // a +++ that arrived in command mode is nothing
197 +
		p++;
198 +
	}
199 +
	while (*p == ' ') {
200 +
		p++;
201 +
	}
202 +
	if (*p == '\0') {
203 +
		return;
204 +
	}
205 +
	if (toupper((unsigned char)p[0]) != 'A' || toupper((unsigned char)p[1]) != 'T') {
206 +
		reply("ERROR");
207 +
		return;
208 +
	}
209 +
	p += 2;
210 +
	if (*p == '\0') {
211 +
		reply("OK");
212 +
		return;
213 +
	}
214 +
	switch (toupper((unsigned char)*p)) {
215 +
		case 'H':
216 +
			if (card.online || card.connecting) {
217 +
				hangup();
218 +
				reply("OK");
219 +
			} else {
220 +
				reply("ERROR");
221 +
			}
222 +
			return;
223 +
		case 'D':
224 +
			if (card.online || card.connecting) {
225 +
				reply("ERROR");
226 +
				return;
227 +
			}
228 +
			dial(p + 1);
229 +
			return;
230 +
		case 'E':
231 +
			card.echo = (p[1] != '0');
232 +
			reply("OK");
233 +
			return;
234 +
		case 'I':
235 +
			rx_str("\r\nZimodem-ish 1.0 (x16emu virtual card)\r\n");
236 +
			rx_str("CONNECTED TO x16emu (127.0.0.1)\r\n");
237 +
			reply("OK");
238 +
			return;
239 +
		case 'O':
240 +
			if (card.online) {
241 +
				card.data_mode = true;
242 +
				reply("CONNECT");
243 +
			} else {
244 +
				reply("ERROR");
245 +
			}
246 +
			return;
247 +
		case 'Z':
248 +
			hangup();
249 +
			card.echo = true;
250 +
			reply("OK");
251 +
			return;
252 +
		default:
253 +
			reply("ERROR");
254 +
			return;
255 +
	}
256 +
}
257 +
258 +
static void tx_byte(uint8_t b)
259 +
{
260 +
	if (card.data_mode) {
261 +
		// +++ with nothing else around it escapes to command mode. The guard
262 +
		// time is not modelled; three plusses in a row are enough here.
263 +
		if (b == '+') {
264 +
			if (++card.plus_count == 3) {
265 +
				card.plus_count = 0;
266 +
				card.data_mode = false;
267 +
				reply("OK");
268 +
			}
269 +
			return;
270 +
		}
271 +
		if (card.plus_count) {
272 +
			// they were data after all
273 +
#if NETCARD_SOCKETS
274 +
			if (card.fd >= 0) {
275 +
				const char plus[3] = {'+', '+', '+'};
276 +
				(void)!write(card.fd, plus, card.plus_count);
277 +
			}
278 +
#endif
279 +
			card.plus_count = 0;
280 +
		}
281 +
#if NETCARD_SOCKETS
282 +
		if (card.fd >= 0) {
283 +
			(void)!write(card.fd, &b, 1);
284 +
		}
285 +
#endif
286 +
		return;
287 +
	}
288 +
	if (card.echo) {
289 +
		rx_put(b);
290 +
	}
291 +
	if (b == '\r' || b == '\n') {
292 +
		if (card.line_len > 0) {
293 +
			card.line[card.line_len] = '\0';
294 +
			card.line_len = 0;
295 +
			command(card.line);
296 +
		}
297 +
		return;
298 +
	}
299 +
	if (b == 8 || b == 127) {
300 +
		if (card.line_len > 0) {
301 +
			card.line_len--;
302 +
		}
303 +
		return;
304 +
	}
305 +
	if (card.line_len < LINE_CAP - 1) {
306 +
		card.line[card.line_len++] = (char)b;
307 +
	}
308 +
}
309 +
310 +
// Called once a frame from the main loop: finish a connection in progress,
311 +
// and move bytes from the wire into the receive FIFO.
312 +
void netcard_step(void)
313 +
{
314 +
#if NETCARD_SOCKETS
315 +
	if (card.connecting) {
316 +
		fd_set w;
317 +
		struct timeval tv = {0, 0};
318 +
		FD_ZERO(&w);
319 +
		FD_SET(card.fd, &w);
320 +
		if (select(card.fd + 1, NULL, &w, NULL, &tv) > 0) {
321 +
			int err = 0;
322 +
			socklen_t len = sizeof err;
323 +
			(void)getsockopt(card.fd, SOL_SOCKET, SO_ERROR, &err, &len);
324 +
			card.connecting = false;
325 +
			if (err != 0) {
326 +
				hangup();
327 +
				reply("NO CARRIER");
328 +
			} else {
329 +
				card.online = true;
330 +
				card.data_mode = true;
331 +
				card.plus_count = 0;
332 +
				reply("CONNECT");
333 +
			}
334 +
		}
335 +
		return;
336 +
	}
337 +
	if (card.online && card.fd >= 0) {
338 +
		uint8_t buf[512];
339 +
		for (;;) {
340 +
			ssize_t n = read(card.fd, buf, sizeof buf);
341 +
			if (n > 0) {
342 +
				for (ssize_t i = 0; i < n; i++) {
343 +
					rx_put(buf[i]);
344 +
				}
345 +
				if (n < (ssize_t)sizeof buf) {
346 +
					break;
347 +
				}
348 +
			} else if (n == 0) {
349 +
				hangup();
350 +
				reply("NO CARRIER");
351 +
				break;
352 +
			} else {
353 +
				break;    // EAGAIN: nothing waiting
354 +
			}
355 +
		}
356 +
	}
357 +
#endif
358 +
}
359 +
360 +
uint8_t netcard_read(uint8_t reg, bool debugOn)
361 +
{
362 +
	if (reg >= 8) {
363 +
		return 0x9f;    // the DE-9 half: not here
364 +
	}
365 +
	switch (reg) {
366 +
		case 0:
367 +
			if (card.lcr & LCR_DLAB) {
368 +
				return card.dll;
369 +
			}
370 +
			if (card.rx_head == card.rx_tail) {
371 +
				return 0;
372 +
			}
373 +
			{
374 +
				uint8_t b = card.rx[card.rx_head];
375 +
				if (!debugOn) {
376 +
					card.rx_head = (card.rx_head + 1) % RX_CAP;
377 +
				}
378 +
				return b;
379 +
			}
380 +
		case 1:
381 +
			return (card.lcr & LCR_DLAB) ? card.dlm : card.ier;
382 +
		case 2:
383 +
			return 0x01 | (card.fcr & 0x01 ? 0xc0 : 0);   // no interrupt pending
384 +
		case 3:
385 +
			return card.lcr;
386 +
		case 4:
387 +
			return card.mcr & 0x3f;
388 +
		case 5: {
389 +
			uint8_t lsr = LSR_THRE | LSR_TEMT;
390 +
			if (card.rx_head != card.rx_tail) {
391 +
				lsr |= LSR_DR;
392 +
			}
393 +
			if (card.overrun) {
394 +
				lsr |= LSR_OE;
395 +
				if (!debugOn) {
396 +
					card.overrun = false;
397 +
				}
398 +
			}
399 +
			return lsr;
400 +
		}
401 +
		case 6: {
402 +
			uint8_t msr = MSR_CTS | MSR_DSR;
403 +
			if (card.online) {
404 +
				msr |= MSR_DCD;
405 +
			}
406 +
			return msr;
407 +
		}
408 +
		case 7:
409 +
			return card.scr;
410 +
	}
411 +
	return 0x9f;
412 +
}
413 +
414 +
void netcard_write(uint8_t reg, uint8_t val)
415 +
{
416 +
	if (reg >= 8) {
417 +
		return;
418 +
	}
419 +
	switch (reg) {
420 +
		case 0:
421 +
			if (card.lcr & LCR_DLAB) {
422 +
				card.dll = val;
423 +
			} else {
424 +
				tx_byte(val);
425 +
			}
426 +
			break;
427 +
		case 1:
428 +
			if (card.lcr & LCR_DLAB) {
429 +
				card.dlm = val;
430 +
			} else {
431 +
				card.ier = val & 0x0f;
432 +
			}
433 +
			break;
434 +
		case 2:
435 +
			card.fcr = val;
436 +
			if (val & 0x02) {    // clear the receive FIFO
437 +
				card.rx_head = card.rx_tail = 0;
438 +
			}
439 +
			break;
440 +
		case 3:
441 +
			card.lcr = val;
442 +
			break;
443 +
		case 4:
444 +
			card.mcr = val;
445 +
			break;
446 +
		case 7:
447 +
			card.scr = val;
448 +
			break;
449 +
		default:
450 +
			break;
451 +
	}
452 +
}
added src/netcard.h +25 −0
1 +
// Commander X16 Emulator — a virtual serial/network card.
2 +
//
3 +
// A 16450-compatible UART at an expansion base with a Zimodem-enough modem
4 +
// behind it: Hayes AT commands in command mode, and a dial that opens a real
5 +
// TCP socket from the emulator process, after which bytes flow both ways.
6 +
// Enough for a program written against the TexElec serial/ESP32 card to be
7 +
// tested here, dialling a service on the developer's machine, without the
8 +
// card. It is not a model of the card's timing; a byte written is on the
9 +
// wire at once and a byte from the wire is readable at once.
10 +
//
11 +
// Only the card's network half (the eight registers at the base) exists.
12 +
// The DE-9 serial half, eight bytes above it, reads as open bus.
13 +
14 +
#pragma once
15 +
16 +
#include <stdbool.h>
17 +
#include <stdint.h>
18 +
19 +
extern bool has_netcard;
20 +
extern uint16_t netcard_addr;
21 +
22 +
void netcard_init(void);
23 +
void netcard_step(void);
24 +
uint8_t netcard_read(uint8_t reg, bool debugOn);
25 +
void netcard_write(uint8_t reg, uint8_t val);
added src/rendertext.c +201 −0
1 +
#include <string.h>
2 +
#include <SDL.h>
3 +
#ifdef __MINGW32__
4 +
#include <ctype.h>
5 +
#endif
6 +
#include "rendertext.h"
7 +
8 +
#define CHARACTER_WIDTH 5
9 +
#define CHARACTER_HEIGHT 7
10 +
#define TEXTURE_HEIGHT (CHARACTER_HEIGHT * 1)
11 +
#define TEXTURE_WIDTH (CHARACTER_WIDTH * 0x60)
12 +
13 +
// Text Area origin => debug area
14 +
int xPos = 0;
15 +
int yPos = 0;
16 +
17 +
// font texture
18 +
SDL_Texture *fontTexture;
19 +
int textureInitialized = 0;
20 +
21 +
// *******************************************************************************************
22 +
// left trim string
23 +
//
24 +
char *ltrim(char *s)
25 +
{
26 +
	while(isspace((unsigned char)*s)) s++;
27 +
	return s;
28 +
}
29 +
30 +
// *******************************************************************************************
31 +
//
32 +
//										Simple 5 x 7 Font
33 +
//
34 +
// *******************************************************************************************
35 +
36 +
static unsigned char fontdata[] = {
37 +
	0x00, 0x00, 0x00, 0x00, 0x00,   // 0x20 (space)
38 +
	0x00, 0x00, 0x5F, 0x00, 0x00,   // 0x21 '!'
39 +
	0x00, 0x07, 0x00, 0x07, 0x00,   // 0x22 '"'
40 +
	0x14, 0x7F, 0x14, 0x7F, 0x14,   // 0x23 '#'
41 +
	0x24, 0x2A, 0x7F, 0x2A, 0x12,   // 0x24 '$'
42 +
	0x23, 0x13, 0x08, 0x64, 0x62,   // 0x25 '%'
43 +
	0x36, 0x49, 0x55, 0x22, 0x50,   // 0x26 '&'
44 +
	0x00, 0x05, 0x03, 0x00, 0x00,   // 0x27 '''
45 +
	0x00, 0x1C, 0x22, 0x41, 0x00,   // 0x28 '('
46 +
	0x00, 0x41, 0x22, 0x1C, 0x00,   // 0x29 ')'
47 +
	0x08, 0x2A, 0x1C, 0x2A, 0x08,   // 0x2A '*'
48 +
	0x08, 0x08, 0x3E, 0x08, 0x08,   // 0x2B '+'
49 +
	0x00, 0x50, 0x30, 0x00, 0x00,   // 0x2C ','
50 +
	0x08, 0x08, 0x08, 0x08, 0x08,   // 0x2D '-'
51 +
	0x00, 0x60, 0x60, 0x00, 0x00,   // 0x2E '.'
52 +
	0x20, 0x10, 0x08, 0x04, 0x02,   // 0x2F '/'
53 +
	0x3E, 0x51, 0x49, 0x45, 0x3E,   // 0x30 '0'
54 +
	0x00, 0x42, 0x7F, 0x40, 0x00,   // 0x31 '1'
55 +
	0x42, 0x61, 0x51, 0x49, 0x46,   // 0x32 '2'
56 +
	0x21, 0x41, 0x45, 0x4B, 0x31,   // 0x33 '3'
57 +
	0x18, 0x14, 0x12, 0x7F, 0x10,   // 0x34 '4'
58 +
	0x27, 0x45, 0x45, 0x45, 0x39,   // 0x35 '5'
59 +
	0x3C, 0x4A, 0x49, 0x49, 0x30,   // 0x36 '6'
60 +
	0x01, 0x71, 0x09, 0x05, 0x03,   // 0x37 '7'
61 +
	0x36, 0x49, 0x49, 0x49, 0x36,   // 0x38 '8'
62 +
	0x06, 0x49, 0x49, 0x29, 0x1E,   // 0x39 '9'
63 +
	0x00, 0x36, 0x36, 0x00, 0x00,   // 0x3A ':'
64 +
	0x00, 0x56, 0x36, 0x00, 0x00,   // 0x3B ';'
65 +
	0x00, 0x08, 0x14, 0x22, 0x41,   // 0x3C '<'
66 +
	0x14, 0x14, 0x14, 0x14, 0x14,   // 0x3D '='
67 +
	0x41, 0x22, 0x14, 0x08, 0x00,   // 0x3E '>'
68 +
	0x02, 0x01, 0x51, 0x09, 0x06,   // 0x3F '?'
69 +
	0x32, 0x49, 0x79, 0x41, 0x3E,   // 0x40 '@'
70 +
	0x7E, 0x11, 0x11, 0x11, 0x7E,   // 0x41 'A'
71 +
	0x7F, 0x49, 0x49, 0x49, 0x36,   // 0x42 'B'
72 +
	0x3E, 0x41, 0x41, 0x41, 0x22,   // 0x43 'C'
73 +
	0x7F, 0x41, 0x41, 0x22, 0x1C,   // 0x44 'D'
74 +
	0x7F, 0x49, 0x49, 0x49, 0x41,   // 0x45 'E'
75 +
	0x7F, 0x09, 0x09, 0x01, 0x01,   // 0x46 'F'
76 +
	0x3E, 0x41, 0x41, 0x51, 0x32,   // 0x47 'G'
77 +
	0x7F, 0x08, 0x08, 0x08, 0x7F,   // 0x48 'H'
78 +
	0x00, 0x41, 0x7F, 0x41, 0x00,   // 0x49 'I'
79 +
	0x20, 0x40, 0x41, 0x3F, 0x01,   // 0x4A 'J'
80 +
	0x7F, 0x08, 0x14, 0x22, 0x41,   // 0x4B 'K'
81 +
	0x7F, 0x40, 0x40, 0x40, 0x40,   // 0x4C 'L'
82 +
	0x7F, 0x02, 0x04, 0x02, 0x7F,   // 0x4D 'M'
83 +
	0x7F, 0x04, 0x08, 0x10, 0x7F,   // 0x4E 'N'
84 +
	0x3E, 0x41, 0x41, 0x41, 0x3E,   // 0x4F 'O'
85 +
	0x7F, 0x09, 0x09, 0x09, 0x06,   // 0x50 'P'
86 +
	0x3E, 0x41, 0x51, 0x21, 0x5E,   // 0x51 'Q'
87 +
	0x7F, 0x09, 0x19, 0x29, 0x46,   // 0x52 'R'
88 +
	0x46, 0x49, 0x49, 0x49, 0x31,   // 0x53 'S'
89 +
	0x01, 0x01, 0x7F, 0x01, 0x01,   // 0x54 'T'
90 +
	0x3F, 0x40, 0x40, 0x40, 0x3F,   // 0x55 'U'
91 +
	0x1F, 0x20, 0x40, 0x20, 0x1F,   // 0x56 'V'
92 +
	0x7F, 0x20, 0x18, 0x20, 0x7F,   // 0x57 'W'
93 +
	0x63, 0x14, 0x08, 0x14, 0x63,   // 0x58 'X'
94 +
	0x03, 0x04, 0x78, 0x04, 0x03,   // 0x59 'Y'
95 +
	0x61, 0x51, 0x49, 0x45, 0x43,   // 0x5A 'Z'
96 +
	0x00, 0x00, 0x7F, 0x41, 0x41,   // 0x5B '['
97 +
	0x02, 0x04, 0x08, 0x10, 0x20,   // 0x5C '\'
98 +
	0x41, 0x41, 0x7F, 0x00, 0x00,   // 0x5D ']'
99 +
	0x04, 0x02, 0x01, 0x02, 0x04,   // 0x5E '^'
100 +
	0x40, 0x40, 0x40, 0x40, 0x40,   // 0x5F '_'
101 +
	0x00, 0x01, 0x02, 0x04, 0x00,   // 0x60 '`'
102 +
	0x20, 0x54, 0x54, 0x54, 0x78,   // 0x61 'a'
103 +
	0x7F, 0x48, 0x44, 0x44, 0x38,   // 0x62 'b'
104 +
	0x38, 0x44, 0x44, 0x44, 0x20,   // 0x63 'c'
105 +
	0x38, 0x44, 0x44, 0x48, 0x7F,   // 0x64 'd'
106 +
	0x38, 0x54, 0x54, 0x54, 0x18,   // 0x65 'e'
107 +
	0x08, 0x7E, 0x09, 0x01, 0x02,   // 0x66 'f'
108 +
	0x08, 0x14, 0x54, 0x54, 0x3C,   // 0x67 'g'
109 +
	0x7F, 0x08, 0x04, 0x04, 0x78,   // 0x68 'h'
110 +
	0x00, 0x44, 0x7D, 0x40, 0x00,   // 0x69 'i'
111 +
	0x20, 0x40, 0x44, 0x3D, 0x00,   // 0x6A 'j'
112 +
	0x00, 0x7F, 0x10, 0x28, 0x44,   // 0x6B 'k'
113 +
	0x00, 0x41, 0x7F, 0x40, 0x00,   // 0x6C 'l'
114 +
	0x7C, 0x04, 0x18, 0x04, 0x78,   // 0x6D 'm'
115 +
	0x7C, 0x08, 0x04, 0x04, 0x78,   // 0x6E 'n'
116 +
	0x38, 0x44, 0x44, 0x44, 0x38,   // 0x6F 'o'
117 +
	0x7C, 0x14, 0x14, 0x14, 0x08,   // 0x70 'p'
118 +
	0x08, 0x14, 0x14, 0x18, 0x7C,   // 0x71 'q'
119 +
	0x7C, 0x08, 0x04, 0x04, 0x08,   // 0x72 'r'
120 +
	0x48, 0x54, 0x54, 0x54, 0x20,   // 0x73 's'
121 +
	0x04, 0x3F, 0x44, 0x40, 0x20,   // 0x74 't'
122 +
	0x3C, 0x40, 0x40, 0x20, 0x7C,   // 0x75 'u'
123 +
	0x1C, 0x20, 0x40, 0x20, 0x1C,   // 0x76 'v'
124 +
	0x3C, 0x40, 0x30, 0x40, 0x3C,   // 0x77 'w'
125 +
	0x44, 0x28, 0x10, 0x28, 0x44,   // 0x78 'x'
126 +
	0x0C, 0x50, 0x50, 0x50, 0x3C,   // 0x79 'y'
127 +
	0x44, 0x64, 0x54, 0x4C, 0x44,   // 0x7A 'z'
128 +
	0x00, 0x08, 0x36, 0x41, 0x00,   // 0x7B '{'
129 +
	0x00, 0x00, 0x7F, 0x00, 0x00,   // 0x7C '|'
130 +
	0x00, 0x41, 0x36, 0x08, 0x00,   // 0x7D '}'
131 +
	0x08, 0x08, 0x2A, 0x1C, 0x08,   // 0x7E ->'
132 +
	0x08, 0x1C, 0x2A, 0x08, 0x08,   // 0x7F <-'
133 +
};
134 +
135 +
// *******************************************************************************************
136 +
//
137 +
//										Initialize charset
138 +
//
139 +
// *******************************************************************************************
140 +
141 +
void DEBUGInitChars(SDL_Renderer *renderer) {
142 +
	uint16_t textureData[TEXTURE_WIDTH * TEXTURE_HEIGHT];
143 +
	memset(textureData, 0, sizeof textureData);
144 +
145 +
	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
146 +
	fontTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA4444, SDL_TEXTUREACCESS_STATIC, TEXTURE_WIDTH, TEXTURE_HEIGHT);
147 +
	for (int ch =0x00; ch<0x60; ch++){
148 +
		int rcx = 0;
149 +
		for (int x1 = 0; x1 < 5; x1++) {
150 +
			int rcy = 0;
151 +
			int pixData = fontdata[ch * 5 + x1];
152 +
			while (pixData != 0) {
153 +
				textureData[ch*CHARACTER_WIDTH + rcy*TEXTURE_WIDTH + rcx] = (pixData & 1) ? 0xFFFF : 0x0000;
154 +
				pixData = pixData >> 1;
155 +
				rcy++;
156 +
			}
157 +
			rcx++;
158 +
		}
159 +
	}
160 +
	SDL_UpdateTexture(fontTexture, NULL, &textureData, TEXTURE_WIDTH*2);
161 +
	textureInitialized = 1;
162 +
}
163 +
164 +
// *******************************************************************************************
165 +
//
166 +
//										Write character
167 +
//
168 +
// *******************************************************************************************
169 +
170 +
void DEBUGWrite(SDL_Renderer *renderer, int x, int y, int ch, SDL_Color colour) {
171 +
	if (!textureInitialized) {
172 +
		DEBUGInitChars(renderer);
173 +
	}
174 +
	SDL_SetTextureColorMod(fontTexture, colour.r, colour.g, colour.b);
175 +
	ch-=0x20;
176 +
	SDL_Rect srcRect = {
177 +
		ch* CHARACTER_WIDTH,
178 +
		0,
179 +
		CHARACTER_WIDTH,
180 +
		CHARACTER_HEIGHT
181 +
	};
182 +
	SDL_Rect dstRect = {
183 +
		x*(CHARACTER_WIDTH+1) + xPos,
184 +
		y*(CHARACTER_HEIGHT+1) + yPos,
185 +
		CHARACTER_WIDTH,
186 +
		CHARACTER_HEIGHT
187 +
	};
188 +
	SDL_RenderCopy(renderer, fontTexture, &srcRect, &dstRect);
189 +
}
190 +
191 +
// *******************************************************************************************
192 +
//
193 +
//										Write String
194 +
//
195 +
// *******************************************************************************************
196 +
197 +
void DEBUGString(SDL_Renderer *renderer, int x, int y, char *s, SDL_Color colour) {
198 +
	while (*s != '\0') {
199 +
		DEBUGWrite(renderer, x++, y, *s++, colour);
200 +
	}
201 +
}
added src/rendertext.h +17 −0
1 +
2 +
#ifndef _RENDERTEXT_H
3 +
#define _RENDERTEXT_H
4 +
5 +
#include <SDL.h>
6 +
7 +
#define CHAR_SCALE 		(1)										// character pixel size.
8 +
9 +
extern int xPos;
10 +
extern int yPos;
11 +
12 +
void DEBUGInitChars(SDL_Renderer *renderer);
13 +
void DEBUGWrite(SDL_Renderer *renderer, int x, int y, int ch, SDL_Color colour);
14 +
void DEBUGString(SDL_Renderer *renderer, int x, int y, char *s, SDL_Color colour);
15 +
char *ltrim(char *s);
16 +
17 +
#endif
added src/rom_symbols.h +2 −0
1 +
#define NDX 0x00A80A
2 +
#define KEYD 0x00A800
added src/rtc.c +259 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2021 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
// MCP7940N RTC
6 +
// * RTC
7 +
//   * 24h and AM/PM supported
8 +
//   * oscillator can be turned off
9 +
//   * alarms etc. not supported
10 +
11 +
#include <stdio.h>
12 +
#include <stdbool.h>
13 +
#include <time.h>
14 +
#include "rtc.h"
15 +
#include "glue.h"
16 +
17 +
bool nvram_dirty = false;
18 +
uint8_t nvram[0x40];
19 +
20 +
static bool running;
21 +
static bool vbaten;
22 +
static bool h24;
23 +
24 +
static unsigned int clocks;
25 +
static int seconds;
26 +
static int minutes;
27 +
static int hours;
28 +
static int day_of_week;
29 +
static int day;
30 +
static int month;
31 +
static int year;
32 +
33 +
#define I2C_DATA_LEN 16
34 +
static uint8_t i2c_data[I2C_DATA_LEN];
35 +
static uint8_t i2c_data_pos = 0;
36 +
37 +
void
38 +
rtc_i2c_data(uint8_t v) {
39 +
	if (i2c_data_pos < I2C_DATA_LEN) {
40 +
		i2c_data[i2c_data_pos] = v;
41 +
		i2c_data_pos++;
42 +
	}
43 +
}
44 +
45 +
46 +
47 +
#define BCD(a) (((a) / 10) << 4 | ((a) % 10))
48 +
#define UNBCD(a) (((a) >> 4) * 10 + ((a) & 0xf))
49 +
50 +
void
51 +
rtc_init(bool set_system_time)
52 +
{
53 +
	vbaten = true;
54 +
	h24 = true;
55 +
	clocks = 0;
56 +
57 +
	if (set_system_time) {
58 +
		running = true;
59 +
		time_t t = time(NULL);
60 +
		struct tm tm = *localtime(&t);
61 +
		seconds = tm.tm_sec;
62 +
		minutes = tm.tm_min;
63 +
		hours = tm.tm_hour;
64 +
		day_of_week = (tm.tm_wday == 0 ? 7 : tm.tm_wday);
65 +
		day = tm.tm_mday;
66 +
		month = tm.tm_mon + 1;
67 +
		year = tm.tm_year - 100;
68 +
	} else {
69 +
		running = false; // yes, the MCP7940N starts out this way!
70 +
		seconds = 0;
71 +
		minutes = 0;
72 +
		hours = 0;
73 +
		day_of_week = 1;
74 +
		day = 1;
75 +
		month = 1;
76 +
		year = 0;
77 +
	}
78 +
}
79 +
80 +
static uint8_t days_per_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
81 +
82 +
bool
83 +
is_leap_year() {
84 +
	// the clock does 2000-2099, where the "% 4 == 0" rule applies
85 +
	return !(year & 3);
86 +
}
87 +
88 +
void
89 +
rtc_step(int c)
90 +
{
91 +
	if (!running) {
92 +
		return;
93 +
	}
94 +
95 +
	clocks += c;
96 +
	if (clocks < (MHZ * 1000000)) {
97 +
		return;
98 +
	}
99 +
100 +
	clocks -= (MHZ * 1000000);
101 +
	seconds++;
102 +
	if (seconds < 60) {
103 +
		return;
104 +
	}
105 +
106 +
	seconds = 0;
107 +
	minutes++;
108 +
	if (minutes < 60) {
109 +
		return;
110 +
	}
111 +
112 +
	minutes = 0;
113 +
	hours++;
114 +
	if (hours < 24) {
115 +
		return;
116 +
	}
117 +
118 +
	hours = 0;
119 +
	day_of_week++;
120 +
	if (day_of_week > 7) {
121 +
		day_of_week = 1;
122 +
	}
123 +
	day++;
124 +
	uint8_t dpm = days_per_month[month - 1];
125 +
	if (month == 2 && is_leap_year()) {
126 +
		dpm++;
127 +
	}
128 +
	if (day <= dpm) {
129 +
		return;
130 +
	}
131 +
132 +
	day = 1;
133 +
	month++;
134 +
	if (month <= 12) {
135 +
		return;
136 +
	}
137 +
138 +
	month = 1;
139 +
	year++;
140 +
	if (year == 100) {
141 +
		year = 0; // Y2.1K problem! ;-)
142 +
	}
143 +
}
144 +
145 +
146 +
uint8_t
147 +
rtc_read() {
148 +
	//    printf("RTC READ $%02X\n", a);
149 +
	uint8_t ret;
150 +
	switch (i2c_data[0]) {
151 +
		case 0:
152 +
			ret = BCD(seconds) | (running << 7);
153 +
			break;
154 +
		case 1:
155 +
			ret = BCD(minutes);
156 +
			break;
157 +
		case 2: {
158 +
			uint8_t h = hours;
159 +
			bool pm = false;
160 +
			if (!h24) {
161 +
				// AM/PM
162 +
				if (h >= 12) {
163 +
					pm = true;
164 +
					h -= 12;
165 +
				}
166 +
				if (h == 0) {
167 +
					h = 12;
168 +
				}
169 +
			}
170 +
			h = BCD(h);
171 +
			h |= pm << 5;
172 +
			h |= (!h24) << 6;
173 +
			ret = h;
174 +
			break;
175 +
		}
176 +
		case 3: {
177 +
			uint8_t v = day_of_week;
178 +
			v |= vbaten << 3;
179 +
			v |= running << 5;
180 +
			ret = v;
181 +
			break;
182 +
		}
183 +
		case 4:
184 +
			ret = BCD(day);
185 +
			break;
186 +
		case 5:
187 +
			ret = BCD(month) | is_leap_year() << 5;
188 +
			break;
189 +
		case 6:
190 +
			ret = BCD(year);
191 +
			break;
192 +
		default:
193 +
			if (i2c_data[0] >= 0x20 && i2c_data[0] < 0x60) {
194 +
				ret = nvram[i2c_data[0] - 0x20];
195 +
			} else if (i2c_data[0] >= 0x60) {
196 +
				ret = 0xff;
197 +
			} else {
198 +
				ret = 0;
199 +
			}
200 +
			break;
201 +
	}
202 +
203 +
	i2c_data_pos = 0;
204 +
	return ret;
205 +
}
206 +
207 +
void
208 +
rtc_write() {
209 +
	//    printf("RTC WRITE $%02X, $%02X\n", a, v);
210 +
	switch (i2c_data[0]) {
211 +
		case 0:
212 +
			running = !!(i2c_data[1] & 0x80);
213 +
			seconds = UNBCD(i2c_data[1] & 0x7f);
214 +
			break;
215 +
		case 1:
216 +
			minutes = UNBCD(i2c_data[1]);
217 +
			break;
218 +
		case 2: {
219 +
			h24 = !(i2c_data[1] & 0x40);
220 +
			uint8_t h = i2c_data[1] & 0x3f;
221 +
			bool pm = false;
222 +
			if (!h24) {
223 +
				pm = i2c_data[1] & 0x20;
224 +
				h &= 0x1f;
225 +
			}
226 +
			h = UNBCD(h);
227 +
			if (!h24 && h == 12) {
228 +
				h = 0;
229 +
			}
230 +
			if (pm) {
231 +
				h += 12;
232 +
			}
233 +
			hours = h;
234 +
			break;
235 +
		}
236 +
		case 3: {
237 +
			day_of_week = i2c_data[1] & 7;
238 +
			vbaten = !!(i2c_data[1] & 0x20);
239 +
			break;
240 +
		}
241 +
		case 4:
242 +
			day = UNBCD(i2c_data[1]);
243 +
			break;
244 +
		case 5:
245 +
			month = UNBCD(i2c_data[1]);
246 +
			break;
247 +
		case 6:
248 +
			year = UNBCD(i2c_data[1]);
249 +
			break;
250 +
		default:
251 +
			if (i2c_data[0] >= 0x20 && i2c_data[0] < 0x60) {
252 +
				nvram[i2c_data[0]- 0x20] = i2c_data[1];
253 +
				nvram_dirty = true;
254 +
			}
255 +
	}
256 +
257 +
	i2c_data_pos = 0;
258 +
}
259 +
added src/rtc.h +17 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2021 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _RTC_H_
6 +
#define _RTC_H_
7 +
8 +
#include <stdint.h>
9 +
10 +
void rtc_i2c_data(uint8_t v);
11 +
void rtc_init(bool set_system_time);
12 +
void rtc_set_system_time();
13 +
void rtc_step(int c);
14 +
uint8_t rtc_read();
15 +
void rtc_write();
16 +
17 +
#endif
added src/sdcard.c +396 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// Copyright (c) 2020 Frank van den Hoef
4 +
// All rights reserved. License: 2-clause BSD
5 +
#ifndef __APPLE__
6 +
#define _XOPEN_SOURCE   600
7 +
#define _POSIX_C_SOURCE 1
8 +
#endif
9 +
10 +
#include <limits.h>
11 +
#include <stdio.h>
12 +
#include <stdbool.h>
13 +
#include <string.h>
14 +
#include "sdcard.h"
15 +
#include "files.h"
16 +
17 +
//#define VERBOSE 1
18 +
19 +
// MMC/SD command (SPI mode)
20 +
enum {
21 +
	CMD0   = 0,         // GO_IDLE_STATE
22 +
	CMD1   = 1,         // SEND_OP_COND
23 +
	ACMD41 = 0x80 | 41, // SEND_OP_COND (SDC)
24 +
	CMD8   = 8,         // SEND_IF_COND
25 +
	CMD9   = 9,         // SEND_CSD
26 +
	CMD10  = 10,        // SEND_CID
27 +
	CMD12  = 12,        // STOP_TRANSMISSION
28 +
	CMD13  = 13,        // SEND_STATUS
29 +
	ACMD13 = 0x80 | 13, // SD_STATUS (SDC)
30 +
	CMD16  = 16,        // SET_BLOCKLEN
31 +
	CMD17  = 17,        // READ_SINGLE_BLOCK
32 +
	CMD18  = 18,        // READ_MULTIPLE_BLOCK
33 +
	CMD23  = 23,        // SET_BLOCK_COUNT
34 +
	ACMD23 = 0x80 | 23, // SET_WR_BLK_ERASE_COUNT (SDC)
35 +
	CMD24  = 24,        // WRITE_BLOCK
36 +
	CMD25  = 25,        // WRITE_MULTIPLE_BLOCK
37 +
	CMD32  = 32,        // ERASE_WR_BLK_START
38 +
	CMD33  = 33,        // ERASE_WR_BLK_END
39 +
	CMD38  = 38,        // ERASE
40 +
	CMD55  = 55,        // APP_CMD
41 +
	CMD58  = 58,        // READ_OCR
42 +
};
43 +
44 +
static char sdcard_path[PATH_MAX] = "";
45 +
static struct x16file *sdcard_file = NULL;
46 +
bool sdcard_attached = false;
47 +
48 +
static uint8_t rxbuf[3 + 512];
49 +
static int rxbuf_idx;
50 +
static uint32_t lba;
51 +
static uint8_t last_cmd;
52 +
static bool is_acmd = false;
53 +
static bool is_idle = true;
54 +
static bool is_initialized = false;
55 +
static bool ongoing_multiblock_read = false;
56 +
57 +
static const uint8_t *response = NULL;
58 +
static int response_length = 0;
59 +
static int response_counter = 0;
60 +
61 +
static bool selected = false;
62 +
63 +
void
64 +
sdcard_set_path(char const *path)
65 +
{
66 +
	sdcard_detach();
67 +
68 +
	strncpy(sdcard_path, path, PATH_MAX);
69 +
	sdcard_path[PATH_MAX-1] = '\0';
70 +
71 +
	sdcard_attach();
72 +
}
73 +
74 +
bool
75 +
sdcard_path_is_set()
76 +
{
77 +
	return strlen(sdcard_path) > 0;
78 +
}
79 +
80 +
void
81 +
sdcard_attach()
82 +
{
83 +
	if (!sdcard_attached && sdcard_path_is_set()) {
84 +
		sdcard_file = x16open(sdcard_path, "r+b");
85 +
		if(sdcard_file == NULL) {
86 +
			printf("Cannot open SDCard file %s!\n", sdcard_path);
87 +
			return;
88 +
		}
89 +
90 +
		printf("SD card attached.\n");
91 +
		sdcard_attached = true;
92 +
		is_initialized = false;
93 +
	}
94 +
}
95 +
96 +
void
97 +
sdcard_detach()
98 +
{
99 +
	if (sdcard_attached) {
100 +
		x16close(sdcard_file);
101 +
		sdcard_file = NULL;
102 +
103 +
		printf("SD card detached.\n");
104 +
		sdcard_attached = false;
105 +
	}
106 +
}
107 +
108 +
void
109 +
sdcard_select(bool select)
110 +
{
111 +
	selected = select;
112 +
	rxbuf_idx = 0;
113 +
#if defined(VERBOSE) && VERBOSE >= 2
114 +
	printf("*** SD card select: %u\n", select);
115 +
#endif
116 +
}
117 +
118 +
static void
119 +
set_response_csd(void)
120 +
{
121 +
	static uint8_t rr[] = {
122 +
		0xff, // dummy
123 +
		0xff, // dummy
124 +
		0x00, // R1 response
125 +
		0xff, // dummy
126 +
		0xfe, // begin block
127 +
		0x40, // CSD_STRUCTURE [7:6] = 1, RESERVED [5:0] = 0
128 +
		0x0e, // TAAC = 0x0e
129 +
		0x00, // NSAC = 0x00
130 +
		0x32, // TRAN_SPEED = 0x32
131 +
		0x5b, // CCC (11:4)
132 +
		0x59, // CCC (3:0) [7-4], READ_BL_LEN [3-0]
133 +
		0x00, // READ_BL_PARTIAL [7], WRITE_BLK_MISALIGN [6], READ_BLK_MISALIGN [5], DSR_IMP [4], RESERVED [3:0]
134 +
		0x00, // RESERVED [7:6], C_SIZE(21:16) [5:0]
135 +
		0x00, // C_SIZE(15:8)
136 +
		0x00, // C_SIZE(7:0)
137 +
		0x7f, // RESERVED [7], ERASE_BLK_EN[6], SECTOR_SIZE(6:1) [5:0]
138 +
		0x80, // SECTOR_SIZE(0) [7], WP_GRP_SIZE [6:0]
139 +
		0x0a, // WP_GRP_ENABLE [7], RESERVED [6:5], R2W_FACTOR [4:2], WRITE_BL_LEN (3:2) [1:0]
140 +
		0x40, // WRITE_BL_LEN (1:0) [7:6], WRITE_BL_PARTIAL [5], RESERVED [4:0]
141 +
		0x00, // FILE_FORMAT_GRP [7] = 0, COPY [6], PERM_WRITE_PROTECT [5], TMP_WRITE_PROTECT [4], RESERVED [3:0]
142 +
		0x01 // CRC[7:1], ALWAYS_1 [0]
143 +
		};
144 +
	uint64_t c_size = (x16size(sdcard_file) >> 19)-1;
145 +
	rr[12] |= (c_size >> 16) & 0x3f;
146 +
	rr[13] = (c_size >> 8) & 0xff;
147 +
	rr[14] = c_size & 0xff;
148 +
149 +
	response = rr;
150 +
	response_length = 21;
151 +
}
152 +
153 +
static void
154 +
set_response_r1(void)
155 +
{
156 +
	static uint8_t r1;
157 +
	r1 = is_idle ? 1 : 0;
158 +
	response = &r1;
159 +
	response_length = 1;
160 +
}
161 +
162 +
static void
163 +
set_response_r2(void)
164 +
{
165 +
	if (is_initialized) {
166 +
		static const uint8_t r2[] = {0x00, 0x00};
167 +
		response = r2;
168 +
		response_length = sizeof(r2);
169 +
	} else {
170 +
		static const uint8_t r2[] = {0x1F, 0xFF};
171 +
		response = r2;
172 +
		response_length = sizeof(r2);
173 +
	}
174 +
}
175 +
176 +
static void
177 +
set_response_r3(void)
178 +
{
179 +
	static const uint8_t r3[] = {0xC0, 0xFF, 0x80, 0x00};
180 +
	response = r3;
181 +
	response_length = sizeof(r3);
182 +
}
183 +
184 +
static void
185 +
set_response_r7(void)
186 +
{
187 +
	static const uint8_t r7[] = {1, 0x00, 0x00, 0x01, 0xAA};
188 +
	response = r7;
189 +
	response_length = sizeof(r7);
190 +
}
191 +
192 +
// Return length of reply
193 +
static int loadBlock(uint8_t *dest)
194 +
{
195 +
	int response_length = 0;
196 +
197 +
	dest[0] = 0xFE; // Data token for CMD17/18
198 +
#ifdef VERBOSE
199 +
	printf("*** SD Reading LBA %d\n", lba);
200 +
#endif
201 +
	if ((Sint64)lba * 512 >= x16size(sdcard_file)) {
202 +
		dest[0] = 0x08; // Error token: out of range
203 +
		response_length = 1;
204 +
	} else {
205 +
		x16seek(sdcard_file, (Sint64)lba * 512, XSEEK_SET);
206 +
		int bytes_read = x16read(sdcard_file, &dest[1], 1, 512);
207 +
		if (bytes_read != 512) {
208 +
			printf("Warning: short read!\n");
209 +
		}
210 +
		response_length = 1 + 512 + 2;
211 +
	}
212 +
	return response_length;
213 +
}
214 +
215 +
uint8_t
216 +
sdcard_handle(uint8_t inbyte)
217 +
{
218 +
	if (!selected || (sdcard_file == NULL)) {
219 +
		return 0xFF;
220 +
	}
221 +
	// printf("sdcard_handle: %02X\n", inbyte);
222 +
223 +
	uint8_t outbyte = 0xFF;
224 +
225 +
	if (rxbuf_idx == 0 && inbyte == 0xFF) {
226 +
		// send response data
227 +
		if (response) {
228 +
			outbyte = response[response_counter++];
229 +
			if (response_counter == response_length) {
230 +
				if (ongoing_multiblock_read) {
231 +
					static uint8_t read_multiblock_next_response[1 + 512 + 2];
232 +
					// Prepare next multiblock reply
233 +
					lba++;
234 +
					response_length = loadBlock(&read_multiblock_next_response[0]);
235 +
					// Stop multiblock read if error
236 +
					if (response_length == 1) {
237 +
						ongoing_multiblock_read = false;
238 +
					}
239 +
					response = read_multiblock_next_response;
240 +
					response_counter = 0;
241 +
				} else  {
242 +
					response = NULL;
243 +
					ongoing_multiblock_read = false;
244 +
				}
245 +
			}
246 +
		}
247 +
248 +
	} else {
249 +
		rxbuf[rxbuf_idx++] = inbyte;
250 +
251 +
		if ((rxbuf[0] & 0xC0) == 0x40 && rxbuf_idx == 6) {
252 +
			rxbuf_idx = 0;
253 +
254 +
			// Check for start-bit + transmission bit
255 +
			if ((rxbuf[0] & 0xC0) != 0x40) {
256 +
				response = NULL;
257 +
				return 0xFF;
258 +
			}
259 +
			rxbuf[0] &= 0x3F;
260 +
261 +
			// Use upper command bit to indicate this is an ACMD
262 +
			if (is_acmd) {
263 +
				rxbuf[0] |= 0x80;
264 +
				is_acmd = false;
265 +
			}
266 +
267 +
			last_cmd = rxbuf[0];
268 +
269 +
#if defined(VERBOSE) && VERBOSE >= 2
270 +
			printf("*** SD %sCMD%d -> Response:", (rxbuf[0] & 0x80) ? "A" : "", rxbuf[0] & 0x3F);
271 +
#endif
272 +
			switch (rxbuf[0]) {
273 +
				case CMD0: {
274 +
					// GO_IDLE_STATE: Resets the SD Memory Card
275 +
					is_idle = true;
276 +
					set_response_r1();
277 +
					break;
278 +
				}
279 +
280 +
				case CMD8: {
281 +
					// SEND_IF_COND: Sends SD Memory Card interface condition that includes host supply voltage
282 +
					set_response_r7();
283 +
					break;
284 +
				}
285 +
286 +
				case CMD9: {
287 +
					// SEND_CSD: Sends card-specific data
288 +
					set_response_csd();
289 +
					break;
290 +
				}
291 +
292 +
				case ACMD41: {
293 +
					// SD_SEND_OP_COND: Sends host capacity support information and activated the card's initialization process
294 +
					is_idle = false;
295 +
					is_initialized = true;
296 +
					set_response_r1();
297 +
					break;
298 +
				}
299 +
300 +
				case CMD12: {
301 +
					// STOP_TRANSMISSION: Abort ongoing multiple block read
302 +
					ongoing_multiblock_read = false;
303 +
					set_response_r1();
304 +
					break;
305 +
				}
306 +
307 +
				case CMD13: {
308 +
					// SEND_STATUS: Asks the selected card to send its status register
309 +
					set_response_r2();
310 +
					break;
311 +
				}
312 +
				case CMD16: {
313 +
					// SET_BLOCKLEN: In case of non-SDHC card, this sets the block length. Block length of SDHC/SDXC cards are fixed to 512 bytes.
314 +
					set_response_r1();
315 +
					break;
316 +
				}
317 +
				case CMD18: {
318 +
					// READ_MULTIPLE_BLOCK
319 +
					ongoing_multiblock_read = true;
320 +
					// Fallthrough
321 +
				}
322 +
				case CMD17: {
323 +
					// READ_SINGLE_BLOCK
324 +
					lba = (rxbuf[1] << 24) | (rxbuf[2] << 16) | (rxbuf[3] << 8) | rxbuf[4];
325 +
					static uint8_t read_block_response[2 + 512 + 2];
326 +
					read_block_response[0] = 0; // R1 response to command
327 +
					response_length = 1 + loadBlock(&read_block_response[1]);
328 +
					// Stop multiblock read if error
329 +
					if (response_length == 2) {
330 +
						ongoing_multiblock_read = false;
331 +
					}
332 +
					response = read_block_response;
333 +
					break;
334 +
				}
335 +
336 +
				case CMD24: {
337 +
					// WRITE_BLOCK
338 +
					lba = (rxbuf[1] << 24) | (rxbuf[2] << 16) | (rxbuf[3] << 8) | rxbuf[4];
339 +
					if (rxbuf_idx > 4 && (Sint64)lba * 512 >= x16size(sdcard_file)) {
340 +
						static uint8_t bad_lba[2] = {0x00, 0x08};
341 +
						response = bad_lba;
342 +
						response_length = 2;
343 +
					} else {
344 +
						set_response_r1();
345 +
					}
346 +
					break;
347 +
				}
348 +
349 +
				case CMD55: {
350 +
					// APP_CMD: Next command is an application specific command
351 +
					is_acmd = true;
352 +
					set_response_r1();
353 +
					break;
354 +
				}
355 +
356 +
				case CMD58: {
357 +
					// READ_OCR: Read the OCR register of the card
358 +
					set_response_r3();
359 +
					break;
360 +
				}
361 +
362 +
				default: {
363 +
					set_response_r1();
364 +
					break;
365 +
				}
366 +
			}
367 +
			response_counter = 0;
368 +
369 +
#if defined(VERBOSE) && VERBOSE >= 2
370 +
			for (int i = 0; i < (response_length < 16 ? response_length : 16); i++) {
371 +
				printf(" %02X", response[i]);
372 +
			}
373 +
			printf("\n");
374 +
#endif
375 +
376 +
		} else if (rxbuf_idx == 515) {
377 +
			rxbuf_idx = 0;
378 +
			// Check for 'start block' byte
379 +
			if (last_cmd == CMD24 && rxbuf[0] == 0xFE) {
380 +
#ifdef VERBOSE
381 +
				printf("*** SD Writing LBA %d\n", lba);
382 +
#endif
383 +
				if ((Sint64)lba * 512 >= x16size(sdcard_file)) {
384 +
					// do nothing?
385 +
				} else {
386 +
					x16seek(sdcard_file, (Sint64)lba * 512, XSEEK_SET);
387 +
					int bytes_written = x16write(sdcard_file, rxbuf + 1, 1, 512);
388 +
					if (bytes_written != 512) {
389 +
						printf("Warning: short write!\n");
390 +
					}
391 +
				}
392 +
			}
393 +
		}
394 +
	}
395 +
	return outbyte;
396 +
}
added src/sdcard.h +19 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
#ifndef _SD_CARD_H_
5 +
#define _SD_CARD_H_
6 +
#include <inttypes.h>
7 +
#include <stdbool.h>
8 +
#include <SDL.h>
9 +
10 +
extern bool sdcard_attached;
11 +
void sdcard_set_path(char const *path);
12 +
bool sdcard_path_is_set();
13 +
void sdcard_attach();
14 +
void sdcard_detach();
15 +
16 +
void sdcard_select(bool select);
17 +
uint8_t sdcard_handle(uint8_t inbyte);
18 +
19 +
#endif
added src/serial.c +253 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2022 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
// Commodore Bus emulation, L1: Serial Bus
6 +
// This code is hacky, buggy and incomplete, but
7 +
// it helped bringing up the serial bus on real
8 +
// hardware.
9 +
10 +
#include <stdio.h>
11 +
#include <stdint.h>
12 +
#include <stdbool.h>
13 +
#include "serial.h"
14 +
#include "ieee.h"
15 +
#include "glue.h"
16 +
17 +
serial_port_t serial_port;
18 +
19 +
static int state = 0;
20 +
static bool valid;
21 +
static int bit;
22 +
static uint8_t byte;
23 +
static bool listening = false;
24 +
static bool talking = false;
25 +
static bool during_atn = false;
26 +
static bool eoi = false;
27 +
static bool fnf = false; // file not found
28 +
static int clocks_since_last_change = 0;
29 +
30 +
#define printf(...)
31 +
32 +
static uint8_t
33 +
read_byte(bool *eoi)
34 +
{
35 +
	uint8_t byte;
36 +
	int ret = ACPTR(&byte);
37 +
	*eoi = ret >= 0;
38 +
	return byte;
39 +
}
40 +
41 +
bool
42 +
serial_port_read_clk()
43 +
{
44 +
	return serial_port.out.clk & serial_port.in.clk;
45 +
}
46 +
47 +
bool
48 +
serial_port_read_data()
49 +
{
50 +
	return serial_port.out.data & serial_port.in.data;
51 +
}
52 +
53 +
void
54 +
serial_step(int clocks)
55 +
{
56 +
	bool print = false;
57 +
58 +
	static bool old_atn = false, old_clk = false, old_data = false;
59 +
	if (old_atn == serial_port.in.atn &&
60 +
		old_clk == serial_port_read_clk() &&
61 +
		old_data == serial_port_read_data()) {
62 +
		clocks_since_last_change += clocks;
63 +
		if (state == 2 && valid == true && bit == 0 && clocks_since_last_change > 200 * MHZ) {
64 +
			if (clocks_since_last_change < (200 + 60) * MHZ) {
65 +
				printf("XXX EOI ACK\n");
66 +
				serial_port.out.data = 0;
67 +
				eoi = true;
68 +
			} else {
69 +
				printf("XXX EOI ACK END\n");
70 +
				serial_port.out.data = 1;
71 +
				clocks_since_last_change = 0;
72 +
			}
73 +
			print = true;
74 +
		}
75 +
		if (state == 10 && clocks_since_last_change > 60 * MHZ) {
76 +
			serial_port.out.clk = 1;
77 +
			state = 11;
78 +
			clocks_since_last_change = 0;
79 +
			print = true;
80 +
		} else if (state == 11 && serial_port_read_data() && !fnf) {
81 +
			clocks_since_last_change = 0;
82 +
			byte = read_byte(&eoi);
83 +
			bit = 0;
84 +
			valid = true;
85 +
			if (eoi) {
86 +
				printf("XXXEOI1\n");
87 +
				state = 12;
88 +
			} else {
89 +
				printf("XXXEOI0\n");
90 +
				serial_port.out.clk = 0;
91 +
				state = 13;
92 +
			}
93 +
			print = true;
94 +
		} else if (state == 12 && clocks_since_last_change > 512 * MHZ) {
95 +
			// EOI delay
96 +
			// XXX we'd have to check for the ACK
97 +
			clocks_since_last_change = 0;
98 +
			serial_port.out.clk = 0;
99 +
			state = 13;
100 +
			print = true;
101 +
		} else if (state == 13 && clocks_since_last_change > 60 * MHZ) {
102 +
			if (valid) {
103 +
				// send bit
104 +
				serial_port.out.data = (byte >> bit) & 1;
105 +
				serial_port.out.clk = 1;
106 +
				printf("*** BIT%d OUT: %d\n", bit, serial_port.out.data);
107 +
				bit++;
108 +
				if (bit == 8) {
109 +
					state = 14;
110 +
				}
111 +
			} else {
112 +
				serial_port.out.clk = 0;
113 +
			}
114 +
			valid = !valid;
115 +
			clocks_since_last_change = 0;
116 +
			print = true;
117 +
		} else if (state == 14 && clocks_since_last_change > 60 * MHZ) {
118 +
			serial_port.out.data = 1;
119 +
			serial_port.out.clk = 0;
120 +
			state = 10;
121 +
			clocks_since_last_change = 0;
122 +
			print = true;
123 +
		}
124 +
	} else {
125 +
		clocks_since_last_change = 0;
126 +
127 +
		printf("-SERIAL IN { ATN:%d CLK:%d DATA:%d }\n", serial_port.in.atn, old_clk, old_data);
128 +
		printf("+SERIAL IN { ATN:%d CLK:%d DATA:%d } --- IN { CLK:%d DATA:%d } OUT { CLK:%d DATA:%d } -- #%d\n", serial_port.in.atn, serial_port_read_clk(), serial_port_read_data(), serial_port.in.clk, serial_port.in.data, serial_port.out.clk, serial_port.out.data, state);
129 +
130 +
		if (!during_atn && serial_port.in.atn) {
131 +
			serial_port.out.data = 0;
132 +
			state = 99;
133 +
			during_atn = true;
134 +
			printf("XXX START OF ATN\n");
135 +
		}
136 +
137 +
		switch(state) {
138 +
			case 0:
139 +
				break;
140 +
			case 99:
141 +
				// wait for CLK=0
142 +
				if (!serial_port_read_clk()) {
143 +
					state = 1;
144 +
				}
145 +
				break;
146 +
			case 1:
147 +
				if (during_atn && !serial_port.in.atn) {
148 +
					// cancelled ATN
149 +
					serial_port.out.data = 1;
150 +
					serial_port.out.clk = 1;
151 +
					during_atn = false;
152 +
					printf("*** END OF ATN\n");
153 +
					if (listening) {
154 +
						// keep holding DATA to indicate we're here
155 +
						serial_port.out.data = 0;
156 +
						printf("XXX START OF DATA RECEIVE\n");
157 +
					} else if (talking) {
158 +
						serial_port.out.clk = 0;
159 +
						state = 10;
160 +
						printf("XXX START OF DATA SEND\n");
161 +
					} else {
162 +
						state = 0;
163 +
					}
164 +
					break;
165 +
				}
166 +
				// wait for CLK=1
167 +
				if (serial_port_read_clk()) {
168 +
					serial_port.out.data = 1;
169 +
					state = 2;
170 +
					valid = true;
171 +
					bit = 0;
172 +
					byte = 0;
173 +
					eoi = false;
174 +
					printf("XXX START OF BYTE\n");
175 +
				}
176 +
				break;
177 +
			case 2:
178 +
				if (during_atn && !serial_port.in.atn) {
179 +
					// cancelled ATN
180 +
					serial_port.out.data = 1;
181 +
					serial_port.out.clk = 1;
182 +
					printf("*** XEND OF ATN\n");
183 +
					state = 0;
184 +
					break;
185 +
				}
186 +
				if (valid) {
187 +
					// wait for CLK=0, data not valid
188 +
					if (!serial_port_read_clk()) {
189 +
						valid = false;
190 +
						printf("XXX NOT VALID\n");
191 +
					}
192 +
				} else {
193 +
					// wait for CLK=1, data valid
194 +
					if (serial_port_read_clk()) {
195 +
						bool b = serial_port_read_data();
196 +
						byte |= (b << bit);
197 +
						printf("*** BIT%d IN: %d\n", bit, b);
198 +
						valid = true;
199 +
						if (++bit == 8) {
200 +
							printf("*** %s BYTE IN: %02x%s\n", during_atn ? "ATN" : "DATA", byte, eoi ? " (EOI)" : "");
201 +
							if (during_atn) {
202 +
								printf("IEEE CMD %x\n", byte);
203 +
								switch (byte & 0x60) {
204 +
									case 0x20:
205 +
										if (byte == 0x3f) {
206 +
											UNLSN();
207 +
											listening = false;
208 +
										} else {
209 +
											LISTEN(byte);
210 +
											listening = true;
211 +
										}
212 +
										break;
213 +
									case 0x40:
214 +
										if (byte == 0x5f) {
215 +
											UNTLK();
216 +
											talking = false;
217 +
										} else {
218 +
											TALK(byte);
219 +
											talking = true;
220 +
										}
221 +
										break;
222 +
									case 0x60:
223 +
										if (listening) {
224 +
											SECOND(byte);
225 +
										} else { // talking
226 +
											TKSA(byte);
227 +
										}
228 +
										break;
229 +
								}
230 +
							} else {
231 +
								CIOUT(byte);
232 +
							}
233 +
							serial_port.out.data = 0;
234 +
							state = 1;
235 +
						}
236 +
					}
237 +
				}
238 +
				break;
239 +
			case 3:
240 +
				break;
241 +
		}
242 +
		print = true;
243 +
	}
244 +
245 +
	if (print) {
246 +
		printf(">SERIAL IN { ATN:%d CLK:%d DATA:%d } --- IN { CLK:%d DATA:%d } OUT { CLK:%d DATA:%d } -- #%d\n", serial_port.in.atn, serial_port_read_clk(), serial_port_read_data(), serial_port.in.clk, serial_port.in.data, serial_port.out.clk, serial_port.out.data, state);
247 +
	}
248 +
249 +
	old_atn = serial_port.in.atn;
250 +
	old_clk = serial_port_read_clk();
251 +
	old_data = serial_port_read_data();
252 +
}
253 +
added src/serial.h +33 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2022 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef SERIAL_H
6 +
#define SERIAL_H
7 +
8 +
#include <stdint.h>
9 +
10 +
#define SERIAL_ATNIN_MASK   (1<<3)
11 +
#define SERIAL_CLOCKIN_MASK (1<<4)
12 +
#define SERIAL_DATAIN_MASK  (1<<5)
13 +
14 +
typedef struct {
15 +
	struct {
16 +
		int atn;
17 +
		int clk;
18 +
		int data;
19 +
	} in;
20 +
	struct {
21 +
		int clk;
22 +
		int data;
23 +
	} out;
24 +
} serial_port_t;
25 +
26 +
extern serial_port_t serial_port;
27 +
28 +
void serial_step(int clocks);
29 +
30 +
bool serial_port_read_clk();
31 +
bool serial_port_read_data();
32 +
33 +
#endif
added src/smc.c +176 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2021 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
// System Management Controller
6 +
7 +
#include <stdio.h>
8 +
#include <stdbool.h>
9 +
#include <stdlib.h>
10 +
#include "smc.h"
11 +
#include "glue.h"
12 +
#include "i2c.h"
13 +
#ifdef __EMSCRIPTEN__
14 +
#include <emscripten.h>
15 +
#endif
16 +
17 +
// 0x01 0x00      - Power Off
18 +
// 0x01 0x01      - Hard Reboot
19 +
// 0x02 0x00      - Reset Button Press
20 +
// 0x03 0x00      - NMI Button Press
21 +
// 0x04 0x00-0xFF - Power LED Level (PWM)
22 +
// 0x05 0x00-0xFF - Activity LED Level (PWM)
23 +
24 +
uint8_t default_read_op = 0x41;
25 +
uint8_t default_read_state = 0;
26 +
uint8_t activity_led;
27 +
uint8_t mse_count = 0;
28 +
#define I2C_DATA_LEN 16
29 +
static uint8_t i2c_data[I2C_DATA_LEN];
30 +
static uint8_t i2c_data_pos = 0;
31 +
bool smc_requested_reset = false;
32 +
33 +
void
34 +
smc_i2c_data(uint8_t v) {
35 +
	if (i2c_data_pos < I2C_DATA_LEN) {
36 +
		i2c_data[i2c_data_pos] = v;
37 +
		i2c_data_pos++;
38 +
	}
39 +
}
40 +
41 +
uint8_t
42 +
smc_read() {
43 +
	uint8_t mouse_id;
44 +
	uint8_t mouse_size;
45 +
	uint8_t ret;
46 +
47 +
	switch (i2c_data[0]){
48 +
		case 0x43: // keycode + mouse
49 +
			if (default_read_state == 0) {
50 +
				default_read_state = 1;
51 +
				ret = i2c_kbd_buffer_next();
52 +
				break;
53 +
			}
54 +
			// fall through
55 +
		case 0x42: // mouse only
56 +
		// fall through
57 +
		// Offset that returns three bytes from mouse buffer (one movement packet) or a single zero if there is not complete packet in the buffer
58 +
		// mse_count keeps track of which one of the three bytes it's sending
59 +
		case 0x21:
60 +
			mouse_id = mouse_get_device_id();
61 +
			if (mouse_id == 3 || mouse_id == 4) {
62 +
				mouse_size = 4;
63 +
			}
64 +
			else {
65 +
				mouse_size = 3;
66 +
			}
67 +
68 +
			if (mse_count == 0 && i2c_mse_buffer_count() >= mouse_size) {		// If start of packet, check if there are at least three bytes in the buffer
69 +
				mse_count++;
70 +
				ret = i2c_mse_buffer_next();
71 +
			}
72 +
			else if (mse_count > 0) {								// If we have already started sending bytes, assume there is enough data in the buffer
73 +
				mse_count++;
74 +
				if (mse_count == mouse_size) {
75 +
					mse_count = 0;
76 +
					default_read_state = 0;
77 +
				}
78 +
				ret = i2c_mse_buffer_next();
79 +
			}
80 +
			else {													// Return a single zero if no complete packet available
81 +
				mse_count = 0;
82 +
				default_read_state = 0;
83 +
				ret = 0x00;
84 +
			}
85 +
			break;
86 +
		case 0x41: // keycode only
87 +
			default_read_state = 0;
88 +
			// fall through
89 +
		// Offset that returns one byte from the keyboard buffer
90 +
		case 0x07:
91 +
			ret = i2c_kbd_buffer_next();
92 +
			break;
93 +
		// Offset, initially used for debugging in the SMC, but now
94 +
		// also used to inform kernal whether power on was done by
95 +
		// holding the power button
96 +
		case 0x09:
97 +
			if (pwr_long_press) {
98 +
				// Reset pwr_long_press so a reset will start normally
99 +
				pwr_long_press = false;
100 +
				ret = 1;
101 +
			} else {
102 +
				ret = 0;
103 +
			}
104 +
			break;
105 +
106 +
		case 0x22:
107 +
			ret = mouse_get_device_id();
108 +
			break;
109 +
110 +
		case 0x30:
111 +
			ret = SMC_VERSION_MAJOR;
112 +
			break;
113 +
114 +
		case 0x31:
115 +
			ret = SMC_VERSION_MINOR;
116 +
			break;
117 +
118 +
		case 0x32:
119 +
			ret = SMC_VERSION_PATCH;
120 +
			break;
121 +
122 +
		default:
123 +
			ret = 0xff;
124 +
	}
125 +
126 +
	i2c_data[0] = default_read_op;
127 +
	i2c_data_pos = 0;
128 +
	return ret;
129 +
}
130 +
131 +
void
132 +
smc_write() {
133 +
	switch (i2c_data[0]) {
134 +
		case 1:
135 +
			if (i2c_data[1] == 0) {
136 +
				printf("SMC Power Off.\n");
137 +
				main_shutdown();
138 +
#ifdef __EMSCRIPTEN__
139 +
				emscripten_force_exit(0);
140 +
#endif
141 +
				exit(0);
142 +
			} else if (i2c_data[1] == 1) {
143 +
				smc_requested_reset = true;
144 +
			}
145 +
			break;
146 +
		case 2:
147 +
			if (i2c_data[1] == 0) {
148 +
				smc_requested_reset = true;
149 +
			}
150 +
			break;
151 +
		case 3:
152 +
			if (i2c_data[1] == 0) {
153 +
				nmi6502();
154 +
			}
155 +
			break;
156 +
		case 4:
157 +
			// Power LED is not controllable
158 +
			break;
159 +
		case 5:
160 +
			activity_led = i2c_data[1] >= 128 ? 255 : 0;
161 +
			break;
162 +
163 +
		case 0x20:
164 +
			mouse_set_device_id(i2c_data[1]);
165 +
			break;
166 +
167 +
		case 0x40:
168 +
			default_read_op = i2c_data[1];
169 +
			default_read_state = 0;
170 +
			break;
171 +
	}
172 +
173 +
	i2c_data[0] = default_read_op;
174 +
	i2c_data_pos = 0;
175 +
}
176 +
added src/smc.h +21 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2021 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _SMC_H_
6 +
#define _SMC_H_
7 +
8 +
#define SMC_VERSION_MAJOR 47
9 +
#define SMC_VERSION_MINOR 0
10 +
#define SMC_VERSION_PATCH 0
11 +
12 +
#include <stdint.h>
13 +
14 +
extern void nmi6502();
15 +
void smc_i2c_data(uint8_t v);
16 +
uint8_t smc_read();
17 +
void smc_write();
18 +
19 +
extern bool smc_requested_reset;
20 +
21 +
#endif
added src/testbench.c +348 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdio.h>
6 +
#include <stdlib.h>
7 +
#include <string.h>
8 +
#include "memory.h"
9 +
#include "cpu/fake6502.h"
10 +
#include "glue.h"
11 +
#include "testbench.h"
12 +
13 +
int hex_to_int8(char* str);
14 +
int hex_to_int16(char* str);
15 +
bool hex_validate(char* str);
16 +
void invalid();
17 +
void ready();
18 +
19 +
size_t get_testline(char **lineptr, size_t *n, FILE *stream)
20 +
{
21 +
    char *cptr;
22 +
    size_t maxlen;
23 +
    size_t slen;
24 +
    int c;
25 +
26 +
    if(*lineptr == NULL) {
27 +
        *lineptr = malloc(256);
28 +
        *n = 256;
29 +
    }
30 +
31 +
    cptr = *lineptr;
32 +
    maxlen = *n;
33 +
    slen = 0;
34 +
35 +
    do {
36 +
        c = fgetc(stream);
37 +
38 +
        if (c == EOF) {
39 +
            break;
40 +
        }
41 +
42 +
        *cptr = c;
43 +
        ++cptr;
44 +
        ++slen;
45 +
46 +
        if (slen >= maxlen) {
47 +
            maxlen = maxlen << 1;
48 +
            *lineptr = realloc(*lineptr, maxlen);
49 +
            cptr = *lineptr + slen;
50 +
        }
51 +
    } while(c != '\n');
52 +
53 +
    *cptr = '\0';
54 +
    *n = maxlen;
55 +
    return slen;
56 +
}
57 +
58 +
void testbench_init()
59 +
{
60 +
    char* line = NULL;
61 +
62 +
    char addr[5] = "0000";
63 +
    char addr2[5] = "0000";
64 +
    char val[3] = "00";
65 +
66 +
    int iaddr, iaddr2, ival;
67 +
    size_t slen = 0;
68 +
69 +
    bool init_done = false;
70 +
71 +
    ready();
72 +
73 +
    while (!init_done) {
74 +
        size_t len = get_testline(&line, &slen, stdin);     //Read command from stdin
75 +
76 +
		if(len==0) {
77 +
			puts("Exit testbench.");
78 +
			exit(0);
79 +
		}
80 +
81 +
        if (strncmp(line, "RAM", 3) == 0) {                 //Set RAM bank
82 +
            if (len < 7) {
83 +
                invalid();
84 +
            } else {
85 +
                strncpy(val, line + 4, 2);
86 +
                ival = hex_to_int8(val);
87 +
                if (ival == -1) {
88 +
                    invalid();
89 +
                } else {
90 +
                    memory_set_ram_bank((uint8_t)ival);
91 +
                    ready();
92 +
                }
93 +
            }
94 +
        }
95 +
96 +
        else if (strncmp(line, "ROM", 3) == 0) {            //Set ROM bank
97 +
            if (len < 7) {
98 +
                invalid();
99 +
            } else {
100 +
                strncpy(val, line + 4, 2);
101 +
                ival = hex_to_int8(val);
102 +
                if (ival == -1) {
103 +
                    invalid();
104 +
                } else {
105 +
                    memory_set_rom_bank((uint8_t)ival);
106 +
                    ready();
107 +
                }
108 +
            }
109 +
        }
110 +
111 +
        else if (strncmp(line, "STM", 3) == 0) {            //Set memory address value
112 +
            if (len < 12) {
113 +
                invalid();
114 +
            } else {
115 +
                strncpy(addr, line + 4, 4);
116 +
                strncpy(val, line + 9, 2);
117 +
118 +
                iaddr = hex_to_int16(addr);
119 +
                ival = hex_to_int8(val);
120 +
121 +
                if (iaddr == -1 || ival == -1) {
122 +
                    invalid();
123 +
                } else {
124 +
                    write6502((uint16_t)iaddr, regs.db, (uint8_t)ival);
125 +
                    ready();
126 +
                }
127 +
            }
128 +
        }
129 +
130 +
        else if (strncmp(line, "FLM", 3) == 0) {            //Fill memory address range with value
131 +
            if (len < 17) {
132 +
                invalid();
133 +
            } else {
134 +
                strncpy(addr, line + 4, 4);
135 +
                strncpy(addr2, line + 9, 4);
136 +
                strncpy(val, line + 14, 2);
137 +
138 +
                iaddr = hex_to_int16(addr);
139 +
                iaddr2 = hex_to_int16(addr2);
140 +
                ival = hex_to_int8(val);
141 +
142 +
                if (iaddr == -1 || iaddr2 == -1 || ival == -1) {
143 +
                    invalid();
144 +
                } else {
145 +
                    for (; iaddr <= iaddr2; iaddr++) {
146 +
                        write6502((uint16_t)iaddr, regs.db, (uint8_t)ival);
147 +
                    }
148 +
                    ready();
149 +
                }
150 +
            }
151 +
        }
152 +
153 +
        else if (strncmp(line, "STA", 3) == 0) {            //Set accumulator value
154 +
            if (len < 7) {
155 +
                invalid();
156 +
            } else {
157 +
                strncpy(val, line + 4, 2);
158 +
                ival = hex_to_int8(val);
159 +
                if (ival == -1) {
160 +
                    invalid();
161 +
                } else {
162 +
                    regs.a = (uint8_t)ival;
163 +
                    ready();
164 +
                }
165 +
            }
166 +
        }
167 +
168 +
        else if (strncmp(line, "STX", 3) == 0) {            //Set X register value
169 +
            if (len < 7) {
170 +
                invalid();
171 +
            } else {
172 +
                strncpy(val, line + 4, 2);
173 +
                ival = hex_to_int8(val);
174 +
                if (ival == -1) {
175 +
                    invalid();
176 +
                } else {
177 +
                    regs.x = (uint8_t)ival;
178 +
                    ready();
179 +
                }
180 +
            }
181 +
        }
182 +
183 +
        else if (strncmp(line, "STY", 3) == 0) {            //Set Y register value
184 +
            if (len < 7) {
185 +
                invalid();
186 +
            } else {
187 +
                strncpy(val, line + 4, 2);
188 +
                ival = hex_to_int8(val);
189 +
                if (ival == -1) {
190 +
                    invalid();
191 +
                } else {
192 +
                    regs.y = (uint8_t)ival;
193 +
                    ready();
194 +
                }
195 +
            }
196 +
        }
197 +
198 +
        else if (strncmp(line, "SST", 3) == 0) {            //Set status register value
199 +
            if (len < 7) {
200 +
                invalid();
201 +
            } else {
202 +
                strncpy(val, line + 4, 2);
203 +
                ival = hex_to_int8(val);
204 +
                if (ival == -1) {
205 +
                    invalid();
206 +
                } else {
207 +
                    regs.status = (uint8_t)ival;
208 +
                    ready();
209 +
                }
210 +
            }
211 +
        }
212 +
213 +
        else if (strncmp(line, "SSP", 3) == 0) {            //Set stack pointer value
214 +
            if (len < 7) {
215 +
                invalid();
216 +
            } else {
217 +
                strncpy(val, line + 4, 2);
218 +
                ival = hex_to_int8(val);
219 +
                if (ival == -1) {
220 +
                    invalid();
221 +
                } else {
222 +
                    regs.sp = (regs.sp & 0xFF00) | (uint8_t)ival;
223 +
                    ready();
224 +
                }
225 +
            }
226 +
        }
227 +
228 +
        else if (strncmp(line, "RUN", 3) == 0) {            //Run code at address
229 +
            if (len < 9) {
230 +
                invalid();
231 +
            } else {
232 +
                strncpy(addr, line + 4, 4);
233 +
234 +
                iaddr = hex_to_int16(addr);
235 +
                if (iaddr == -1) {
236 +
                    invalid();
237 +
                } else {
238 +
                    write6502(regs.sp, 0, (0xfffd -1) >> 8);
239 +
                    decrement_wrap_at_page_boundary(&regs.sp);
240 +
                    write6502(regs.sp, 0, (0xfffd - 1) & 255);
241 +
                    decrement_wrap_at_page_boundary(&regs.sp);
242 +
                    regs.pc = (uint16_t)iaddr;
243 +
244 +
                    init_done=true;
245 +
                }
246 +
            }
247 +
        }
248 +
249 +
        else if(strncmp(line, "RQM", 3) == 0) {             //Request memory address value
250 +
            if (len < 9) {
251 +
                invalid();
252 +
            } else {
253 +
                strncpy(addr, line + 4, 4);
254 +
                iaddr = hex_to_int16(addr);
255 +
                if (iaddr == -1) {
256 +
                    invalid();
257 +
                } else {
258 +
                    printf("%lx\n", (long)debug_read6502((uint16_t)iaddr, regs.db, USE_CURRENT_X16_BANK));
259 +
                    fflush(stdout);
260 +
                }
261 +
            }
262 +
        }
263 +
264 +
        else if(strncmp(line, "RQA", 3) == 0) {             //Request accumulator value
265 +
            printf("%lx\n", (long)regs.a);
266 +
            fflush(stdout);
267 +
        }
268 +
269 +
        else if(strncmp(line, "RQX", 3) == 0) {             //Request X register value
270 +
            printf("%lx\n", (long)regs.xl);
271 +
            fflush(stdout);
272 +
        }
273 +
274 +
        else if(strncmp(line, "RQY", 3) == 0) {             //Request Y register value
275 +
            printf("%lx\n", (long)regs.yl);
276 +
            fflush(stdout);
277 +
        }
278 +
279 +
        else if(strncmp(line, "RST", 3) == 0) {             //Request status register value
280 +
            printf("%lx\n", (long)regs.status);
281 +
            fflush(stdout);
282 +
        }
283 +
284 +
        else if(strncmp(line, "RSP", 3) == 0) {             //Request stack pointer value
285 +
            printf("%lx\n", (long)regs.sp);
286 +
            fflush(stdout);
287 +
        }
288 +
289 +
        else {
290 +
            printf("ERR Unknown command\n");
291 +
            fflush(stdout);
292 +
        }
293 +
    }
294 +
    free(line);
295 +
}
296 +
297 +
int hex_to_int8(char* str)
298 +
{
299 +
    int val;
300 +
301 +
    if (strlen(str) != 2 || !hex_validate(str)) {
302 +
        return -1;
303 +
    } else {
304 +
        val = (int)strtol(str, NULL, 16);
305 +
        if (val < 0 || val > 0xff) return -1; else return val;
306 +
    }
307 +
}
308 +
309 +
int hex_to_int16(char* str)
310 +
{
311 +
    int val;
312 +
313 +
    if (strlen(str) != 4 || !hex_validate(str)) {
314 +
        return -1;
315 +
    } else {
316 +
        val = (int)strtol(str, NULL, 16);
317 +
        if (val < 0 || val > 0xffff) return -1; else return val;
318 +
    }
319 +
}
320 +
321 +
bool hex_validate(char* str)
322 +
{
323 +
    int i=0;
324 +
    char c;
325 +
326 +
    c = str[i];
327 +
    while (c != 0) {
328 +
        if ( (c>= '0' && c<= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
329 +
            i++;
330 +
        } else {
331 +
            return false;
332 +
        }
333 +
        c = str[i];
334 +
    }
335 +
    return true;
336 +
}
337 +
338 +
void invalid()
339 +
{
340 +
    printf("ERR Invalid command\n");
341 +
    fflush(stdout);
342 +
}
343 +
344 +
void ready()
345 +
{
346 +
    printf("RDY\n");
347 +
    fflush(stdout);
348 +
}
added src/testbench.h +63 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
/*
6 +
TESTBENCH
7 +
---------
8 +
9 +
1. General
10 +
11 +
The testbench option is intended to be used for unit testing where the test runner is
12 +
made in a high level language. The test runner starts the emulator with the
13 +
-testbench option. During the test, the test runner communicates with the emulator
14 +
over stdin/stdout.
15 +
16 +
17 +
2. Messages from the emulator to the test runner (stdout)
18 +
19 +
RDY                 Sent when the emulator is ready to receive commands from the test runner
20 +
<hex value>         Emulator response to get requests
21 +
ERR <message>       Invalid arguments
22 +
STP                 Sent when STP instruction is executed
23 +
24 +
25 +
3. Messages from the test runner to the emulator (stdin)
26 +
27 +
RAM                 Set RAM bank
28 +
ROM                 Set ROM bank
29 +
STM nnnn nn         Set value of memory address nnnn to nn
30 +
FLM nnnn nnnn nn    Fill memory address range nnnn to nnnn with value nn
31 +
STA nn              Set accumulator value to nn
32 +
STX nn              Set X register value to nn
33 +
STY nn              Set Y register value to nn
34 +
SST nn              Set status register value to nn
35 +
SSP nn              Set stack pointer value to nn
36 +
RUN nnnn            Execute code at address nnnn, when done emulator respond with RDY
37 +
RQM nnnn            Request value of memory address nnnn
38 +
RQA                 Request value of accumulator
39 +
RQX                 Request value of X register
40 +
RQY                 Request value of Y register
41 +
RST                 Request value of status register
42 +
RSP                 Request value of stack pointer
43 +
44 +
45 +
4. Message formatting
46 +
47 +
Commands sent to the emulator must be formatted precisely as follows:
48 +
49 +
- One command per line written to stdin
50 +
- The first three characters is the command in upper case
51 +
- 16 bit values must be written with exactly four digits in hex format
52 +
- 8 bit values must be written with exactly two digits in hex format
53 +
- There is exactly one character separating the command field and each value field; it doesn't matter what character is used as separator
54 +
55 +
*/
56 +
57 +
58 +
#ifndef TEST_H
59 +
#define TEST_H
60 +
61 +
void testbench_init();
62 +
63 +
#endif
added src/timing.c +72 −0
1 +
#ifndef __APPLE__
2 +
#define _XOPEN_SOURCE   600
3 +
#define _POSIX_C_SOURCE 1
4 +
#endif
5 +
#include "glue.h"
6 +
#include "video.h"
7 +
#include "cpu/fake6502.h"
8 +
#include <SDL.h>
9 +
#include <stdio.h>
10 +
#include <unistd.h>
11 +
12 +
uint32_t frames;
13 +
uint32_t sdlTicks_base;
14 +
uint32_t last_perf_update;
15 +
uint32_t clockticks6502_old;
16 +
int64_t cpu_ticks;
17 +
int64_t last_perf_cpu_ticks;
18 +
char window_title[255];
19 +
20 +
void
21 +
timing_init() {
22 +
	frames = 0;
23 +
	sdlTicks_base = SDL_GetTicks();
24 +
	last_perf_update = 0;
25 +
	last_perf_cpu_ticks = 0;
26 +
	clockticks6502_old = clockticks6502;
27 +
	cpu_ticks = 0;
28 +
}
29 +
30 +
void
31 +
timing_update()
32 +
{
33 +
	frames++;
34 +
	cpu_ticks += clockticks6502 - clockticks6502_old;
35 +
	clockticks6502_old = clockticks6502;
36 +
	uint32_t sdlTicks = SDL_GetTicks() - sdlTicks_base;
37 +
	int64_t diff_time = cpu_ticks / MHZ - sdlTicks * 1000LL;
38 +
	if (!warp_mode && diff_time > 0) {
39 +
		if (diff_time >= 1000000) {
40 +
			sleep(diff_time / 1000000);
41 +
			diff_time %= 1000000;
42 +
		}
43 +
		usleep(diff_time);
44 +
	}
45 +
46 +
	if (sdlTicks - last_perf_update > 5000) {
47 +
		uint32_t perf = (cpu_ticks - last_perf_cpu_ticks) / (MHZ * 50000);
48 +
49 +
		if (perf < 100 || warp_mode) {
50 +
			sprintf(window_title, WINDOW_TITLE " (%d%%)%s", perf, mouse_grabbed ? MOUSE_GRAB_MSG : "");
51 +
		} else {
52 +
			sprintf(window_title, WINDOW_TITLE "%s", mouse_grabbed ? MOUSE_GRAB_MSG : "");
53 +
		}
54 +
55 +
		video_update_title(window_title);
56 +
57 +
		last_perf_cpu_ticks = cpu_ticks;
58 +
		last_perf_update = sdlTicks;
59 +
	}
60 +
61 +
	if (log_speed) {
62 +
		float frames_behind = -((float)diff_time * 6e-5);
63 +
		int load = (int)((1 + frames_behind) * 100);
64 +
		printf("Load: %d%%\n", load > 100 ? 100 : load);
65 +
66 +
		if ((int)frames_behind > 0) {
67 +
			printf("Rendering is behind %d frames.\n", -(int)frames_behind);
68 +
		} else {
69 +
		}
70 +
	}
71 +
}
72 +
added src/timing.h +7 −0
1 +
#ifndef TIMING_H
2 +
#define TIMING_H
3 +
4 +
void timing_init();
5 +
void timing_update();
6 +
7 +
#endif
added src/utf8.h +68 −0
1 +
/* Branchless UTF-8 decoder
2 +
 *
3 +
 * This is free and unencumbered software released into the public domain.
4 +
 */
5 +
#ifndef UTF8_H
6 +
#define UTF8_H
7 +
8 +
#include <stdint.h>
9 +
10 +
/* Decode the next character, C, from BUF, reporting errors in E.
11 +
 *
12 +
 * Since this is a branchless decoder, four bytes will be read from the
13 +
 * buffer regardless of the actual length of the next character. This
14 +
 * means the buffer _must_ have at least three bytes of zero padding
15 +
 * following the end of the data stream.
16 +
 *
17 +
 * Errors are reported in E, which will be non-zero if the parsed
18 +
 * character was somehow invalid: invalid byte sequence, non-canonical
19 +
 * encoding, or a surrogate half.
20 +
 *
21 +
 * The function returns a pointer to the next character. When an error
22 +
 * occurs, this pointer will be a guess that depends on the particular
23 +
 * error, but it will always advance at least one byte.
24 +
 */
25 +
static void *
26 +
utf8_decode(void *buf, uint32_t *c, int *e)
27 +
{
28 +
	static const char lengths[] = {
29 +
		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
30 +
		0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0
31 +
	};
32 +
	static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
33 +
	static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
34 +
	static const int shiftc[] = {0, 18, 12, 6, 0};
35 +
	static const int shifte[] = {0, 6, 4, 2, 0};
36 +
37 +
	unsigned char *s = buf;
38 +
	int len = lengths[s[0] >> 3];
39 +
40 +
	/* Compute the pointer to the next character early so that the next
41 +
	 * iteration can start working on the next character. Neither Clang
42 +
	 * nor GCC figure out this reordering on their own.
43 +
	 */
44 +
	unsigned char *next = s + len + !len;
45 +
46 +
	/* Assume a four-byte character and load four bytes. Unused bits are
47 +
	 * shifted out.
48 +
	 */
49 +
	*c  = (uint32_t)(s[0] & masks[len]) << 18;
50 +
	*c |= (uint32_t)(s[1] & 0x3f) << 12;
51 +
	*c |= (uint32_t)(s[2] & 0x3f) <<  6;
52 +
	*c |= (uint32_t)(s[3] & 0x3f) <<  0;
53 +
	*c >>= shiftc[len];
54 +
55 +
	/* Accumulate the various error conditions. */
56 +
	*e  = (*c < mins[len]) << 6; // non-canonical encoding
57 +
	*e |= ((*c >> 11) == 0x1b) << 7;  // surrogate half?
58 +
	*e |= (*c > 0x10FFFF) << 8;  // out of range?
59 +
	*e |= (s[1] & 0xc0) >> 2;
60 +
	*e |= (s[2] & 0xc0) >> 4;
61 +
	*e |= (s[3]       ) >> 6;
62 +
	*e ^= 0x2a; // top two bits of each tail byte correct?
63 +
	*e >>= shifte[len];
64 +
65 +
	return next;
66 +
}
67 +
68 +
#endif
added src/utf8_encode.h +55 −0
1 +
#pragma once
2 +
3 +
#include <stdint.h>
4 +
5 +
/**
6 +
 * Encode a code point using UTF-8
7 +
 *
8 +
 * @author Ondřej Hruška <[email protected]>
9 +
 * @license MIT
10 +
 *
11 +
 * @param out - output buffer (min 5 characters), will be 0-terminated
12 +
 * @param utf - code point 0-0x10FFFF
13 +
 * @return number of bytes on success, 0 on failure (also produces U+FFFD, which uses 3 bytes)
14 +
 */
15 +
static int utf8_encode(char *out, uint32_t utf)
16 +
{
17 +
	if (utf <= 0x7F) {
18 +
		// Plain ASCII
19 +
		out[0] = (char) utf;
20 +
		out[1] = 0;
21 +
		return 1;
22 +
	}
23 +
	else if (utf <= 0x07FF) {
24 +
		// 2-byte unicode
25 +
		out[0] = (char) (((utf >> 6) & 0x1F) | 0xC0);
26 +
		out[1] = (char) (((utf >> 0) & 0x3F) | 0x80);
27 +
		out[2] = 0;
28 +
		return 2;
29 +
	}
30 +
	else if (utf <= 0xFFFF) {
31 +
		// 3-byte unicode
32 +
		out[0] = (char) (((utf >> 12) & 0x0F) | 0xE0);
33 +
		out[1] = (char) (((utf >>  6) & 0x3F) | 0x80);
34 +
		out[2] = (char) (((utf >>  0) & 0x3F) | 0x80);
35 +
		out[3] = 0;
36 +
		return 3;
37 +
	}
38 +
	else if (utf <= 0x10FFFF) {
39 +
		// 4-byte unicode
40 +
		out[0] = (char) (((utf >> 18) & 0x07) | 0xF0);
41 +
		out[1] = (char) (((utf >> 12) & 0x3F) | 0x80);
42 +
		out[2] = (char) (((utf >>  6) & 0x3F) | 0x80);
43 +
		out[3] = (char) (((utf >>  0) & 0x3F) | 0x80);
44 +
		out[4] = 0;
45 +
		return 4;
46 +
	}
47 +
	else {
48 +
		// error - use replacement character
49 +
		out[0] = (char) 0xEF;
50 +
		out[1] = (char) 0xBF;
51 +
		out[2] = (char) 0xBD;
52 +
		out[3] = 0;
53 +
		return 0;
54 +
	}
55 +
}
added src/vera_pcm.c +182 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2020 Frank van den Hoef
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include "vera_pcm.h"
6 +
#include <stdio.h>
7 +
8 +
static uint8_t  fifo[4096];
9 +
static unsigned fifo_wridx;
10 +
static unsigned fifo_rdidx;
11 +
static unsigned fifo_cnt;
12 +
13 +
static uint8_t ctrl;
14 +
static uint8_t rate;
15 +
static uint8_t loop;
16 +
17 +
static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64};
18 +
19 +
static int16_t cur_l, cur_r;
20 +
static uint8_t phase;
21 +
22 +
static void
23 +
fifo_reset(void)
24 +
{
25 +
	fifo_wridx = 0;
26 +
	fifo_rdidx = 0;
27 +
	fifo_cnt   = 0;
28 +
}
29 +
30 +
static void
31 +
fifo_restart(void)
32 +
{
33 +
	fifo_rdidx = 0;
34 +
	fifo_cnt = fifo_wridx;
35 +
}
36 +
37 +
void
38 +
pcm_reset(void)
39 +
{
40 +
	fifo_reset();
41 +
	ctrl  = 0;
42 +
	rate  = 0;
43 +
	cur_l = 0;
44 +
	cur_r = 0;
45 +
	phase = 0;
46 +
}
47 +
48 +
void
49 +
pcm_write_ctrl(uint8_t val)
50 +
{
51 +
	if ((val & 0xc0) == 0xc0) {
52 +
		loop = true;
53 +
	} else {
54 +
		loop = false;
55 +
		if (val & 0x80) {
56 +
			fifo_reset();
57 +
		}
58 +
	}
59 +
	if (val & 0x40) {
60 +
		fifo_restart();
61 +
	}
62 +
	ctrl = val & 0x3F;
63 +
}
64 +
65 +
uint8_t
66 +
pcm_read_ctrl(void)
67 +
{
68 +
	uint8_t result = ctrl;
69 +
	if (fifo_cnt == sizeof(fifo) - 1) {
70 +
		result |= 0x80;
71 +
	}
72 +
	if (fifo_cnt == 0) {
73 +
		result |= 0x40;
74 +
	}
75 +
	return result;
76 +
}
77 +
78 +
void
79 +
pcm_write_rate(uint8_t val)
80 +
{
81 +
	rate = (val > 128) ? (256 - val) : val;
82 +
}
83 +
84 +
uint8_t
85 +
pcm_read_rate(void)
86 +
{
87 +
	return rate;
88 +
}
89 +
90 +
void
91 +
pcm_write_fifo(uint8_t val)
92 +
{
93 +
	if (fifo_cnt < sizeof(fifo) - 1) {
94 +
		fifo[fifo_wridx++] = val;
95 +
		if (fifo_wridx == sizeof(fifo)) {
96 +
			fifo_wridx = 0;
97 +
		}
98 +
		fifo_cnt++;
99 +
	}
100 +
}
101 +
102 +
static uint8_t
103 +
read_fifo()
104 +
{
105 +
	static uint8_t result = 0;
106 +
	if (fifo_cnt == 0) {
107 +
		return 0;
108 +
	}
109 +
	result = fifo[fifo_rdidx++];
110 +
	if (fifo_rdidx == sizeof(fifo)) {
111 +
		fifo_rdidx = 0;
112 +
	}
113 +
	fifo_cnt--;
114 +
	return result;
115 +
}
116 +
117 +
bool
118 +
pcm_is_fifo_almost_empty(void)
119 +
{
120 +
	return fifo_cnt < 1024;
121 +
}
122 +
123 +
void
124 +
pcm_render(int16_t *buf, unsigned num_samples)
125 +
{
126 +
	while (num_samples--) {
127 +
		uint8_t old_phase = phase;
128 +
		phase += rate;
129 +
		if ((old_phase & 0x80) != (phase & 0x80)) {
130 +
			if (fifo_cnt == 0) {
131 +
				cur_l = 0;
132 +
				cur_r = 0;
133 +
			} else {
134 +
				switch ((ctrl >> 4) & 3) {
135 +
					case 0: { // mono 8-bit
136 +
						cur_l = (int16_t)read_fifo() << 8;
137 +
						cur_r = cur_l;
138 +
						break;
139 +
					}
140 +
					case 1: { // stereo 8-bit
141 +
						if (fifo_cnt < 2) {
142 +
							fifo_cnt = 0;
143 +
							fifo_rdidx = fifo_wridx;
144 +
						} else {
145 +
							cur_l = read_fifo() << 8;
146 +
							cur_r = read_fifo() << 8;
147 +
						}
148 +
						break;
149 +
					}
150 +
					case 2: { // mono 16-bit
151 +
						if (fifo_cnt < 2) {
152 +
							fifo_cnt = 0;
153 +
							fifo_rdidx = fifo_wridx;
154 +
						} else {
155 +
							cur_l = read_fifo();
156 +
							cur_l |= read_fifo() << 8;
157 +
							cur_r = cur_l;
158 +
						}
159 +
						break;
160 +
					}
161 +
					case 3: { // stereo 16-bit
162 +
						if (fifo_cnt < 4) {
163 +
							fifo_cnt = 0;
164 +
							fifo_rdidx = fifo_wridx;
165 +
						} else {
166 +
							cur_l = read_fifo();
167 +
							cur_l |= read_fifo() << 8;
168 +
							cur_r = read_fifo();
169 +
							cur_r |= read_fifo() << 8;
170 +
						}
171 +
						break;
172 +
					}
173 +
				}
174 +
				if (loop && fifo_cnt == 0) {
175 +
					fifo_restart();
176 +
				}
177 +
			}
178 +
		}
179 +
		*(buf++) = (int16_t)((int32_t)cur_l * volume_lut[ctrl & 0xF] / 64);
180 +
		*(buf++) = (int16_t)((int32_t)cur_r * volume_lut[ctrl & 0xF] / 64);
181 +
	}
182 +
}
added src/vera_pcm.h +17 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2020 Frank van den Hoef
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#pragma once
6 +
7 +
#include <stdint.h>
8 +
#include <stdbool.h>
9 +
10 +
void    pcm_reset(void);
11 +
void    pcm_write_ctrl(uint8_t val);
12 +
uint8_t pcm_read_ctrl(void);
13 +
void    pcm_write_rate(uint8_t val);
14 +
uint8_t pcm_read_rate(void);
15 +
void    pcm_write_fifo(uint8_t val);
16 +
void    pcm_render(int16_t *buf, unsigned num_samples);
17 +
bool    pcm_is_fifo_almost_empty(void);
added src/vera_psg.c +126 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2020 Frank van den Hoef
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include "vera_psg.h"
6 +
7 +
#include <stdbool.h>
8 +
#include <string.h>
9 +
10 +
enum waveform {
11 +
	WF_PULSE = 0,
12 +
	WF_SAWTOOTH,
13 +
	WF_TRIANGLE,
14 +
	WF_NOISE,
15 +
};
16 +
17 +
struct channel {
18 +
	uint16_t freq;
19 +
	uint16_t volume;
20 +
	bool     left, right;
21 +
	uint8_t  pw;
22 +
	uint8_t  waveform;
23 +
24 +
	uint16_t noiseval;
25 +
	uint32_t phase;
26 +
};
27 +
28 +
static struct channel channels[16];
29 +
30 +
static uint16_t volume_lut[64] = {
31 +
	  0,                                           4,   8,  12,
32 +
	 16,  17,  18,  20,  21,  22,  23,  25,  26,  28,  30,  31,
33 +
	 33,  35,  37,  40,  42,  45,  47,  50,  53,  56,  60,  63,
34 +
	 67,  71,  75,  80,  85,  90,  95, 101, 107, 113, 120, 127,
35 +
	135, 143, 151, 160, 170, 180, 191, 202, 214, 227, 241, 255,
36 +
	270, 286, 303, 321, 341, 361, 382, 405, 429, 455, 482, 511
37 +
};
38 +
39 +
static uint16_t noise_state;
40 +
41 +
void
42 +
psg_reset(void)
43 +
{
44 +
	memset(channels, 0, sizeof(channels));
45 +
	noise_state = 1;
46 +
}
47 +
48 +
void
49 +
psg_writereg(uint8_t reg, uint8_t val)
50 +
{
51 +
	reg &= 0x3f;
52 +
53 +
	int ch  = reg / 4;
54 +
	int idx = reg & 3;
55 +
56 +
	switch (idx) {
57 +
		case 0: channels[ch].freq = (channels[ch].freq & 0xFF00) | val; break;
58 +
		case 1: channels[ch].freq = (channels[ch].freq & 0x00FF) | (val << 8); break;
59 +
		case 2: {
60 +
			channels[ch].right  = (val & 0x80) != 0;
61 +
			channels[ch].left   = (val & 0x40) != 0;
62 +
			channels[ch].volume = volume_lut[val & 0x3F];
63 +
			break;
64 +
		}
65 +
		case 3: {
66 +
			channels[ch].pw       = val & 0x3F;
67 +
			channels[ch].waveform = val >> 6;
68 +
			break;
69 +
		}
70 +
	}
71 +
}
72 +
73 +
static void
74 +
render(int16_t *left, int16_t *right)
75 +
{
76 +
	int16_t l = 0;
77 +
	int16_t r = 0;
78 +
79 +
	for (int i = 0; i < 16; i++) {
80 +
		// In FPGA implementation, noise values are generated every system clock and
81 +
		// the channel update is run sequentially. So, even if both two channels are
82 +
		// fetching a noise value in the same sample, they should have different values
83 +
		noise_state = (noise_state << 1) | (((noise_state >> 1) ^ (noise_state >> 2) ^ (noise_state >> 4) ^ (noise_state >> 15)) & 1);
84 +
85 +
		struct channel *ch = &channels[i];
86 +
87 +
		uint32_t new_phase = (ch->left || ch->right) ? ((ch->phase + ch->freq) & 0x1FFFF) : 0;
88 +
		if ((ch->phase & 0x10000) && !(new_phase & 0x10000)) {
89 +
			ch->noiseval = (noise_state >> 1) & 0x3F;
90 +
		}
91 +
		ch->phase = new_phase;
92 +
93 +
		uint32_t v = 0;
94 +
		switch (ch->waveform) {
95 +
			case WF_PULSE: v = ((ch->phase >> 10) > ch->pw) ? 0 : 0x3F; break;
96 +
    		case WF_SAWTOOTH: v = (ch->phase >> 11) ^ ((ch->pw ^ 0x3f) & 0x3f); break;
97 +
			case WF_TRIANGLE: v = ((ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F)) ^ ((ch->pw ^ 0x3f) & 0x3f); break;		
98 +
			case WF_NOISE: v = ch->noiseval; break;
99 +
		}
100 +
		int16_t sv = (v ^ 0x20);
101 +
		if (sv & 0x20) {
102 +
			sv |= 0xFFC0;
103 +
		}
104 +
105 +
		int16_t val = sv * ch->volume;
106 +
107 +
		if (ch->left) {
108 +
			l += val >> 3;
109 +
		}
110 +
		if (ch->right) {
111 +
			r += val >> 3;
112 +
		}
113 +
	}
114 +
115 +
	*left  = l;
116 +
	*right = r;
117 +
}
118 +
119 +
void
120 +
psg_render(int16_t *buf, unsigned num_samples)
121 +
{
122 +
	while (num_samples--) {
123 +
		render(&buf[0], &buf[1]);
124 +
		buf += 2;
125 +
	}
126 +
}
added src/vera_psg.h +11 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2020 Frank van den Hoef
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#pragma once
6 +
7 +
#include <stdint.h>
8 +
9 +
void psg_reset(void);
10 +
void psg_writereg(uint8_t reg, uint8_t val);
11 +
void psg_render(int16_t *buf, unsigned num_samples);
added src/vera_spi.c +84 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <stdio.h>
6 +
#include <stdbool.h>
7 +
#include "sdcard.h"
8 +
9 +
#define SPI_CLOCK_RATE_MHZ 12.5f
10 +
11 +
bool ss;
12 +
bool busy;
13 +
bool autotx;
14 +
uint8_t sending_byte, received_byte;
15 +
float outcounter;
16 +
17 +
void
18 +
vera_spi_init()
19 +
{
20 +
	ss = false;
21 +
	busy = false;
22 +
	autotx = false;
23 +
	received_byte = 0xff;
24 +
}
25 +
26 +
void
27 +
vera_spi_step(int mhz, int clocks)
28 +
{
29 +
	if (busy) {
30 +
		outcounter += (float)clocks * SPI_CLOCK_RATE_MHZ / (float)mhz;
31 +
		if (outcounter >= 10) { // A value of 9 here is closer to reality, but hardware
32 +
		                        // can take slightly longer depending on clock-alignment.
33 +
								// 10 cycles here should be safe and won't succeed in emulation
34 +
								// while failing on hardware.
35 +
			busy = false;
36 +
			if (sdcard_attached) {
37 +
				received_byte = sdcard_handle(sending_byte);
38 +
			} else {
39 +
				received_byte = 0xff;
40 +
			}
41 +
		}
42 +
	}
43 +
}
44 +
45 +
uint8_t
46 +
vera_spi_read(uint8_t reg)
47 +
{
48 +
	switch (reg) {
49 +
		case 0:
50 +
			if (autotx && ss && !busy) {
51 +
				// autotx mode will automatically send $FF after each read
52 +
				sending_byte = 0xff;
53 +
				busy = true;
54 +
				outcounter = 0;
55 +
			}
56 +
			return received_byte;
57 +
		case 1:
58 +
			return busy << 7 | autotx << 2 | ss;
59 +
	}
60 +
	return 0;
61 +
}
62 +
63 +
void
64 +
vera_spi_write(uint8_t reg, uint8_t value)
65 +
{
66 +
	switch (reg) {
67 +
		case 0:
68 +
			if (ss && !busy) {
69 +
				sending_byte = value;
70 +
				busy = true;
71 +
				outcounter = 0;
72 +
			}
73 +
			break;
74 +
		case 1:
75 +
			if (ss != (value & 1)) {
76 +
				ss = value & 1;
77 +
				if (ss) {
78 +
					sdcard_select(ss);
79 +
				}
80 +
			}
81 +
			autotx = !!(value & 4);
82 +
			break;
83 +
	}
84 +
}
added src/vera_spi.h +10 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include <inttypes.h>
6 +
7 +
void vera_spi_init();
8 +
void vera_spi_step(int mhz, int clocks);
9 +
uint8_t vera_spi_read(uint8_t address);
10 +
void vera_spi_write(uint8_t address, uint8_t value);
added src/version.h +7 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019, 2024 Michael Steil, et al
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#define VER "49"
6 +
#define VER_NAME "Pyrite"
7 +
#define VER_INFO "### Release 49 (\"Pyrite\")"
added src/via.c +362 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include "via.h"
6 +
#include "i2c.h"
7 +
#include "memory.h"
8 +
#include "serial.h"
9 +
#include <stdio.h>
10 +
#include <stdlib.h>
11 +
#include <time.h>
12 +
#include <string.h>
13 +
//XXX
14 +
#include "glue.h"
15 +
#include "joystick.h"
16 +
17 +
typedef struct {
18 +
	unsigned timer_count[2];
19 +
	unsigned pb6_pulse_counts;
20 +
	uint8_t registers[15];
21 +
	bool timer1_m1;
22 +
	bool timer_running[2];
23 +
	bool pb7_output;
24 +
} via_t;
25 +
26 +
static via_t via[2];
27 +
28 +
// only internal logic is handled here, see via1/2 calls for external
29 +
// operations specific to each unit
30 +
31 +
static void
32 +
via_init(via_t *via)
33 +
{
34 +
	// timer latches, timer counters and SR are not cleared
35 +
	for (int i = 0; i < 4; i++) via->registers[i] = 0;
36 +
	for (int i = 11; i < 15; i++) via->registers[i] = 0;
37 +
	via->timer_running[0] = false;
38 +
	via->timer_running[1] = false;
39 +
	via->timer1_m1 = false;
40 +
	via->pb7_output = true;
41 +
}
42 +
43 +
static void
44 +
via_clear_pra_irqs(via_t *via)
45 +
{
46 +
	via->registers[13] &= ~0x02;
47 +
	if ((via->registers[12] & 0b00001010) != 0b00000010) {
48 +
		via->registers[13] &= ~0x01;
49 +
	}
50 +
}
51 +
52 +
static void
53 +
via_clear_prb_irqs(via_t *via)
54 +
{
55 +
	via->registers[13] &= ~0x10;
56 +
	if ((via->registers[12] & 0b10100000) != 0b00100000) {
57 +
		via->registers[13] &= ~0x08;
58 +
	}
59 +
}
60 +
61 +
static uint8_t
62 +
via_read(via_t *via, uint8_t reg, bool debug)
63 +
{
64 +
	uint8_t ifr;
65 +
	bool    irq;
66 +
	switch (reg) {
67 +
		case 0: // IRB
68 +
			if (!debug) via_clear_prb_irqs(via);
69 +
			return via->registers[0];
70 +
		case 1: // IRA
71 +
		case 15:
72 +
			if (!debug) via_clear_pra_irqs(via);
73 +
			return via->registers[1];
74 +
		case 4: // T1L
75 +
			if (!debug) via->registers[13] &= ~0x40;
76 +
			return (uint8_t)(via->timer_count[0] & 0xff);
77 +
		case 5: // T1H
78 +
			return (uint8_t)(via->timer_count[0] >> 8);
79 +
		case 8: // T2L
80 +
			if (!debug) via->registers[13] &= ~0x20;
81 +
			return (uint8_t)(via->timer_count[1] & 0xff);
82 +
		case 9: // T2H
83 +
			return (uint8_t)(via->timer_count[1] >> 8);
84 +
		case 10: // SR
85 +
			if (!debug) via->registers[13] &= ~0x04;
86 +
			return via->registers[10];
87 +
		case 13: // IFR
88 +
			ifr = via->registers[13];
89 +
			irq = (ifr & via->registers[14]) != 0;
90 +
			return ((uint8_t)irq << 7) | ifr;
91 +
		case 14: // IER
92 +
			return via->registers[14] | 0x80;
93 +
		default:
94 +
			return via->registers[reg];
95 +
	}
96 +
}
97 +
98 +
static void
99 +
via_write(via_t *via, uint8_t reg, uint8_t value)
100 +
{
101 +
	switch (reg) {
102 +
		case 0: // ORB
103 +
			via_clear_prb_irqs(via);
104 +
			via->registers[0] = value;
105 +
			break;
106 +
		case 1: // ORA
107 +
		case 15:
108 +
			via_clear_pra_irqs(via);
109 +
			via->registers[1] = value;
110 +
			break;
111 +
		case 4: // T1L
112 +
			via->registers[6] = value;
113 +
			break;
114 +
		case 5: // T1H
115 +
		case 7: // T1LH
116 +
			via->registers[13] &= ~0x40;
117 +
			via->registers[7] = value;
118 +
			if (reg == 5) {
119 +
				via->timer_count[0] = ((unsigned)value << 8) | via->registers[6];
120 +
				via->timer_running[0] = true;
121 +
				via->pb7_output = false;
122 +
			}
123 +
			break;
124 +
		case 9: // T2H
125 +
			via->registers[13] &= ~0x20;
126 +
			via->timer_count[1] = ((unsigned)value << 8) | via->registers[8];
127 +
			via->timer_running[1] = true;
128 +
			break;
129 +
		case 10: // SR
130 +
			via->registers[13] &= ~0x04;
131 +
			via->registers[10] = value;
132 +
			break;
133 +
		case 13: // IFR
134 +
			via->registers[13] &= ~(value & 0x7f);
135 +
			break;
136 +
		case 14: // IER
137 +
			if (value & 0x80) {
138 +
				via->registers[14] |= value & 0x7f;
139 +
			} else {
140 +
				via->registers[14] &= ~value & 0x7f;
141 +
			}
142 +
			break;
143 +
		default:
144 +
			via->registers[reg] = value;
145 +
	}
146 +
}
147 +
148 +
static void
149 +
via_step(via_t *via, unsigned clocks)
150 +
{
151 +
	// TODO there's currently no timestamp mechanism to mark exact transition
152 +
	// times, since there's currently no peripherals that require those
153 +
	uint8_t acr = via->registers[11];
154 +
	uint8_t ifr = via->registers[13];
155 +
	// handle timers
156 +
	unsigned cnt;
157 +
	uint32_t tclk, tclk_s, reload;
158 +
	// counter always update even if it's not "running"
159 +
	cnt = via->timer_count[0];
160 +
	tclk = clocks;
161 +
	while (tclk > 0) {
162 +
		if (via->timer1_m1) {
163 +
			reload = (((uint32_t)via->registers[7] << 8) | via->registers[6]);
164 +
			tclk_s = reload + 1;
165 +
			if (tclk < tclk_s) tclk_s = tclk;
166 +
			cnt = reload - tclk_s + 1;
167 +
			via->timer1_m1 = false;
168 +
		} else if (cnt < tclk) {
169 +
			if (via->timer_running[0]) {
170 +
				ifr |= 0x40;
171 +
				via->pb7_output ^= true;
172 +
				if (!(acr & 0x40)) via->timer_running[0] = false;
173 +
			}
174 +
			if (tclk - cnt == 1) {
175 +
				// special, -1 state
176 +
				cnt = 0xffff;
177 +
				via->timer1_m1 = true;
178 +
				tclk_s = 1;
179 +
			} else {
180 +
				reload = (((uint32_t)via->registers[7] << 8) | via->registers[6]);
181 +
				tclk_s = cnt + reload + 2;
182 +
				if (tclk < tclk_s) tclk_s = tclk;
183 +
				cnt += reload - tclk_s + 2;
184 +
			}
185 +
		} else {
186 +
			cnt -= tclk;
187 +
			break;
188 +
		}
189 +
		tclk -= tclk_s;
190 +
	}
191 +
	via->timer_count[0] = (unsigned)cnt;
192 +
193 +
	cnt = via->timer_count[1];
194 +
	tclk = (acr & 0x20) ? via->pb6_pulse_counts : clocks;
195 +
	via->pb6_pulse_counts = 0;
196 +
	if (cnt < tclk) {
197 +
		if (via->timer_running[1]) {
198 +
			ifr |= 0x20;
199 +
			via->timer_running[1] = false;
200 +
		}
201 +
		via->timer_count[1] = 0x10000 + cnt - tclk;
202 +
	} else {
203 +
		via->timer_count[1] -= tclk;
204 +
	}
205 +
206 +
	// TODO Cxx pin and shift register handling
207 +
	
208 +
	via->registers[13] = ifr;
209 +
}
210 +
211 +
//
212 +
// VIA#1
213 +
//
214 +
// PA0: I2CDATA   I2C DATA
215 +
// PA1: I2CCLK    I2C CLK
216 +
// PA2: NESLATCH  NES LATCH (for all controllers)
217 +
// PA3: NESCLK    NES CLK   (for all controllers)
218 +
// PA4: NESDAT3   NES DATA  (controller 3)
219 +
// PA5: NESDAT2   NES DATA  (controller 2)
220 +
// PA6: NESDAT1   NES DATA  (controller 1)
221 +
// PA7: NESDAT0   NES DATA  (controller 0)
222 +
// PB0: -         Unused
223 +
// PB1: -         Unused
224 +
// PB2: -         Unused
225 +
// PB3: IECATTO   Serial ATN  out
226 +
// PB4: IECCLKO   Serial CLK  out
227 +
// PB5: IECDATAO  Serial DATA out
228 +
// PB6: IECCLKI   Serial CLK  in
229 +
// PB7: IECDATAI  Serial DATA in
230 +
// CA1: -         Unused
231 +
// CA2: -         Unused
232 +
// CB1: IECSRQ
233 +
// CB2: -         Unused
234 +
235 +
void
236 +
via1_init()
237 +
{
238 +
	via_init(&via[0]);
239 +
	i2c_port.clk_in = 1;
240 +
	serial_port.in.atn = 0;
241 +
	serial_port.in.clk = 0;
242 +
	serial_port.in.data = 0;
243 +
	serial_port.out.clk = 1;
244 +
	serial_port.out.data = 1;
245 +
}
246 +
247 +
uint8_t
248 +
via1_read(uint8_t reg, bool debug)
249 +
{
250 +
	// DDR=0 (input)  -> take input bit
251 +
	// DDR=1 (output) -> take output bit
252 +
	// For now, just assume that I2C peripherals always drive all lines and VIA
253 +
	// overrides them with their input values based on current DDR bits
254 +
	switch (reg) {
255 +
		case 0: // PB
256 +
			if (!debug) via_clear_prb_irqs(&via[0]);
257 +
			if (via[0].registers[11] & 2) {
258 +
				// TODO latching mechanism (requires IEC implementation)
259 +
				return 0;
260 +
			} else {
261 +
				return
262 +
					(~via[0].registers[2] & (
263 +
						(serial_port_read_clk() << 6) |
264 +
						(serial_port_read_data() << 7)
265 +
					)) |
266 +
					(via[0].registers[2] & (
267 +
						(serial_port.in.atn << 3) |
268 +
						((!serial_port.in.clk) << 4) |
269 +
						((!serial_port.in.data) << 5)
270 +
					));
271 +
			}
272 +
			
273 +
		case 1: // PA
274 +
		case 15:
275 +
			i2c_step();
276 +
			if (!debug) via_clear_pra_irqs(&via[0]);
277 +
			if (via[0].registers[11] & 1) {
278 +
				// CA1 is currently not connected to anything (?)
279 +
				return 0;
280 +
			} else {
281 +
				return (~via[0].registers[3] & i2c_port.data_out) |					//I2C Data: PA0=1 if DDR bit is 0 (input) and data_out is 1; usage of data_out and data_in is a bit confusing...
282 +
					(via[0].registers[3] & i2c_port.data_in) |						//I2C Data: PA0=1 if DDR bit is 1 (output) and data_in is 1
283 +
					(~via[0].registers[3] & I2C_CLK_MASK ) |						//I2C Clock: PA1=1 if DDR bit is 0 (input), simulating an input pull-up
284 +
					(via[0].registers[3] & i2c_port.clk_in) |						//I2C Clock: PA1=1 if DDR bit is 1 (output) and clk_in is 1, simulating a pin driven by the VIA 
285 +
					Joystick_data;
286 +
			}
287 +
288 +
		default:
289 +
			return via_read(&via[0], reg, debug);
290 +
	}
291 +
}
292 +
293 +
void
294 +
via1_write(uint8_t reg, uint8_t value)
295 +
{
296 +
	via_write(&via[0], reg, value);
297 +
	if (reg == 0 || reg == 2) {
298 +
		// PB
299 +
		const uint8_t pb = via[0].registers[0] | ~via[0].registers[2];
300 +
		serial_port.in.atn = (pb & SERIAL_ATNIN_MASK) != 0;
301 +
		serial_port.in.clk = (pb & SERIAL_CLOCKIN_MASK) == 0;
302 +
		serial_port.in.data = (pb & SERIAL_DATAIN_MASK) == 0;
303 +
304 +
	} else if (reg == 1 || reg == 3) {
305 +
		i2c_step();
306 +
		// PA
307 +
		const uint8_t pa = via[0].registers[1] | ~via[0].registers[3];
308 +
		i2c_port.data_in = pa & I2C_DATA_MASK;										//Sets data_in = 1 if the corresponding DDR bit is 0 (input), simulates a pull-up
309 +
		i2c_port.clk_in = (pa & I2C_CLK_MASK) >> 1;									//Sets clk_in = 1 if pin is an input, simulates a pull-up
310 +
		joystick_set_latch(via[0].registers[1] & JOY_LATCH_MASK);
311 +
		joystick_set_clock(via[0].registers[1] & JOY_CLK_MASK);
312 +
	}
313 +
}
314 +
315 +
void
316 +
via1_step(unsigned clocks)
317 +
{
318 +
	via_step(&via[0], clocks);
319 +
}
320 +
321 +
bool
322 +
via1_irq()
323 +
{
324 +
	return (via[0].registers[13] & via[0].registers[14]) != 0;
325 +
}
326 +
327 +
//
328 +
// VIA#2
329 +
//
330 +
// PA/PB: user port
331 +
// for now, just assume that all user ports are not connected
332 +
// and reads return output register (open bus behavior)
333 +
334 +
void
335 +
via2_init()
336 +
{
337 +
	via_init(&via[1]);
338 +
}
339 +
340 +
uint8_t
341 +
via2_read(uint8_t reg, bool debug)
342 +
{
343 +
	return via_read(&via[1], reg, debug);
344 +
}
345 +
346 +
void
347 +
via2_write(uint8_t reg, uint8_t value)
348 +
{
349 +
	via_write(&via[1], reg, value);
350 +
}
351 +
352 +
void
353 +
via2_step(unsigned clocks)
354 +
{
355 +
	via_step(&via[1], clocks);
356 +
}
357 +
358 +
bool
359 +
via2_irq()
360 +
{
361 +
	return (via[1].registers[13] & via[1].registers[14]) != 0;
362 +
}
added src/via.h +23 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef VIA_H
6 +
#define VIA_H
7 +
8 +
#include <stdint.h>
9 +
#include <stdbool.h>
10 +
11 +
void via1_init();
12 +
uint8_t via1_read(uint8_t reg, bool debug);
13 +
void via1_write(uint8_t reg, uint8_t value);
14 +
void via1_step(unsigned clocks);
15 +
bool via1_irq();
16 +
17 +
void via2_init();
18 +
uint8_t via2_read(uint8_t reg, bool debug);
19 +
void via2_write(uint8_t reg, uint8_t value);
20 +
void via2_step(unsigned clocks);
21 +
bool via2_irq();
22 +
23 +
#endif
added src/video.c +2457 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// Copyright (c) 2020 Frank van den Hoef
4 +
// All rights reserved. License: 2-clause BSD
5 +
6 +
#include "video.h"
7 +
#include "memory.h"
8 +
#include "glue.h"
9 +
#include "debugger.h"
10 +
#include "keyboard.h"
11 +
#include "gif.h"
12 +
#include "joystick.h"
13 +
#include "vera_spi.h"
14 +
#include "vera_psg.h"
15 +
#include "vera_pcm.h"
16 +
#include "icon.h"
17 +
#include "sdcard.h"
18 +
#include "i2c.h"
19 +
#include "audio.h"
20 +
21 +
#include <stdbool.h>
22 +
#include <limits.h>
23 +
#include <stdint.h>
24 +
#include <time.h>
25 +
26 +
#ifdef __EMSCRIPTEN__
27 +
#include "emscripten.h"
28 +
#endif
29 +
30 +
#ifndef __EMSCRIPTEN__
31 +
#define STB_IMAGE_WRITE_IMPLEMENTATION
32 +
#include "stb_image_write.h"
33 +
#endif
34 +
35 +
#define APPROX_TITLEBAR_HEIGHT 30
36 +
37 +
#define VERA_VERSION_MAJOR  47
38 +
#define VERA_VERSION_MINOR  0
39 +
#define VERA_VERSION_PATCH  2
40 +
41 +
#define ADDR_VRAM_START     0x00000
42 +
#define ADDR_VRAM_END       0x20000
43 +
#define ADDR_PSG_START      0x1F9C0
44 +
#define ADDR_PSG_END        0x1FA00
45 +
#define ADDR_PALETTE_START  0x1FA00
46 +
#define ADDR_PALETTE_END    0x1FC00
47 +
#define ADDR_SPRDATA_START  0x1FC00
48 +
#define ADDR_SPRDATA_END    0x20000
49 +
50 +
#define NUM_SPRITES 128
51 +
52 +
// both VGA and NTSC
53 +
#define SCAN_HEIGHT 525
54 +
#define PIXEL_FREQ 25.0
55 +
56 +
// VGA
57 +
#define VGA_SCAN_WIDTH 800
58 +
#define VGA_Y_OFFSET 0
59 +
60 +
// NTSC: 262.5 lines per frame, lower field first
61 +
#define NTSC_HALF_SCAN_WIDTH 794
62 +
#define NTSC_X_OFFSET 270
63 +
#define NTSC_Y_OFFSET_LOW 42
64 +
#define NTSC_Y_OFFSET_HIGH 568
65 +
#define TITLE_SAFE_X 0.067
66 +
#define TITLE_SAFE_Y 0.05
67 +
68 +
// visible area we're drawing
69 +
#define SCREEN_WIDTH 640
70 +
#define SCREEN_HEIGHT 480
71 +
72 +
#define SCREEN_RAM_OFFSET 0x00000
73 +
74 +
#ifdef __APPLE__
75 +
#define LSHORTCUT_KEY SDL_SCANCODE_LGUI
76 +
#define RSHORTCUT_KEY SDL_SCANCODE_RGUI
77 +
#else
78 +
#define LSHORTCUT_KEY SDL_SCANCODE_LCTRL
79 +
#define RSHORTCUT_KEY SDL_SCANCODE_RCTRL
80 +
#endif
81 +
82 +
// When rendering a layer line, we can amortize some of the cost by calculating multiple pixels at a time.
83 +
#define LAYER_PIXELS_PER_ITERATION 8
84 +
85 +
#define MAX(a,b) ((a) > (b) ? a : b)
86 +
87 +
static SDL_Window *window;
88 +
static SDL_Renderer *renderer;
89 +
static SDL_Texture *sdlTexture;
90 +
static bool is_fullscreen = false;
91 +
bool mouse_grabbed = false;
92 +
bool no_keyboard_capture = false;
93 +
bool kernal_mouse_enabled = false;
94 +
95 +
static uint8_t video_ram[0x20000];
96 +
static uint8_t palette[256 * 2];
97 +
static uint8_t sprite_data[128][8];
98 +
99 +
// I/O registers
100 +
static uint32_t io_addr[2];
101 +
static uint8_t io_rddata[2];
102 +
static uint8_t io_inc[2];
103 +
static uint8_t io_addrsel;
104 +
static uint8_t io_dcsel;
105 +
106 +
static uint8_t ien;
107 +
static uint8_t isr;
108 +
109 +
static uint16_t irq_line;
110 +
111 +
static uint8_t reg_layer[2][7];
112 +
113 +
#define COMPOSER_SLOTS 4*64
114 +
static uint8_t reg_composer[COMPOSER_SLOTS];
115 +
static uint8_t prev_reg_composer[2][COMPOSER_SLOTS];
116 +
117 +
static uint8_t layer_line[2][SCREEN_WIDTH];
118 +
static uint8_t sprite_line_col[SCREEN_WIDTH];
119 +
static uint8_t sprite_line_z[SCREEN_WIDTH];
120 +
static uint8_t sprite_line_mask[SCREEN_WIDTH];
121 +
static uint8_t sprite_line_collisions;
122 +
static bool layer_line_enable[2];
123 +
static bool old_layer_line_enable[2];
124 +
static bool old_sprite_line_enable;
125 +
static bool sprite_line_enable;
126 +
127 +
////////////////////////////////////////////////////////////
128 +
// FX registers
129 +
////////////////////////////////////////////////////////////
130 +
static uint8_t fx_addr1_mode;
131 +
132 +
// These are all 16.16 fixed point in the emulator
133 +
// even though the VERA uses smaller bit widths
134 +
// for the whole and fractional parts.
135 +
//
136 +
// Sign extension is done manually when assigning negative numbers
137 +
//
138 +
// Native VERA bit widths are shown below.
139 +
static uint32_t fx_x_pixel_increment;  // 11.9 fixed point (6.9 without 32x multiplier, 11.4 with 32x multiplier on)
140 +
static uint32_t fx_y_pixel_increment;  // 11.9 fixed point (6.9 without 32x multiplier, 11.4 with 32x multiplier on)
141 +
static uint32_t fx_x_pixel_position;   // 11.9 fixed point
142 +
static uint32_t fx_y_pixel_position;   // 11.9 fixed point
143 +
144 +
static uint16_t fx_poly_fill_length;      // 10 bits
145 +
146 +
static uint32_t fx_affine_tile_base;
147 +
static uint32_t fx_affine_map_base;
148 +
149 +
static uint8_t fx_affine_map_size;
150 +
151 +
static bool fx_4bit_mode;
152 +
static bool fx_16bit_hop;
153 +
static bool fx_cache_byte_cycling;
154 +
static bool fx_cache_fill;
155 +
static bool fx_cache_write;
156 +
static bool fx_trans_writes;
157 +
158 +
static bool fx_2bit_poly;
159 +
static bool fx_2bit_poking;
160 +
161 +
static bool fx_cache_increment_mode;
162 +
static bool fx_cache_nibble_index;
163 +
static uint8_t fx_cache_byte_index;
164 +
static bool fx_multiplier;
165 +
static bool fx_subtract;
166 +
167 +
static bool fx_affine_clip;
168 +
169 +
static uint8_t fx_16bit_hop_align;
170 +
171 +
static bool fx_nibble_bit[2];
172 +
static bool fx_nibble_incr[2];
173 +
174 +
static uint8_t fx_cache[4];
175 +
176 +
static int32_t fx_mult_accumulator;
177 +
178 +
static const uint8_t vera_version_string[] = {'V',
179 +
	VERA_VERSION_MAJOR,
180 +
	VERA_VERSION_MINOR,
181 +
	VERA_VERSION_PATCH
182 +
};
183 +
184 +
float vga_scan_pos_x;
185 +
uint16_t vga_scan_pos_y;
186 +
float ntsc_half_cnt;
187 +
uint16_t ntsc_scan_pos_y;
188 +
int frame_count = 0;
189 +
190 +
static uint8_t framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4];
191 +
#ifndef __EMSCRIPTEN__
192 +
static uint8_t png_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 3];
193 +
#endif
194 +
195 +
static GifWriter gif_writer;
196 +
197 +
static const uint16_t default_palette[] = {
198 +
0x000,0xfff,0x800,0xafe,0xc4c,0x0c5,0x00a,0xee7,0xd85,0x640,0xf77,0x333,0x777,0xaf6,0x08f,0xbbb,0x000,0x111,0x222,0x333,0x444,0x555,0x666,0x777,0x888,0x999,0xaaa,0xbbb,0xccc,0xddd,0xeee,0xfff,0x211,0x433,0x644,0x866,0xa88,0xc99,0xfbb,0x211,0x422,0x633,0x844,0xa55,0xc66,0xf77,0x200,0x411,0x611,0x822,0xa22,0xc33,0xf33,0x200,0x400,0x600,0x800,0xa00,0xc00,0xf00,0x221,0x443,0x664,0x886,0xaa8,0xcc9,0xfeb,0x211,0x432,0x653,0x874,0xa95,0xcb6,0xfd7,0x210,0x431,0x651,0x862,0xa82,0xca3,0xfc3,0x210,0x430,0x640,0x860,0xa80,0xc90,0xfb0,0x121,0x343,0x564,0x786,0x9a8,0xbc9,0xdfb,0x121,0x342,0x463,0x684,0x8a5,0x9c6,0xbf7,0x120,0x241,0x461,0x582,0x6a2,0x8c3,0x9f3,0x120,0x240,0x360,0x480,0x5a0,0x6c0,0x7f0,0x121,0x343,0x465,0x686,0x8a8,0x9ca,0xbfc,0x121,0x242,0x364,0x485,0x5a6,0x6c8,0x7f9,0x020,0x141,0x162,0x283,0x2a4,0x3c5,0x3f6,0x020,0x041,0x061,0x082,0x0a2,0x0c3,0x0f3,0x122,0x344,0x466,0x688,0x8aa,0x9cc,0xbff,0x122,0x244,0x366,0x488,0x5aa,0x6cc,0x7ff,0x022,0x144,0x166,0x288,0x2aa,0x3cc,0x3ff,0x022,0x044,0x066,0x088,0x0aa,0x0cc,0x0ff,0x112,0x334,0x456,0x668,0x88a,0x9ac,0xbcf,0x112,0x224,0x346,0x458,0x56a,0x68c,0x79f,0x002,0x114,0x126,0x238,0x24a,0x35c,0x36f,0x002,0x014,0x016,0x028,0x02a,0x03c,0x03f,0x112,0x334,0x546,0x768,0x98a,0xb9c,0xdbf,0x112,0x324,0x436,0x648,0x85a,0x96c,0xb7f,0x102,0x214,0x416,0x528,0x62a,0x83c,0x93f,0x102,0x204,0x306,0x408,0x50a,0x60c,0x70f,0x212,0x434,0x646,0x868,0xa8a,0xc9c,0xfbe,0x211,0x423,0x635,0x847,0xa59,0xc6b,0xf7d,0x201,0x413,0x615,0x826,0xa28,0xc3a,0xf3c,0x201,0x403,0x604,0x806,0xa08,0xc09,0xf0b
199 +
};
200 +
201 +
uint8_t video_space_read(uint32_t address);
202 +
static void video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size);
203 +
204 +
static void refresh_palette();
205 +
206 +
void
207 +
mousegrab_toggle() {
208 +
	mouse_grabbed = !mouse_grabbed;
209 +
	SDL_SetWindowGrab(window, mouse_grabbed && !no_keyboard_capture);
210 +
	SDL_SetRelativeMouseMode(mouse_grabbed);
211 +
	SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
212 +
	sprintf(window_title, WINDOW_TITLE "%s", mouse_grabbed ? MOUSE_GRAB_MSG : "");
213 +
	video_update_title(window_title);
214 +
}
215 +
216 +
void
217 +
video_reset()
218 +
{
219 +
	// init I/O registers
220 +
	memset(io_addr, 0, sizeof(io_addr));
221 +
	memset(io_inc, 0, sizeof(io_inc));
222 +
	io_addrsel = 0;
223 +
	io_dcsel = 0;
224 +
	io_rddata[0] = 0;
225 +
	io_rddata[1] = 0;
226 +
227 +
	ien = 0;
228 +
	isr = 0;
229 +
	irq_line = 0;
230 +
231 +
	// init Layer registers
232 +
	memset(reg_layer, 0, sizeof(reg_layer));
233 +
234 +
	// init composer registers
235 +
	memset(reg_composer, 0, sizeof(reg_composer));
236 +
	reg_composer[1] = 128; // hscale = 1.0
237 +
	reg_composer[2] = 128; // vscale = 1.0
238 +
	reg_composer[5] = 640 >> 2;
239 +
	reg_composer[7] = 480 >> 1;
240 +
241 +
	// Initialize FX registers
242 +
	fx_addr1_mode = 0;
243 +
	fx_x_pixel_position = 0x8000;
244 +
	fx_y_pixel_position = 0x8000;
245 +
	fx_x_pixel_increment = 0;
246 +
	fx_y_pixel_increment = 0;
247 +
248 +
	fx_cache_write = false;
249 +
	fx_cache_fill = false;
250 +
	fx_4bit_mode = false;
251 +
	fx_16bit_hop = false;
252 +
	fx_subtract = false;
253 +
	fx_cache_byte_cycling = false;
254 +
	fx_trans_writes = false;
255 +
	fx_multiplier = false;
256 +
257 +
	fx_mult_accumulator = 0;
258 +
259 +
	fx_2bit_poly = false;
260 +
	fx_2bit_poking = false;
261 +
262 +
	fx_cache_nibble_index = 0;
263 +
	fx_cache_byte_index = 0;
264 +
	fx_cache_increment_mode = 0;
265 +
266 +
	fx_cache[0] = 0;
267 +
	fx_cache[1] = 0;
268 +
	fx_cache[2] = 0;
269 +
	fx_cache[3] = 0;
270 +
271 +
	fx_16bit_hop_align = 0;
272 +
273 +
	fx_nibble_bit[0] = false;
274 +
	fx_nibble_bit[1] = false;
275 +
	fx_nibble_incr[0] = false;
276 +
	fx_nibble_incr[1] = false;
277 +
278 +
	fx_poly_fill_length = 0;
279 +
	fx_affine_tile_base = 0;
280 +
	fx_affine_map_base = 0;
281 +
	fx_affine_map_size = 2;
282 +
	fx_affine_clip = false;
283 +
284 +
	// init sprite data
285 +
	memset(sprite_data, 0, sizeof(sprite_data));
286 +
287 +
	// copy palette
288 +
	memcpy(palette, default_palette, sizeof(palette));
289 +
	for (int i = 0; i < 256; i++) {
290 +
		palette[i * 2 + 0] = default_palette[i] & 0xff;
291 +
		palette[i * 2 + 1] = default_palette[i] >> 8;
292 +
	}
293 +
294 +
	refresh_palette();
295 +
296 +
	// fill video RAM with random data
297 +
	for (int i = 0; i < 128 * 1024; i++) {
298 +
		video_ram[i] = rand();
299 +
	}
300 +
301 +
	sprite_line_collisions = 0;
302 +
303 +
	vga_scan_pos_x = 0;
304 +
	vga_scan_pos_y = 0;
305 +
	ntsc_half_cnt = 0;
306 +
	ntsc_scan_pos_y = 0;
307 +
308 +
	psg_reset();
309 +
	pcm_reset();
310 +
}
311 +
312 +
bool
313 +
video_init(int window_scale, float screen_x_scale, char *quality, bool fullscreen, float opacity)
314 +
{
315 +
	uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
316 +
317 +
#ifdef __EMSCRIPTEN__
318 +
	// Setting this flag would render the web canvas outside of its bounds on high dpi screens
319 +
	window_flags &= ~SDL_WINDOW_ALLOW_HIGHDPI;
320 +
#endif
321 +
322 +
	video_reset();
323 +
324 +
	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, quality);
325 +
	SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); // Grabs keyboard shortcuts from the system during window grab
326 +
	SDL_CreateWindowAndRenderer(SCREEN_WIDTH * window_scale * screen_x_scale, SCREEN_HEIGHT * window_scale, window_flags, &window, &renderer);
327 +
#ifndef __MORPHOS__
328 +
	SDL_SetWindowResizable(window, true);
329 +
#endif
330 +
	SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH * screen_x_scale, SCREEN_HEIGHT);
331 +
332 +
	sdlTexture = SDL_CreateTexture(renderer,
333 +
									SDL_PIXELFORMAT_RGB888,
334 +
									SDL_TEXTUREACCESS_STREAMING,
335 +
									SCREEN_WIDTH, SCREEN_HEIGHT);
336 +
337 +
	SDL_SetWindowTitle(window, WINDOW_TITLE);
338 +
	SDL_SetWindowIcon(window, CommanderX16Icon());
339 +
	if (fullscreen) {
340 +
		is_fullscreen = true;
341 +
		SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
342 +
	} else {
343 +
		int winX, winY;
344 +
		SDL_GetWindowPosition(window, &winX, &winY);
345 +
		if (winX < 0 || winY < APPROX_TITLEBAR_HEIGHT) {
346 +
			winX = winX < 0 ? 0 : winX;
347 +
			winY = winY < APPROX_TITLEBAR_HEIGHT ? APPROX_TITLEBAR_HEIGHT : winY;
348 +
			SDL_SetWindowPosition(window, winX, winY);
349 +
		}
350 +
	}
351 +
352 +
	SDL_SetWindowOpacity(window, opacity);
353 +
354 +
#ifdef _WIN32
355 +
	extern void video_win32_set_rounded_corners(SDL_Window *window);
356 +
	video_win32_set_rounded_corners(window);
357 +
#endif
358 +
359 +
	if (record_gif != RECORD_GIF_DISABLED) {
360 +
		if (!strcmp(gif_path+strlen(gif_path)-5, ",wait")) {
361 +
			// wait for POKE
362 +
			record_gif = RECORD_GIF_PAUSED;
363 +
			// move the string terminator to remove the ",wait"
364 +
			gif_path[strlen(gif_path)-5] = 0;
365 +
		} else {
366 +
			// start now
367 +
			record_gif = RECORD_GIF_ACTIVE;
368 +
		}
369 +
		if (!GifBegin(&gif_writer, gif_path, SCREEN_WIDTH, SCREEN_HEIGHT, 1, 8, false)) {
370 +
			record_gif = RECORD_GIF_DISABLED;
371 +
		}
372 +
	}
373 +
374 +
	if (debugger_enabled) {
375 +
		DEBUGInitUI(renderer);
376 +
	}
377 +
378 +
	if (grab_mouse && !mouse_grabbed)
379 +
		mousegrab_toggle();
380 +
381 +
	return true;
382 +
}
383 +
384 +
struct video_layer_properties
385 +
{
386 +
	uint8_t color_depth;
387 +
	uint32_t map_base;
388 +
	uint32_t tile_base;
389 +
390 +
	bool text_mode;
391 +
	bool text_mode_256c;
392 +
	bool tile_mode;
393 +
	bool bitmap_mode;
394 +
395 +
	uint16_t hscroll;
396 +
	uint16_t vscroll;
397 +
398 +
	uint8_t  mapw_log2;
399 +
	uint8_t  maph_log2;
400 +
	uint16_t tilew;
401 +
	uint16_t tileh;
402 +
	uint8_t  tilew_log2;
403 +
	uint8_t  tileh_log2;
404 +
405 +
	uint16_t mapw_max;
406 +
	uint16_t maph_max;
407 +
	uint16_t tilew_max;
408 +
	uint16_t tileh_max;
409 +
	uint16_t layerw_max;
410 +
	uint16_t layerh_max;
411 +
412 +
	uint8_t tile_size_log2;
413 +
414 +
	int min_eff_x;
415 +
	int max_eff_x;
416 +
417 +
	uint8_t bits_per_pixel;
418 +
	uint8_t first_color_pos;
419 +
	uint8_t color_mask;
420 +
	uint8_t color_fields_max;
421 +
};
422 +
423 +
#define NUM_LAYERS 2
424 +
struct video_layer_properties layer_properties[NUM_LAYERS];
425 +
struct video_layer_properties prev_layer_properties[2][NUM_LAYERS];
426 +
427 +
static int
428 +
calc_layer_eff_x(const struct video_layer_properties *props, const int x)
429 +
{
430 +
	return (x + props->hscroll) & (props->layerw_max);
431 +
}
432 +
433 +
static int
434 +
calc_layer_eff_y(const struct video_layer_properties *props, const int y)
435 +
{
436 +
	return (y + props->vscroll) & (props->layerh_max);
437 +
}
438 +
439 +
static uint32_t
440 +
calc_layer_map_addr_base2(const struct video_layer_properties *props, const int eff_x, const int eff_y)
441 +
{
442 +
	// Slightly faster on some platforms because we know that tilew and tileh are powers of 2.
443 +
	return props->map_base + ((((eff_y >> props->tileh_log2) << props->mapw_log2) + (eff_x >> props->tilew_log2)) << 1);
444 +
}
445 +
446 +
//TODO: Unused in all current cases. Delete? Or leave commented as a reminder?
447 +
//static uint32_t
448 +
//calc_layer_map_addr(struct video_layer_properties *props, int eff_x, int eff_y)
449 +
//{
450 +
//	return props->map_base + ((eff_y / props->tileh) * props->mapw + (eff_x / props->tilew)) * 2;
451 +
//}
452 +
static void
453 +
refresh_layer_properties(const uint8_t layer)
454 +
{
455 +
	struct video_layer_properties* props = &layer_properties[layer];
456 +
457 +
	uint16_t prev_layerw_max = props->layerw_max;
458 +
	uint16_t prev_hscroll = props->hscroll;
459 +
460 +
	props->color_depth    = reg_layer[layer][0] & 0x3;
461 +
	props->map_base       = reg_layer[layer][1] << 9;
462 +
	props->tile_base      = (reg_layer[layer][2] & 0xFC) << 9;
463 +
	props->bitmap_mode    = (reg_layer[layer][0] & 0x4) != 0;
464 +
	props->text_mode      = (props->color_depth == 0) && !props->bitmap_mode;
465 +
	props->text_mode_256c = (reg_layer[layer][0] & 8) != 0;
466 +
	props->tile_mode      = !props->bitmap_mode && !props->text_mode;
467 +
468 +
	if (!props->bitmap_mode) {
469 +
		props->hscroll = reg_layer[layer][3] | (reg_layer[layer][4] & 0xf) << 8;
470 +
		props->vscroll = reg_layer[layer][5] | (reg_layer[layer][6] & 0xf) << 8;
471 +
	} else {
472 +
		props->hscroll = 0;
473 +
		props->vscroll = 0;
474 +
	}
475 +
476 +
	uint16_t mapw = 0;
477 +
	uint16_t maph = 0;
478 +
	props->tilew = 0;
479 +
	props->tileh = 0;
480 +
481 +
	if (props->tile_mode || props->text_mode) {
482 +
		props->mapw_log2 = 5 + ((reg_layer[layer][0] >> 4) & 3);
483 +
		props->maph_log2 = 5 + ((reg_layer[layer][0] >> 6) & 3);
484 +
		mapw      = 1 << props->mapw_log2;
485 +
		maph      = 1 << props->maph_log2;
486 +
487 +
		// Scale the tiles or text characters according to TILEW and TILEH.
488 +
		props->tilew_log2 = 3 + (reg_layer[layer][2] & 1);
489 +
		props->tileh_log2 = 3 + ((reg_layer[layer][2] >> 1) & 1);
490 +
		props->tilew      = 1 << props->tilew_log2;
491 +
		props->tileh      = 1 << props->tileh_log2;
492 +
	} else if (props->bitmap_mode) {
493 +
		// bitmap mode is basically tiled mode with a single huge tile
494 +
		props->tilew = (reg_layer[layer][2] & 1) ? 640 : 320;
495 +
		props->tileh = SCREEN_HEIGHT;
496 +
	}
497 +
498 +
	// We know mapw, maph, tilew, and tileh are powers of two in all cases except bitmap modes, and any products of that set will be powers of two,
499 +
	// so there's no need to modulo against them if we have bitmasks we can bitwise-and against.
500 +
501 +
	props->mapw_max = mapw - 1;
502 +
	props->maph_max = maph - 1;
503 +
	props->tilew_max = props->tilew - 1;
504 +
	props->tileh_max = props->tileh - 1;
505 +
	props->layerw_max = (mapw * props->tilew) - 1;
506 +
	props->layerh_max = (maph * props->tileh) - 1;
507 +
508 +
	// Find min/max eff_x for bulk reading in tile data during draw.
509 +
	if (prev_layerw_max != props->layerw_max || prev_hscroll != props->hscroll) {
510 +
		int min_eff_x = INT_MAX;
511 +
		int max_eff_x = INT_MIN;
512 +
		for (int x = 0; x < SCREEN_WIDTH; ++x) {
513 +
			int eff_x = calc_layer_eff_x(props, x);
514 +
			if (eff_x < min_eff_x) {
515 +
				min_eff_x = eff_x;
516 +
			}
517 +
			if (eff_x > max_eff_x) {
518 +
				max_eff_x = eff_x;
519 +
			}
520 +
		}
521 +
		props->min_eff_x = min_eff_x;
522 +
		props->max_eff_x = max_eff_x;
523 +
	}
524 +
525 +
	props->bits_per_pixel = 1 << props->color_depth;
526 +
	props->tile_size_log2 = props->tilew_log2 + props->tileh_log2 + props->color_depth - 3;
527 +
528 +
	props->first_color_pos  = 8 - props->bits_per_pixel;
529 +
	props->color_mask       = (1 << props->bits_per_pixel) - 1;
530 +
	props->color_fields_max = (8 >> props->color_depth) - 1;
531 +
}
532 +
533 +
struct video_sprite_properties
534 +
{
535 +
	int8_t sprite_zdepth;
536 +
	uint8_t sprite_collision_mask;
537 +
538 +
	int16_t sprite_x;
539 +
	int16_t sprite_y;
540 +
	uint8_t sprite_width_log2;
541 +
	uint8_t sprite_height_log2;
542 +
	uint8_t sprite_width;
543 +
	uint8_t sprite_height;
544 +
545 +
	bool hflip;
546 +
	bool vflip;
547 +
548 +
	uint8_t color_mode;
549 +
	uint32_t sprite_address;
550 +
551 +
	uint16_t palette_offset;
552 +
};
553 +
554 +
#ifndef __EMSCRIPTEN__
555 +
static void
556 +
screenshot(void)
557 +
{
558 +
	char path[PATH_MAX];
559 +
	const time_t now = time(NULL);
560 +
	strftime(path, PATH_MAX, "x16emu-%Y-%m-%d-%H-%M-%S.png", localtime(&now));
561 +
562 +
	memset(png_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT * 3);
563 +
564 +
	// The framebuffer stores pixels in BRGA but we want RGB:
565 +
	for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
566 +
		png_buffer[(i*3)+0] = framebuffer[(i*4)+2];
567 +
		png_buffer[(i*3)+1] = framebuffer[(i*4)+1];
568 +
		png_buffer[(i*3)+2] = framebuffer[(i*4)+0];
569 +
	}
570 +
571 +
	if (stbi_write_png(path, SCREEN_WIDTH, SCREEN_HEIGHT, 3, png_buffer, SCREEN_WIDTH*3)) {
572 +
		printf("Wrote screenshot to %s\n", path);
573 +
	} else {
574 +
		printf("WARNING: Couldn't write screenshot to %s\n", path);
575 +
	}
576 +
}
577 +
#endif
578 +
579 +
/* Control API accessors (retro-remote-debug-controller). */
580 +
const uint8_t *
581 +
video_get_framebuffer(int *width, int *height)
582 +
{
583 +
	if (width) *width = SCREEN_WIDTH;
584 +
	if (height) *height = SCREEN_HEIGHT;
585 +
	return framebuffer;
586 +
}
587 +
588 +
uint32_t
589 +
video_get_frame_count(void)
590 +
{
591 +
	return (uint32_t)frame_count;
592 +
}
593 +
594 +
struct video_sprite_properties sprite_properties[128];
595 +
596 +
static void
597 +
refresh_sprite_properties(const uint16_t sprite)
598 +
{
599 +
	struct video_sprite_properties* props = &sprite_properties[sprite];
600 +
601 +
	props->sprite_zdepth = (sprite_data[sprite][6] >> 2) & 3;
602 +
	props->sprite_collision_mask = sprite_data[sprite][6] & 0xf0;
603 +
604 +
	props->sprite_x = sprite_data[sprite][2] | (sprite_data[sprite][3] & 3) << 8;
605 +
	props->sprite_y = sprite_data[sprite][4] | (sprite_data[sprite][5] & 3) << 8;
606 +
	props->sprite_width_log2  = (((sprite_data[sprite][7] >> 4) & 3) + 3);
607 +
	props->sprite_height_log2 = ((sprite_data[sprite][7] >> 6) + 3);
608 +
	props->sprite_width       = 1 << props->sprite_width_log2;
609 +
	props->sprite_height      = 1 << props->sprite_height_log2;
610 +
611 +
	// fix up negative coordinates
612 +
	if (props->sprite_x >= 0x400 - props->sprite_width) {
613 +
		props->sprite_x -= 0x400;
614 +
	}
615 +
	if (props->sprite_y >= 0x400 - props->sprite_height) {
616 +
		props->sprite_y -= 0x400;
617 +
	}
618 +
619 +
	props->hflip = sprite_data[sprite][6] & 1;
620 +
	props->vflip = (sprite_data[sprite][6] >> 1) & 1;
621 +
622 +
	props->color_mode     = (sprite_data[sprite][1] >> 7) & 1;
623 +
	props->sprite_address = sprite_data[sprite][0] << 5 | (sprite_data[sprite][1] & 0xf) << 13;
624 +
625 +
	props->palette_offset = (sprite_data[sprite][7] & 0x0f) << 4;
626 +
}
627 +
628 +
struct video_palette
629 +
{
630 +
	uint32_t entries[256];
631 +
	bool dirty;
632 +
};
633 +
634 +
struct video_palette video_palette;
635 +
636 +
static void
637 +
refresh_palette() {
638 +
	const uint8_t out_mode = reg_composer[0] & 3;
639 +
	const bool chroma_disable = ((reg_composer[0] & 0x07) == 6);
640 +
	for (int i = 0; i < 256; ++i) {
641 +
		uint8_t r;
642 +
		uint8_t g;
643 +
		uint8_t b;
644 +
		if (out_mode == 0) {
645 +
			// video generation off
646 +
			// -> show blue screen
647 +
			r = 0;
648 +
			g = 0;
649 +
			b = 255;
650 +
		} else {
651 +
			uint16_t entry = palette[i * 2] | palette[i * 2 + 1] << 8;
652 +
			r = ((entry >> 8) & 0xf) << 4 | ((entry >> 8) & 0xf);
653 +
			g = ((entry >> 4) & 0xf) << 4 | ((entry >> 4) & 0xf);
654 +
			b = (entry & 0xf) << 4 | (entry & 0xf);
655 +
			if (chroma_disable) {
656 +
				r = g = b = (r + b + g) / 3;
657 +
			}
658 +
		}
659 +
660 +
		video_palette.entries[i] = (uint32_t)(r << 16) | ((uint32_t)g << 8) | ((uint32_t)b);
661 +
	}
662 +
	video_palette.dirty = false;
663 +
}
664 +
665 +
static void
666 +
expand_4bpp_data(uint8_t *dst, const uint8_t *src, int dst_size)
667 +
{
668 +
	while (dst_size >= 2) {
669 +
		*dst = (*src) >> 4;
670 +
		++dst;
671 +
		*dst = (*src) & 0xf;
672 +
		++dst;
673 +
674 +
		++src;
675 +
		dst_size -= 2;
676 +
	}
677 +
}
678 +
679 +
static void
680 +
render_sprite_line(const uint16_t y)
681 +
{
682 +
	memset(sprite_line_col, 0, SCREEN_WIDTH);
683 +
	memset(sprite_line_z, 0, SCREEN_WIDTH);
684 +
	memset(sprite_line_mask, 0, SCREEN_WIDTH);
685 +
686 +
	uint16_t sprite_budget = 800 + 1;
687 +
	for (int i = 0; i < NUM_SPRITES; i++) {
688 +
		// one clock per lookup
689 +
		sprite_budget--; if (sprite_budget == 0) break;
690 +
		const struct video_sprite_properties *props = &sprite_properties[i];
691 +
692 +
		if (props->sprite_zdepth == 0) {
693 +
			continue;
694 +
		}
695 +
696 +
		// check whether this line falls within the sprite
697 +
		if (y < props->sprite_y || y >= props->sprite_y + props->sprite_height) {
698 +
			continue;
699 +
		}
700 +
701 +
		const uint16_t eff_sy = props->vflip ? ((props->sprite_height - 1) - (y - props->sprite_y)) : (y - props->sprite_y);
702 +
703 +
		int16_t       eff_sx      = (props->hflip ? (props->sprite_width - 1) : 0);
704 +
		const int16_t eff_sx_incr = props->hflip ? -1 : 1;
705 +
706 +
		const uint8_t *bitmap_data = video_ram + props->sprite_address + (eff_sy << (props->sprite_width_log2 - (1 - props->color_mode)));
707 +
708 +
		uint8_t unpacked_sprite_line[64];
709 +
		const uint16_t width = (props->sprite_width<64? props->sprite_width : 64);
710 +
		const uint8_t vram_fetch_mask = ((2 - props->color_mode) << 2) - 1;
711 +
		if (props->color_mode == 0) {
712 +
			// 4bpp
713 +
			expand_4bpp_data(unpacked_sprite_line, bitmap_data, width);
714 +
		} else {
715 +
			// 8bpp
716 +
			memcpy(unpacked_sprite_line, bitmap_data, width);
717 +
		}
718 +
719 +
		for (uint16_t sx = 0; sx < props->sprite_width; ++sx) {
720 +
			const uint16_t line_x = props->sprite_x + sx;
721 +
			if (line_x >= SCREEN_WIDTH) {
722 +
				eff_sx += eff_sx_incr;
723 +
				continue;
724 +
			}
725 +
726 +
			// one clock per fetched 32 bits
727 +
			if (!(sx & vram_fetch_mask)) {
728 +
				sprite_budget--; if (sprite_budget == 0) break;
729 +
			}
730 +
731 +
			// one clock per rendered pixel
732 +
			sprite_budget--; if (sprite_budget == 0) break;
733 +
734 +
			uint8_t col_index = unpacked_sprite_line[eff_sx];
735 +
			eff_sx += eff_sx_incr;
736 +
737 +
			// palette offset
738 +
			if (col_index > 0) {
739 +
				sprite_line_collisions |= sprite_line_mask[line_x] & props->sprite_collision_mask;
740 +
				sprite_line_mask[line_x] |= props->sprite_collision_mask;
741 +
742 +
				if (props->sprite_zdepth > sprite_line_z[line_x]) {
743 +
					if (col_index < 16) {
744 +
						col_index += props->palette_offset;
745 +
					}
746 +
					sprite_line_col[line_x] = col_index;
747 +
					sprite_line_z[line_x] = props->sprite_zdepth;
748 +
				}
749 +
			}
750 +
		}
751 +
	}
752 +
}
753 +
754 +
static void
755 +
render_layer_line_text(uint8_t layer, uint16_t y)
756 +
{
757 +
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
758 +
	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
759 +
760 +
	const uint8_t max_pixels_per_byte = (8 >> props->color_depth) - 1;
761 +
	const int     eff_y               = calc_layer_eff_y(props0, y);
762 +
	const int     yy                  = eff_y & props->tileh_max;
763 +
764 +
	// additional bytes to reach the correct line of the tile
765 +
	const uint32_t y_add = (yy << props->tilew_log2) >> 3;
766 +
767 +
	const uint32_t map_addr_begin = calc_layer_map_addr_base2(props, props->min_eff_x, eff_y);
768 +
	const uint32_t map_addr_end   = calc_layer_map_addr_base2(props, props->max_eff_x, eff_y);
769 +
	const int      size           = (map_addr_end - map_addr_begin) + 2;
770 +
771 +
	uint8_t tile_bytes[512]; // max 256 tiles, 2 bytes each.
772 +
	video_space_read_range(tile_bytes, map_addr_begin, size);
773 +
774 +
	uint32_t tile_start;
775 +
776 +
	uint8_t  fg_color;
777 +
	uint8_t  bg_color;
778 +
	uint8_t  s;
779 +
	uint8_t  color_shift;
780 +
781 +
	{
782 +
		const int eff_x = calc_layer_eff_x(props, 0);
783 +
		const int xx    = eff_x & props->tilew_max;
784 +
785 +
		// extract all information from the map
786 +
		const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
787 +
788 +
		const uint8_t tile_index = tile_bytes[map_addr];
789 +
		const uint8_t byte1      = tile_bytes[map_addr + 1];
790 +
791 +
		if (!props->text_mode_256c) {
792 +
			fg_color = byte1 & 15;
793 +
			bg_color = byte1 >> 4;
794 +
		} else {
795 +
			fg_color = byte1;
796 +
			bg_color = 0;
797 +
		}
798 +
799 +
		// offset within tilemap of the current tile
800 +
		tile_start = tile_index << props->tile_size_log2;
801 +
802 +
		// additional bytes to reach the correct column of the tile
803 +
		const uint16_t x_add       = xx >> 3;
804 +
		const uint32_t tile_offset = tile_start + y_add + x_add;
805 +
806 +
		s           = video_space_read(props->tile_base + tile_offset);
807 +
		color_shift = max_pixels_per_byte - (xx & 0x7);
808 +
	}
809 +
810 +
	// Render tile line.
811 +
	for (int x = 0; x < SCREEN_WIDTH; x++) {
812 +
		// Scrolling
813 +
		const int eff_x = calc_layer_eff_x(props, x);
814 +
		const int xx = eff_x & props->tilew_max;
815 +
816 +
		if ((eff_x & 0x7) == 0) {
817 +
			if ((eff_x & props->tilew_max) == 0) {
818 +
				// extract all information from the map
819 +
				const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
820 +
821 +
				const uint8_t tile_index = tile_bytes[map_addr];
822 +
				const uint8_t byte1      = tile_bytes[map_addr + 1];
823 +
824 +
				if (!props->text_mode_256c) {
825 +
					fg_color = byte1 & 15;
826 +
					bg_color = byte1 >> 4;
827 +
				} else {
828 +
					fg_color = byte1;
829 +
					bg_color = 0;
830 +
				}
831 +
832 +
				// offset within tilemap of the current tile
833 +
				tile_start = tile_index << props->tile_size_log2;
834 +
			}
835 +
836 +
			// additional bytes to reach the correct column of the tile
837 +
			const uint16_t x_add       = xx >> 3;
838 +
			const uint32_t tile_offset = tile_start + y_add + x_add;
839 +
840 +
			s           = video_space_read(props->tile_base + tile_offset);
841 +
			color_shift = max_pixels_per_byte;
842 +
		}
843 +
844 +
		// convert tile byte to indexed color
845 +
		const uint8_t col_index = (s >> color_shift) & 1;
846 +
		--color_shift;
847 +
		layer_line[layer][x] = col_index ? fg_color : bg_color;
848 +
	}
849 +
}
850 +
851 +
static void
852 +
render_layer_line_tile(uint8_t layer, uint16_t y)
853 +
{
854 +
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
855 +
	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
856 +
857 +
	const uint8_t max_pixels_per_byte = (8 >> props->color_depth) - 1;
858 +
	const int     eff_y               = calc_layer_eff_y(props0, y);
859 +
	const uint8_t yy                  = eff_y & props->tileh_max;
860 +
	const uint8_t yy_flip             = yy ^ props->tileh_max;
861 +
	const uint32_t y_add              = (yy << ((props->tilew_log2 + props->color_depth - 3) & 31));
862 +
	const uint32_t y_add_flip         = (yy_flip << ((props->tilew_log2 + props->color_depth - 3) & 31));
863 +
864 +
	const uint32_t map_addr_begin = calc_layer_map_addr_base2(props, props->min_eff_x, eff_y);
865 +
	const uint32_t map_addr_end   = calc_layer_map_addr_base2(props, props->max_eff_x, eff_y);
866 +
	const int      size           = (map_addr_end - map_addr_begin) + 2;
867 +
868 +
	uint8_t tile_bytes[512]; // max 256 tiles, 2 bytes each.
869 +
	video_space_read_range(tile_bytes, map_addr_begin, size);
870 +
871 +
	uint8_t  palette_offset;
872 +
	bool     vflip;
873 +
	bool     hflip;
874 +
	uint32_t tile_start;
875 +
	uint8_t  s;
876 +
	uint8_t  color_shift;
877 +
	int8_t   color_shift_incr;
878 +
879 +
	{
880 +
		const int eff_x = calc_layer_eff_x(props, 0);
881 +
882 +
		// extract all information from the map
883 +
		const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
884 +
885 +
		const uint8_t byte0 = tile_bytes[map_addr];
886 +
		const uint8_t byte1 = tile_bytes[map_addr + 1];
887 +
888 +
		// Tile Flipping
889 +
		vflip = (byte1 >> 3) & 1;
890 +
		hflip = (byte1 >> 2) & 1;
891 +
892 +
		palette_offset = byte1 & 0xf0;
893 +
894 +
		// offset within tilemap of the current tile
895 +
		const uint16_t tile_index = byte0 | ((byte1 & 3) << 8);
896 +
		tile_start                = tile_index << props->tile_size_log2;
897 +
898 +
		color_shift_incr = hflip ? props->bits_per_pixel : -props->bits_per_pixel;
899 +
900 +
		int xx = eff_x & props->tilew_max;
901 +
		if (hflip) {
902 +
			xx          = xx ^ (props->tilew_max);
903 +
			color_shift = 0;
904 +
		} else {
905 +
			color_shift = props->first_color_pos;
906 +
		}
907 +
908 +
		// additional bytes to reach the correct column of the tile
909 +
		uint16_t x_add       = (xx << props->color_depth) >> 3;
910 +
		uint32_t tile_offset = tile_start + (vflip ? y_add_flip : y_add) + x_add;
911 +
912 +
		s = video_space_read(props->tile_base + tile_offset);
913 +
	}
914 +
915 +
916 +
	// Render tile line.
917 +
	for (int x = 0; x < SCREEN_WIDTH; x++) {
918 +
		const int eff_x = calc_layer_eff_x(props, x);
919 +
920 +
		if ((eff_x & max_pixels_per_byte) == 0) {
921 +
			if ((eff_x & props->tilew_max) == 0) {
922 +
				// extract all information from the map
923 +
				const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
924 +
925 +
				const uint8_t byte0 = tile_bytes[map_addr];
926 +
				const uint8_t byte1 = tile_bytes[map_addr + 1];
927 +
928 +
				// Tile Flipping
929 +
				vflip = (byte1 >> 3) & 1;
930 +
				hflip = (byte1 >> 2) & 1;
931 +
932 +
				palette_offset = byte1 & 0xf0;
933 +
934 +
				// offset within tilemap of the current tile
935 +
				const uint16_t tile_index = byte0 | ((byte1 & 3) << 8);
936 +
				tile_start                = tile_index << props->tile_size_log2;
937 +
938 +
				color_shift_incr = hflip ? props->bits_per_pixel : -props->bits_per_pixel;
939 +
			}
940 +
941 +
			int xx = eff_x & props->tilew_max;
942 +
			if (hflip) {
943 +
				xx = xx ^ (props->tilew_max);
944 +
				color_shift = 0;
945 +
			} else {
946 +
				color_shift = props->first_color_pos;
947 +
			}
948 +
949 +
			// additional bytes to reach the correct column of the tile
950 +
			const uint16_t x_add       = (xx << props->color_depth) >> 3;
951 +
			const uint32_t tile_offset = tile_start + (vflip ? y_add_flip : y_add) + x_add;
952 +
953 +
			s = video_space_read(props->tile_base + tile_offset);
954 +
		}
955 +
956 +
		// convert tile byte to indexed color
957 +
		uint8_t col_index = (s >> color_shift) & props->color_mask;
958 +
		color_shift += color_shift_incr;
959 +
960 +
		// Apply Palette Offset
961 +
		if (col_index > 0 && col_index < 16) {
962 +
			col_index += palette_offset;
963 +
			if (props->text_mode_256c) {
964 +
				col_index |= 0x80;
965 +
			}
966 +
		}
967 +
		layer_line[layer][x] = col_index;
968 +
	}
969 +
}
970 +
971 +
972 +
static void
973 +
render_layer_line_bitmap(uint8_t layer, uint16_t y)
974 +
{
975 +
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
976 +
//	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
977 +
978 +
	int yy = y % props->tileh;
979 +
	// additional bytes to reach the correct line of the tile
980 +
	uint32_t y_add = (yy * props->tilew * props->bits_per_pixel) >> 3;
981 +
982 +
	// Render tile line.
983 +
	for (int x = 0; x < SCREEN_WIDTH; x++) {
984 +
		int xx = x % props->tilew;
985 +
986 +
		// extract all information from the map
987 +
		uint8_t palette_offset = reg_layer[layer][4] & 0xf;
988 +
989 +
		// additional bytes to reach the correct column of the tile
990 +
		uint16_t x_add = (xx * props->bits_per_pixel) >> 3;
991 +
		uint32_t tile_offset = y_add + x_add;
992 +
		uint8_t s = video_space_read(props->tile_base + tile_offset);
993 +
994 +
		// convert tile byte to indexed color
995 +
		uint8_t col_index = (s >> (props->first_color_pos - ((xx & props->color_fields_max) << props->color_depth))) & props->color_mask;
996 +
997 +
		// Apply Palette Offset
998 +
		if (col_index > 0 && col_index < 16) {
999 +
			col_index += palette_offset << 4;
1000 +
			if (props->text_mode_256c) {
1001 +
				col_index |= 0x80;
1002 +
			}
1003 +
		}
1004 +
		layer_line[layer][x] = col_index;
1005 +
	}
1006 +
}
1007 +
1008 +
static uint8_t calculate_line_col_index(uint8_t spr_zindex, uint8_t spr_col_index, uint8_t l1_col_index, uint8_t l2_col_index)
1009 +
{
1010 +
	uint8_t col_index = 0;
1011 +
	switch (spr_zindex) {
1012 +
		case 3:
1013 +
			col_index = spr_col_index ? spr_col_index : (l2_col_index ? l2_col_index : l1_col_index);
1014 +
			break;
1015 +
		case 2:
1016 +
			col_index = l2_col_index ? l2_col_index : (spr_col_index ? spr_col_index : l1_col_index);
1017 +
			break;
1018 +
		case 1:
1019 +
			col_index = l2_col_index ? l2_col_index : (l1_col_index ? l1_col_index : spr_col_index);
1020 +
			break;
1021 +
		case 0:
1022 +
			col_index = l2_col_index ? l2_col_index : l1_col_index;
1023 +
			break;
1024 +
	}
1025 +
	return col_index;
1026 +
}
1027 +
1028 +
static void
1029 +
render_line(uint16_t y, float scan_pos_x)
1030 +
{
1031 +
	static uint16_t y_prev;
1032 +
	static uint16_t s_pos_x_p;
1033 +
	static uint32_t eff_y_fp; // 16.16 fixed point
1034 +
	static uint32_t eff_x_fp; // 16.16 fixed point
1035 +
1036 +
	static uint8_t col_line[SCREEN_WIDTH];
1037 +
1038 +
	uint8_t dc_video = reg_composer[0];
1039 +
	uint16_t vstart = reg_composer[6] << 1;
1040 +
1041 +
	if (y != y_prev) {
1042 +
		y_prev = y;
1043 +
		s_pos_x_p = 0;
1044 +
1045 +
		// Copy the composer array to 2-line history buffer
1046 +
		// so that the raster effects that happen on a delay take effect
1047 +
		// at exactly the right time
1048 +
1049 +
		// This simulates different effects happening at render,
1050 +
		// render but delayed until the next line, or applied mid-line
1051 +
		// at scan-out
1052 +
1053 +
		memcpy(prev_reg_composer[1], prev_reg_composer[0], sizeof(*reg_composer) * COMPOSER_SLOTS);
1054 +
		memcpy(prev_reg_composer[0], reg_composer, sizeof(*reg_composer) * COMPOSER_SLOTS);
1055 +
1056 +
		// Same with the layer properties
1057 +
1058 +
		memcpy(prev_layer_properties[1], prev_layer_properties[0], sizeof(*layer_properties) * NUM_LAYERS);
1059 +
		memcpy(prev_layer_properties[0], layer_properties, sizeof(*layer_properties) * NUM_LAYERS);
1060 +
1061 +
		if ((dc_video & 3) > 1) { // 480i or 240p
1062 +
			if ((y >> 1) == 0) {
1063 +
				eff_y_fp = y*(prev_reg_composer[1][2] << 9);
1064 +
			} else if ((y & 0xfffe) > vstart) {
1065 +
				eff_y_fp += (prev_reg_composer[1][2] << 10);
1066 +
			}
1067 +
		} else {
1068 +
			if (y == 0) {
1069 +
				eff_y_fp = 0;
1070 +
			} else if (y > vstart) {
1071 +
				eff_y_fp += (prev_reg_composer[1][2] << 9);
1072 +
			}
1073 +
		}
1074 +
	}
1075 +
1076 +
	if ((dc_video & 8) && (dc_video & 3) > 1) { // progressive NTSC/RGB mode
1077 +
		y &= 0xfffe;
1078 +
	}
1079 +
1080 +
	// refresh palette for next entry
1081 +
	if (video_palette.dirty) {
1082 +
		refresh_palette();
1083 +
	}
1084 +
1085 +
	if (y >= SCREEN_HEIGHT) {
1086 +
		return;
1087 +
	}
1088 +
1089 +
	uint16_t s_pos_x = round(scan_pos_x);
1090 +
	if (s_pos_x > SCREEN_WIDTH) {
1091 +
		s_pos_x = SCREEN_WIDTH;
1092 +
	}
1093 +
1094 +
	if (s_pos_x_p == 0) {
1095 +
		eff_x_fp = 0;
1096 +
	}
1097 +
1098 +
	uint8_t out_mode = reg_composer[0] & 3;
1099 +
1100 +
	uint8_t border_color = reg_composer[3];
1101 +
	uint16_t hstart = reg_composer[4] << 2;
1102 +
	uint16_t hstop = reg_composer[5] << 2;
1103 +
	uint16_t vstop = reg_composer[7] << 1;
1104 +
1105 +
	uint16_t eff_y = (eff_y_fp >> 16);
1106 +
	if (eff_y >= 480) eff_y = 480 - (y & 1);
1107 +
1108 +
	layer_line_enable[0] = dc_video & 0x10;
1109 +
	layer_line_enable[1] = dc_video & 0x20;
1110 +
	sprite_line_enable   = dc_video & 0x40;
1111 +
1112 +
	// clear layer_line if layer gets disabled
1113 +
	for (uint8_t layer = 0; layer < 2; layer++) {
1114 +
		if (!layer_line_enable[layer] && old_layer_line_enable[layer]) {
1115 +
			for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1116 +
				layer_line[layer][i] = 0;
1117 +
			}
1118 +
		}
1119 +
		if (s_pos_x_p == 0)
1120 +
			old_layer_line_enable[layer] = layer_line_enable[layer];
1121 +
	}
1122 +
1123 +
	// clear sprite_line if sprites get disabled
1124 +
	if (!sprite_line_enable && old_sprite_line_enable) {
1125 +
		for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1126 +
			sprite_line_col[i] = 0;
1127 +
			sprite_line_z[i] = 0;
1128 +
			sprite_line_mask[i] = 0;
1129 +
		}
1130 +
	}
1131 +
1132 +
	if (s_pos_x_p == 0)
1133 +
		old_sprite_line_enable = sprite_line_enable;
1134 +
1135 +
1136 +
1137 +
	if (sprite_line_enable) {
1138 +
		render_sprite_line(eff_y);
1139 +
	}
1140 +
1141 +
	if (warp_mode && (frame_count & 63)) {
1142 +
		// sprites were needed for the collision IRQ, but we can skip
1143 +
		// everything else if we're in warp mode, most of the time
1144 +
		return;
1145 +
	}
1146 +
1147 +
	if (layer_line_enable[0]) {
1148 +
		if (prev_layer_properties[1][0].text_mode) {
1149 +
			render_layer_line_text(0, eff_y);
1150 +
		} else if (prev_layer_properties[1][0].bitmap_mode) {
1151 +
			render_layer_line_bitmap(0, eff_y);
1152 +
		} else {
1153 +
			render_layer_line_tile(0, eff_y);
1154 +
		}
1155 +
	}
1156 +
	if (layer_line_enable[1]) {
1157 +
		if (prev_layer_properties[1][1].text_mode) {
1158 +
			render_layer_line_text(1, eff_y);
1159 +
		} else if (prev_layer_properties[1][1].bitmap_mode) {
1160 +
			render_layer_line_bitmap(1, eff_y);
1161 +
		} else {
1162 +
			render_layer_line_tile(1, eff_y);
1163 +
		}
1164 +
	}
1165 +
1166 +
	// If video output is enabled, calculate color indices for line.
1167 +
	if (out_mode != 0) {
1168 +
		// Add border after if required.
1169 +
		if (y < vstart || y >= vstop) {
1170 +
			uint32_t border_fill = border_color;
1171 +
			border_fill = border_fill | (border_fill << 8);
1172 +
			border_fill = border_fill | (border_fill << 16);
1173 +
			memset(col_line, border_fill, SCREEN_WIDTH);
1174 +
		} else {
1175 +
			hstart = hstart < 640 ? hstart : 640;
1176 +
			hstop = hstop < 640 ? hstop : 640;
1177 +
1178 +
			for (uint16_t x = s_pos_x_p; x < hstart && x < s_pos_x; ++x) {
1179 +
				col_line[x] = border_color;
1180 +
			}
1181 +
1182 +
			const uint32_t scale = reg_composer[1];
1183 +
			for (uint16_t x = MAX(hstart, s_pos_x_p); x < hstop && x < s_pos_x; ++x) {
1184 +
				uint16_t eff_x = eff_x_fp >> 16;
1185 +
				col_line[x] = (eff_x < SCREEN_WIDTH) ? calculate_line_col_index(sprite_line_z[eff_x], sprite_line_col[eff_x], layer_line[0][eff_x], layer_line[1][eff_x]) : 0;
1186 +
				eff_x_fp += (scale << 9);
1187 +
			}
1188 +
			for (uint16_t x = hstop; x < s_pos_x; ++x) {
1189 +
				col_line[x] = border_color;
1190 +
			}
1191 +
		}
1192 +
	}
1193 +
1194 +
	// Look up all color indices.
1195 +
	uint32_t* framebuffer4_begin = ((uint32_t*)framebuffer) + (y * SCREEN_WIDTH) + s_pos_x_p;
1196 +
	{
1197 +
		uint32_t* framebuffer4 = framebuffer4_begin;
1198 +
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++) {
1199 +
			*framebuffer4++ = video_palette.entries[col_line[x]];
1200 +
		}
1201 +
	}
1202 +
1203 +
	// NTSC overscan
1204 +
	if (out_mode == 2) {
1205 +
		uint32_t* framebuffer4 = framebuffer4_begin;
1206 +
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++)
1207 +
		{
1208 +
			if (x < SCREEN_WIDTH * TITLE_SAFE_X ||
1209 +
				x > SCREEN_WIDTH * (1 - TITLE_SAFE_X) ||
1210 +
				y < SCREEN_HEIGHT * TITLE_SAFE_Y ||
1211 +
				y > SCREEN_HEIGHT * (1 - TITLE_SAFE_Y)) {
1212 +
1213 +
				// Divide RGB elements by 4.
1214 +
				*framebuffer4 &= 0x00fcfcfc;
1215 +
				*framebuffer4 >>= 2;
1216 +
			}
1217 +
			framebuffer4++;
1218 +
		}
1219 +
	}
1220 +
1221 +
	s_pos_x_p = s_pos_x;
1222 +
}
1223 +
1224 +
static void
1225 +
update_isr_and_coll(uint16_t y, uint16_t compare)
1226 +
{
1227 +
	if (y == SCREEN_HEIGHT) {
1228 +
		if (sprite_line_collisions != 0) {
1229 +
			isr |= 4;
1230 +
		}
1231 +
		isr = (isr & 0xf) | sprite_line_collisions;
1232 +
		sprite_line_collisions = 0;
1233 +
		isr |= 1; // VSYNC IRQ
1234 +
	}
1235 +
	if (y == compare) { // LINE IRQ
1236 +
		isr |= 2;
1237 +
	}
1238 +
}
1239 +
1240 +
bool
1241 +
video_step(float mhz, float steps, bool midline)
1242 +
{
1243 +
	uint16_t y = 0;
1244 +
	bool ntsc_mode = reg_composer[0] & 2;
1245 +
	bool new_frame = false;
1246 +
	vga_scan_pos_x += PIXEL_FREQ * steps / mhz;
1247 +
	if (vga_scan_pos_x > VGA_SCAN_WIDTH) {
1248 +
		vga_scan_pos_x -= VGA_SCAN_WIDTH;
1249 +
		if (!ntsc_mode) {
1250 +
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, VGA_SCAN_WIDTH);
1251 +
		}
1252 +
		vga_scan_pos_y++;
1253 +
		if (vga_scan_pos_y == SCAN_HEIGHT) {
1254 +
			vga_scan_pos_y = 0;
1255 +
			if (!ntsc_mode) {
1256 +
				new_frame = true;
1257 +
				frame_count++;
1258 +
			}
1259 +
		}
1260 +
		if (!ntsc_mode) {
1261 +
			update_isr_and_coll(vga_scan_pos_y - VGA_Y_OFFSET, irq_line);
1262 +
		}
1263 +
	} else if (midline) {
1264 +
		if (!ntsc_mode) {
1265 +
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, vga_scan_pos_x);
1266 +
		}
1267 +
	}
1268 +
	ntsc_half_cnt += PIXEL_FREQ * steps / mhz;
1269 +
	if (ntsc_half_cnt > NTSC_HALF_SCAN_WIDTH) {
1270 +
		ntsc_half_cnt -= NTSC_HALF_SCAN_WIDTH;
1271 +
		if (ntsc_mode) {
1272 +
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1273 +
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1274 +
				if ((y & 1) == 0) {
1275 +
					render_line(y, NTSC_HALF_SCAN_WIDTH);
1276 +
				}
1277 +
			} else {
1278 +
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1279 +
				if ((y & 1) == 0) {
1280 +
					render_line(y | 1, NTSC_HALF_SCAN_WIDTH);
1281 +
				}
1282 +
			}
1283 +
		}
1284 +
		ntsc_scan_pos_y++;
1285 +
		if (ntsc_scan_pos_y == SCAN_HEIGHT) {
1286 +
			reg_composer[0] |= 0x80;
1287 +
			if (ntsc_mode) {
1288 +
				new_frame = true;
1289 +
				frame_count++;
1290 +
			}
1291 +
		}
1292 +
		if (ntsc_scan_pos_y == SCAN_HEIGHT*2) {
1293 +
			reg_composer[0] &= ~0x80;
1294 +
			ntsc_scan_pos_y = 0;
1295 +
			if (ntsc_mode) {
1296 +
				new_frame = true;
1297 +
				frame_count++;
1298 +
			}
1299 +
		}
1300 +
		if (ntsc_mode) {
1301 +
			// this is correct enough for even screen heights
1302 +
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1303 +
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW, irq_line & ~1);
1304 +
			} else {
1305 +
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH, irq_line & ~1);
1306 +
			}
1307 +
		}
1308 +
	} else if (midline) {
1309 +
		if (ntsc_mode) {
1310 +
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1311 +
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1312 +
				if ((y & 1) == 0) {
1313 +
					render_line(y, ntsc_half_cnt);
1314 +
				}
1315 +
			} else {
1316 +
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1317 +
				if ((y & 1) == 0) {
1318 +
					render_line(y | 1, ntsc_half_cnt);
1319 +
				}
1320 +
			}
1321 +
		}
1322 +
	}
1323 +
1324 +
	return new_frame;
1325 +
}
1326 +
1327 +
bool
1328 +
video_get_irq_out()
1329 +
{
1330 +
	uint8_t tmp_isr = isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
1331 +
	return (tmp_isr & ien) != 0;
1332 +
}
1333 +
1334 +
//
1335 +
// saves the video memory and register content into a file
1336 +
//
1337 +
1338 +
void
1339 +
video_save(SDL_RWops *f)
1340 +
{
1341 +
	SDL_RWwrite(f, &video_ram[0], sizeof(uint8_t), sizeof(video_ram));
1342 +
	SDL_RWwrite(f, &reg_composer[0], sizeof(uint8_t), sizeof(reg_composer));
1343 +
	SDL_RWwrite(f, &palette[0], sizeof(uint8_t), sizeof(palette));
1344 +
	SDL_RWwrite(f, &reg_layer[0][0], sizeof(uint8_t), sizeof(reg_layer));
1345 +
	SDL_RWwrite(f, &sprite_data[0], sizeof(uint8_t), sizeof(sprite_data));
1346 +
}
1347 +
1348 +
bool
1349 +
video_update()
1350 +
{
1351 +
	static bool cmd_down = false;
1352 +
	static bool alt_down = false;
1353 +
	bool mouse_changed = false;
1354 +
1355 +
	// for activity LED, overlay red 8x4 square into top right of framebuffer
1356 +
	// for progressive modes, draw LED only on even scanlines
1357 +
	for (int y = 0; y < 4; y+=1+!!((reg_composer[0] & 0x0b) > 0x09)) {
1358 +
		for (int x = SCREEN_WIDTH - 8; x < SCREEN_WIDTH; x++) {
1359 +
			uint8_t b = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0];
1360 +
			uint8_t g = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1];
1361 +
			uint8_t r = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2];
1362 +
			r = (uint32_t)r * (255 - activity_led) / 255 + activity_led;
1363 +
			g = (uint32_t)g * (255 - activity_led) / 255;
1364 +
			b = (uint32_t)b * (255 - activity_led) / 255;
1365 +
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0] = b;
1366 +
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1] = g;
1367 +
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2] = r;
1368 +
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 3] = 0x00;
1369 +
		}
1370 +
	}
1371 +
1372 +
	SDL_UpdateTexture(sdlTexture, NULL, framebuffer, SCREEN_WIDTH * 4);
1373 +
1374 +
	if (record_gif > RECORD_GIF_PAUSED) {
1375 +
		if(!GifWriteFrame(&gif_writer, framebuffer, SCREEN_WIDTH, SCREEN_HEIGHT, 2, 8, false)) {
1376 +
			// if that failed, stop recording
1377 +
			GifEnd(&gif_writer);
1378 +
			record_gif = RECORD_GIF_DISABLED;
1379 +
			printf("Unexpected end of recording.\n");
1380 +
		}
1381 +
		if (record_gif == RECORD_GIF_SINGLE) { // if single-shot stop recording
1382 +
			record_gif = RECORD_GIF_PAUSED;  // need to close in video_end()
1383 +
		}
1384 +
	}
1385 +
1386 +
	SDL_RenderClear(renderer);
1387 +
	SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
1388 +
1389 +
	if (debugger_enabled && showDebugOnRender != 0) {
1390 +
		DEBUGRenderDisplay(SCREEN_WIDTH, SCREEN_HEIGHT);
1391 +
		SDL_RenderPresent(renderer);
1392 +
		return true;
1393 +
	}
1394 +
1395 +
	SDL_RenderPresent(renderer);
1396 +
1397 +
	SDL_Event event;
1398 +
	while (SDL_PollEvent(&event)) {
1399 +
		if (event.type == SDL_QUIT) {
1400 +
			return false;
1401 +
		}
1402 +
		if (event.type == SDL_KEYDOWN) {
1403 +
			bool consumed = false;
1404 +
			if (cmd_down && !(disable_emu_cmd_keys || mouse_grabbed)) {
1405 +
				if (event.key.keysym.sym == SDLK_s) {
1406 +
					machine_dump("user keyboard request");
1407 +
					consumed = true;
1408 +
				} else if (event.key.keysym.sym == SDLK_r) {
1409 +
					machine_reset();
1410 +
					consumed = true;
1411 +
				} else if (event.key.keysym.sym == SDLK_BACKSPACE) {
1412 +
					machine_nmi();
1413 +
					consumed = true;
1414 +
				} else if (event.key.keysym.sym == SDLK_v) {
1415 +
					machine_paste(SDL_GetClipboardText(), true);
1416 +
					consumed = true;
1417 +
				} else if (event.key.keysym.sym == SDLK_f || event.key.keysym.sym == SDLK_RETURN) {
1418 +
					is_fullscreen = !is_fullscreen;
1419 +
					SDL_SetWindowFullscreen(window, is_fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
1420 +
					consumed = true;
1421 +
				} else if (event.key.keysym.sym == SDLK_PLUS || event.key.keysym.sym == SDLK_EQUALS) {
1422 +
					machine_toggle_warp();
1423 +
					consumed = true;
1424 +
				} else if (event.key.keysym.sym == SDLK_a) {
1425 +
					sdcard_attach();
1426 +
					consumed = true;
1427 +
				} else if (event.key.keysym.sym == SDLK_d) {
1428 +
					sdcard_detach();
1429 +
					consumed = true;
1430 +
#ifndef __EMSCRIPTEN__
1431 +
				} else if (event.key.keysym.sym == SDLK_p) {
1432 +
					screenshot();
1433 +
					consumed = true;
1434 +
#endif
1435 +
				}
1436 +
			}
1437 +
			if (cmd_down) {
1438 +
				if (event.key.keysym.sym == SDLK_m) {
1439 +
					mousegrab_toggle();
1440 +
					consumed = true;
1441 +
				}
1442 +
			}
1443 +
			if (alt_down && !disable_emu_cmd_keys) {
1444 +
				// Capture ALT+F4 key sequence to exit emulator if it is not handled by SDL
1445 +
				if (event.key.keysym.sym == SDLK_F4) {
1446 +
					printf("Shutdown requested by ALT+F4\n");
1447 +
					main_shutdown();
1448 +
#ifdef __EMSCRIPTEN__
1449 +
					emscripten_force_exit(0);
1450 +
#endif
1451 +
					exit(0);
1452 +
				}
1453 +
			}
1454 +
			if (!consumed) {
1455 +
				if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
1456 +
					cmd_down = true;
1457 +
				}
1458 +
				if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
1459 +
					alt_down = true;
1460 +
				}
1461 +
				handle_keyboard(true, event.key.keysym.sym, event.key.keysym.scancode);
1462 +
			}
1463 +
			continue;
1464 +
		}
1465 +
		if (event.type == SDL_KEYUP) {
1466 +
			if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
1467 +
				cmd_down = false;
1468 +
			}
1469 +
			if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
1470 +
				alt_down = false;
1471 +
			}
1472 +
			handle_keyboard(false, event.key.keysym.sym, event.key.keysym.scancode);
1473 +
			continue;
1474 +
		}
1475 +
		if (event.type == SDL_MOUSEBUTTONDOWN) {
1476 +
			switch (event.button.button) {
1477 +
				case SDL_BUTTON_LEFT:
1478 +
					mouse_button_down(0);
1479 +
					mouse_changed = true;
1480 +
					break;
1481 +
				case SDL_BUTTON_RIGHT:
1482 +
					mouse_button_down(1);
1483 +
					mouse_changed = true;
1484 +
					break;
1485 +
				case SDL_BUTTON_MIDDLE:
1486 +
					mouse_button_down(2);
1487 +
					mouse_changed = true;
1488 +
					break;
1489 +
			}
1490 +
		}
1491 +
		if (event.type == SDL_MOUSEBUTTONUP) {
1492 +
			switch (event.button.button) {
1493 +
				case SDL_BUTTON_LEFT:
1494 +
					mouse_button_up(0);
1495 +
					mouse_changed = true;
1496 +
					break;
1497 +
				case SDL_BUTTON_RIGHT:
1498 +
					mouse_button_up(1);
1499 +
					mouse_changed = true;
1500 +
					break;
1501 +
				case SDL_BUTTON_MIDDLE:
1502 +
					mouse_button_up(2);
1503 +
					mouse_changed = true;
1504 +
					break;
1505 +
			}
1506 +
		}
1507 +
		if (event.type == SDL_MOUSEMOTION) {
1508 +
			static int mouse_x;
1509 +
			static int mouse_y;
1510 +
			if (mouse_grabbed) {
1511 +
				mouse_move(event.motion.xrel, event.motion.yrel);
1512 +
			} else {
1513 +
				mouse_move(event.motion.x - mouse_x, event.motion.y - mouse_y);
1514 +
			}
1515 +
			mouse_x = event.motion.x;
1516 +
			mouse_y = event.motion.y;
1517 +
			mouse_changed = true;
1518 +
		}
1519 +
		if (event.type == SDL_MOUSEWHEEL) {
1520 +
			mouse_set_wheel(event.wheel.y);
1521 +
			mouse_changed = true;
1522 +
		}
1523 +
1524 +
		if (event.type == SDL_JOYDEVICEADDED) {
1525 +
			joystick_add(event.jdevice.which);
1526 +
		}
1527 +
	    if (event.type == SDL_JOYDEVICEREMOVED) {
1528 +
		    joystick_remove(event.jdevice.which);
1529 +
	    }
1530 +
	    if (event.type == SDL_CONTROLLERBUTTONDOWN) {
1531 +
		    joystick_button_down(event.cbutton.which, event.cbutton.button);
1532 +
	    }
1533 +
		if (event.type == SDL_CONTROLLERBUTTONUP) {
1534 +
		    joystick_button_up(event.cbutton.which, event.cbutton.button);
1535 +
	    }
1536 +
1537 +
	}
1538 +
	if (mouse_changed) {
1539 +
		mouse_send_state();
1540 +
	}
1541 +
	return true;
1542 +
}
1543 +
1544 +
void
1545 +
video_end()
1546 +
{
1547 +
	if (debugger_enabled) {
1548 +
		DEBUGFreeUI();
1549 +
	}
1550 +
1551 +
	if (record_gif != RECORD_GIF_DISABLED) {
1552 +
		GifEnd(&gif_writer);
1553 +
		record_gif = RECORD_GIF_DISABLED;
1554 +
	}
1555 +
1556 +
	is_fullscreen = false;
1557 +
	SDL_SetWindowFullscreen(window, 0);
1558 +
	SDL_DestroyRenderer(renderer);
1559 +
	SDL_DestroyWindow(window);
1560 +
}
1561 +
1562 +
1563 +
static const int increments[32] = {
1564 +
	0,   0,
1565 +
	1,   -1,
1566 +
	2,   -2,
1567 +
	4,   -4,
1568 +
	8,   -8,
1569 +
	16,  -16,
1570 +
	32,  -32,
1571 +
	64,  -64,
1572 +
	128, -128,
1573 +
	256, -256,
1574 +
	512, -512,
1575 +
	40,  -40,
1576 +
	80,  -80,
1577 +
	160, -160,
1578 +
	320, -320,
1579 +
	640, -640,
1580 +
};
1581 +
1582 +
uint32_t
1583 +
video_get_address(uint8_t sel)
1584 +
{
1585 +
	uint32_t address = io_addr[sel];
1586 +
	return address & 0x1ffff;
1587 +
}
1588 +
1589 +
uint32_t
1590 +
get_and_inc_address(uint8_t sel, bool write)
1591 +
{
1592 +
	uint32_t address = io_addr[sel];
1593 +
	int16_t incr = increments[io_inc[sel]];
1594 +
1595 +
	if (fx_4bit_mode && fx_nibble_incr[sel] && !incr) {
1596 +
		if (fx_nibble_bit[sel]) {
1597 +
			if ((io_inc[sel] & 1) == 0) io_addr[sel] += 1;
1598 +
			fx_nibble_bit[sel] = 0;
1599 +
		} else {
1600 +
			if (io_inc[sel] & 1) io_addr[sel] -= 1;
1601 +
			fx_nibble_bit[sel] = 1;
1602 +
		}
1603 +
	}
1604 +
1605 +
	if (sel == 1 && fx_16bit_hop) {
1606 +
		if (incr == 4) {
1607 +
			if (fx_16bit_hop_align == (address & 0x3))
1608 +
				incr = 1;
1609 +
			else
1610 +
				incr = 3;
1611 +
		} else if (incr == 320) {
1612 +
			if (fx_16bit_hop_align == (address & 0x3))
1613 +
				incr = 1;
1614 +
			else
1615 +
				incr = 319;
1616 +
		}
1617 +
	}
1618 +
1619 +
	io_addr[sel] += incr;
1620 +
1621 +
	if (sel == 1 && fx_addr1_mode == 1) { // FX line draw mode
1622 +
		fx_x_pixel_position += fx_x_pixel_increment;
1623 +
		if (fx_x_pixel_position & 0x10000) {
1624 +
			fx_x_pixel_position &= ~0x10000;
1625 +
			if (fx_4bit_mode && fx_nibble_incr[0]) {
1626 +
				if (fx_nibble_bit[1]) {
1627 +
					if ((io_inc[0] & 1) == 0) io_addr[1] += 1;
1628 +
					fx_nibble_bit[1] = 0;
1629 +
				} else {
1630 +
					if (io_inc[0] & 1) io_addr[1] -= 1;
1631 +
					fx_nibble_bit[1] = 1;
1632 +
				}
1633 +
			}
1634 +
			io_addr[1] += increments[io_inc[0]];
1635 +
		}
1636 +
	} else if (fx_addr1_mode == 2 && write == false) { // FX polygon fill mode
1637 +
		fx_x_pixel_position += fx_x_pixel_increment;
1638 +
		fx_y_pixel_position += fx_y_pixel_increment;
1639 +
		fx_poly_fill_length = ((int32_t) fx_y_pixel_position >> 16) - ((int32_t) fx_x_pixel_position >> 16);
1640 +
		if (sel == 0 && fx_cache_byte_cycling && !fx_cache_fill) {
1641 +
			fx_cache_byte_index = (fx_cache_byte_index + 1) & 3;
1642 +
		}
1643 +
		if (sel == 1) {
1644 +
			if (fx_4bit_mode) {
1645 +
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 17);
1646 +
				fx_nibble_bit[1] = (fx_x_pixel_position >> 16) & 1;
1647 +
			} else {
1648 +
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 16);
1649 +
			}
1650 +
		}
1651 +
	} else if (sel == 1 && fx_addr1_mode == 3 && write == false) { // FX affine mode
1652 +
		fx_x_pixel_position += fx_x_pixel_increment;
1653 +
		fx_y_pixel_position += fx_y_pixel_increment;
1654 +
	}
1655 +
	return address;
1656 +
}
1657 +
1658 +
void
1659 +
fx_affine_prefetch(void)
1660 +
{
1661 +
	if (fx_addr1_mode != 3) return; // only if affine mode is selected
1662 +
1663 +
	uint32_t address;
1664 +
	uint8_t affine_x_tile = (fx_x_pixel_position >> 19) & 0xff;
1665 +
	uint8_t affine_y_tile = (fx_y_pixel_position >> 19) & 0xff;
1666 +
	uint8_t affine_x_sub_tile = (fx_x_pixel_position >> 16) & 0x07;
1667 +
	uint8_t affine_y_sub_tile = (fx_y_pixel_position >> 16) & 0x07;
1668 +
1669 +
	if (!fx_affine_clip) { // wrap
1670 +
		affine_x_tile &= fx_affine_map_size - 1;
1671 +
		affine_y_tile &= fx_affine_map_size - 1;
1672 +
	}
1673 +
1674 +
	if (affine_x_tile >= fx_affine_map_size || affine_y_tile >= fx_affine_map_size) {
1675 +
		// We clipped, return value for tile 0
1676 +
		address = fx_affine_tile_base + (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
1677 +
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
1678 +
	} else {
1679 +
		// Get the address within the tile map
1680 +
		address = fx_affine_map_base + (affine_y_tile * fx_affine_map_size) + affine_x_tile;
1681 +
		// Now translate that to the tile base address
1682 +
		uint8_t affine_tile_idx = video_space_read(address);
1683 +
		address = fx_affine_tile_base + (affine_tile_idx << (6 - fx_4bit_mode));
1684 +
		// Now add the sub-tile address
1685 +
		address += (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
1686 +
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
1687 +
	}
1688 +
	io_addr[1] = address;
1689 +
	io_rddata[1] = video_space_read(address);
1690 +
}
1691 +
1692 +
//
1693 +
// Vera: Internal Video Address Space
1694 +
//
1695 +
1696 +
uint8_t
1697 +
video_space_read(uint32_t address)
1698 +
{
1699 +
	return video_ram[address & 0x1FFFF];
1700 +
}
1701 +
1702 +
static void
1703 +
video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size)
1704 +
{
1705 +
	if (address >= ADDR_VRAM_START && (address+size) <= ADDR_VRAM_END) {
1706 +
		memcpy(dest, &video_ram[address], size);
1707 +
	} else {
1708 +
		for(int i = 0; i < size; ++i) {
1709 +
			*dest++ = video_space_read(address + i);
1710 +
		}
1711 +
	}
1712 +
}
1713 +
1714 +
void
1715 +
video_space_write(uint32_t address, uint8_t value)
1716 +
{
1717 +
	video_ram[address & 0x1FFFF] = value;
1718 +
1719 +
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
1720 +
		audio_render();
1721 +
		psg_writereg(address & 0x3f, value);
1722 +
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
1723 +
		palette[address & 0x1ff] = value;
1724 +
		video_palette.dirty = true;
1725 +
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
1726 +
		sprite_data[(address >> 3) & 0x7f][address & 0x7] = value;
1727 +
		refresh_sprite_properties((address >> 3) & 0x7f);
1728 +
	}
1729 +
}
1730 +
1731 +
1732 +
void
1733 +
fx_video_space_write(uint32_t address, bool nibble, uint8_t value)
1734 +
{
1735 +
	if (fx_4bit_mode) {
1736 +
		if (nibble) {
1737 +
			if (!fx_trans_writes || (value & 0x0f) > 0) {
1738 +
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
1739 +
			}
1740 +
		} else {
1741 +
			if (!fx_trans_writes || (value & 0xf0) > 0) {
1742 +
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
1743 +
			}
1744 +
		}
1745 +
	} else {
1746 +
		if (!fx_trans_writes || value > 0) video_ram[address & 0x1FFFF] = value;
1747 +
	}
1748 +
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
1749 +
		audio_render();
1750 +
		psg_writereg(address & 0x3f, value);
1751 +
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
1752 +
		palette[address & 0x1ff] = value;
1753 +
		video_palette.dirty = true;
1754 +
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
1755 +
		sprite_data[(address >> 3) & 0x7f][address & 0x7] = value;
1756 +
		refresh_sprite_properties((address >> 3) & 0x7f);
1757 +
	}
1758 +
}
1759 +
1760 +
void
1761 +
fx_vram_cache_write(uint32_t address, uint8_t value, uint8_t mask)
1762 +
{
1763 +
	if (!fx_trans_writes || value > 0) {
1764 +
		switch (mask) {
1765 +
			case 0:
1766 +
				video_ram[address & 0x1FFFF] = value;
1767 +
				break;
1768 +
			case 1:
1769 +
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
1770 +
				break;
1771 +
			case 2:
1772 +
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
1773 +
				break;
1774 +
			case 3:
1775 +
				// Do nothing
1776 +
				break;
1777 +
		}
1778 +
	}
1779 +
}
1780 +
1781 +
uint32_t video_get_fx_accum() {
1782 +
	return fx_mult_accumulator;
1783 +
}
1784 +
uint8_t video_get_dc_value(uint8_t reg) {
1785 +
	switch (reg & 0x1F) {
1786 +
		case 0x00:
1787 +
		case 0x01:
1788 +
		case 0x02:
1789 +
		case 0x03:
1790 +
		case 0x04:
1791 +
		case 0x05:
1792 +
		case 0x06:
1793 +
		case 0x07:
1794 +
		case 0x08:
1795 +
		case 0x09:
1796 +
		case 0x0a:
1797 +
		case 0x0c:
1798 +
		case 0x0d:
1799 +
		case 0x0e:
1800 +
		case 0x0f:
1801 +
			return reg_composer[reg];
1802 +
			break;
1803 +
		case 0x0b:
1804 +
			return reg_composer[reg] & 0x3f;
1805 +
			break;
1806 +
		case 0x10: // DCSEL=4, $9F29
1807 +
			return (fx_x_pixel_position >> 16) & 0xff;
1808 +
			break;
1809 +
		case 0x11: // DCSEL=4, $9F2A
1810 +
			return ((fx_x_pixel_position >> 24) & 0x07) | (fx_x_pixel_position & 0x80);
1811 +
			break;
1812 +
		case 0x12: // DCSEL=4, $9F2B
1813 +
			return (fx_y_pixel_position >> 16) & 0xff;
1814 +
			break;
1815 +
		case 0x13: // DCSEL=4, $9F2C
1816 +
			return ((fx_y_pixel_position >> 24) & 0x07) | (fx_y_pixel_position & 0x80);
1817 +
			break;
1818 +
		case 0x14: // DCSEL=5, $9F29
1819 +
			return (fx_x_pixel_position >> 8) & 0xff;
1820 +
			break;
1821 +
		case 0x15: // DCSEL=4, $9F2A
1822 +
			return (fx_y_pixel_position >> 8) & 0xff;
1823 +
			break;
1824 +
		case 0x16: // DCSEL=5, 0x9F2B
1825 +
			if (fx_poly_fill_length >= 768) {
1826 +
				return ((fx_2bit_poly && fx_addr1_mode == 2) ? 0x00 : 0x80);
1827 +
			}
1828 +
			if (fx_4bit_mode) {
1829 +
				if (fx_2bit_poly && fx_addr1_mode == 2) {
1830 +
					return ((fx_y_pixel_position & 0x00008000) >> 8) |
1831 +
						((fx_x_pixel_position >> 11) & 0x60) |
1832 +
						((fx_x_pixel_position >> 14) & 0x10) |
1833 +
						((fx_poly_fill_length & 0x0007) << 1) |
1834 +
						((fx_x_pixel_position & 0x00008000) >> 15);
1835 +
				} else {
1836 +
					return ((!!(fx_poly_fill_length & 0xfff8)) << 7) |
1837 +
						((fx_x_pixel_position >> 11) & 0x60) |
1838 +
						((fx_x_pixel_position >> 14) & 0x10) |
1839 +
						((fx_poly_fill_length & 0x0007) << 1);
1840 +
				}
1841 +
			} else {
1842 +
				return ((!!(fx_poly_fill_length & 0xfff0)) << 7) |
1843 +
					((fx_x_pixel_position >> 11) & 0x60) |
1844 +
					((fx_poly_fill_length & 0x000f) << 1);
1845 +
			}
1846 +
			break;
1847 +
		case 0x17: // DCSEL=5, 0x9F2C
1848 +
			return ((fx_poly_fill_length & 0x03f8) >> 2);
1849 +
			break;
1850 +
		case 0x18: // DCSEL=6, 0x9F29
1851 +
			return fx_cache[0];
1852 +
			break;
1853 +
		case 0x19: // DCSEL=6, 0x9F2A
1854 +
			return fx_cache[1];
1855 +
			break;
1856 +
		case 0x1a: // DCSEL=6, 0x9F2B
1857 +
			return fx_cache[2];
1858 +
			break;
1859 +
		case 0x1b: // DCSEL=6, 0x9F2C
1860 +
			return fx_cache[3];
1861 +
			break;
1862 +
		default:
1863 +
			break;
1864 +
	}
1865 +
1866 +
	return vera_version_string[reg % 4];
1867 +
}
1868 +
1869 +
//
1870 +
// Vera: 6502 I/O Interface
1871 +
//
1872 +
// if debugOn, read without any side effects (registers & memory unchanged)
1873 +
1874 +
static void check_not_readonly(uint8_t reg) {
1875 +
	bool wrong = false;
1876 +
	switch(io_dcsel) {
1877 +
		case 5: {
1878 +
			switch(reg) {
1879 +
				case 0x0b:		// DCSEL=5 FX_POLY_FILL_L
1880 +
				case 0x0c:		// DCSEL=5 FX_POLY_FILL_H
1881 +
					wrong=true;
1882 +
					break;
1883 +
			}
1884 +
			break;
1885 +
		}
1886 +
		case 63: {
1887 +
			switch(reg) {
1888 +
				case 0x09:		// DCSEL=63 DC_VER0
1889 +
				case 0x0a:		// DCSEL=63 DC_VER1
1890 +
				case 0x0b:		// DCSEL=63 DC_VER2
1891 +
				case 0x0c:		// DCSEL=63 DC_VER3
1892 +
					wrong=true;
1893 +
					break;
1894 +
			}
1895 +
			break;
1896 +
		}
1897 +
	}
1898 +
1899 +
	if(wrong)
1900 +
		printf("Warning: %04X wrote to read-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
1901 +
}
1902 +
1903 +
static void check_not_writeonly(uint8_t reg) {
1904 +
	bool wrong = false;
1905 +
	switch(io_dcsel) {
1906 +
		case 2: {
1907 +
			switch(reg) {
1908 +
				case 0x0a:		// DCSEL=2 FX_TILEBASE
1909 +
				case 0x0b:		// DCSEL=2 FX_MAPBASE
1910 +
				case 0x0c:		// DCSEL=2 FX_MULT
1911 +
					wrong=true;
1912 +
					break;
1913 +
			}
1914 +
			break;
1915 +
		}
1916 +
		case 3:
1917 +
		case 4: {
1918 +
			switch(reg) {
1919 +
				case 0x09:		// DCSEL=3/4 FX_X_INCR_L/FX_X_POS_L
1920 +
				case 0x0a:		// DCSEL=3/4 FX_X_INCR_H/FX_X_POS_H
1921 +
				case 0x0b:		// DCSEL=3/4 FX_Y_INCR_L/FX_Y_POS_L
1922 +
				case 0x0c:		// DCSEL=3/4 FX_Y_INCR_H/FX_Y_POS_H
1923 +
					wrong=true;
1924 +
					break;
1925 +
			}
1926 +
			break;
1927 +
		}
1928 +
		case 5: {
1929 +
			switch(reg) {
1930 +
				case 0x09:		// DCSEL=5 FX_X_POS_S
1931 +
				case 0x0a:		// DCSEL=5 FX_Y_POS_S
1932 +
					wrong=true;
1933 +
					break;
1934 +
			}
1935 +
			break;
1936 +
		}
1937 +
		case 6: {
1938 +
			switch(reg) {
1939 +
				case 0x0b:		// DCSEL=6 FX_CACHE_H
1940 +
				case 0x0c:		// DCSEL=6 FX_CACHE_U
1941 +
					wrong=true;
1942 +
					break;
1943 +
			}
1944 +
			break;
1945 +
		}
1946 +
	}
1947 +
1948 +
	if(wrong)
1949 +
		printf("Warning: %04X read from write-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
1950 +
}
1951 +
1952 +
uint8_t video_read(uint8_t reg, bool debugOn) {
1953 +
	bool ntsc_mode = reg_composer[0] & 2;
1954 +
	uint16_t scanline = ntsc_mode ? ntsc_scan_pos_y % SCAN_HEIGHT : vga_scan_pos_y;
1955 +
	if (scanline >= 512) scanline=511;
1956 +
1957 +
	check_not_writeonly(reg);
1958 +
1959 +
	switch (reg & 0x1F) {
1960 +
		case 0x00: return io_addr[io_addrsel] & 0xff;
1961 +
		case 0x01: return (io_addr[io_addrsel] >> 8) & 0xff;
1962 +
		case 0x02: return (io_addr[io_addrsel] >> 16) | (fx_nibble_bit[io_addrsel] << 1) | (fx_nibble_incr[io_addrsel] << 2) | (io_inc[io_addrsel] << 3);
1963 +
		case 0x03:
1964 +
		case 0x04: {
1965 +
			if (debugOn) {
1966 +
				return io_rddata[reg - 3];
1967 +
			}
1968 +
1969 +
			bool addr_nibble = fx_nibble_bit[reg - 3];
1970 +
			uint32_t address = get_and_inc_address(reg - 3, false);
1971 +
1972 +
			uint8_t value = io_rddata[reg - 3];
1973 +
1974 +
			if (reg == 4 && fx_addr1_mode == 3)
1975 +
				fx_affine_prefetch();
1976 +
			else
1977 +
				io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
1978 +
1979 +
			if (fx_cache_fill) {
1980 +
				if (fx_4bit_mode) {
1981 +
					uint8_t nibble_read = (addr_nibble ? ((value & 0x0f) << 4) : (value & 0xf0));
1982 +
1983 +
					if (fx_cache_nibble_index) {
1984 +
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0xf0) | (nibble_read >> 4);
1985 +
						fx_cache_nibble_index = 0;
1986 +
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
1987 +
					} else {
1988 +
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0x0f) | (nibble_read);
1989 +
						fx_cache_nibble_index = 1;
1990 +
					}
1991 +
				} else {
1992 +
					fx_cache[fx_cache_byte_index] = value;
1993 +
					if (fx_cache_increment_mode)
1994 +
						fx_cache_byte_index = (fx_cache_byte_index & 0x2) | ((fx_cache_byte_index + 1) & 0x1);
1995 +
					else
1996 +
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
1997 +
				}
1998 +
			}
1999 +
2000 +
			if (log_video) {
2001 +
				printf("READ  video_space[$%X] = $%02X\n", address, value);
2002 +
			}
2003 +
			return value;
2004 +
		}
2005 +
		case 0x05: return (io_dcsel << 1) | io_addrsel;
2006 +
		case 0x06: return ((irq_line & 0x100) >> 1) | ((scanline & 0x100) >> 2) | (ien & 0xF);
2007 +
		case 0x07: return isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
2008 +
		case 0x08: return scanline & 0xFF;
2009 +
2010 +
		case 0x09:
2011 +
		case 0x0A:
2012 +
		case 0x0B:
2013 +
		case 0x0C: {
2014 +
			int i = reg - 0x09 + (io_dcsel << 2);
2015 +
			if (debugOn) return video_get_dc_value(i);
2016 +
			switch (i) {
2017 +
				case 0x00:
2018 +
				case 0x01:
2019 +
				case 0x02:
2020 +
				case 0x03:
2021 +
				case 0x04:
2022 +
				case 0x05:
2023 +
				case 0x06:
2024 +
				case 0x07:
2025 +
				case 0x08:
2026 +
				case 0x16: // DCSEL=5, 0x9F2B
2027 +
				case 0x17: // DCSEL=5, 0x9F2C
2028 +
					return video_get_dc_value(i);
2029 +
					break;
2030 +
				case 0x18: // DCSEL=6, 0x9F29
2031 +
					fx_mult_accumulator = 0;
2032 +
					// fall out of the switch
2033 +
					break;
2034 +
				case 0x19: // DCSEL=6, 0x9F2A
2035 +
					; // <- avoids the error in some compilers about a declaration after a label
2036 +
					int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2037 +
					if (fx_subtract)
2038 +
						fx_mult_accumulator -= m_result;
2039 +
					else
2040 +
						fx_mult_accumulator += m_result;
2041 +
					// fall out of the switch
2042 +
					break;
2043 +
				default:
2044 +
					// The rest of the space is write-only,
2045 +
					// so reading the values out instead returns the version string.
2046 +
					// fall out of the switch
2047 +
					break;
2048 +
			}
2049 +
			return vera_version_string[i % 4];
2050 +
			break;
2051 +
		}
2052 +
		case 0x0D:
2053 +
		case 0x0E:
2054 +
		case 0x0F:
2055 +
		case 0x10:
2056 +
		case 0x11:
2057 +
		case 0x12:
2058 +
		case 0x13: return reg_layer[0][reg - 0x0D];
2059 +
2060 +
		case 0x14:
2061 +
		case 0x15:
2062 +
		case 0x16:
2063 +
		case 0x17:
2064 +
		case 0x18:
2065 +
		case 0x19:
2066 +
		case 0x1A: return reg_layer[1][reg - 0x14];
2067 +
2068 +
		case 0x1B: audio_render(); return pcm_read_ctrl();
2069 +
		case 0x1C: return pcm_read_rate();
2070 +
		case 0x1D: return 0;
2071 +
2072 +
		case 0x1E:
2073 +
		case 0x1F: return vera_spi_read(reg & 1);
2074 +
	}
2075 +
	return 0;
2076 +
}
2077 +
2078 +
void video_write(uint8_t reg, uint8_t value) {
2079 +
	// if (reg > 4) {
2080 +
	// 	printf("ioregisters[0x%02X] = 0x%02X\n", reg, value);
2081 +
	// }
2082 +
	//	printf("ioregisters[%d] = $%02X\n", reg, value);
2083 +
2084 +
	check_not_readonly(reg);
2085 +
2086 +
	switch (reg & 0x1F) {
2087 +
		case 0x00:
2088 +
			if (fx_2bit_poly && fx_4bit_mode && fx_addr1_mode == 2 && io_addrsel == 1) {
2089 +
				fx_2bit_poking = true;
2090 +
				io_addr[1] = (io_addr[1] & 0x1fffc) | (value & 0x3);
2091 +
			} else {
2092 +
				io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x1ff00) | value;
2093 +
				if (fx_16bit_hop && io_addrsel == 1)
2094 +
					fx_16bit_hop_align = value & 3;
2095 +
			}
2096 +
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2097 +
			break;
2098 +
		case 0x01:
2099 +
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x100ff) | (value << 8);
2100 +
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2101 +
			break;
2102 +
		case 0x02:
2103 +
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x0ffff) | ((value & 0x1) << 16);
2104 +
			fx_nibble_bit[io_addrsel] = (value >> 1) & 0x1;
2105 +
			fx_nibble_incr[io_addrsel] = (value >> 2) & 0x1;
2106 +
			io_inc[io_addrsel]  = value >> 3;
2107 +
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2108 +
			break;
2109 +
		case 0x03:
2110 +
		case 0x04: {
2111 +
			if (fx_2bit_poking && fx_addr1_mode) {
2112 +
				fx_2bit_poking = false;
2113 +
				uint8_t mask = value >> 6;
2114 +
				switch (mask) {
2115 +
					case 0x00:
2116 +
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0xc0) | (io_rddata[1] & 0x3f);
2117 +
						break;
2118 +
					case 0x01:
2119 +
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x30) | (io_rddata[1] & 0xcf);
2120 +
						break;
2121 +
					case 0x02:
2122 +
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x0c) | (io_rddata[1] & 0xf3);
2123 +
						break;
2124 +
					case 0x03:
2125 +
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x03) | (io_rddata[1] & 0xfc);
2126 +
						break;
2127 +
				}
2128 +
				break; // break out of the enclosing switch statement early, too
2129 +
			}
2130 +
2131 +
			if (enable_midline)
2132 +
				video_step(MHZ, 0, true); // potential midline raster effect
2133 +
			bool nibble = fx_nibble_bit[reg - 3];
2134 +
			uint32_t address = get_and_inc_address(reg - 3, true);
2135 +
			if (log_video) {
2136 +
				printf("WRITE video_space[$%X] = $%02X\n", address, value);
2137 +
			}
2138 +
2139 +
			uint8_t wrdata_to_use;
2140 +
			uint8_t ram_wrdata[4];
2141 +
			uint8_t nibble_mask[4];
2142 +
			uint8_t cache_to_use[4];
2143 +
2144 +
			if (fx_multiplier) {
2145 +
				int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2146 +
				if (fx_subtract)
2147 +
					m_result = fx_mult_accumulator - m_result;
2148 +
				else
2149 +
					m_result = fx_mult_accumulator + m_result;
2150 +
				cache_to_use[0] = (m_result) & 0xff;
2151 +
				cache_to_use[1] = (m_result >> 8) & 0xff;
2152 +
				cache_to_use[2] = (m_result >> 16) & 0xff;
2153 +
				cache_to_use[3] = (m_result >> 24) & 0xff;
2154 +
			} else {
2155 +
				memcpy(cache_to_use, fx_cache, sizeof(fx_cache));
2156 +
			}
2157 +
2158 +
			if (fx_cache_byte_cycling) {
2159 +
				wrdata_to_use = fx_cache[fx_cache_byte_index];
2160 +
			} else {
2161 +
				wrdata_to_use = value;
2162 +
			}
2163 +
2164 +
			if (fx_cache_write && !fx_cache_byte_cycling) {
2165 +
				ram_wrdata[0] = cache_to_use[0];
2166 +
				ram_wrdata[1] = cache_to_use[1];
2167 +
				ram_wrdata[2] = cache_to_use[2];
2168 +
				ram_wrdata[3] = cache_to_use[3];
2169 +
			} else {
2170 +
				ram_wrdata[0] = wrdata_to_use;
2171 +
				ram_wrdata[1] = wrdata_to_use;
2172 +
				ram_wrdata[2] = wrdata_to_use;
2173 +
				ram_wrdata[3] = wrdata_to_use;
2174 +
			}
2175 +
2176 +
			if (fx_cache_write) {
2177 +
				address &= 0x1fffc;
2178 +
				if (fx_trans_writes) {
2179 +
					if (fx_4bit_mode) {
2180 +
						nibble_mask[0] = (((ram_wrdata[0] & 0xf0) == 0) << 1) | ((ram_wrdata[0] & 0x0f) == 0);
2181 +
						nibble_mask[1] = (((ram_wrdata[1] & 0xf0) == 0) << 1) | ((ram_wrdata[1] & 0x0f) == 0);
2182 +
						nibble_mask[2] = (((ram_wrdata[2] & 0xf0) == 0) << 1) | ((ram_wrdata[2] & 0x0f) == 0);
2183 +
						nibble_mask[3] = (((ram_wrdata[3] & 0xf0) == 0) << 1) | ((ram_wrdata[3] & 0x0f) == 0);
2184 +
					} else {
2185 +
						nibble_mask[0] = (ram_wrdata[0] != 0) ? 0 : 3;
2186 +
						nibble_mask[1] = (ram_wrdata[1] != 0) ? 0 : 3;
2187 +
						nibble_mask[2] = (ram_wrdata[2] != 0) ? 0 : 3;
2188 +
						nibble_mask[3] = (ram_wrdata[3] != 0) ? 0 : 3;
2189 +
					}
2190 +
				} else {
2191 +
					nibble_mask[0] = value & 0x3;
2192 +
					nibble_mask[1] = (value >> 2) & 0x3;
2193 +
					nibble_mask[2] = (value >> 4) & 0x3;
2194 +
					nibble_mask[3] = (value >> 6) & 0x3;
2195 +
				}
2196 +
2197 +
				fx_vram_cache_write(address+0, ram_wrdata[0], nibble_mask[0]);
2198 +
				fx_vram_cache_write(address+1, ram_wrdata[1], nibble_mask[1]);
2199 +
				fx_vram_cache_write(address+2, ram_wrdata[2], nibble_mask[2]);
2200 +
				fx_vram_cache_write(address+3, ram_wrdata[3], nibble_mask[3]);
2201 +
			} else {
2202 +
				fx_video_space_write(address, nibble, wrdata_to_use); // Normal write
2203 +
			}
2204 +
2205 +
			io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
2206 +
			break;
2207 +
		}
2208 +
		case 0x05:
2209 +
			if (value & 0x80) {
2210 +
				video_reset();
2211 +
			}
2212 +
			io_dcsel = (value >> 1) & 0x3f;
2213 +
			io_addrsel = value & 1;
2214 +
			break;
2215 +
		case 0x06:
2216 +
			irq_line = (irq_line & 0xFF) | ((value >> 7) << 8);
2217 +
			ien = value & 0xF;
2218 +
			break;
2219 +
		case 0x07:
2220 +
			isr &= value ^ 0xff;
2221 +
			break;
2222 +
		case 0x08:
2223 +
			irq_line = (irq_line & 0x100) | value;
2224 +
			break;
2225 +
2226 +
		case 0x09:
2227 +
		case 0x0A:
2228 +
		case 0x0B:
2229 +
		case 0x0C: {
2230 +
			video_step(MHZ, 0, true); // potential midline raster effect
2231 +
			int i = reg - 0x09 + (io_dcsel << 2);
2232 +
			if (i == 0) {
2233 +
				// if progressive mode field goes from 0 to 1
2234 +
				// or if mode goes from vga to something else with
2235 +
				// progressive mode on, clear the framebuffer
2236 +
				if (((reg_composer[0] & 0x8) == 0 && (value & 0x8)) ||
2237 +
					((reg_composer[0] & 0x3) == 1 && (value & 0x3) > 1 && (value & 0x8))) {
2238 +
					memset(framebuffer, 0x00, SCREEN_WIDTH * SCREEN_HEIGHT * 4);
2239 +
				}
2240 +
2241 +
				// interlace field bit is read-only
2242 +
				reg_composer[0] = (reg_composer[0] & ~0x7f) | (value & 0x7f);
2243 +
				video_palette.dirty = true;
2244 +
			} else {
2245 +
				reg_composer[i] = value;
2246 +
			}
2247 +
2248 +
			switch (i) {
2249 +
				case 0x08: // DCSEL=2, $9F29
2250 +
					fx_addr1_mode = value & 0x03;
2251 +
					fx_4bit_mode = (value & 0x04) >> 2;
2252 +
					fx_16bit_hop = (value & 0x08) >> 3;
2253 +
					fx_cache_byte_cycling = (value & 0x10) >> 4;
2254 +
					fx_cache_fill = (value & 0x20) >> 5;
2255 +
					fx_cache_write = (value & 0x40) >> 6;
2256 +
					fx_trans_writes = (value & 0x80) >> 7;
2257 +
					break;
2258 +
				case 0x09: // DCSEL=2, $9F2A
2259 +
					fx_affine_tile_base = (value & 0xfc) << 9;
2260 +
					fx_affine_clip = (value & 0x02) >> 1;
2261 +
					fx_2bit_poly = (value & 0x01);
2262 +
					break;
2263 +
				case 0x0a: // DCSEL=2, $9F2B
2264 +
					fx_affine_map_base = (value & 0xfc) << 9;
2265 +
					fx_affine_map_size = 2 << ((value & 0x03) << 1);
2266 +
					break;
2267 +
				case 0x0b: // DCSEL=2, $9F2C
2268 +
					fx_cache_increment_mode = value & 0x01;
2269 +
					fx_cache_nibble_index = (value & 0x02) >> 1;
2270 +
					fx_cache_byte_index = (value & 0x0c) >> 2;
2271 +
					fx_multiplier = (value & 0x10) >> 4;
2272 +
					fx_subtract = (value & 0x20) >> 5;
2273 +
					if (value & 0x40) { // accumulate
2274 +
						int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2275 +
						if (fx_subtract)
2276 +
							fx_mult_accumulator -= m_result;
2277 +
						else
2278 +
							fx_mult_accumulator += m_result;
2279 +
					}
2280 +
					if (value & 0x80) { // reset accumulator
2281 +
						fx_mult_accumulator = 0;
2282 +
					}
2283 +
					break;
2284 +
				case 0x0c: // DCSEL=3, $9F29
2285 +
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
2286 +
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2287 +
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
2288 +
					break;
2289 +
				case 0x0d: // DCSEL=3, $9F2A
2290 +
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
2291 +
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2292 +
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
2293 +
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
2294 +
						// Reset subpixel to 0.5
2295 +
						fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0000) | 0x00008000;
2296 +
					}
2297 +
					break;
2298 +
				case 0x0e: // DCSEL=3, $9F2B
2299 +
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
2300 +
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2301 +
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
2302 +
					break;
2303 +
				case 0x0f: // DCSEL=3, $9F2C
2304 +
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
2305 +
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2306 +
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
2307 +
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
2308 +
						// Reset subpixel to 0.5
2309 +
						fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0000) | 0x00008000;
2310 +
					}
2311 +
					break;
2312 +
				case 0x10: // DCSEL=4, $9F29
2313 +
					fx_x_pixel_position = (fx_x_pixel_position & 0x0700ff80) | (value << 16);
2314 +
					fx_affine_prefetch();
2315 +
					break;
2316 +
				case 0x11: // DCSEL=4, $9F2A
2317 +
					fx_x_pixel_position = (fx_x_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
2318 +
					fx_affine_prefetch();
2319 +
					break;
2320 +
				case 0x12: // DCSEL=4, $9F2B
2321 +
					fx_y_pixel_position = (fx_y_pixel_position & 0x0700ff80) | (value << 16);
2322 +
					fx_affine_prefetch();
2323 +
					break;
2324 +
				case 0x13: // DCSEL=4, $9F2C
2325 +
					fx_y_pixel_position = (fx_y_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
2326 +
					fx_affine_prefetch();
2327 +
					break;
2328 +
				case 0x14: // DCSEL=5, $9F29
2329 +
					fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0080) | (value << 8);
2330 +
					break;
2331 +
				case 0x15: // DCSEL=5, $9F2A
2332 +
					fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0080) | (value << 8);
2333 +
					break;
2334 +
				case 0x18: // DCSEL=6, $9F29
2335 +
					fx_cache[0] = value;
2336 +
					break;
2337 +
				case 0x19: // DCSEL=6, $9F2A
2338 +
					fx_cache[1] = value;
2339 +
					break;
2340 +
				case 0x1a: // DCSEL=6, $9F2B
2341 +
					fx_cache[2] = value;
2342 +
					break;
2343 +
				case 0x1b: // DCSEL=6, $9F2C
2344 +
					fx_cache[3] = value;
2345 +
					break;
2346 +
			}
2347 +
			break;
2348 +
		}
2349 +
2350 +
		case 0x0D:
2351 +
		case 0x0E:
2352 +
		case 0x0F:
2353 +
		case 0x10:
2354 +
		case 0x11:
2355 +
		case 0x12:
2356 +
		case 0x13:
2357 +
			video_step(MHZ, 0, true); // potential midline raster effect
2358 +
			reg_layer[0][reg - 0x0D] = value;
2359 +
			refresh_layer_properties(0);
2360 +
			break;
2361 +
2362 +
		case 0x14:
2363 +
		case 0x15:
2364 +
		case 0x16:
2365 +
		case 0x17:
2366 +
		case 0x18:
2367 +
		case 0x19:
2368 +
		case 0x1A:
2369 +
			video_step(MHZ, 0, true); // potential midline raster effect
2370 +
			reg_layer[1][reg - 0x14] = value;
2371 +
			refresh_layer_properties(1);
2372 +
			break;
2373 +
2374 +
		case 0x1B: audio_render(); pcm_write_ctrl(value); break;
2375 +
		case 0x1C: audio_render(); pcm_write_rate(value); break;
2376 +
		case 0x1D: audio_render(); pcm_write_fifo(value); break;
2377 +
2378 +
		case 0x1E:
2379 +
		case 0x1F:
2380 +
			vera_spi_write(reg & 1, value);
2381 +
			break;
2382 +
	}
2383 +
}
2384 +
2385 +
void
2386 +
video_update_title(const char* window_title)
2387 +
{
2388 +
	SDL_SetWindowTitle(window, window_title);
2389 +
}
2390 +
2391 +
bool video_is_tilemap_address(int addr)
2392 +
{
2393 +
	for (int l = 0; l < 2; ++l) {
2394 +
		struct video_layer_properties *props = &layer_properties[l];
2395 +
		if (addr < props->map_base) {
2396 +
			continue;
2397 +
		}
2398 +
		if (addr >= props->map_base + (2 << (props->mapw_log2 + props->maph_log2))) {
2399 +
			continue;
2400 +
		}
2401 +
2402 +
		return true;
2403 +
	}
2404 +
	return false;
2405 +
}
2406 +
2407 +
bool video_is_tiledata_address(int addr)
2408 +
{
2409 +
	for (int l = 0; l < 2; ++l) {
2410 +
		struct video_layer_properties *props = &layer_properties[l];
2411 +
		if (addr < props->tile_base) {
2412 +
			continue;
2413 +
		}
2414 +
		int tile_size = props->tilew * props->tileh * props->bits_per_pixel / 8;
2415 +
		if (addr >= props->tile_base + tile_size * (props->bits_per_pixel == 1 ? 256 : 1024)) {
2416 +
			continue;
2417 +
		}
2418 +
2419 +
		return true;
2420 +
	}
2421 +
	return false;
2422 +
}
2423 +
2424 +
bool video_is_special_address(int addr)
2425 +
{
2426 +
	return addr >= 0x1F9C0;
2427 +
}
2428 +
2429 +
void
2430 +
stop6502(uint16_t address, uint8_t bank) {
2431 +
	if (debugger_enabled) {
2432 +
		DEBUGBreakToDebugger();
2433 +
	} else if (testbench) {
2434 +
		printf("STP\n");
2435 +
        fflush(stdout);
2436 +
	} else {
2437 +
		int return_btn;
2438 +
		char error_message[80];
2439 +
		const SDL_MessageBoxButtonData btns[2] = {
2440 +
			{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Reset Machine"},
2441 +
			{SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, -1, "Ignore"}
2442 +
		};
2443 +
		const SDL_MessageBoxData msg_box = {
2444 +
			SDL_MESSAGEBOX_ERROR, window, "Error", error_message,
2445 +
			2, btns, NULL
2446 +
		};
2447 +
2448 +
		if (is_gen2) {
2449 +
			sprintf(error_message, "Encountered stop instruction at address $%06X. CPU cannot continue.", (regs.k << 16) | address);
2450 +
		} else {
2451 +
			sprintf(error_message, "Encountered stop instruction at address $%04X. CPU cannot continue.", address);
2452 +
		}
2453 +
		if (SDL_ShowMessageBox(&msg_box, &return_btn) == 0 && return_btn == 0) {
2454 +
			machine_reset();
2455 +
		};
2456 +
	}
2457 +
}
added src/video.h +42 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2019 Michael Steil
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef _VIDEO_H_
6 +
#define _VIDEO_H_
7 +
8 +
#include <stdbool.h>
9 +
#include <stdint.h>
10 +
#include <stdio.h>
11 +
#include <SDL.h>
12 +
#include "glue.h"
13 +
14 +
bool video_init(int window_scale, float screen_x_scale, char *quality, bool fullscreen, float opacity);
15 +
void video_reset(void);
16 +
bool video_step(float mhz, float steps, bool midline);
17 +
bool video_update(void);
18 +
void video_end(void);
19 +
bool video_get_irq_out(void);
20 +
void video_save(SDL_RWops *f);
21 +
uint8_t video_read(uint8_t reg, bool debugOn);
22 +
void video_write(uint8_t reg, uint8_t value);
23 +
void video_update_title(const char* window_title);
24 +
25 +
uint8_t via1_read(uint8_t reg, bool debug);
26 +
void via1_write(uint8_t reg, uint8_t value);
27 +
28 +
// For debugging purposes only:
29 +
uint8_t video_space_read(uint32_t address);
30 +
void video_space_write(uint32_t address, uint8_t value);
31 +
32 +
bool video_is_tilemap_address(int addr);
33 +
bool video_is_tiledata_address(int addr);
34 +
bool video_is_special_address(int addr);
35 +
36 +
uint32_t video_get_address(uint8_t sel);
37 +
uint32_t video_get_fx_accum(void);
38 +
uint8_t video_get_dc_value(uint8_t reg);
39 +
const uint8_t *video_get_framebuffer(int *width, int *height);
40 +
uint32_t video_get_frame_count(void);
41 +
42 +
#endif
added src/video_win32.c +18 −0
1 +
// All rights reserved. License: 2-clause BSD
2 +
3 +
#include <SDL.h>
4 +
#include <SDL_syswm.h>
5 +
6 +
#include <windows.h>
7 +
#include <dwmapi.h>
8 +
9 +
void video_win32_set_rounded_corners(SDL_Window *window)
10 +
{
11 +
	SDL_SysWMinfo wmInfo;
12 +
	SDL_VERSION(&wmInfo.version);
13 +
	SDL_GetWindowWMInfo(window, &wmInfo);
14 +
15 +
	HWND hwnd = wmInfo.info.win.window;
16 +
	DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_ROUNDSMALL;
17 +
	DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference));
18 +
}
added src/wav_recorder.c +226 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2022 Stephen Horn
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#include "wav_recorder.h"
6 +
7 +
#include "SDL.h"
8 +
#include "audio.h"
9 +
#include "glue.h"
10 +
#include <string.h>
11 +
#include <stdlib.h>
12 +
#include <stdio.h>
13 +
14 +
#pragma pack(push, 1)
15 +
typedef struct {
16 +
	char     chunk_id[4];
17 +
	uint32_t size;
18 +
	char     wave_id[4];
19 +
} riff_chunk;
20 +
21 +
typedef struct {
22 +
	char     chunk_id[4];
23 +
	uint32_t size;
24 +
	uint16_t format_tag;
25 +
	uint16_t channels;
26 +
	uint32_t samples_per_sec;
27 +
	uint32_t bytes_per_sec;
28 +
	uint16_t block_align;
29 +
	uint16_t bits_per_sample;
30 +
} fmt_chunk;
31 +
32 +
typedef struct {
33 +
	char     chunk_id[4];
34 +
	uint32_t size;
35 +
} data_chunk;
36 +
37 +
typedef struct {
38 +
	riff_chunk riff;
39 +
	fmt_chunk  fmt;
40 +
	data_chunk data;
41 +
} file_header;
42 +
#pragma pack(pop)
43 +
44 +
static SDL_RWops * wav_file = NULL;
45 +
static file_header wav_header;
46 +
static uint32_t    wav_samples_written = 0;
47 +
48 +
static void
49 +
wav_init_file_header(file_header *header)
50 +
{
51 +
	header->riff.chunk_id[0]    = 'R';
52 +
	header->riff.chunk_id[1]    = 'I';
53 +
	header->riff.chunk_id[2]    = 'F';
54 +
	header->riff.chunk_id[3]    = 'F';
55 +
	header->riff.size           = 4;
56 +
	header->riff.wave_id[0]     = 'W';
57 +
	header->riff.wave_id[1]     = 'A';
58 +
	header->riff.wave_id[2]     = 'V';
59 +
	header->riff.wave_id[3]     = 'E';
60 +
	header->fmt.chunk_id[0]     = 'f';
61 +
	header->fmt.chunk_id[1]     = 'm';
62 +
	header->fmt.chunk_id[2]     = 't';
63 +
	header->fmt.chunk_id[3]     = ' ';
64 +
	header->fmt.size            = 16;
65 +
	header->fmt.format_tag      = 0x0001; // WAVE_FORMAT_PCM
66 +
	header->fmt.channels        = 2;
67 +
	header->fmt.samples_per_sec = 0;
68 +
	header->fmt.bytes_per_sec   = 0;
69 +
	header->fmt.block_align     = 0;
70 +
	header->fmt.bits_per_sample = 16 * 2;
71 +
	header->data.chunk_id[0]    = 'd';
72 +
	header->data.chunk_id[1]    = 'a';
73 +
	header->data.chunk_id[2]    = 't';
74 +
	header->data.chunk_id[3]    = 'a';
75 +
	header->data.size           = 0;
76 +
}
77 +
78 +
static void
79 +
wav_update_sizes()
80 +
{
81 +
	wav_header.data.size = sizeof(int16_t) * wav_header.fmt.channels * wav_samples_written;
82 +
	wav_header.riff.size = 4 + sizeof(fmt_chunk) + sizeof(data_chunk) + (wav_header.data.size);
83 +
}
84 +
85 +
static void
86 +
wav_begin(const char *path, int32_t sample_rate)
87 +
{
88 +
	wav_file = SDL_RWFromFile(path, "wb");
89 +
	if (wav_file) {
90 +
		wav_init_file_header(&wav_header);
91 +
		wav_header.fmt.samples_per_sec = sample_rate;
92 +
		wav_header.fmt.bytes_per_sec   = sample_rate * sizeof(int16_t) * wav_header.fmt.channels;
93 +
		wav_header.fmt.block_align     = sizeof(int16_t) * wav_header.fmt.channels;
94 +
		wav_header.fmt.bits_per_sample = (sizeof(int16_t)) << 3;
95 +
96 +
		const size_t written = SDL_RWwrite(wav_file, &wav_header, sizeof(file_header), 1);
97 +
		if (written == 0) {
98 +
			SDL_RWclose(wav_file);
99 +
			wav_file = NULL;
100 +
		}
101 +
	}
102 +
}
103 +
104 +
static void
105 +
wav_end()
106 +
{
107 +
	if (wav_file != NULL) {
108 +
		wav_update_sizes();
109 +
		SDL_RWseek(wav_file, 0, RW_SEEK_SET);
110 +
		SDL_RWwrite(wav_file, &wav_header, sizeof(file_header), 1);
111 +
		SDL_RWclose(wav_file);
112 +
		wav_file = NULL;
113 +
	}
114 +
}
115 +
116 +
static void
117 +
wav_add(const int16_t *samples, const int num_samples)
118 +
{
119 +
	if (wav_file) {
120 +
		const size_t bytes   = sizeof(int16_t) * 2 * num_samples;
121 +
		const size_t written = SDL_RWwrite(wav_file, samples, bytes, 1);
122 +
		if (written == 0) {
123 +
			SDL_RWclose(wav_file);
124 +
			wav_file = NULL;
125 +
		} else {
126 +
			wav_samples_written += num_samples;
127 +
		}
128 +
	}
129 +
}
130 +
131 +
// WAV recorder states
132 +
typedef enum {
133 +
	RECORD_WAV_DISABLED = 0,
134 +
	RECORD_WAV_PAUSED,
135 +
	RECORD_WAV_AUTOSTARTING,
136 +
	RECORD_WAV_RECORDING
137 +
} wav_recorder_state_t;
138 +
139 +
static wav_recorder_state_t Wav_record_state = RECORD_WAV_DISABLED;
140 +
static char *               Wav_path         = NULL;
141 +
142 +
void
143 +
wav_recorder_shutdown()
144 +
{
145 +
	if (Wav_record_state == RECORD_WAV_RECORDING) {
146 +
		wav_end();
147 +
	}
148 +
}
149 +
150 +
void
151 +
wav_recorder_process(const int16_t *samples, const int num_samples)
152 +
{
153 +
	int i;
154 +
	if (Wav_record_state == RECORD_WAV_AUTOSTARTING) {
155 +
		for (i = 0; i < num_samples; ++i) {
156 +
			if (samples[i] != 0) {
157 +
				Wav_record_state = RECORD_WAV_RECORDING;
158 +
				wav_begin(Wav_path, host_sample_rate);
159 +
				break;
160 +
			}
161 +
		}
162 +
	}
163 +
164 +
	if (Wav_record_state == RECORD_WAV_RECORDING) {
165 +
		wav_add(samples, num_samples);
166 +
	}
167 +
}
168 +
169 +
void
170 +
wav_recorder_set(wav_recorder_command_t command)
171 +
{
172 +
	if (Wav_record_state != RECORD_WAV_DISABLED) {
173 +
		switch (command) {
174 +
			case RECORD_WAV_PAUSE:
175 +
				Wav_record_state = RECORD_WAV_PAUSED;
176 +
				break;
177 +
			case RECORD_WAV_RECORD:
178 +
				Wav_record_state = RECORD_WAV_RECORDING;
179 +
				wav_begin(Wav_path, host_sample_rate);
180 +
				break;
181 +
			case RECORD_WAV_AUTOSTART:
182 +
				Wav_record_state = RECORD_WAV_AUTOSTARTING;
183 +
				break;
184 +
			default:
185 +
				printf("Unknown command %d passed to wav_recorder_set.\n", (int)command);
186 +
				break;
187 +
		}
188 +
	}
189 +
}
190 +
191 +
uint8_t
192 +
wav_recorder_get_state()
193 +
{
194 +
	return (uint8_t)Wav_record_state;
195 +
}
196 +
197 +
void
198 +
wav_recorder_set_path(const char *path)
199 +
{
200 +
	if (Wav_record_state == RECORD_WAV_RECORDING) {
201 +
		wav_end();
202 +
	}
203 +
204 +
	if (Wav_path != NULL) {
205 +
		free(Wav_path);
206 +
		Wav_path = NULL;
207 +
	}
208 +
209 +
	if (path != NULL) {
210 +
		Wav_path = malloc(sizeof(char) * (strlen(path) + 1));
211 +
		strcpy(Wav_path, path);
212 +
213 +
		if (!strcmp(Wav_path + strlen(Wav_path) - 5, ",wait")) {
214 +
			Wav_path[strlen(Wav_path) - 5] = 0;
215 +
			Wav_record_state               = RECORD_WAV_PAUSED;
216 +
		} else if (!strcmp(Wav_path + strlen(Wav_path) - 5, ",auto")) {
217 +
			Wav_path[strlen(Wav_path) - 5] = 0;
218 +
			Wav_record_state               = RECORD_WAV_AUTOSTARTING;
219 +
		} else {
220 +
			Wav_record_state = RECORD_WAV_RECORDING;
221 +
			wav_begin(Wav_path, host_sample_rate);
222 +
		}
223 +
	} else {
224 +
		Wav_record_state = RECORD_WAV_DISABLED;
225 +
	}
226 +
}
added src/wav_recorder.h +24 −0
1 +
// Commander X16 Emulator
2 +
// Copyright (c) 2022 Stephen Horn
3 +
// All rights reserved. License: 2-clause BSD
4 +
5 +
#ifndef WAV_RECORDER_H
6 +
#define WAV_RECORDER_H
7 +
8 +
#include <stdint.h>
9 +
10 +
typedef enum {
11 +
	RECORD_WAV_PAUSE = 0,
12 +
	RECORD_WAV_RECORD,
13 +
	RECORD_WAV_AUTOSTART,
14 +
} wav_recorder_command_t;
15 +
16 +
void wav_recorder_shutdown();
17 +
void wav_recorder_process(const int16_t *samples, const int num_samples);
18 +
19 +
void    wav_recorder_set(wav_recorder_command_t command);
20 +
uint8_t wav_recorder_get_state();
21 +
22 +
void wav_recorder_set_path(const char *path);
23 +
24 +
#endif
added src/ymglue.cpp +131 −0
1 +
#include "ymglue.h"
2 +
#include "ymfm_opm.h"
3 +
#include <cstdint>
4 +
5 +
class ym2151_interface : public ymfm::ymfm_interface {
6 +
	public:
7 +
		ym2151_interface():
8 +
			m_chip(*this),
9 +
			m_timers{0, 0},
10 +
			m_busy_timer{ 0 },
11 +
			m_irq_status{ false }
12 +
		{ }
13 +
		~ym2151_interface() { }
14 +
15 +
		virtual void ymfm_sync_mode_write(uint8_t data) override {
16 +
			m_engine->engine_mode_write(data);
17 +
		}
18 +
19 +
		virtual void ymfm_sync_check_interrupts() override {
20 +
			m_engine->engine_check_interrupts();
21 +
		}
22 +
23 +
		virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) override {
24 +
			if (tnum >= 2) return;
25 +
			m_timers[tnum] = duration_in_clocks;
26 +
		}
27 +
28 +
		virtual void ymfm_set_busy_end(uint32_t clocks) override {
29 +
			m_busy_timer = clocks;
30 +
		}
31 +
32 +
		virtual bool ymfm_is_busy() override {
33 +
			return m_busy_timer > 0;
34 +
		}
35 +
36 +
		virtual void ymfm_update_irq(bool asserted) override {
37 +
			m_irq_status = asserted;
38 +
		}
39 +
40 +
		void update_clocks(int cycles) {
41 +
			m_busy_timer = std::max(0, m_busy_timer - (64 * cycles));
42 +
			for (int i = 0; i < 2; ++i) {
43 +
				if (m_timers[i] > 0) {
44 +
					m_timers[i] = std::max(0, m_timers[i] - (64 * cycles));
45 +
					if (m_timers[i] <= 0) {
46 +
						m_engine->engine_timer_expired(i);
47 +
					}
48 +
				}
49 +
			}	
50 +
		}
51 +
52 +
		void write(uint8_t addr, uint8_t value) {
53 +
			if (!ymfm_is_busy()) {
54 +
				m_chip.write_address(addr);
55 +
				m_chip.write_data(value);
56 +
			} else {
57 +
				printf("YM2151 write received while busy.\n");
58 +
			}
59 +
		}
60 +
61 +
		void generate(int16_t* output, uint32_t numsamples) {
62 +
			int s = 0;
63 +
			int ls, rs;
64 +
			update_clocks(numsamples);
65 +
			for (uint32_t i = 0; i < numsamples; i++) {
66 +
				m_chip.generate(&opm_out);
67 +
				ls = opm_out.data[0];
68 +
				rs = opm_out.data[1];
69 +
				if (ls < -32768) ls = -32768;
70 +
				if (ls > 32767) ls = 32767;
71 +
				if (rs < -32768) rs = -32768;
72 +
				if (rs > 32767) rs = 32767;
73 +
				output[s++] = ls;
74 +
				output[s++] = rs;
75 +
			}
76 +
		}
77 +
78 +
		uint8_t read_status() {
79 +
			return m_chip.read_status();
80 +
		}
81 +
82 +
		bool irq() {
83 +
			return m_irq_status;
84 +
		}
85 +
86 +
	private:
87 +
		ymfm::ym2151 m_chip;
88 +
		int32_t m_timers[2];
89 +
		int32_t m_busy_timer;
90 +
		bool m_irq_status;
91 +
92 +
		ymfm::ym2151::output_data opm_out;
93 +
};
94 +
95 +
namespace {
96 +
	ym2151_interface opm_iface;
97 +
	bool initialized = false;
98 +
}
99 +
100 +
extern "C" {
101 +
	void YM_Create(int clock) {
102 +
		// clock is fixed at 3.579545MHz
103 +
	}
104 +
105 +
	void YM_init(int sample_rate, int frame_rate) {
106 +
		// args are ignored
107 +
		initialized = true;
108 +
	}
109 +
110 +
	void YM_stream_update(uint16_t* output, uint32_t numsamples) {
111 +
		if (initialized) opm_iface.generate((int16_t*)output, numsamples);
112 +
	}
113 +
114 +
	void YM_write_reg(uint8_t reg, uint8_t val) {
115 +
		if (initialized) opm_iface.write(reg, val);
116 +
	}
117 +
118 +
	uint8_t YM_read_status() {
119 +
		if (initialized)
120 +
			return opm_iface.read_status();
121 +
		else
122 +
			return 0x00; // prevent programs that wait for the busy flag to clear from locking up (emulator compromise)
123 +
	}
124 +
125 +
	bool YM_irq() {
126 +
		if (initialized)
127 +
			return opm_iface.irq();
128 +
		else
129 +
			return false;
130 +
	}
131 +
}
added src/ymglue.h +20 −0
1 +
#ifndef YMGLUE_H
2 +
#define YMGLUE_H
3 +
4 +
#ifdef __cplusplus
5 +
extern "C" {
6 +
#endif
7 +
	#include <stdint.h>
8 +
9 +
	uint8_t YM_read_status(void);
10 +
	void YM_Create(int clock);
11 +
	void YM_init(int sample_rate, int frame_rate);
12 +
	void YM_stream_update(uint16_t* output, uint32_t numsamples);
13 +
	void YM_write_reg(uint8_t reg, uint8_t val);
14 +
	bool YM_irq(void);
15 +
16 +
#ifdef __cplusplus
17 +
}
18 +
#endif
19 +
20 +
#endif
added symbolize.sh +15 −0
1 +
#!/bin/bash
2 +
3 +
echo "uint16_t addresses_bank$1[] = {"
4 +
for i in $(cat $2 | grep "^.....[ABCDEF]" | cut -c 6-9); do
5 +
	echo -n "0x$i, "
6 +
done
7 +
echo
8 +
echo "};"
9 +
10 +
echo "char *labels_bank$1[] = {"
11 +
for i in $(cat $2 | grep "^.....[ABCDEF]" | cut -c 12-); do
12 +
	echo -n "\"$i\", "
13 +
done
14 +
echo
15 +
echo "};"
added test.cfg +46 −0
1 +
-desc "Test Cartridge"
2 +
-author "Stephen Horn"
3 +
-copyright "(C) Stephen Horn 2023"
4 +
-version "TEST V.1"
5 +
-rom_file 32 test-001.bin
6 +
-rom_file 33 test-002.bin
7 +
-rom_file 34 test-003.bin
8 +
-rom_file 35 test-004.bin
9 +
-rom_file 36 test-005.bin
10 +
-rom_file 37 test-006.bin
11 +
-rom_file 38 test-007.bin
12 +
-rom_file 39 test-008.bin
13 +
-rom_file 40 test-009.bin
14 +
-rom_file 41 test-010.bin
15 +
-rom_file 42 test-011.bin
16 +
-rom_file 43 test-012.bin
17 +
-rom_file 44 test-013.bin
18 +
-rom_file 45 test-014.bin
19 +
-rom_file 46 test-015.bin
20 +
-rom_file 47 test-016.bin
21 +
-rom_file 48 test-017.bin
22 +
-rom_file 49 test-018.bin
23 +
-rom_file 50 test-019.bin
24 +
-rom_file 51 test-020.bin
25 +
-rom_file 52 test-021.bin
26 +
-rom_file 53 test-022.bin
27 +
-rom_file 54 test-023.bin
28 +
-rom_file 55 test-024.bin
29 +
-rom_file 56 test-025.bin
30 +
-rom_file 57 test-026.bin
31 +
-rom_file 58 test-027.bin
32 +
-rom_file 59 test-028.bin
33 +
-nvram_file 60 test-029.bin
34 +
-nvram_file 61 test-030.bin
35 +
-nvram_file 62 test-031.bin
36 +
-nvram_file 63 test-032.bin
37 +
-nvram_file 64 test-033.bin
38 +
-nvram_file 65 test-034.bin
39 +
-nvram_file 66 test-035.bin
40 +
-nvram_file 67 test-036.bin
41 +
-nvram_file 68 test-037.bin
42 +
-fill $aa
43 +
-nvram_value 69 70
44 +
-fill $bb
45 +
-nvram_value 71 72
46 +
-o "testcart.crt"
added testbench/example.py +27 −0
1 +
import unittest
2 +
from testbench import X16TestBench
3 +
from testbench import Status
4 +
5 +
class TestTest(unittest.TestCase):
6 +
    e = None
7 +
8 +
    def __init__(self, *args, **kwargs):
9 +
        super(TestTest, self).__init__(*args, **kwargs)
10 +
        
11 +
        self.e = X16TestBench("../x16emu")
12 +
        self.e.waitReady()
13 +
14 +
    def test_add(self):
15 +
        self.e.setMemory(0x6000, 0x18)   #clc
16 +
        self.e.setMemory(0x6001, 0xa9)   #lda #ff
17 +
        self.e.setMemory(0x6002, 0xff)
18 +
        self.e.setMemory(0x6003, 0x69)   #adc #1
19 +
        self.e.setMemory(0x6004, 0x01)
20 +
        self.e.setMemory(0x6005, 0x60)   #rts
21 +
        self.e.run(0x6000)
22 +
        
23 +
        self.assertEqual(self.e.getA(), 0)
24 +
        self.assertNotEqual(self.e.getStatus() & Status.C, 0)
25 +
        
26 +
if __name__ == '__main__':
27 +
    unittest.main()
added testbench/selftest.py +233 −0
1 +
import unittest
2 +
from testbench import X16TestBench
3 +
from testbench import Status
4 +
5 +
class SelfTest(unittest.TestCase):
6 +
    e = None
7 +
8 +
    def __init__(self, *args, **kwargs):
9 +
        super(SelfTest, self).__init__(*args, **kwargs)
10 +
        
11 +
        self.e = X16TestBench("../x16emu", ["-warp"])
12 +
        self.e.waitReady()
13 +
14 +
    def test_rombank(self):
15 +
        self.e.setRomBank(0)
16 +
        self.assertEqual(self.e.getMemory(1),0)
17 +
18 +
        self.e.setRomBank(10)
19 +
        self.assertEqual(self.e.getMemory(1),10)
20 +
21 +
    def test_rambank(self):
22 +
        self.e.setRamBank(0)
23 +
        self.assertEqual(self.e.getMemory(0),0)
24 +
25 +
        self.e.setRamBank(10)
26 +
        self.assertEqual(self.e.getMemory(0),10)
27 +
28 +
    def test_mem(self):
29 +
        #Set and retrieve value 0
30 +
        self.e.setMemory(0x6000, 0)
31 +
        self.assertEqual(self.e.getMemory(0x6000), 0)
32 +
33 +
        #Set and retrieve value 255
34 +
        self.e.setMemory(0x6000, 255)
35 +
        self.assertEqual(self.e.getMemory(0x6000), 255)
36 +
37 +
        #Set value -1, should cause an exception
38 +
        with self.assertRaises(Exception):
39 +
            self.e.setMemory(0x6000, -1)
40 +
41 +
        #Set value 256, should cause an exception
42 +
        with self.assertRaises(Exception):
43 +
            self.e.setMemory(0x6000, 256)
44 +
45 +
        #Use address -1, should cause an exception
46 +
        with self.assertRaises(Exception):
47 +
            self.e.setMemory(-1, 0)
48 +
        
49 +
        #Use address 10000, should cause an exception
50 +
        with self.assertRaises(Exception):
51 +
            self.e.setMemory(0x10000, 0)
52 +
53 +
    def test_fillmemory(self):
54 +
        self.e.fillMemory(0x6000, 0x61ff, 0x80)
55 +
        self.e.fillMemory(0x6001, 0x61fe, 0xff)
56 +
        
57 +
        self.assertEqual(self.e.getMemory(0x6000), 0x80)
58 +
        self.assertEqual(self.e.getMemory(0x6001), 0xff)
59 +
        self.assertEqual(self.e.getMemory(0x61fe), 0xff)
60 +
        self.assertEqual(self.e.getMemory(0x61ff), 0x80)
61 +
62 +
    def test_a(self):
63 +
        #Set and retrieve value 0
64 +
        self.e.setA(0)
65 +
        self.assertEqual(self.e.getA(), 0)
66 +
67 +
        #Set and retrieve value 255
68 +
        self.e.setA(255)
69 +
        self.assertEqual(self.e.getA(), 255)
70 +
71 +
        #Set value -1, should cause an exception
72 +
        with self.assertRaises(Exception):
73 +
            self.e.setA(-1)
74 +
75 +
        #Set value 256, should cause an exception
76 +
        with self.assertRaises(Exception):
77 +
            self.e.setA(256)
78 +
79 +
    def test_x(self):
80 +
        #Set and retrieve value 0
81 +
        self.e.setX(0)
82 +
        self.assertEqual(self.e.getX(), 0)
83 +
84 +
        #Set and retrieve value 255
85 +
        self.e.setX(255)
86 +
        self.assertEqual(self.e.getX(), 255)
87 +
88 +
        #Set value -1, should cause an exception
89 +
        with self.assertRaises(Exception):
90 +
            self.e.setX(-1)
91 +
92 +
        #Set value 256, should cause an exception
93 +
        with self.assertRaises(Exception):
94 +
            self.e.setX(256)
95 +
96 +
    def test_y(self):
97 +
        #Set and retrieve value 0
98 +
        self.e.setY(0)
99 +
        self.assertEqual(self.e.getY(), 0)
100 +
101 +
        #Set and retrieve value 255
102 +
        self.e.setY(255)
103 +
        self.assertEqual(self.e.getY(), 255)
104 +
105 +
        #Set value -1, should cause an exception
106 +
        with self.assertRaises(Exception):
107 +
            self.e.setY(-1)
108 +
109 +
        #Set value 256, should cause an exception
110 +
        with self.assertRaises(Exception):
111 +
            self.e.setY(256)
112 +
113 +
    def test_stackpointer(self):
114 +
        #Set and retrieve value 0
115 +
        self.e.setStackPointer(0)
116 +
        self.assertEqual(self.e.getStackPointer(), 0)
117 +
118 +
        #Set and retrieve value 255
119 +
        self.e.setStackPointer(255)
120 +
        self.assertEqual(self.e.getStackPointer(), 255)
121 +
122 +
        #Set value -1, should cause an exception
123 +
        with self.assertRaises(Exception):
124 +
            self.e.setStackPointer(-1)
125 +
126 +
        #Set value 256, should cause an exception
127 +
        with self.assertRaises(Exception):
128 +
            self.e.setStackPointer(256)
129 +
130 +
    def test_statusregister(self):
131 +
        #Set and retrieve value 0
132 +
        self.e.setStatus(0)
133 +
        self.assertEqual(self.e.getStatus(), 0)
134 +
135 +
        #Set and retrieve value 255
136 +
        self.e.setStatus(255)
137 +
        self.assertEqual(self.e.getStatus(), 255)
138 +
139 +
        #Set value -1, should cause an exception
140 +
        with self.assertRaises(Exception):
141 +
            self.e.setStatus(-1)
142 +
143 +
        #Set value 256, should cause an exception
144 +
        with self.assertRaises(Exception):
145 +
            self.e.setStatus(256)
146 +
147 +
    def test_carryflag(self):
148 +
        self.e.setStatus(self.e.getStatus() & (255-Status.C))
149 +
150 +
        self.e.setMemory(0x6000, 0x38)      #sec
151 +
        self.e.setMemory(0x6001, 0x60)      #rts
152 +
        self.e.run(0x6000)
153 +
        self.assertEqual(self.e.getStatus() & Status.C, Status.C)
154 +
155 +
        self.e.setMemory(0x6000, 0x18)      #clc
156 +
        self.e.run(0x6000)
157 +
        self.assertNotEqual(self.e.getStatus() & Status.C, Status.C)
158 +
159 +
    def test_zeroflag(self):
160 +
        #Clear zero flag
161 +
        self.e.setStatus(self.e.getStatus() & (255-Status.Z))
162 +
163 +
        #Run code that sets zero flag
164 +
        self.e.setMemory(0x6000, 0xa9)      #lda #0
165 +
        self.e.setMemory(0x6001, 0x00)
166 +
        self.e.setMemory(0x6002, 0x60)      #rts
167 +
        self.e.run(0x6000)
168 +
        self.assertEqual(self.e.getStatus() & Status.Z, Status.Z)
169 +
170 +
        #Run code that clears zero flag
171 +
        self.e.setMemory(0x6000, 0xa9)      #lda #1
172 +
        self.e.setMemory(0x6001, 0x01)
173 +
        self.e.setMemory(0x6002, 0x60)      #rts
174 +
        self.e.run(0x6000)
175 +
        self.assertNotEqual(self.e.getStatus() & Status.Z, Status.Z)
176 +
177 +
    def test_overflowflag(self):
178 +
        #Clear carry and overflow flags
179 +
        self.e.setStatus(self.e.getStatus() & (255-Status.C-Status.V))
180 +
181 +
        #Run code that doesn't cause overflow
182 +
        self.e.setMemory(0x6000, 0xa9)      #lda #1
183 +
        self.e.setMemory(0x6001, 0x01)
184 +
        self.e.setMemory(0x6002, 0x69)      #adc #1
185 +
        self.e.setMemory(0x6003, 0x01)
186 +
        self.e.setMemory(0x6004, 0x60)      #rts
187 +
        self.e.run(0x6000)
188 +
        self.assertNotEqual(self.e.getStatus() & Status.V, Status.V)
189 +
190 +
        #Run code that causes overflow
191 +
        self.e.setMemory(0x6000, 0xa9)      #lda #80
192 +
        self.e.setMemory(0x6001, 0x80)
193 +
        self.e.setMemory(0x6002, 0x69)      #adc #ff
194 +
        self.e.setMemory(0x6003, 0xff)
195 +
        self.e.setMemory(0x6004, 0x60)      #rts
196 +
        self.e.run(0x6000)
197 +
        self.assertEqual(self.e.getStatus() & Status.V, Status.V)
198 +
199 +
    def test_negativeflag(self):
200 +
        #Clear negative flag
201 +
        self.e.setStatus(self.e.getStatus() & (255-Status.N))
202 +
203 +
        #Run code that should set negative flag
204 +
        self.e.setMemory(0x6000, 0x38)      #sec
205 +
        self.e.setMemory(0x6001, 0xa9)      #lda #0
206 +
        self.e.setMemory(0x6002, 0x00)
207 +
        self.e.setMemory(0x6003, 0xe9)      #sbc #1
208 +
        self.e.setMemory(0x6004, 0x01)
209 +
        self.e.setMemory(0x6005, 0x60)      #rts
210 +
        self.e.run(0x6000)
211 +
        self.assertEqual(self.e.getStatus() & Status.N, Status.N)
212 +
213 +
        #Run code that should clear the negative flag
214 +
        self.e.setMemory(0x6000, 0x38)      #sec
215 +
        self.e.setMemory(0x6001, 0xa9)      #lda #ff
216 +
        self.e.setMemory(0x6002, 0xff)
217 +
        self.e.setMemory(0x6003, 0xe9)      #sbc #81
218 +
        self.e.setMemory(0x6004, 0x81)
219 +
        self.e.setMemory(0x6005, 0x60)      #rts
220 +
        self.e.run(0x6000)
221 +
        self.assertNotEqual(self.e.getStatus() & Status.N, Status.N)
222 +
223 +
    def test_run(self):
224 +
        #Use address -1, should cause an exception
225 +
        with self.assertRaises(Exception):
226 +
            self.e.run(-1)
227 +
        
228 +
        #Use address 10000, should cause an exception
229 +
        with self.assertRaises(Exception):
230 +
            self.e.run(0x10000)
231 +
232 +
if __name__ == '__main__':
233 +
    unittest.main()
added testbench/testbench.py +185 −0
1 +
import signal
2 +
import subprocess
3 +
4 +
class Status:
5 +
    C = 1
6 +
    Z = 2
7 +
    I = 4
8 +
    D = 8
9 +
    V = 64
10 +
    N = 128
11 +
12 +
class X16TestBench:
13 +
    emu = None
14 +
    labels = dict()
15 +
16 +
    """
17 +
    Class constructor.
18 +
19 +
    Args:
20 +
        emulatorpath: Path to the x16emu executable
21 +
        emulatoroptions: A list of launch options used when starting the emulator
22 +
23 +
    Raises:
24 +
        FileNotFoundError: Happens if emulatorpath not valid
25 +
    """
26 +
    def __init__(self, emulatorpath, emulatoroptions=[]):
27 +
        path = [emulatorpath, "-testbench"]
28 +
        for o in emulatoroptions:
29 +
            path.append(o)
30 +
        
31 +
        self.emu = subprocess.Popen(path, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
32 +
    
33 +
    def __del__(self):
34 +
        self.emu.stdout.close()
35 +
        self.emu.stdin.close()
36 +
        self.emu.terminate()
37 +
        self.emu.wait()
38 +
39 +
    """
40 +
    Waits until the emulator has sent the ready signal ("RDY").
41 +
42 +
    Args:
43 +
        timeout: Number of seconds until abort waiting
44 +
45 +
    Raises:
46 +
        Exception: If aborted by timeout
47 +
    """
48 +
    def waitReady(self, timeout=5):
49 +
        #Set timeout
50 +
        signal.signal(signal.SIGALRM, self.__timeout)
51 +
        signal.alarm(timeout)
52 +
53 +
        r = ""
54 +
        while r.startswith("RDY")==False:
55 +
            r = self.__readline()
56 +
            if r.startswith("ERR"):
57 +
                raise Exception(r[3:].strip())
58 +
59 +
    def __writeline(self, str):
60 +
        self.emu.stdin.write((str + "\n").encode('utf-8'))
61 +
        self.emu.stdin.flush()
62 +
63 +
    def __readline(self):
64 +
        return self.emu.stdout.readline().decode('utf-8')
65 +
66 +
    def __getresponse(self):
67 +
        r = ""
68 +
        while r == "":
69 +
            r = self.__readline()
70 +
        
71 +
        if r.startswith("ERR"):
72 +
            raise Exception("Invalid command")
73 +
        else:
74 +
            return r
75 +
76 +
    def __tohex8(self, val):
77 +
        if val < 0 or val > 255:
78 +
            raise Exception("Value out of range")
79 +
        else:
80 +
            return "%0.2X" % val
81 +
82 +
    def __tohex16(self, val):
83 +
        if val < 0 or val > 65535:
84 +
            raise Exception("Value out of range")
85 +
        else:
86 +
            return "%0.4X" % val
87 +
88 +
    def __toint8(self, val):
89 +
        i = int(val, 16)
90 +
        if i < 0 or i > 255:
91 +
            raise Exception("Value out of range")
92 +
        else:
93 +
            return i
94 +
    
95 +
    def __toint16(self, val):
96 +
        i = int(val, 16)
97 +
        if i < 0 or i > 65535:
98 +
            raise Exception("Value out of range")
99 +
        else:
100 +
            return i
101 +
102 +
    def __timeout(self, s, f):
103 +
        raise Exception("Timeout error")
104 +
105 +
    """
106 +
    Opens and reads a text file in VICE format, to import label names 
107 +
    and corresponding memory addresses.
108 +
109 +
    Params:
110 +
        path: Path to the label file
111 +
112 +
    Raises:
113 +
        FileNotFoundError
114 +
    """
115 +
    def importViceLabels(self, path):
116 +
        f = open(path, "r")
117 +
        for line in f.readlines():
118 +
            fields = line.split(" ")
119 +
            if len(fields) >= 3:
120 +
                self.labels[fields[2][1:].strip()] = self.__toint16(fields[1].strip())
121 +
        f.close()
122 +
123 +
    def setRamBank(self, value):
124 +
        self.__writeline("RAM " + self.__tohex8(value))
125 +
        self.waitReady()
126 +
127 +
    def setRomBank(self, value):
128 +
        self.__writeline("ROM " + self.__tohex8(value))
129 +
        self.waitReady()
130 +
    
131 +
    def setMemory(self, address, value):
132 +
        self.__writeline("STM " + self.__tohex16(address) + " " + self.__tohex8(value))
133 +
        self.waitReady()
134 +
135 +
    def fillMemory(self, address1, address2, value):
136 +
        self.__writeline("FLM " + self.__tohex16(address1) + " " + self.__tohex16(address2) + " " + self.__tohex8(value))
137 +
        self.waitReady()
138 +
139 +
    def setA(self, value):
140 +
        self.__writeline("STA " + self.__tohex8(value))
141 +
        self.waitReady()
142 +
143 +
    def setX(self, value):
144 +
        self.__writeline("STX " + self.__tohex8(value))
145 +
        self.waitReady()
146 +
147 +
    def setY(self, value):
148 +
        self.__writeline("STY " + self.__tohex8(value))
149 +
        self.waitReady()
150 +
151 +
    def setStatus(self, value):
152 +
        self.__writeline("SST " + self.__tohex8(value))
153 +
        self.waitReady()
154 +
    
155 +
    def setStackPointer(self, value):
156 +
        self.__writeline("SSP " + self.__tohex8(value))
157 +
        self.waitReady()
158 +
159 +
    def run(self, address, timeout=5):
160 +
        self.__writeline("RUN " + self.__tohex16(address))
161 +
        self.waitReady(timeout)
162 +
163 +
    def getMemory(self, address):
164 +
        self.__writeline("RQM " + self.__tohex16(address))
165 +
        return self.__toint8(self.__getresponse())
166 +
167 +
    def getA(self):
168 +
        self.__writeline("RQA")
169 +
        return self.__toint8(self.__getresponse())
170 +
171 +
    def getX(self):
172 +
        self.__writeline("RQX")
173 +
        return self.__toint8(self.__getresponse())
174 +
175 +
    def getY(self):
176 +
        self.__writeline("RQY")
177 +
        return self.__toint8(self.__getresponse())
178 +
179 +
    def getStatus(self):
180 +
        self.__writeline("RST")
181 +
        return self.__toint8(self.__getresponse())
182 +
183 +
    def getStackPointer(self):
184 +
        self.__writeline("RSP")
185 +
        return self.__toint8(self.__getresponse())
added webassembly/WebAssembly.md +36 −0
1 +
## WebAssembly/HTML5 Support
2 +
The Commander X16 emulator supports a HTML/WebAssembly build target using the [Emscripten compiler](https://emscripten.org/).
3 +
4 +
## Demo
5 +
[HTML X16 Emulator Demo](https://sebastianvog.github.io/x16-emulator/x16emu.html)
6 +
## Installation
7 +
Follow installation steps from [here](https://emscripten.org/docs/getting_started/downloads.html#)
8 +
9 +
## Build
10 +
11 +
    make wasm
12 +
13 +
This outputs the following artifacts in the build directory, which can be uploaded to any web server.
14 +
15 +
	ex16mu.data x16emu.html x16emu.js x16mu.wasm webassembly/styles.css webassembly/main.js
16 +
17 +
You will also need the system ROM (rom.bin) which you can build from the [X16 ROM source](https://github.com/commanderx16/x16-rom) or take from the latest [binary release](https://github.com/commanderx16/x16-emulator/releases).
18 +
(It is not always guaranteed though that the latest binary release is compatible with the current state of the emulator source.)
19 +
20 +
### Testing
21 +
To run a test webserver:
22 +
23 +
    python -m SimpleHTTPServer 8080
24 +
25 +
And start [http://localhost:8080/x16emu.html](http://localhost:8080/x16emu.html)
26 +
27 +
Note: You will get the error `wasm streaming compile failed: TypeError: Failed to execute 'compile' on 'WebAssembly'...`
28 +
This is because SimpleHTTPServer does not serve .wasm files with the correct mime type. don't worry, everything will still work fine though.
29 +
30 +
### Supported Browsers
31 +
Working Browsers are Chrome, Safari, FireFox, Opera and Microsoft Edge (Beta)
32 +
33 +
### Known Issues
34 +
* Speed of the webassembly emulation is lacking, compared to the native version
35 +
* No support to load PRGs (yet)
36 +
* Doesn't display particularly well in portrait mode on mobile
added webassembly/jszip.min.js +13 −0
1 +
/*!
2 +
3 +
JSZip v3.3.0 - A JavaScript class for generating and reading zip files
4 +
<http://stuartk.com/jszip>
5 +
6 +
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
7 +
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
8 +
9 +
JSZip uses the library pako released under the MIT license :
10 +
https://github.com/nodeca/pako/blob/master/LICENSE
11 +
*/
12 +
13 +
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=t()}}(function(){return function s(a,o,h){function u(r,t){if(!o[r]){if(!a[r]){var e="function"==typeof require&&require;if(!t&&e)return e(r,!0);if(l)return l(r,!0);var i=new Error("Cannot find module '"+r+"'");throw i.code="MODULE_NOT_FOUND",i}var n=o[r]={exports:{}};a[r][0].call(n.exports,function(t){var e=a[r][1][t];return u(e||t)},n,n.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,t=0;t<h.length;t++)u(h[t]);return u}({1:[function(t,e,r){"use strict";var c=t("./utils"),d=t("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(t){for(var e,r,i,n,s,a,o,h=[],u=0,l=t.length,f=l,d="string"!==c.getTypeOf(t);u<t.length;)f=l-u,i=d?(e=t[u++],r=u<l?t[u++]:0,u<l?t[u++]:0):(e=t.charCodeAt(u++),r=u<l?t.charCodeAt(u++):0,u<l?t.charCodeAt(u++):0),n=e>>2,s=(3&e)<<4|r>>4,a=1<f?(15&r)<<2|i>>6:64,o=2<f?63&i:64,h.push(p.charAt(n)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(t){var e,r,i,n,s,a,o=0,h=0,u="data:";if(t.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"")).length/4;if(t.charAt(t.length-1)===p.charAt(64)&&f--,t.charAt(t.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=d.uint8array?new Uint8Array(0|f):new Array(0|f);o<t.length;)e=p.indexOf(t.charAt(o++))<<2|(n=p.indexOf(t.charAt(o++)))>>4,r=(15&n)<<4|(s=p.indexOf(t.charAt(o++)))>>2,i=(3&s)<<6|(a=p.indexOf(t.charAt(o++))),l[h++]=e,64!==s&&(l[h++]=r),64!==a&&(l[h++]=i);return l}},{"./support":30,"./utils":32}],2:[function(t,e,r){"use strict";var i=t("./external"),n=t("./stream/DataWorker"),s=t("./stream/DataLengthProbe"),a=t("./stream/Crc32Probe");s=t("./stream/DataLengthProbe");function o(t,e,r,i,n){this.compressedSize=t,this.uncompressedSize=e,this.crc32=r,this.compression=i,this.compressedContent=n}o.prototype={getContentWorker:function(){var t=new n(i.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new s("data_length")),e=this;return t.on("end",function(){if(this.streamInfo.data_length!==e.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),t},getCompressedWorker:function(){return new n(i.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(t,e,r){return t.pipe(new a).pipe(new s("uncompressedSize")).pipe(e.compressWorker(r)).pipe(new s("compressedSize")).withStreamInfo("compression",e)},e.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(t,e,r){"use strict";var i=t("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(t){return new i("STORE compression")},uncompressWorker:function(){return new i("STORE decompression")}},r.DEFLATE=t("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(t,e,r){"use strict";var i=t("./utils");var o=function(){for(var t,e=[],r=0;r<256;r++){t=r;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[r]=t}return e}();e.exports=function(t,e){return void 0!==t&&t.length?"string"!==i.getTypeOf(t)?function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;a<s;a++)t=t>>>8^n[255&(t^e[a])];return-1^t}(0|e,t,t.length,0):function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;a<s;a++)t=t>>>8^n[255&(t^e.charCodeAt(a))];return-1^t}(0|e,t,t.length,0):0}},{"./utils":32}],5:[function(t,e,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(t,e,r){"use strict";var i=null;i="undefined"!=typeof Promise?Promise:t("lie"),e.exports={Promise:i}},{lie:37}],7:[function(t,e,r){"use strict";var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,n=t("pako"),s=t("./utils"),a=t("./stream/GenericWorker"),o=i?"uint8array":"array";function h(t,e){a.call(this,"FlateWorker/"+t),this._pako=null,this._pakoAction=t,this._pakoOptions=e,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(t){this.meta=t.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,t.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new n[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var e=this;this._pako.onData=function(t){e.push({data:t,meta:e.meta})}},r.compressWorker=function(t){return new h("Deflate",t)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(t,e,r){"use strict";function A(t,e){var r,i="";for(r=0;r<e;r++)i+=String.fromCharCode(255&t),t>>>=8;return i}function i(t,e,r,i,n,s){var a,o,h=t.file,u=t.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),d=I.transformTo("string",O.utf8encode(h.name)),c=h.comment,p=I.transformTo("string",s(c)),m=I.transformTo("string",O.utf8encode(c)),_=d.length!==h.name.length,g=m.length!==c.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};e&&!r||(x.crc32=t.crc32,x.compressedSize=t.compressedSize,x.uncompressedSize=t.uncompressedSize);var S=0;e&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===n?(C=798,z|=function(t,e){var r=t;return t||(r=e?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(t){return 63&(t||0)}(h.dosPermissions)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+d,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\0",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+A(p.length,2)+"\0\0\0\0"+A(z,4)+A(i,4)+f+b+p}}var I=t("../utils"),n=t("../stream/GenericWorker"),O=t("../utf8"),B=t("../crc32"),R=t("../signature");function s(t,e,r,i){n.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=e,this.zipPlatform=r,this.encodeFileName=i,this.streamFiles=t,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,n),s.prototype.push=function(t){var e=t.meta.percent||0,r=this.entriesCount,i=this._sources.length;this.accumulate?this.contentBuffer.push(t):(this.bytesWritten+=t.data.length,n.prototype.push.call(this,{data:t.data,meta:{currentFile:this.currentFile,percent:r?(e+100*(r-i-1))/r:100}}))},s.prototype.openedSource=function(t){this.currentSourceOffset=this.bytesWritten,this.currentFile=t.file.name;var e=this.streamFiles&&!t.file.dir;if(e){var r=i(t,e,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},s.prototype.closedSource=function(t){this.accumulate=!1;var e=this.streamFiles&&!t.file.dir,r=i(t,e,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),e)this.push({data:function(t){return R.DATA_DESCRIPTOR+A(t.crc32,4)+A(t.compressedSize,4)+A(t.uncompressedSize,4)}(t),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=function(){for(var t=this.bytesWritten,e=0;e<this.dirRecords.length;e++)this.push({data:this.dirRecords[e],meta:{percent:100}});var r=this.bytesWritten-t,i=function(t,e,r,i,n){var s=I.transformTo("string",n(i));return R.CENTRAL_DIRECTORY_END+"\0\0\0\0"+A(t,2)+A(t,2)+A(e,4)+A(r,4)+A(s.length,2)+s}(this.dirRecords.length,r,t,this.zipComment,this.encodeFileName);this.push({data:i,meta:{percent:100}})},s.prototype.prepareNextSource=function(){this.previous=this._sources.shift(),this.openedSource(this.previous.streamInfo),this.isPaused?this.previous.pause():this.previous.resume()},s.prototype.registerPrevious=function(t){this._sources.push(t);var e=this;return t.on("data",function(t){e.processChunk(t)}),t.on("end",function(){e.closedSource(e.previous.streamInfo),e._sources.length?e.prepareNextSource():e.end()}),t.on("error",function(t){e.error(t)}),this},s.prototype.resume=function(){return!!n.prototype.resume.call(this)&&(!this.previous&&this._sources.length?(this.prepareNextSource(),!0):this.previous||this._sources.length||this.generatedError?void 0:(this.end(),!0))},s.prototype.error=function(t){var e=this._sources;if(!n.prototype.error.call(this,t))return!1;for(var r=0;r<e.length;r++)try{e[r].error(t)}catch(t){}return!0},s.prototype.lock=function(){n.prototype.lock.call(this);for(var t=this._sources,e=0;e<t.length;e++)t[e].lock()},e.exports=s},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(t,e,r){"use strict";var u=t("../compressions"),i=t("./ZipFileWorker");r.generateWorker=function(t,a,e){var o=new i(a.streamFiles,e,a.platform,a.encodeFileName),h=0;try{t.forEach(function(t,e){h++;var r=function(t,e){var r=t||e,i=u[r];if(!i)throw new Error(r+" is not a valid compression method !");return i}(e.options.compression,a.compression),i=e.options.compressionOptions||a.compressionOptions||{},n=e.dir,s=e.date;e._compressWorker(r,i).withStreamInfo("file",{name:t,dir:n,date:s,comment:e.comment||"",unixPermissions:e.unixPermissions,dosPermissions:e.dosPermissions}).pipe(o)}),o.entriesCount=h}catch(t){o.error(t)}return o}},{"../compressions":3,"./ZipFileWorker":8}],10:[function(t,e,r){"use strict";function i(){if(!(this instanceof i))return new i;if(arguments.length)throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");this.files={},this.comment=null,this.root="",this.clone=function(){var t=new i;for(var e in this)"function"!=typeof this[e]&&(t[e]=this[e]);return t}}(i.prototype=t("./object")).loadAsync=t("./load"),i.support=t("./support"),i.defaults=t("./defaults"),i.version="3.4.0",i.loadAsync=function(t,e){return(new i).loadAsync(t,e)},i.external=t("./external"),e.exports=i},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(t,e,r){"use strict";var i=t("./utils"),n=t("./external"),o=t("./utf8"),h=(i=t("./utils"),t("./zipEntries")),s=t("./stream/Crc32Probe"),u=t("./nodejsUtils");function l(i){return new n.Promise(function(t,e){var r=i.decompressed.getContentWorker().pipe(new s);r.on("error",function(t){e(t)}).on("end",function(){r.streamInfo.crc32!==i.decompressed.crc32?e(new Error("Corrupted zip : CRC32 mismatch")):t()}).resume()})}e.exports=function(t,s){var a=this;return s=i.extend(s||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:o.utf8decode}),u.isNode&&u.isStream(t)?n.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")):i.prepareContent("the loaded zip file",t,!0,s.optimizedBinaryString,s.base64).then(function(t){var e=new h(s);return e.load(t),e}).then(function(t){var e=[n.Promise.resolve(t)],r=t.files;if(s.checkCRC32)for(var i=0;i<r.length;i++)e.push(l(r[i]));return n.Promise.all(e)}).then(function(t){for(var e=t.shift(),r=e.files,i=0;i<r.length;i++){var n=r[i];a.file(n.fileNameStr,n.decompressed,{binary:!0,optimizedBinaryString:!0,date:n.date,dir:n.dir,comment:n.fileCommentStr.length?n.fileCommentStr:null,unixPermissions:n.unixPermissions,dosPermissions:n.dosPermissions,createFolders:s.createFolders})}return e.zipComment.length&&(a.comment=e.zipComment),a})}},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(t,e,r){"use strict";var i=t("../utils"),n=t("../stream/GenericWorker");function s(t,e){n.call(this,"Nodejs stream input adapter for "+t),this._upstreamEnded=!1,this._bindStream(e)}i.inherits(s,n),s.prototype._bindStream=function(t){var e=this;(this._stream=t).pause(),t.on("data",function(t){e.push({data:t,meta:{percent:0}})}).on("error",function(t){e.isPaused?this.generatedError=t:e.error(t)}).on("end",function(){e.isPaused?e._upstreamEnded=!0:e.end()})},s.prototype.pause=function(){return!!n.prototype.pause.call(this)&&(this._stream.pause(),!0)},s.prototype.resume=function(){return!!n.prototype.resume.call(this)&&(this._upstreamEnded?this.end():this._stream.resume(),!0)},e.exports=s},{"../stream/GenericWorker":28,"../utils":32}],13:[function(t,e,r){"use strict";var n=t("readable-stream").Readable;function i(t,e,r){n.call(this,e),this._helper=t;var i=this;t.on("data",function(t,e){i.push(t)||i._helper.pause(),r&&r(e)}).on("error",function(t){i.emit("error",t)}).on("end",function(){i.push(null)})}t("../utils").inherits(i,n),i.prototype._read=function(){this._helper.resume()},e.exports=i},{"../utils":32,"readable-stream":16}],14:[function(t,e,r){"use strict";e.exports={isNode:"undefined"!=typeof Buffer,newBufferFrom:function(t,e){if(Buffer.from&&Buffer.from!==Uint8Array.from)return Buffer.from(t,e);if("number"==typeof t)throw new Error('The "data" argument must not be a number');return new Buffer(t,e)},allocBuffer:function(t){if(Buffer.alloc)return Buffer.alloc(t);var e=new Buffer(t);return e.fill(0),e},isBuffer:function(t){return Buffer.isBuffer(t)},isStream:function(t){return t&&"function"==typeof t.on&&"function"==typeof t.pause&&"function"==typeof t.resume}}},{}],15:[function(t,e,r){"use strict";function s(t,e,r){var i,n=u.getTypeOf(e),s=u.extend(r||{},f);s.date=s.date||new Date,null!==s.compression&&(s.compression=s.compression.toUpperCase()),"string"==typeof s.unixPermissions&&(s.unixPermissions=parseInt(s.unixPermissions,8)),s.unixPermissions&&16384&s.unixPermissions&&(s.dir=!0),s.dosPermissions&&16&s.dosPermissions&&(s.dir=!0),s.dir&&(t=g(t)),s.createFolders&&(i=_(t))&&b.call(this,i,!0);var a="string"===n&&!1===s.binary&&!1===s.base64;r&&void 0!==r.binary||(s.binary=!a),(e instanceof d&&0===e.uncompressedSize||s.dir||!e||0===e.length)&&(s.base64=!1,s.binary=!0,e="",s.compression="STORE",n="string");var o=null;o=e instanceof d||e instanceof l?e:p.isNode&&p.isStream(e)?new m(t,e):u.prepareContent(t,e,s.binary,s.optimizedBinaryString,s.base64);var h=new c(t,o,s);this.files[t]=h}var n=t("./utf8"),u=t("./utils"),l=t("./stream/GenericWorker"),a=t("./stream/StreamHelper"),f=t("./defaults"),d=t("./compressedObject"),c=t("./zipObject"),o=t("./generate"),p=t("./nodejsUtils"),m=t("./nodejs/NodejsStreamInputAdapter"),_=function(t){"/"===t.slice(-1)&&(t=t.substring(0,t.length-1));var e=t.lastIndexOf("/");return 0<e?t.substring(0,e):""},g=function(t){return"/"!==t.slice(-1)&&(t+="/"),t},b=function(t,e){return e=void 0!==e?e:f.createFolders,t=g(t),this.files[t]||s.call(this,t,null,{dir:!0,createFolders:e}),this.files[t]};function h(t){return"[object RegExp]"===Object.prototype.toString.call(t)}var i={load:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},forEach:function(t){var e,r,i;for(e in this.files)this.files.hasOwnProperty(e)&&(i=this.files[e],(r=e.slice(this.root.length,e.length))&&e.slice(0,this.root.length)===this.root&&t(r,i))},filter:function(r){var i=[];return this.forEach(function(t,e){r(t,e)&&i.push(e)}),i},file:function(t,e,r){if(1!==arguments.length)return t=this.root+t,s.call(this,t,e,r),this;if(h(t)){var i=t;return this.filter(function(t,e){return!e.dir&&i.test(t)})}var n=this.files[this.root+t];return n&&!n.dir?n:null},folder:function(r){if(!r)return this;if(h(r))return this.filter(function(t,e){return e.dir&&r.test(t)});var t=this.root+r,e=b.call(this,t),i=this.clone();return i.root=e.name,i},remove:function(r){r=this.root+r;var t=this.files[r];if(t||("/"!==r.slice(-1)&&(r+="/"),t=this.files[r]),t&&!t.dir)delete this.files[r];else for(var e=this.filter(function(t,e){return e.name.slice(0,r.length)===r}),i=0;i<e.length;i++)delete this.files[e[i].name];return this},generate:function(t){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},generateInternalStream:function(t){var e,r={};try{if((r=u.extend(t||{},{streamFiles:!1,compression:"STORE",compressionOptions:null,type:"",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:n.utf8encode})).type=r.type.toLowerCase(),r.compression=r.compression.toUpperCase(),"binarystring"===r.type&&(r.type="string"),!r.type)throw new Error("No output type specified.");u.checkSupport(r.type),"darwin"!==r.platform&&"freebsd"!==r.platform&&"linux"!==r.platform&&"sunos"!==r.platform||(r.platform="UNIX"),"win32"===r.platform&&(r.platform="DOS");var i=r.comment||this.comment||"";e=o.generateWorker(this,r,i)}catch(t){(e=new l("error")).error(t)}return new a(e,r.type||"string",r.mimeType)},generateAsync:function(t,e){return this.generateInternalStream(t).accumulate(e)},generateNodeStream:function(t,e){return(t=t||{}).type||(t.type="nodebuffer"),this.generateInternalStream(t).toNodejsStream(e)}};e.exports=i},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(t,e,r){e.exports=t("stream")},{stream:void 0}],17:[function(t,e,r){"use strict";var i=t("./DataReader");function n(t){i.call(this,t);for(var e=0;e<this.data.length;e++)t[e]=255&t[e]}t("../utils").inherits(n,i),n.prototype.byteAt=function(t){return this.data[this.zero+t]},n.prototype.lastIndexOfSignature=function(t){for(var e=t.charCodeAt(0),r=t.charCodeAt(1),i=t.charCodeAt(2),n=t.charCodeAt(3),s=this.length-4;0<=s;--s)if(this.data[s]===e&&this.data[s+1]===r&&this.data[s+2]===i&&this.data[s+3]===n)return s-this.zero;return-1},n.prototype.readAndCheckSignature=function(t){var e=t.charCodeAt(0),r=t.charCodeAt(1),i=t.charCodeAt(2),n=t.charCodeAt(3),s=this.readData(4);return e===s[0]&&r===s[1]&&i===s[2]&&n===s[3]},n.prototype.readData=function(t){if(this.checkOffset(t),0===t)return[];var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./DataReader":18}],18:[function(t,e,r){"use strict";var i=t("../utils");function n(t){this.data=t,this.length=t.length,this.index=0,this.zero=0}n.prototype={checkOffset:function(t){this.checkIndex(this.index+t)},checkIndex:function(t){if(this.length<this.zero+t||t<0)throw new Error("End of data reached (data length = "+this.length+", asked index = "+t+"). Corrupted zip ?")},setIndex:function(t){this.checkIndex(t),this.index=t},skip:function(t){this.setIndex(this.index+t)},byteAt:function(t){},readInt:function(t){var e,r=0;for(this.checkOffset(t),e=this.index+t-1;e>=this.index;e--)r=(r<<8)+this.byteAt(e);return this.index+=t,r},readString:function(t){return i.transformTo("string",this.readData(t))},readData:function(t){},lastIndexOfSignature:function(t){},readAndCheckSignature:function(t){},readDate:function(){var t=this.readInt(4);return new Date(Date.UTC(1980+(t>>25&127),(t>>21&15)-1,t>>16&31,t>>11&31,t>>5&63,(31&t)<<1))}},e.exports=n},{"../utils":32}],19:[function(t,e,r){"use strict";var i=t("./Uint8ArrayReader");function n(t){i.call(this,t)}t("../utils").inherits(n,i),n.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(t,e,r){"use strict";var i=t("./DataReader");function n(t){i.call(this,t)}t("../utils").inherits(n,i),n.prototype.byteAt=function(t){return this.data.charCodeAt(this.zero+t)},n.prototype.lastIndexOfSignature=function(t){return this.data.lastIndexOf(t)-this.zero},n.prototype.readAndCheckSignature=function(t){return t===this.readData(4)},n.prototype.readData=function(t){this.checkOffset(t);var e=this.data.slice(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./DataReader":18}],21:[function(t,e,r){"use strict";var i=t("./ArrayReader");function n(t){i.call(this,t)}t("../utils").inherits(n,i),n.prototype.readData=function(t){if(this.checkOffset(t),0===t)return new Uint8Array(0);var e=this.data.subarray(this.zero+this.index,this.zero+this.index+t);return this.index+=t,e},e.exports=n},{"../utils":32,"./ArrayReader":17}],22:[function(t,e,r){"use strict";var i=t("../utils"),n=t("../support"),s=t("./ArrayReader"),a=t("./StringReader"),o=t("./NodeBufferReader"),h=t("./Uint8ArrayReader");e.exports=function(t){var e=i.getTypeOf(t);return i.checkSupport(e),"string"!==e||n.uint8array?"nodebuffer"===e?new o(t):n.uint8array?new h(i.transformTo("uint8array",t)):new s(i.transformTo("array",t)):new a(t)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(t,e,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(t,e,r){"use strict";var i=t("./GenericWorker"),n=t("../utils");function s(t){i.call(this,"ConvertWorker to "+t),this.destType=t}n.inherits(s,i),s.prototype.processChunk=function(t){this.push({data:n.transformTo(this.destType,t.data),meta:t.meta})},e.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(t,e,r){"use strict";var i=t("./GenericWorker"),n=t("../crc32");function s(){i.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}t("../utils").inherits(s,i),s.prototype.processChunk=function(t){this.streamInfo.crc32=n(t.data,this.streamInfo.crc32||0),this.push(t)},e.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(t,e,r){"use strict";var i=t("../utils"),n=t("./GenericWorker");function s(t){n.call(this,"DataLengthProbe for "+t),this.propName=t,this.withStreamInfo(t,0)}i.inherits(s,n),s.prototype.processChunk=function(t){if(t){var e=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=e+t.data.length}n.prototype.processChunk.call(this,t)},e.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(t,e,r){"use strict";var i=t("../utils"),n=t("./GenericWorker");function s(t){n.call(this,"DataWorker");var e=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,t.then(function(t){e.dataIsReady=!0,e.data=t,e.max=t&&t.length||0,e.type=i.getTypeOf(t),e.isPaused||e._tickAndRepeat()},function(t){e.error(t)})}i.inherits(s,n),s.prototype.cleanUp=function(){n.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!n.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,i.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(i.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var t=null,e=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":t=this.data.substring(this.index,e);break;case"uint8array":t=this.data.subarray(this.index,e);break;case"array":case"nodebuffer":t=this.data.slice(this.index,e)}return this.index=e,this.push({data:t,meta:{percent:this.max?this.index/this.max*100:0}})},e.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(t,e,r){"use strict";function i(t){this.name=t||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}i.prototype={push:function(t){this.emit("data",t)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(t){this.emit("error",t)}return!0},error:function(t){return!this.isFinished&&(this.isPaused?this.generatedError=t:(this.isFinished=!0,this.emit("error",t),this.previous&&this.previous.error(t),this.cleanUp()),!0)},on:function(t,e){return this._listeners[t].push(e),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(t,e){if(this._listeners[t])for(var r=0;r<this._listeners[t].length;r++)this._listeners[t][r].call(this,e)},pipe:function(t){return t.registerPrevious(this)},registerPrevious:function(t){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.streamInfo=t.streamInfo,this.mergeStreamInfo(),this.previous=t;var e=this;return t.on("data",function(t){e.processChunk(t)}),t.on("end",function(){e.end()}),t.on("error",function(t){e.error(t)}),this},pause:function(){return!this.isPaused&&!this.isFinished&&(this.isPaused=!0,this.previous&&this.previous.pause(),!0)},resume:function(){if(!this.isPaused||this.isFinished)return!1;var t=this.isPaused=!1;return this.generatedError&&(this.error(this.generatedError),t=!0),this.previous&&this.previous.resume(),!t},flush:function(){},processChunk:function(t){this.push(t)},withStreamInfo:function(t,e){return this.extraStreamInfo[t]=e,this.mergeStreamInfo(),this},mergeStreamInfo:function(){for(var t in this.extraStreamInfo)this.extraStreamInfo.hasOwnProperty(t)&&(this.streamInfo[t]=this.extraStreamInfo[t])},lock:function(){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.isLocked=!0,this.previous&&this.previous.lock()},toString:function(){var t="Worker "+this.name;return this.previous?this.previous+" -> "+t:t}},e.exports=i},{}],29:[function(t,e,r){"use strict";var h=t("../utils"),n=t("./ConvertWorker"),s=t("./GenericWorker"),u=t("../base64"),i=t("../support"),a=t("../external"),o=null;if(i.nodestream)try{o=t("../nodejs/NodejsStreamOutputAdapter")}catch(t){}function l(t,o){return new a.Promise(function(e,r){var i=[],n=t._internalType,s=t._outputType,a=t._mimeType;t.on("data",function(t,e){i.push(t),o&&o(e)}).on("error",function(t){i=[],r(t)}).on("end",function(){try{var t=function(t,e,r){switch(t){case"blob":return h.newBlob(h.transformTo("arraybuffer",e),r);case"base64":return u.encode(e);default:return h.transformTo(t,e)}}(s,function(t,e){var r,i=0,n=null,s=0;for(r=0;r<e.length;r++)s+=e[r].length;switch(t){case"string":return e.join("");case"array":return Array.prototype.concat.apply([],e);case"uint8array":for(n=new Uint8Array(s),r=0;r<e.length;r++)n.set(e[r],i),i+=e[r].length;return n;case"nodebuffer":return Buffer.concat(e);default:throw new Error("concat : unsupported type '"+t+"'")}}(n,i),a);e(t)}catch(t){r(t)}i=[]}).resume()})}function f(t,e,r){var i=e;switch(e){case"blob":case"arraybuffer":i="uint8array";break;case"base64":i="string"}try{this._internalType=i,this._outputType=e,this._mimeType=r,h.checkSupport(i),this._worker=t.pipe(new n(i)),t.lock()}catch(t){this._worker=new s("error"),this._worker.error(t)}}f.prototype={accumulate:function(t){return l(this,t)},on:function(t,e){var r=this;return"data"===t?this._worker.on(t,function(t){e.call(r,t.data,t.meta)}):this._worker.on(t,function(){h.delay(e,arguments,r)}),this},resume:function(){return h.delay(this._worker.resume,[],this._worker),this},pause:function(){return this._worker.pause(),this},toNodejsStream:function(t){if(h.checkSupport("nodestream"),"nodebuffer"!==this._outputType)throw new Error(this._outputType+" is not supported by this method");return new o(this,{objectMode:"nodebuffer"!==this._outputType},t)}},e.exports=f},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(t,e,r){"use strict";if(r.base64=!0,r.array=!0,r.string=!0,r.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,r.nodebuffer="undefined"!=typeof Buffer,r.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)r.blob=!1;else{var i=new ArrayBuffer(0);try{r.blob=0===new Blob([i],{type:"application/zip"}).size}catch(t){try{var n=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);n.append(i),r.blob=0===n.getBlob("application/zip").size}catch(t){r.blob=!1}}}try{r.nodestream=!!t("readable-stream").Readable}catch(t){r.nodestream=!1}},{"readable-stream":16}],31:[function(t,e,s){"use strict";for(var o=t("./utils"),h=t("./support"),r=t("./nodejsUtils"),i=t("./stream/GenericWorker"),u=new Array(256),n=0;n<256;n++)u[n]=252<=n?6:248<=n?5:240<=n?4:224<=n?3:192<=n?2:1;u[254]=u[254]=1;function a(){i.call(this,"utf-8 decode"),this.leftOver=null}function l(){i.call(this,"utf-8 encode")}s.utf8encode=function(t){return h.nodebuffer?r.newBufferFrom(t,"utf-8"):function(t){var e,r,i,n,s,a=t.length,o=0;for(n=0;n<a;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),o+=r<128?1:r<2048?2:r<65536?3:4;for(e=h.uint8array?new Uint8Array(o):new Array(o),n=s=0;s<o;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),r<128?e[s++]=r:(r<2048?e[s++]=192|r>>>6:(r<65536?e[s++]=224|r>>>12:(e[s++]=240|r>>>18,e[s++]=128|r>>>12&63),e[s++]=128|r>>>6&63),e[s++]=128|63&r);return e}(t)},s.utf8decode=function(t){return h.nodebuffer?o.transformTo("nodebuffer",t).toString("utf-8"):function(t){var e,r,i,n,s=t.length,a=new Array(2*s);for(e=r=0;e<s;)if((i=t[e++])<128)a[r++]=i;else if(4<(n=u[i]))a[r++]=65533,e+=n-1;else{for(i&=2===n?31:3===n?15:7;1<n&&e<s;)i=i<<6|63&t[e++],n--;1<n?a[r++]=65533:i<65536?a[r++]=i:(i-=65536,a[r++]=55296|i>>10&1023,a[r++]=56320|1023&i)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(t=o.transformTo(h.uint8array?"uint8array":"array",t))},o.inherits(a,i),a.prototype.processChunk=function(t){var e=o.transformTo(h.uint8array?"uint8array":"array",t.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=e;(e=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),e.set(r,this.leftOver.length)}else e=this.leftOver.concat(e);this.leftOver=null}var i=function(t,e){var r;for((e=e||t.length)>t.length&&(e=t.length),r=e-1;0<=r&&128==(192&t[r]);)r--;return r<0?e:0===r?e:r+u[t[r]]>e?r:e}(e),n=e;i!==e.length&&(h.uint8array?(n=e.subarray(0,i),this.leftOver=e.subarray(i,e.length)):(n=e.slice(0,i),this.leftOver=e.slice(i,e.length))),this.push({data:s.utf8decode(n),meta:t.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,i),l.prototype.processChunk=function(t){this.push({data:s.utf8encode(t.data),meta:t.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(t,e,a){"use strict";var o=t("./support"),h=t("./base64"),r=t("./nodejsUtils"),i=t("set-immediate-shim"),u=t("./external");function n(t){return t}function l(t,e){for(var r=0;r<t.length;++r)e[r]=255&t.charCodeAt(r);return e}a.newBlob=function(e,r){a.checkSupport("blob");try{return new Blob([e],{type:r})}catch(t){try{var i=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);return i.append(e),i.getBlob(r)}catch(t){throw new Error("Bug : can't construct the Blob.")}}};var s={stringifyByChunk:function(t,e,r){var i=[],n=0,s=t.length;if(s<=r)return String.fromCharCode.apply(null,t);for(;n<s;)"array"===e||"nodebuffer"===e?i.push(String.fromCharCode.apply(null,t.slice(n,Math.min(n+r,s)))):i.push(String.fromCharCode.apply(null,t.subarray(n,Math.min(n+r,s)))),n+=r;return i.join("")},stringifyByChar:function(t){for(var e="",r=0;r<t.length;r++)e+=String.fromCharCode(t[r]);return e},applyCanBeUsed:{uint8array:function(){try{return o.uint8array&&1===String.fromCharCode.apply(null,new Uint8Array(1)).length}catch(t){return!1}}(),nodebuffer:function(){try{return o.nodebuffer&&1===String.fromCharCode.apply(null,r.allocBuffer(1)).length}catch(t){return!1}}()}};function f(t){var e=65536,r=a.getTypeOf(t),i=!0;if("uint8array"===r?i=s.applyCanBeUsed.uint8array:"nodebuffer"===r&&(i=s.applyCanBeUsed.nodebuffer),i)for(;1<e;)try{return s.stringifyByChunk(t,r,e)}catch(t){e=Math.floor(e/2)}return s.stringifyByChar(t)}function d(t,e){for(var r=0;r<t.length;r++)e[r]=t[r];return e}a.applyFromCharCode=f;var c={};c.string={string:n,array:function(t){return l(t,new Array(t.length))},arraybuffer:function(t){return c.string.uint8array(t).buffer},uint8array:function(t){return l(t,new Uint8Array(t.length))},nodebuffer:function(t){return l(t,r.allocBuffer(t.length))}},c.array={string:f,array:n,arraybuffer:function(t){return new Uint8Array(t).buffer},uint8array:function(t){return new Uint8Array(t)},nodebuffer:function(t){return r.newBufferFrom(t)}},c.arraybuffer={string:function(t){return f(new Uint8Array(t))},array:function(t){return d(new Uint8Array(t),new Array(t.byteLength))},arraybuffer:n,uint8array:function(t){return new Uint8Array(t)},nodebuffer:function(t){return r.newBufferFrom(new Uint8Array(t))}},c.uint8array={string:f,array:function(t){return d(t,new Array(t.length))},arraybuffer:function(t){return t.buffer},uint8array:n,nodebuffer:function(t){return r.newBufferFrom(t)}},c.nodebuffer={string:f,array:function(t){return d(t,new Array(t.length))},arraybuffer:function(t){return c.nodebuffer.uint8array(t).buffer},uint8array:function(t){return d(t,new Uint8Array(t.length))},nodebuffer:n},a.transformTo=function(t,e){if(e=e||"",!t)return e;a.checkSupport(t);var r=a.getTypeOf(e);return c[r][t](e)},a.getTypeOf=function(t){return"string"==typeof t?"string":"[object Array]"===Object.prototype.toString.call(t)?"array":o.nodebuffer&&r.isBuffer(t)?"nodebuffer":o.uint8array&&t instanceof Uint8Array?"uint8array":o.arraybuffer&&t instanceof ArrayBuffer?"arraybuffer":void 0},a.checkSupport=function(t){if(!o[t.toLowerCase()])throw new Error(t+" is not supported by this platform")},a.MAX_VALUE_16BITS=65535,a.MAX_VALUE_32BITS=-1,a.pretty=function(t){var e,r,i="";for(r=0;r<(t||"").length;r++)i+="\\x"+((e=t.charCodeAt(r))<16?"0":"")+e.toString(16).toUpperCase();return i},a.delay=function(t,e,r){i(function(){t.apply(r||null,e||[])})},a.inherits=function(t,e){function r(){}r.prototype=e.prototype,t.prototype=new r},a.extend=function(){var t,e,r={};for(t=0;t<arguments.length;t++)for(e in arguments[t])arguments[t].hasOwnProperty(e)&&void 0===r[e]&&(r[e]=arguments[t][e]);return r},a.prepareContent=function(r,t,i,n,s){return u.Promise.resolve(t).then(function(i){return o.blob&&(i instanceof Blob||-1!==["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(i)))&&"undefined"!=typeof FileReader?new u.Promise(function(e,r){var t=new FileReader;t.onload=function(t){e(t.target.result)},t.onerror=function(t){r(t.target.error)},t.readAsArrayBuffer(i)}):i}).then(function(t){var e=a.getTypeOf(t);return e?("arraybuffer"===e?t=a.transformTo("uint8array",t):"string"===e&&(s?t=h.decode(t):i&&!0!==n&&(t=function(t){return l(t,o.uint8array?new Uint8Array(t.length):new Array(t.length))}(t))),t):u.Promise.reject(new Error("Can't read the data of '"+r+"'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"set-immediate-shim":54}],33:[function(t,e,r){"use strict";var i=t("./reader/readerFor"),n=t("./utils"),s=t("./signature"),a=t("./zipEntry"),o=(t("./utf8"),t("./support"));function h(t){this.files=[],this.loadOptions=t}h.prototype={checkSignature:function(t){if(!this.reader.readAndCheckSignature(t)){this.reader.index-=4;var e=this.reader.readString(4);throw new Error("Corrupted zip or bug: unexpected signature ("+n.pretty(e)+", expected "+n.pretty(t)+")")}},isSignature:function(t,e){var r=this.reader.index;this.reader.setIndex(t);var i=this.reader.readString(4)===e;return this.reader.setIndex(r),i},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var t=this.reader.readData(this.zipCommentLength),e=o.uint8array?"uint8array":"array",r=n.transformTo(e,t);this.zipComment=this.loadOptions.decodeFileName(r)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var t,e,r,i=this.zip64EndOfCentralSize-44;0<i;)t=this.reader.readInt(2),e=this.reader.readInt(4),r=this.reader.readData(e),this.zip64ExtensibleData[t]={id:t,length:e,value:r}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),1<this.disksCount)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var t,e;for(t=0;t<this.files.length;t++)e=this.files[t],this.reader.setIndex(e.localHeaderOffset),this.checkSignature(s.LOCAL_FILE_HEADER),e.readLocalPart(this.reader),e.handleUTF8(),e.processAttributes()},readCentralDir:function(){var t;for(this.reader.setIndex(this.centralDirOffset);this.reader.readAndCheckSignature(s.CENTRAL_FILE_HEADER);)(t=new a({zip64:this.zip64},this.loadOptions)).readCentralPart(this.reader),this.files.push(t);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length)},readEndOfCentral:function(){var t=this.reader.lastIndexOfSignature(s.CENTRAL_DIRECTORY_END);if(t<0)throw!this.isSignature(0,s.LOCAL_FILE_HEADER)?new Error("Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip: can't find end of central directory");this.reader.setIndex(t);var e=t;if(this.checkSignature(s.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===n.MAX_VALUE_16BITS||this.diskWithCentralDirStart===n.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===n.MAX_VALUE_16BITS||this.centralDirRecords===n.MAX_VALUE_16BITS||this.centralDirSize===n.MAX_VALUE_32BITS||this.centralDirOffset===n.MAX_VALUE_32BITS){if(this.zip64=!0,(t=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR))<0)throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(t),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,s.ZIP64_CENTRAL_DIRECTORY_END)&&(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var r=this.centralDirOffset+this.centralDirSize;this.zip64&&(r+=20,r+=12+this.zip64EndOfCentralSize);var i=e-r;if(0<i)this.isSignature(e,s.CENTRAL_FILE_HEADER)||(this.reader.zero=i);else if(i<0)throw new Error("Corrupted zip: missing "+Math.abs(i)+" bytes.")},prepareReader:function(t){this.reader=i(t)},load:function(t){this.prepareReader(t),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},e.exports=h},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utf8":31,"./utils":32,"./zipEntry":34}],34:[function(t,e,r){"use strict";var i=t("./reader/readerFor"),s=t("./utils"),n=t("./compressedObject"),a=t("./crc32"),o=t("./utf8"),h=t("./compressions"),u=t("./support");function l(t,e){this.options=t,this.loadOptions=e}l.prototype={isEncrypted:function(){return 1==(1&this.bitFlag)},useUTF8:function(){return 2048==(2048&this.bitFlag)},readLocalPart:function(t){var e,r;if(t.skip(22),this.fileNameLength=t.readInt(2),r=t.readInt(2),this.fileName=t.readData(this.fileNameLength),t.skip(r),-1===this.compressedSize||-1===this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough information from the central directory (compressedSize === -1 || uncompressedSize === -1)");if(null===(e=function(t){for(var e in h)if(h.hasOwnProperty(e)&&h[e].magic===t)return h[e];return null}(this.compressionMethod)))throw new Error("Corrupted zip : compression "+s.pretty(this.compressionMethod)+" unknown (inner file : "+s.transformTo("string",this.fileName)+")");this.decompressed=new n(this.compressedSize,this.uncompressedSize,this.crc32,e,t.readData(this.compressedSize))},readCentralPart:function(t){this.versionMadeBy=t.readInt(2),t.skip(2),this.bitFlag=t.readInt(2),this.compressionMethod=t.readString(2),this.date=t.readDate(),this.crc32=t.readInt(4),this.compressedSize=t.readInt(4),this.uncompressedSize=t.readInt(4);var e=t.readInt(2);if(this.extraFieldsLength=t.readInt(2),this.fileCommentLength=t.readInt(2),this.diskNumberStart=t.readInt(2),this.internalFileAttributes=t.readInt(2),this.externalFileAttributes=t.readInt(4),this.localHeaderOffset=t.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");t.skip(e),this.readExtraFields(t),this.parseZIP64ExtraField(t),this.fileComment=t.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var t=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),0==t&&(this.dosPermissions=63&this.externalFileAttributes),3==t&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(t){if(this.extraFields[1]){var e=i(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(t){var e,r,i,n=t.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});t.index<n;)e=t.readInt(2),r=t.readInt(2),i=t.readData(r),this.extraFields[e]={id:e,length:r,value:i}},handleUTF8:function(){var t=u.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=o.utf8decode(this.fileName),this.fileCommentStr=o.utf8decode(this.fileComment);else{var e=this.findExtraFieldUnicodePath();if(null!==e)this.fileNameStr=e;else{var r=s.transformTo(t,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(r)}var i=this.findExtraFieldUnicodeComment();if(null!==i)this.fileCommentStr=i;else{var n=s.transformTo(t,this.fileComment);this.fileCommentStr=this.loadOptions.decodeFileName(n)}}},findExtraFieldUnicodePath:function(){var t=this.extraFields[28789];if(t){var e=i(t.value);return 1!==e.readInt(1)?null:a(this.fileName)!==e.readInt(4)?null:o.utf8decode(e.readData(t.length-5))}return null},findExtraFieldUnicodeComment:function(){var t=this.extraFields[25461];if(t){var e=i(t.value);return 1!==e.readInt(1)?null:a(this.fileComment)!==e.readInt(4)?null:o.utf8decode(e.readData(t.length-5))}return null}},e.exports=l},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(t,e,r){"use strict";function i(t,e,r){this.name=t,this.dir=r.dir,this.date=r.date,this.comment=r.comment,this.unixPermissions=r.unixPermissions,this.dosPermissions=r.dosPermissions,this._data=e,this._dataBinary=r.binary,this.options={compression:r.compression,compressionOptions:r.compressionOptions}}var s=t("./stream/StreamHelper"),n=t("./stream/DataWorker"),a=t("./utf8"),o=t("./compressedObject"),h=t("./stream/GenericWorker");i.prototype={internalStream:function(t){var e=null,r="string";try{if(!t)throw new Error("No output type specified.");var i="string"===(r=t.toLowerCase())||"text"===r;"binarystring"!==r&&"text"!==r||(r="string"),e=this._decompressWorker();var n=!this._dataBinary;n&&!i&&(e=e.pipe(new a.Utf8EncodeWorker)),!n&&i&&(e=e.pipe(new a.Utf8DecodeWorker))}catch(t){(e=new h("error")).error(t)}return new s(e,r,"")},async:function(t,e){return this.internalStream(t).accumulate(e)},nodeStream:function(t,e){return this.internalStream(t||"nodebuffer").toNodejsStream(e)},_compressWorker:function(t,e){if(this._data instanceof o&&this._data.compression.magic===t.magic)return this._data.getCompressedWorker();var r=this._decompressWorker();return this._dataBinary||(r=r.pipe(new a.Utf8EncodeWorker)),o.createWorkerFrom(r,t,e)},_decompressWorker:function(){return this._data instanceof o?this._data.getContentWorker():this._data instanceof h?this._data:new n(this._data)}};for(var u=["asText","asBinary","asNodeBuffer","asUint8Array","asArrayBuffer"],l=function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},f=0;f<u.length;f++)i.prototype[u[f]]=l;e.exports=i},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(t,l,e){(function(e){"use strict";var r,i,t=e.MutationObserver||e.WebKitMutationObserver;if(t){var n=0,s=new t(u),a=e.document.createTextNode("");s.observe(a,{characterData:!0}),r=function(){a.data=n=++n%2}}else if(e.setImmediate||void 0===e.MessageChannel)r="document"in e&&"onreadystatechange"in e.document.createElement("script")?function(){var t=e.document.createElement("script");t.onreadystatechange=function(){u(),t.onreadystatechange=null,t.parentNode.removeChild(t),t=null},e.document.documentElement.appendChild(t)}:function(){setTimeout(u,0)};else{var o=new e.MessageChannel;o.port1.onmessage=u,r=function(){o.port2.postMessage(0)}}var h=[];function u(){var t,e;i=!0;for(var r=h.length;r;){for(e=h,h=[],t=-1;++t<r;)e[t]();r=h.length}i=!1}l.exports=function(t){1!==h.push(t)||i||r()}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],37:[function(t,e,r){"use strict";var n=t("immediate");function u(){}var l={},s=["REJECTED"],a=["FULFILLED"],i=["PENDING"];function o(t){if("function"!=typeof t)throw new TypeError("resolver must be a function");this.state=i,this.queue=[],this.outcome=void 0,t!==u&&c(this,t)}function h(t,e,r){this.promise=t,"function"==typeof e&&(this.onFulfilled=e,this.callFulfilled=this.otherCallFulfilled),"function"==typeof r&&(this.onRejected=r,this.callRejected=this.otherCallRejected)}function f(e,r,i){n(function(){var t;try{t=r(i)}catch(t){return l.reject(e,t)}t===e?l.reject(e,new TypeError("Cannot resolve promise with itself")):l.resolve(e,t)})}function d(t){var e=t&&t.then;if(t&&("object"==typeof t||"function"==typeof t)&&"function"==typeof e)return function(){e.apply(t,arguments)}}function c(e,t){var r=!1;function i(t){r||(r=!0,l.reject(e,t))}function n(t){r||(r=!0,l.resolve(e,t))}var s=p(function(){t(n,i)});"error"===s.status&&i(s.value)}function p(t,e){var r={};try{r.value=t(e),r.status="success"}catch(t){r.status="error",r.value=t}return r}(e.exports=o).prototype.finally=function(e){if("function"!=typeof e)return this;var r=this.constructor;return this.then(function(t){return r.resolve(e()).then(function(){return t})},function(t){return r.resolve(e()).then(function(){throw t})})},o.prototype.catch=function(t){return this.then(null,t)},o.prototype.then=function(t,e){if("function"!=typeof t&&this.state===a||"function"!=typeof e&&this.state===s)return this;var r=new this.constructor(u);this.state!==i?f(r,this.state===a?t:e,this.outcome):this.queue.push(new h(r,t,e));return r},h.prototype.callFulfilled=function(t){l.resolve(this.promise,t)},h.prototype.otherCallFulfilled=function(t){f(this.promise,this.onFulfilled,t)},h.prototype.callRejected=function(t){l.reject(this.promise,t)},h.prototype.otherCallRejected=function(t){f(this.promise,this.onRejected,t)},l.resolve=function(t,e){var r=p(d,e);if("error"===r.status)return l.reject(t,r.value);var i=r.value;if(i)c(t,i);else{t.state=a,t.outcome=e;for(var n=-1,s=t.queue.length;++n<s;)t.queue[n].callFulfilled(e)}return t},l.reject=function(t,e){t.state=s,t.outcome=e;for(var r=-1,i=t.queue.length;++r<i;)t.queue[r].callRejected(e);return t},o.resolve=function(t){if(t instanceof this)return t;return l.resolve(new this(u),t)},o.reject=function(t){var e=new this(u);return l.reject(e,t)},o.all=function(t){var r=this;if("[object Array]"!==Object.prototype.toString.call(t))return this.reject(new TypeError("must be an array"));var i=t.length,n=!1;if(!i)return this.resolve([]);var s=new Array(i),a=0,e=-1,o=new this(u);for(;++e<i;)h(t[e],e);return o;function h(t,e){r.resolve(t).then(function(t){s[e]=t,++a!==i||n||(n=!0,l.resolve(o,s))},function(t){n||(n=!0,l.reject(o,t))})}},o.race=function(t){var e=this;if("[object Array]"!==Object.prototype.toString.call(t))return this.reject(new TypeError("must be an array"));var r=t.length,i=!1;if(!r)return this.resolve([]);var n=-1,s=new this(u);for(;++n<r;)a=t[n],e.resolve(a).then(function(t){i||(i=!0,l.resolve(s,t))},function(t){i||(i=!0,l.reject(s,t))});var a;return s}},{immediate:36}],38:[function(t,e,r){"use strict";var i={};(0,t("./lib/utils/common").assign)(i,t("./lib/deflate"),t("./lib/inflate"),t("./lib/zlib/constants")),e.exports=i},{"./lib/deflate":39,"./lib/inflate":40,"./lib/utils/common":41,"./lib/zlib/constants":44}],39:[function(t,e,r){"use strict";var a=t("./zlib/deflate"),o=t("./utils/common"),h=t("./utils/strings"),n=t("./zlib/messages"),s=t("./zlib/zstream"),u=Object.prototype.toString,l=0,f=-1,d=0,c=8;function p(t){if(!(this instanceof p))return new p(t);this.options=o.assign({level:f,method:c,chunkSize:16384,windowBits:15,memLevel:8,strategy:d,to:""},t||{});var e=this.options;e.raw&&0<e.windowBits?e.windowBits=-e.windowBits:e.gzip&&0<e.windowBits&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new s,this.strm.avail_out=0;var r=a.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(r!==l)throw new Error(n[r]);if(e.header&&a.deflateSetHeader(this.strm,e.header),e.dictionary){var i;if(i="string"==typeof e.dictionary?h.string2buf(e.dictionary):"[object ArrayBuffer]"===u.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,(r=a.deflateSetDictionary(this.strm,i))!==l)throw new Error(n[r]);this._dict_set=!0}}function i(t,e){var r=new p(e);if(r.push(t,!0),r.err)throw r.msg||n[r.err];return r.result}p.prototype.push=function(t,e){var r,i,n=this.strm,s=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:!0===e?4:0,"string"==typeof t?n.input=h.string2buf(t):"[object ArrayBuffer]"===u.call(t)?n.input=new Uint8Array(t):n.input=t,n.next_in=0,n.avail_in=n.input.length;do{if(0===n.avail_out&&(n.output=new o.Buf8(s),n.next_out=0,n.avail_out=s),1!==(r=a.deflate(n,i))&&r!==l)return this.onEnd(r),!(this.ended=!0);0!==n.avail_out&&(0!==n.avail_in||4!==i&&2!==i)||("string"===this.options.to?this.onData(h.buf2binstring(o.shrinkBuf(n.output,n.next_out))):this.onData(o.shrinkBuf(n.output,n.next_out)))}while((0<n.avail_in||0===n.avail_out)&&1!==r);return 4===i?(r=a.deflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===l):2!==i||(this.onEnd(l),!(n.avail_out=0))},p.prototype.onData=function(t){this.chunks.push(t)},p.prototype.onEnd=function(t){t===l&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=o.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},r.Deflate=p,r.deflate=i,r.deflateRaw=function(t,e){return(e=e||{}).raw=!0,i(t,e)},r.gzip=function(t,e){return(e=e||{}).gzip=!0,i(t,e)}},{"./utils/common":41,"./utils/strings":42,"./zlib/deflate":46,"./zlib/messages":51,"./zlib/zstream":53}],40:[function(t,e,r){"use strict";var d=t("./zlib/inflate"),c=t("./utils/common"),p=t("./utils/strings"),m=t("./zlib/constants"),i=t("./zlib/messages"),n=t("./zlib/zstream"),s=t("./zlib/gzheader"),_=Object.prototype.toString;function a(t){if(!(this instanceof a))return new a(t);this.options=c.assign({chunkSize:16384,windowBits:0,to:""},t||{});var e=this.options;e.raw&&0<=e.windowBits&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(0<=e.windowBits&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),15<e.windowBits&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new n,this.strm.avail_out=0;var r=d.inflateInit2(this.strm,e.windowBits);if(r!==m.Z_OK)throw new Error(i[r]);this.header=new s,d.inflateGetHeader(this.strm,this.header)}function o(t,e){var r=new a(e);if(r.push(t,!0),r.err)throw r.msg||i[r.err];return r.result}a.prototype.push=function(t,e){var r,i,n,s,a,o,h=this.strm,u=this.options.chunkSize,l=this.options.dictionary,f=!1;if(this.ended)return!1;i=e===~~e?e:!0===e?m.Z_FINISH:m.Z_NO_FLUSH,"string"==typeof t?h.input=p.binstring2buf(t):"[object ArrayBuffer]"===_.call(t)?h.input=new Uint8Array(t):h.input=t,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new c.Buf8(u),h.next_out=0,h.avail_out=u),(r=d.inflate(h,m.Z_NO_FLUSH))===m.Z_NEED_DICT&&l&&(o="string"==typeof l?p.string2buf(l):"[object ArrayBuffer]"===_.call(l)?new Uint8Array(l):l,r=d.inflateSetDictionary(this.strm,o)),r===m.Z_BUF_ERROR&&!0===f&&(r=m.Z_OK,f=!1),r!==m.Z_STREAM_END&&r!==m.Z_OK)return this.onEnd(r),!(this.ended=!0);h.next_out&&(0!==h.avail_out&&r!==m.Z_STREAM_END&&(0!==h.avail_in||i!==m.Z_FINISH&&i!==m.Z_SYNC_FLUSH)||("string"===this.options.to?(n=p.utf8border(h.output,h.next_out),s=h.next_out-n,a=p.buf2string(h.output,n),h.next_out=s,h.avail_out=u-s,s&&c.arraySet(h.output,h.output,n,s,0),this.onData(a)):this.onData(c.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(f=!0)}while((0<h.avail_in||0===h.avail_out)&&r!==m.Z_STREAM_END);return r===m.Z_STREAM_END&&(i=m.Z_FINISH),i===m.Z_FINISH?(r=d.inflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===m.Z_OK):i!==m.Z_SYNC_FLUSH||(this.onEnd(m.Z_OK),!(h.avail_out=0))},a.prototype.onData=function(t){this.chunks.push(t)},a.prototype.onEnd=function(t){t===m.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=c.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},r.Inflate=a,r.inflate=o,r.inflateRaw=function(t,e){return(e=e||{}).raw=!0,o(t,e)},r.ungzip=o},{"./utils/common":41,"./utils/strings":42,"./zlib/constants":44,"./zlib/gzheader":47,"./zlib/inflate":49,"./zlib/messages":51,"./zlib/zstream":53}],41:[function(t,e,r){"use strict";var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;r.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var r=e.shift();if(r){if("object"!=typeof r)throw new TypeError(r+"must be non-object");for(var i in r)r.hasOwnProperty(i)&&(t[i]=r[i])}}return t},r.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,r,i,n){if(e.subarray&&t.subarray)t.set(e.subarray(r,r+i),n);else for(var s=0;s<i;s++)t[n+s]=e[r+s]},flattenChunks:function(t){var e,r,i,n,s,a;for(e=i=0,r=t.length;e<r;e++)i+=t[e].length;for(a=new Uint8Array(i),e=n=0,r=t.length;e<r;e++)s=t[e],a.set(s,n),n+=s.length;return a}},s={arraySet:function(t,e,r,i,n){for(var s=0;s<i;s++)t[n+s]=e[r+s]},flattenChunks:function(t){return[].concat.apply([],t)}};r.setTyped=function(t){t?(r.Buf8=Uint8Array,r.Buf16=Uint16Array,r.Buf32=Int32Array,r.assign(r,n)):(r.Buf8=Array,r.Buf16=Array,r.Buf32=Array,r.assign(r,s))},r.setTyped(i)},{}],42:[function(t,e,r){"use strict";var h=t("./common"),n=!0,s=!0;try{String.fromCharCode.apply(null,[0])}catch(t){n=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(t){s=!1}for(var u=new h.Buf8(256),i=0;i<256;i++)u[i]=252<=i?6:248<=i?5:240<=i?4:224<=i?3:192<=i?2:1;function l(t,e){if(e<65537&&(t.subarray&&s||!t.subarray&&n))return String.fromCharCode.apply(null,h.shrinkBuf(t,e));for(var r="",i=0;i<e;i++)r+=String.fromCharCode(t[i]);return r}u[254]=u[254]=1,r.string2buf=function(t){var e,r,i,n,s,a=t.length,o=0;for(n=0;n<a;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),o+=r<128?1:r<2048?2:r<65536?3:4;for(e=new h.Buf8(o),n=s=0;s<o;n++)55296==(64512&(r=t.charCodeAt(n)))&&n+1<a&&56320==(64512&(i=t.charCodeAt(n+1)))&&(r=65536+(r-55296<<10)+(i-56320),n++),r<128?e[s++]=r:(r<2048?e[s++]=192|r>>>6:(r<65536?e[s++]=224|r>>>12:(e[s++]=240|r>>>18,e[s++]=128|r>>>12&63),e[s++]=128|r>>>6&63),e[s++]=128|63&r);return e},r.buf2binstring=function(t){return l(t,t.length)},r.binstring2buf=function(t){for(var e=new h.Buf8(t.length),r=0,i=e.length;r<i;r++)e[r]=t.charCodeAt(r);return e},r.buf2string=function(t,e){var r,i,n,s,a=e||t.length,o=new Array(2*a);for(r=i=0;r<a;)if((n=t[r++])<128)o[i++]=n;else if(4<(s=u[n]))o[i++]=65533,r+=s-1;else{for(n&=2===s?31:3===s?15:7;1<s&&r<a;)n=n<<6|63&t[r++],s--;1<s?o[i++]=65533:n<65536?o[i++]=n:(n-=65536,o[i++]=55296|n>>10&1023,o[i++]=56320|1023&n)}return l(o,i)},r.utf8border=function(t,e){var r;for((e=e||t.length)>t.length&&(e=t.length),r=e-1;0<=r&&128==(192&t[r]);)r--;return r<0?e:0===r?e:r+u[t[r]]>e?r:e}},{"./common":41}],43:[function(t,e,r){"use strict";e.exports=function(t,e,r,i){for(var n=65535&t|0,s=t>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3<r?2e3:r;s=s+(n=n+e[i++]|0)|0,--a;);n%=65521,s%=65521}return n|s<<16|0}},{}],44:[function(t,e,r){"use strict";e.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],45:[function(t,e,r){"use strict";var o=function(){for(var t,e=[],r=0;r<256;r++){t=r;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[r]=t}return e}();e.exports=function(t,e,r,i){var n=o,s=i+r;t^=-1;for(var a=i;a<s;a++)t=t>>>8^n[255&(t^e[a])];return-1^t}},{}],46:[function(t,e,r){"use strict";var h,d=t("../utils/common"),u=t("./trees"),c=t("./adler32"),p=t("./crc32"),i=t("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,n=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(t,e){return t.msg=i[e],e}function T(t){return(t<<1)-(4<t?9:0)}function D(t){for(var e=t.length;0<=--e;)t[e]=0}function F(t){var e=t.state,r=e.pending;r>t.avail_out&&(r=t.avail_out),0!==r&&(d.arraySet(t.output,e.pending_buf,e.pending_out,r,t.next_out),t.next_out+=r,e.pending_out+=r,t.total_out+=r,t.avail_out-=r,e.pending-=r,0===e.pending&&(e.pending_out=0))}function N(t,e){u._tr_flush_block(t,0<=t.block_start?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,F(t.strm)}function U(t,e){t.pending_buf[t.pending++]=e}function P(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function L(t,e){var r,i,n=t.max_chain_length,s=t.strstart,a=t.prev_length,o=t.nice_match,h=t.strstart>t.w_size-z?t.strstart-(t.w_size-z):0,u=t.window,l=t.w_mask,f=t.prev,d=t.strstart+S,c=u[s+a-1],p=u[s+a];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(u[(r=e)+a]===p&&u[r+a-1]===c&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&s<d);if(i=S-(d-s),s=d-S,a<i){if(t.match_start=e,o<=(a=i))break;c=u[s+a-1],p=u[s+a]}}}while((e=f[e&l])>h&&0!=--n);return a<=t.lookahead?a:t.lookahead}function j(t){var e,r,i,n,s,a,o,h,u,l,f=t.w_size;do{if(n=t.window_size-t.lookahead-t.strstart,t.strstart>=f+(f-z)){for(d.arraySet(t.window,t.window,f,f,0),t.match_start-=f,t.strstart-=f,t.block_start-=f,e=r=t.hash_size;i=t.head[--e],t.head[e]=f<=i?i-f:0,--r;);for(e=r=f;i=t.prev[--e],t.prev[e]=f<=i?i-f:0,--r;);n+=f}if(0===t.strm.avail_in)break;if(a=t.strm,o=t.window,h=t.strstart+t.lookahead,u=n,l=void 0,l=a.avail_in,u<l&&(l=u),r=0===l?0:(a.avail_in-=l,d.arraySet(o,a.input,a.next_in,l,h),1===a.state.wrap?a.adler=c(a.adler,o,l,h):2===a.state.wrap&&(a.adler=p(a.adler,o,l,h)),a.next_in+=l,a.total_in+=l,l),t.lookahead+=r,t.lookahead+t.insert>=x)for(s=t.strstart-t.insert,t.ins_h=t.window[s],t.ins_h=(t.ins_h<<t.hash_shift^t.window[s+1])&t.hash_mask;t.insert&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[s+x-1])&t.hash_mask,t.prev[s&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=s,s++,t.insert--,!(t.lookahead+t.insert<x)););}while(t.lookahead<z&&0!==t.strm.avail_in)}function Z(t,e){for(var r,i;;){if(t.lookahead<z){if(j(t),t.lookahead<z&&e===l)return A;if(0===t.lookahead)break}if(r=0,t.lookahead>=x&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==r&&t.strstart-r<=t.w_size-z&&(t.match_length=L(t,r)),t.match_length>=x)if(i=u._tr_tally(t,t.strstart-t.match_start,t.match_length-x),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=x){for(t.match_length--;t.strstart++,t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart,0!=--t.match_length;);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+1])&t.hash_mask;else i=u._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=t.strstart<x-1?t.strstart:x-1,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}function W(t,e){for(var r,i,n;;){if(t.lookahead<z){if(j(t),t.lookahead<z&&e===l)return A;if(0===t.lookahead)break}if(r=0,t.lookahead>=x&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=x-1,0!==r&&t.prev_length<t.max_lazy_match&&t.strstart-r<=t.w_size-z&&(t.match_length=L(t,r),t.match_length<=5&&(1===t.strategy||t.match_length===x&&4096<t.strstart-t.match_start)&&(t.match_length=x-1)),t.prev_length>=x&&t.match_length<=t.prev_length){for(n=t.strstart+t.lookahead-x,i=u._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-x),t.lookahead-=t.prev_length-1,t.prev_length-=2;++t.strstart<=n&&(t.ins_h=(t.ins_h<<t.hash_shift^t.window[t.strstart+x-1])&t.hash_mask,r=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!=--t.prev_length;);if(t.match_available=0,t.match_length=x-1,t.strstart++,i&&(N(t,!1),0===t.strm.avail_out))return A}else if(t.match_available){if((i=u._tr_tally(t,0,t.window[t.strstart-1]))&&N(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return A}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=u._tr_tally(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<x-1?t.strstart:x-1,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}function M(t,e,r,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=r,this.max_chain=i,this.func=n}function H(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=v,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new d.Buf16(2*w),this.dyn_dtree=new d.Buf16(2*(2*a+1)),this.bl_tree=new d.Buf16(2*(2*o+1)),D(this.dyn_ltree),D(this.dyn_dtree),D(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new d.Buf16(k+1),this.heap=new d.Buf16(2*s+1),D(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new d.Buf16(2*s+1),D(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function G(t){var e;return t&&t.state?(t.total_in=t.total_out=0,t.data_type=n,(e=t.state).pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=e.wrap?C:E,t.adler=2===e.wrap?0:1,e.last_flush=l,u._tr_init(e),m):R(t,_)}function K(t){var e=G(t);return e===m&&function(t){t.window_size=2*t.w_size,D(t.head),t.max_lazy_match=h[t.level].max_lazy,t.good_match=h[t.level].good_length,t.nice_match=h[t.level].nice_length,t.max_chain_length=h[t.level].max_chain,t.strstart=0,t.block_start=0,t.lookahead=0,t.insert=0,t.match_length=t.prev_length=x-1,t.match_available=0,t.ins_h=0}(t.state),e}function Y(t,e,r,i,n,s){if(!t)return _;var a=1;if(e===g&&(e=6),i<0?(a=0,i=-i):15<i&&(a=2,i-=16),n<1||y<n||r!==v||i<8||15<i||e<0||9<e||s<0||b<s)return R(t,_);8===i&&(i=9);var o=new H;return(t.state=o).strm=t,o.wrap=a,o.gzhead=null,o.w_bits=i,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=n+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+x-1)/x),o.window=new d.Buf8(2*o.w_size),o.head=new d.Buf16(o.hash_size),o.prev=new d.Buf16(o.w_size),o.lit_bufsize=1<<n+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new d.Buf8(o.pending_buf_size),o.d_buf=1*o.lit_bufsize,o.l_buf=3*o.lit_bufsize,o.level=e,o.strategy=s,o.method=r,K(t)}h=[new M(0,0,0,0,function(t,e){var r=65535;for(r>t.pending_buf_size-5&&(r=t.pending_buf_size-5);;){if(t.lookahead<=1){if(j(t),0===t.lookahead&&e===l)return A;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+r;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,N(t,!1),0===t.strm.avail_out))return A;if(t.strstart-t.block_start>=t.w_size-z&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):(t.strstart>t.block_start&&(N(t,!1),t.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(t,e){return Y(t,e,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(t,e){return t&&t.state?2!==t.state.wrap?_:(t.state.gzhead=e,m):_},r.deflate=function(t,e){var r,i,n,s;if(!t||!t.state||5<e||e<0)return t?R(t,_):_;if(i=t.state,!t.output||!t.input&&0!==t.avail_in||666===i.status&&e!==f)return R(t,0===t.avail_out?-5:_);if(i.strm=t,r=i.last_flush,i.last_flush=e,i.status===C)if(2===i.wrap)t.adler=0,U(i,31),U(i,139),U(i,8),i.gzhead?(U(i,(i.gzhead.text?1:0)+(i.gzhead.hcrc?2:0)+(i.gzhead.extra?4:0)+(i.gzhead.name?8:0)+(i.gzhead.comment?16:0)),U(i,255&i.gzhead.time),U(i,i.gzhead.time>>8&255),U(i,i.gzhead.time>>16&255),U(i,i.gzhead.time>>24&255),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&(U(i,255&i.gzhead.extra.length),U(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(t.adler=p(t.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=69):(U(i,0),U(i,0),U(i,0),U(i,0),U(i,0),U(i,9===i.level?2:2<=i.strategy||i.level<2?4:0),U(i,3),i.status=E);else{var a=v+(i.w_bits-8<<4)<<8;a|=(2<=i.strategy||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(a|=32),a+=31-a%31,i.status=E,P(i,a),0!==i.strstart&&(P(i,t.adler>>>16),P(i,65535&t.adler)),t.adler=1}if(69===i.status)if(i.gzhead.extra){for(n=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending!==i.pending_buf_size));)U(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=73)}else i.status=73;if(73===i.status)if(i.gzhead.name){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending===i.pending_buf_size)){s=1;break}s=i.gzindex<i.gzhead.name.length?255&i.gzhead.name.charCodeAt(i.gzindex++):0,U(i,s)}while(0!==s);i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),0===s&&(i.gzindex=0,i.status=91)}else i.status=91;if(91===i.status)if(i.gzhead.comment){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),F(t),n=i.pending,i.pending===i.pending_buf_size)){s=1;break}s=i.gzindex<i.gzhead.comment.length?255&i.gzhead.comment.charCodeAt(i.gzindex++):0,U(i,s)}while(0!==s);i.gzhead.hcrc&&i.pending>n&&(t.adler=p(t.adler,i.pending_buf,i.pending-n,n)),0===s&&(i.status=103)}else i.status=103;if(103===i.status&&(i.gzhead.hcrc?(i.pending+2>i.pending_buf_size&&F(t),i.pending+2<=i.pending_buf_size&&(U(i,255&t.adler),U(i,t.adler>>8&255),t.adler=0,i.status=E)):i.status=E),0!==i.pending){if(F(t),0===t.avail_out)return i.last_flush=-1,m}else if(0===t.avail_in&&T(e)<=T(r)&&e!==f)return R(t,-5);if(666===i.status&&0!==t.avail_in)return R(t,-5);if(0!==t.avail_in||0!==i.lookahead||e!==l&&666!==i.status){var o=2===i.strategy?function(t,e){for(var r;;){if(0===t.lookahead&&(j(t),0===t.lookahead)){if(e===l)return A;break}if(t.match_length=0,r=u._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,r&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}(i,e):3===i.strategy?function(t,e){for(var r,i,n,s,a=t.window;;){if(t.lookahead<=S){if(j(t),t.lookahead<=S&&e===l)return A;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=x&&0<t.strstart&&(i=a[n=t.strstart-1])===a[++n]&&i===a[++n]&&i===a[++n]){s=t.strstart+S;do{}while(i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&i===a[++n]&&n<s);t.match_length=S-(s-n),t.match_length>t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=x?(r=u._tr_tally(t,1,t.match_length-x),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(r=u._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),r&&(N(t,!1),0===t.strm.avail_out))return A}return t.insert=0,e===f?(N(t,!0),0===t.strm.avail_out?O:B):t.last_lit&&(N(t,!1),0===t.strm.avail_out)?A:I}(i,e):h[i.level].func(i,e);if(o!==O&&o!==B||(i.status=666),o===A||o===O)return 0===t.avail_out&&(i.last_flush=-1),m;if(o===I&&(1===e?u._tr_align(i):5!==e&&(u._tr_stored_block(i,0,0,!1),3===e&&(D(i.head),0===i.lookahead&&(i.strstart=0,i.block_start=0,i.insert=0))),F(t),0===t.avail_out))return i.last_flush=-1,m}return e!==f?m:i.wrap<=0?1:(2===i.wrap?(U(i,255&t.adler),U(i,t.adler>>8&255),U(i,t.adler>>16&255),U(i,t.adler>>24&255),U(i,255&t.total_in),U(i,t.total_in>>8&255),U(i,t.total_in>>16&255),U(i,t.total_in>>24&255)):(P(i,t.adler>>>16),P(i,65535&t.adler)),F(t),0<i.wrap&&(i.wrap=-i.wrap),0!==i.pending?m:1)},r.deflateEnd=function(t){var e;return t&&t.state?(e=t.state.status)!==C&&69!==e&&73!==e&&91!==e&&103!==e&&e!==E&&666!==e?R(t,_):(t.state=null,e===E?R(t,-3):m):_},r.deflateSetDictionary=function(t,e){var r,i,n,s,a,o,h,u,l=e.length;if(!t||!t.state)return _;if(2===(s=(r=t.state).wrap)||1===s&&r.status!==C||r.lookahead)return _;for(1===s&&(t.adler=c(t.adler,e,l,0)),r.wrap=0,l>=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new d.Buf8(r.w_size),d.arraySet(u,e,l-r.w_size,r.w_size,0),e=u,l=r.w_size),a=t.avail_in,o=t.next_in,h=t.input,t.avail_in=l,t.next_in=0,t.input=e,j(r);r.lookahead>=x;){for(i=r.strstart,n=r.lookahead-(x-1);r.ins_h=(r.ins_h<<r.hash_shift^r.window[i+x-1])&r.hash_mask,r.prev[i&r.w_mask]=r.head[r.ins_h],r.head[r.ins_h]=i,i++,--n;);r.strstart=i,r.lookahead=x-1,j(r)}return r.strstart+=r.lookahead,r.block_start=r.strstart,r.insert=r.lookahead,r.lookahead=0,r.match_length=r.prev_length=x-1,r.match_available=0,t.next_in=o,t.input=h,t.avail_in=a,r.wrap=s,m},r.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./messages":51,"./trees":52}],47:[function(t,e,r){"use strict";e.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},{}],48:[function(t,e,r){"use strict";e.exports=function(t,e){var r,i,n,s,a,o,h,u,l,f,d,c,p,m,_,g,b,v,y,w,k,x,S,z,C;r=t.state,i=t.next_in,z=t.input,n=i+(t.avail_in-5),s=t.next_out,C=t.output,a=s-(e-t.avail_out),o=s+(t.avail_out-257),h=r.dmax,u=r.wsize,l=r.whave,f=r.wnext,d=r.window,c=r.hold,p=r.bits,m=r.lencode,_=r.distcode,g=(1<<r.lenbits)-1,b=(1<<r.distbits)-1;t:do{p<15&&(c+=z[i++]<<p,p+=8,c+=z[i++]<<p,p+=8),v=m[c&g];e:for(;;){if(c>>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(c&(1<<y)-1)];continue e}if(32&y){r.mode=12;break t}t.msg="invalid literal/length code",r.mode=30;break t}w=65535&v,(y&=15)&&(p<y&&(c+=z[i++]<<p,p+=8),w+=c&(1<<y)-1,c>>>=y,p-=y),p<15&&(c+=z[i++]<<p,p+=8,c+=z[i++]<<p,p+=8),v=_[c&b];r:for(;;){if(c>>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(c&(1<<y)-1)];continue r}t.msg="invalid distance code",r.mode=30;break t}if(k=65535&v,p<(y&=15)&&(c+=z[i++]<<p,(p+=8)<y&&(c+=z[i++]<<p,p+=8)),h<(k+=c&(1<<y)-1)){t.msg="invalid distance too far back",r.mode=30;break t}if(c>>>=y,p-=y,(y=s-a)<k){if(l<(y=k-y)&&r.sane){t.msg="invalid distance too far back",r.mode=30;break t}if(S=d,(x=0)===f){if(x+=u-y,y<w){for(w-=y;C[s++]=d[x++],--y;);x=s-k,S=C}}else if(f<y){if(x+=u+f-y,(y-=f)<w){for(w-=y;C[s++]=d[x++],--y;);if(x=0,f<w){for(w-=y=f;C[s++]=d[x++],--y;);x=s-k,S=C}}}else if(x+=f-y,y<w){for(w-=y;C[s++]=d[x++],--y;);x=s-k,S=C}for(;2<w;)C[s++]=S[x++],C[s++]=S[x++],C[s++]=S[x++],w-=3;w&&(C[s++]=S[x++],1<w&&(C[s++]=S[x++]))}else{for(x=s-k;C[s++]=C[x++],C[s++]=C[x++],C[s++]=C[x++],2<(w-=3););w&&(C[s++]=C[x++],1<w&&(C[s++]=C[x++]))}break}}break}}while(i<n&&s<o);i-=w=p>>3,c&=(1<<(p-=w<<3))-1,t.next_in=i,t.next_out=s,t.avail_in=i<n?n-i+5:5-(i-n),t.avail_out=s<o?o-s+257:257-(s-o),r.hold=c,r.bits=p}},{}],49:[function(t,e,r){"use strict";var I=t("../utils/common"),O=t("./adler32"),B=t("./crc32"),R=t("./inffast"),T=t("./inftrees"),D=1,F=2,N=0,U=-2,P=1,i=852,n=592;function L(t){return(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=P,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new I.Buf32(i),e.distcode=e.distdyn=new I.Buf32(n),e.sane=1,e.back=-1,N):U}function o(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,a(t)):U}function h(t,e){var r,i;return t&&t.state?(i=t.state,e<0?(r=0,e=-e):(r=1+(e>>4),e<48&&(e&=15)),e&&(e<8||15<e)?U:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=r,i.wbits=e,o(t))):U}function u(t,e){var r,i;return t?(i=new s,(t.state=i).window=null,(r=h(t,e))!==N&&(t.state=null),r):U}var l,f,d=!0;function j(t){if(d){var e;for(l=new I.Buf32(512),f=new I.Buf32(32),e=0;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(T(D,t.lens,0,288,l,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;T(F,t.lens,0,32,f,0,t.work,{bits:5}),d=!1}t.lencode=l,t.lenbits=9,t.distcode=f,t.distbits=5}function Z(t,e,r,i){var n,s=t.state;return null===s.window&&(s.wsize=1<<s.wbits,s.wnext=0,s.whave=0,s.window=new I.Buf8(s.wsize)),i>=s.wsize?(I.arraySet(s.window,e,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(i<(n=s.wsize-s.wnext)&&(n=i),I.arraySet(s.window,e,r-i,n,s.wnext),(i-=n)?(I.arraySet(s.window,e,r-i,i,0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whave<s.wsize&&(s.whave+=n))),0}r.inflateReset=o,r.inflateReset2=h,r.inflateResetKeep=a,r.inflateInit=function(t){return u(t,15)},r.inflateInit2=u,r.inflate=function(t,e){var r,i,n,s,a,o,h,u,l,f,d,c,p,m,_,g,b,v,y,w,k,x,S,z,C=0,E=new I.Buf8(4),A=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!t||!t.state||!t.output||!t.input&&0!==t.avail_in)return U;12===(r=t.state).mode&&(r.mode=13),a=t.next_out,n=t.output,h=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,u=r.hold,l=r.bits,f=o,d=h,x=N;t:for(;;)switch(r.mode){case P:if(0===r.wrap){r.mode=13;break}for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(2&r.wrap&&35615===u){E[r.check=0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){t.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){t.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){t.msg="invalid window size",r.mode=30;break}r.dmax=1<<k,t.adler=r.check=1,r.mode=512&u?10:12,l=u=0;break;case 2:for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(r.flags=u,8!=(255&r.flags)){t.msg="unknown compression method",r.mode=30;break}if(57344&r.flags){t.msg="unknown header flags set",r.mode=30;break}r.head&&(r.head.text=u>>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.head&&(r.head.time=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.head&&(r.head.xflags=255&u,r.head.os=u>>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.length=u,r.head&&(r.head.extra_len=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(c=r.length)&&(c=o),c&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,i,s,c,k)),512&r.flags&&(r.check=B(r.check,i,c,s)),o-=c,s+=c,r.length-=c),r.length))break t;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break t;for(c=0;k=i[s+c++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&c<o;);if(512&r.flags&&(r.check=B(r.check,i,c,s)),o-=c,s+=c,k)break t}else r.head&&(r.head.name=null);r.length=0,r.mode=8;case 8:if(4096&r.flags){if(0===o)break t;for(c=0;k=i[s+c++],r.head&&k&&r.length<65536&&(r.head.comment+=String.fromCharCode(k)),k&&c<o;);if(512&r.flags&&(r.check=B(r.check,i,c,s)),o-=c,s+=c,k)break t}else r.head&&(r.head.comment=null);r.mode=9;case 9:if(512&r.flags){for(;l<16;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(u!==(65535&r.check)){t.msg="header crc mismatch",r.mode=30;break}l=u=0}r.head&&(r.head.hcrc=r.flags>>9&1,r.head.done=!0),t.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}t.adler=r.check=L(u),l=u=0,r.mode=11;case 11:if(0===r.havedict)return t.next_out=a,t.avail_out=h,t.next_in=s,t.avail_in=o,r.hold=u,r.bits=l,2;t.adler=r.check=1,r.mode=12;case 12:if(5===e||6===e)break t;case 13:if(r.last){u>>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}switch(r.last=1&u,l-=1,3&(u>>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==e)break;u>>>=2,l-=2;break t;case 2:r.mode=17;break;case 3:t.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if((65535&u)!=(u>>>16^65535)){t.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===e)break t;case 15:r.mode=16;case 16:if(c=r.length){if(o<c&&(c=o),h<c&&(c=h),0===c)break t;I.arraySet(n,i,s,c,a),o-=c,s+=c,h-=c,a+=c,r.length-=c;break}r.mode=12;break;case 17:for(;l<14;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(r.nlen=257+(31&u),u>>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286<r.nlen||30<r.ndist){t.msg="too many length or distance symbols",r.mode=30;break}r.have=0,r.mode=18;case 18:for(;r.have<r.ncode;){for(;l<3;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.lens[A[r.have++]]=7&u,u>>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){t.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have<r.nlen+r.ndist;){for(;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(b<16)u>>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(u>>>=_,l-=_,0===r.have){t.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],c=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}l-=_,k=0,c=3+(7&(u>>>=_)),u>>>=3,l-=3}else{for(z=_+7;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}l-=_,k=0,c=11+(127&(u>>>=_)),u>>>=7,l-=7}if(r.have+c>r.nlen+r.ndist){t.msg="invalid bit length repeat",r.mode=30;break}for(;c--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){t.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){t.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){t.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===e)break t;case 20:r.mode=21;case 21:if(6<=o&&258<=h){t.next_out=a,t.avail_out=h,t.next_in=s,t.avail_in=o,r.hold=u,r.bits=l,R(t,d),a=t.next_out,n=t.output,h=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(g&&0==(240&g)){for(v=_,y=g,w=b;g=(C=r.lencode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){t.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.length+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<<r.distbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(0==(240&g)){for(v=_,y=g,w=b;g=(C=r.distcode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){t.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l<z;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}r.offset+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){t.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break t;if(c=d-h,r.offset>c){if((c=r.offset-c)>r.whave&&r.sane){t.msg="invalid distance too far back",r.mode=30;break}p=c>r.wnext?(c-=r.wnext,r.wsize-c):r.wnext-c,c>r.length&&(c=r.length),m=r.window}else m=n,p=a-r.offset,c=r.length;for(h<c&&(c=h),h-=c,r.length-=c;n[a++]=m[p++],--c;);0===r.length&&(r.mode=21);break;case 26:if(0===h)break t;n[a++]=r.length,h--,r.mode=21;break;case 27:if(r.wrap){for(;l<32;){if(0===o)break t;o--,u|=i[s++]<<l,l+=8}if(d-=h,t.total_out+=d,r.total+=d,d&&(t.adler=r.check=r.flags?B(r.check,n,d,a-d):O(r.check,n,d,a-d)),d=h,(r.flags?u:L(u))!==r.check){t.msg="incorrect data check",r.mode=30;break}l=u=0}r.mode=28;case 28:if(r.wrap&&r.flags){for(;l<32;){if(0===o)break t;o--,u+=i[s++]<<l,l+=8}if(u!==(4294967295&r.total)){t.msg="incorrect length check",r.mode=30;break}l=u=0}r.mode=29;case 29:x=1;break t;case 30:x=-3;break t;case 31:return-4;case 32:default:return U}return t.next_out=a,t.avail_out=h,t.next_in=s,t.avail_in=o,r.hold=u,r.bits=l,(r.wsize||d!==t.avail_out&&r.mode<30&&(r.mode<27||4!==e))&&Z(t,t.output,t.next_out,d-t.avail_out)?(r.mode=31,-4):(f-=t.avail_in,d-=t.avail_out,t.total_in+=f,t.total_out+=d,r.total+=d,r.wrap&&d&&(t.adler=r.check=r.flags?B(r.check,n,d,t.next_out-d):O(r.check,n,d,t.next_out-d)),t.data_type=r.bits+(r.last?64:0)+(12===r.mode?128:0)+(20===r.mode||15===r.mode?256:0),(0==f&&0===d||4===e)&&x===N&&(x=-5),x)},r.inflateEnd=function(t){if(!t||!t.state)return U;var e=t.state;return e.window&&(e.window=null),t.state=null,N},r.inflateGetHeader=function(t,e){var r;return t&&t.state?0==(2&(r=t.state).wrap)?U:((r.head=e).done=!1,N):U},r.inflateSetDictionary=function(t,e){var r,i=e.length;return t&&t.state?0!==(r=t.state).wrap&&11!==r.mode?U:11===r.mode&&O(1,e,i,0)!==r.check?-3:Z(t,e,i,i)?(r.mode=31,-4):(r.havedict=1,N):U},r.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./inffast":48,"./inftrees":50}],50:[function(t,e,r){"use strict";var D=t("../utils/common"),F=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],N=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],U=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],P=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];e.exports=function(t,e,r,i,n,s,a,o){var h,u,l,f,d,c,p,m,_,g=o.bits,b=0,v=0,y=0,w=0,k=0,x=0,S=0,z=0,C=0,E=0,A=null,I=0,O=new D.Buf16(16),B=new D.Buf16(16),R=null,T=0;for(b=0;b<=15;b++)O[b]=0;for(v=0;v<i;v++)O[e[r+v]]++;for(k=g,w=15;1<=w&&0===O[w];w--);if(w<k&&(k=w),0===w)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(y=1;y<w&&0===O[y];y++);for(k<y&&(k=y),b=z=1;b<=15;b++)if(z<<=1,(z-=O[b])<0)return-1;if(0<z&&(0===t||1!==w))return-1;for(B[1]=0,b=1;b<15;b++)B[b+1]=B[b]+O[b];for(v=0;v<i;v++)0!==e[r+v]&&(a[B[e[r+v]]++]=v);if(c=0===t?(A=R=a,19):1===t?(A=F,I-=257,R=N,T-=257,256):(A=U,R=P,-1),b=y,d=s,S=v=E=0,l=-1,f=(C=1<<(x=k))-1,1===t&&852<C||2===t&&592<C)return 1;for(;;){for(p=b-S,_=a[v]<c?(m=0,a[v]):a[v]>c?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<<b-S,y=u=1<<x;n[d+(E>>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<<b-1;E&h;)h>>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=e[r+a[v]]}if(k<b&&(E&f)!==l){for(0===S&&(S=k),d+=y,z=1<<(x=b-S);x+S<w&&!((z-=O[x+S])<=0);)x++,z<<=1;if(C+=1<<x,1===t&&852<C||2===t&&592<C)return 1;n[l=E&f]=k<<24|x<<16|d-s|0}}return 0!==E&&(n[d+E]=b-S<<24|64<<16|0),o.bits=k,0}},{"../utils/common":41}],51:[function(t,e,r){"use strict";e.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],52:[function(t,e,r){"use strict";var n=t("../utils/common"),o=0,h=1;function i(t){for(var e=t.length;0<=--e;)t[e]=0}var s=0,a=29,u=256,l=u+1+a,f=30,d=19,_=2*l+1,g=15,c=16,p=7,m=256,b=16,v=17,y=18,w=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],k=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],x=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],z=new Array(2*(l+2));i(z);var C=new Array(2*f);i(C);var E=new Array(512);i(E);var A=new Array(256);i(A);var I=new Array(a);i(I);var O,B,R,T=new Array(f);function D(t,e,r,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=r,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}function F(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}function N(t){return t<256?E[t]:E[256+(t>>>7)]}function U(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function P(t,e,r){t.bi_valid>c-r?(t.bi_buf|=e<<t.bi_valid&65535,U(t,t.bi_buf),t.bi_buf=e>>c-t.bi_valid,t.bi_valid+=r-c):(t.bi_buf|=e<<t.bi_valid&65535,t.bi_valid+=r)}function L(t,e,r){P(t,r[2*e],r[2*e+1])}function j(t,e){for(var r=0;r|=1&t,t>>>=1,r<<=1,0<--e;);return r>>>1}function Z(t,e,r){var i,n,s=new Array(g+1),a=0;for(i=1;i<=g;i++)s[i]=a=a+r[i-1]<<1;for(n=0;n<=e;n++){var o=t[2*n+1];0!==o&&(t[2*n]=j(s[o]++,o))}}function W(t){var e;for(e=0;e<l;e++)t.dyn_ltree[2*e]=0;for(e=0;e<f;e++)t.dyn_dtree[2*e]=0;for(e=0;e<d;e++)t.bl_tree[2*e]=0;t.dyn_ltree[2*m]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function M(t){8<t.bi_valid?U(t,t.bi_buf):0<t.bi_valid&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function H(t,e,r,i){var n=2*e,s=2*r;return t[n]<t[s]||t[n]===t[s]&&i[e]<=i[r]}function G(t,e,r){for(var i=t.heap[r],n=r<<1;n<=t.heap_len&&(n<t.heap_len&&H(e,t.heap[n+1],t.heap[n],t.depth)&&n++,!H(e,i,t.heap[n],t.depth));)t.heap[r]=t.heap[n],r=n,n<<=1;t.heap[r]=i}function K(t,e,r){var i,n,s,a,o=0;if(0!==t.last_lit)for(;i=t.pending_buf[t.d_buf+2*o]<<8|t.pending_buf[t.d_buf+2*o+1],n=t.pending_buf[t.l_buf+o],o++,0===i?L(t,n,e):(L(t,(s=A[n])+u+1,e),0!==(a=w[s])&&P(t,n-=I[s],a),L(t,s=N(--i),r),0!==(a=k[s])&&P(t,i-=T[s],a)),o<t.last_lit;);L(t,m,e)}function Y(t,e){var r,i,n,s=e.dyn_tree,a=e.stat_desc.static_tree,o=e.stat_desc.has_stree,h=e.stat_desc.elems,u=-1;for(t.heap_len=0,t.heap_max=_,r=0;r<h;r++)0!==s[2*r]?(t.heap[++t.heap_len]=u=r,t.depth[r]=0):s[2*r+1]=0;for(;t.heap_len<2;)s[2*(n=t.heap[++t.heap_len]=u<2?++u:0)]=1,t.depth[n]=0,t.opt_len--,o&&(t.static_len-=a[2*n+1]);for(e.max_code=u,r=t.heap_len>>1;1<=r;r--)G(t,s,r);for(n=h;r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],G(t,s,1),i=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=i,s[2*n]=s[2*r]+s[2*i],t.depth[n]=(t.depth[r]>=t.depth[i]?t.depth[r]:t.depth[i])+1,s[2*r+1]=s[2*i+1]=n,t.heap[1]=n++,G(t,s,1),2<=t.heap_len;);t.heap[--t.heap_max]=t.heap[1],function(t,e){var r,i,n,s,a,o,h=e.dyn_tree,u=e.max_code,l=e.stat_desc.static_tree,f=e.stat_desc.has_stree,d=e.stat_desc.extra_bits,c=e.stat_desc.extra_base,p=e.stat_desc.max_length,m=0;for(s=0;s<=g;s++)t.bl_count[s]=0;for(h[2*t.heap[t.heap_max]+1]=0,r=t.heap_max+1;r<_;r++)p<(s=h[2*h[2*(i=t.heap[r])+1]+1]+1)&&(s=p,m++),h[2*i+1]=s,u<i||(t.bl_count[s]++,a=0,c<=i&&(a=d[i-c]),o=h[2*i],t.opt_len+=o*(s+a),f&&(t.static_len+=o*(l[2*i+1]+a)));if(0!==m){do{for(s=p-1;0===t.bl_count[s];)s--;t.bl_count[s]--,t.bl_count[s+1]+=2,t.bl_count[p]--,m-=2}while(0<m);for(s=p;0!==s;s--)for(i=t.bl_count[s];0!==i;)u<(n=t.heap[--r])||(h[2*n+1]!==s&&(t.opt_len+=(s-h[2*n+1])*h[2*n],h[2*n+1]=s),i--)}}(t,e),Z(s,u,t.bl_count)}function X(t,e,r){var i,n,s=-1,a=e[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),e[2*(r+1)+1]=65535,i=0;i<=r;i++)n=a,a=e[2*(i+1)+1],++o<h&&n===a||(o<u?t.bl_tree[2*n]+=o:0!==n?(n!==s&&t.bl_tree[2*n]++,t.bl_tree[2*b]++):o<=10?t.bl_tree[2*v]++:t.bl_tree[2*y]++,s=n,u=(o=0)===a?(h=138,3):n===a?(h=6,3):(h=7,4))}function V(t,e,r){var i,n,s=-1,a=e[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),i=0;i<=r;i++)if(n=a,a=e[2*(i+1)+1],!(++o<h&&n===a)){if(o<u)for(;L(t,n,t.bl_tree),0!=--o;);else 0!==n?(n!==s&&(L(t,n,t.bl_tree),o--),L(t,b,t.bl_tree),P(t,o-3,2)):o<=10?(L(t,v,t.bl_tree),P(t,o-3,3)):(L(t,y,t.bl_tree),P(t,o-11,7));s=n,u=(o=0)===a?(h=138,3):n===a?(h=6,3):(h=7,4)}}i(T);var q=!1;function J(t,e,r,i){P(t,(s<<1)+(i?1:0),3),function(t,e,r,i){M(t),i&&(U(t,r),U(t,~r)),n.arraySet(t.pending_buf,t.window,e,r,t.pending),t.pending+=r}(t,e,r,!0)}r._tr_init=function(t){q||(function(){var t,e,r,i,n,s=new Array(g+1);for(i=r=0;i<a-1;i++)for(I[i]=r,t=0;t<1<<w[i];t++)A[r++]=i;for(A[r-1]=i,i=n=0;i<16;i++)for(T[i]=n,t=0;t<1<<k[i];t++)E[n++]=i;for(n>>=7;i<f;i++)for(T[i]=n<<7,t=0;t<1<<k[i]-7;t++)E[256+n++]=i;for(e=0;e<=g;e++)s[e]=0;for(t=0;t<=143;)z[2*t+1]=8,t++,s[8]++;for(;t<=255;)z[2*t+1]=9,t++,s[9]++;for(;t<=279;)z[2*t+1]=7,t++,s[7]++;for(;t<=287;)z[2*t+1]=8,t++,s[8]++;for(Z(z,l+1,s),t=0;t<f;t++)C[2*t+1]=5,C[2*t]=j(t,5);O=new D(z,w,u+1,l,g),B=new D(C,k,0,f,g),R=new D(new Array(0),x,0,d,p)}(),q=!0),t.l_desc=new F(t.dyn_ltree,O),t.d_desc=new F(t.dyn_dtree,B),t.bl_desc=new F(t.bl_tree,R),t.bi_buf=0,t.bi_valid=0,W(t)},r._tr_stored_block=J,r._tr_flush_block=function(t,e,r,i){var n,s,a=0;0<t.level?(2===t.strm.data_type&&(t.strm.data_type=function(t){var e,r=4093624447;for(e=0;e<=31;e++,r>>>=1)if(1&r&&0!==t.dyn_ltree[2*e])return o;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return h;for(e=32;e<u;e++)if(0!==t.dyn_ltree[2*e])return h;return o}(t)),Y(t,t.l_desc),Y(t,t.d_desc),a=function(t){var e;for(X(t,t.dyn_ltree,t.l_desc.max_code),X(t,t.dyn_dtree,t.d_desc.max_code),Y(t,t.bl_desc),e=d-1;3<=e&&0===t.bl_tree[2*S[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),n=t.opt_len+3+7>>>3,(s=t.static_len+3+7>>>3)<=n&&(n=s)):n=s=r+5,r+4<=n&&-1!==e?J(t,e,r,i):4===t.strategy||s===n?(P(t,2+(i?1:0),3),K(t,z,C)):(P(t,4+(i?1:0),3),function(t,e,r,i){var n;for(P(t,e-257,5),P(t,r-1,5),P(t,i-4,4),n=0;n<i;n++)P(t,t.bl_tree[2*S[n]+1],3);V(t,t.dyn_ltree,e-1),V(t,t.dyn_dtree,r-1)}(t,t.l_desc.max_code+1,t.d_desc.max_code+1,a+1),K(t,t.dyn_ltree,t.dyn_dtree)),W(t),i&&M(t)},r._tr_tally=function(t,e,r){return t.pending_buf[t.d_buf+2*t.last_lit]=e>>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&r,t.last_lit++,0===e?t.dyn_ltree[2*r]++:(t.matches++,e--,t.dyn_ltree[2*(A[r]+u+1)]++,t.dyn_dtree[2*N(e)]++),t.last_lit===t.lit_bufsize-1},r._tr_align=function(t){P(t,2,3),L(t,m,z),function(t){16===t.bi_valid?(U(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):8<=t.bi_valid&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}(t)}},{"../utils/common":41}],53:[function(t,e,r){"use strict";e.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(t,e,r){"use strict";e.exports="function"==typeof setImmediate?setImmediate:function(){var t=[].slice.apply(arguments);t.splice(1,0,0),setTimeout.apply(null,t)}},{}]},{},[10])(10)});
added webassembly/main.js +590 −0
1 +
2 +
// DOM elements
3 +
const statusElement = document.getElementById('status');
4 +
const output = document.getElementById('output');
5 +
const canvas = document.getElementById('canvas');
6 +
const code = document.getElementById('code');
7 +
const progressElement = document.getElementById('progress');
8 +
const spinnerElement = document.getElementById('spinner');
9 +
const volumeElementFullScreen = document.getElementById('fullscreen_volume_icon');
10 +
const volumeElement = document.getElementById('volume_icon');
11 +
12 +
13 +
// Audio Context Setup
14 +
var audioContext;
15 +
16 +
window.addEventListener('load', init, false);
17 +
function init() {
18 +
19 +
    try {
20 +
        window.AudioContext = window.AudioContext || window.webkitAudioContext;
21 +
        audioContext = new AudioContext();
22 +
    }
23 +
    catch (e) {
24 +
        console.log("AudioContext not supported on this Browser.")
25 +
    }
26 +
}
27 +
28 +
//detecting keyboard layout...
29 +
30 +
//define valid layouts (this can be gotten by running the emulator with -keymap)
31 +
const layouts = [
32 +
    'en-us',
33 +
    'en-us-int',
34 +
    'en-gb',
35 +
    'sv',
36 +
    'de',
37 +
    'da',
38 +
    'it',
39 +
    'pl',
40 +
    'nb',
41 +
    'hu',
42 +
    'es',
43 +
    'fi',
44 +
    'pt-br',
45 +
    'cz',
46 +
    'jp',
47 +
    'fr',
48 +
    'de-ch',
49 +
    'en-us-dvo',
50 +
    'et',
51 +
    'fr-be',
52 +
    'fr-ca',
53 +
    'is',
54 +
    'pt',
55 +
    'hr',
56 +
    'sk',
57 +
    'sl',
58 +
    'lv',
59 +
    'lt'
60 +
];
61 +
62 +
lang = getFirstBrowserLanguage().toLowerCase().trim();
63 +
64 +
if (layouts.includes(lang)) {
65 +
    logOutput('Using keyboard map: ' + lang);
66 +
} else {
67 +
    logOutput('Language (' + lang + ') not found in keymaps so using keyboard map: en-us');
68 +
    lang = 'en-us';
69 +
}
70 +
71 +
var url = new URL(window.location.href);
72 +
var manifest_link = url.searchParams.get("manifest");
73 +
var ram_val = url.searchParams.get("ram");
74 +
var cpu_val = url.searchParams.get("cpu");
75 +
var mhz_val = url.searchParams.get("mhz");
76 +
var lang_val = url.searchParams.get("keymap");
77 +
var diag = url.searchParams.get("longpwron");
78 +
var wide = url.searchParams.get("widescreen");
79 +
var capture = url.searchParams.get("capture");
80 +
var midline = url.searchParams.get("midlineeffects");
81 +
82 +
var emuArguments = ['-keymap', lang, '-rtc'];
83 +
84 +
if (ram_val) {
85 +
    emuArguments.push('-ram', ram_val);
86 +
}
87 +
if (cpu_val) {
88 +
    if (cpu_val == 'c816') {
89 +
        emuArguments.push('-c816');
90 +
    } else if (cpu_val == 'c02') {
91 +
	emuArguments.push('-c02');
92 +
    }
93 +
}
94 +
if (mhz_val) {
95 +
    emuArguments.push('-mhz', mhz_val);
96 +
}
97 +
if (lang_val) {
98 +
    emuArguments.push('-keymap', lang_val);
99 +
}
100 +
if (diag != null) {
101 +
    emuArguments.push('-longpwron');
102 +
}
103 +
if (wide != null) {
104 +
    emuArguments.push('-widescreen');
105 +
}
106 +
if (capture != null) {
107 +
    emuArguments.push('-capture');
108 +
}
109 +
if (midline != null) {
110 +
    emuArguments.push('-midline-effects');
111 +
}
112 +
113 +
if (manifest_link) {
114 +
    openFs();
115 +
}
116 +
117 +
var Module = {
118 +
119 +
    preRun: [
120 +
        function () { //Set the keyboard handling element (it's document by default). Keystrokes are stopped from propagating by emscripten, maybe there's an option to disable this?
121 +
            ENV.SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = "#canvas";
122 +
        },
123 +
        function () {
124 +
125 +
            if (manifest_link) {
126 +
                loadManifestLink();
127 +
            }
128 +
        }
129 +
    ],
130 +
    postRun: [
131 +
        function () {
132 +
            canvas.focus();
133 +
        }
134 +
    ],
135 +
    arguments: emuArguments,
136 +
    print: (function () {
137 +
138 +
        if (output) output.value = ''; // clear browser cache
139 +
        return function (text) {
140 +
            if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
141 +
            logOutput(text);
142 +
        };
143 +
    })(),
144 +
    printErr: function (text) {
145 +
        if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
146 +
147 +
        // filtering out some known issues for easier reporting from people who have startup problems
148 +
        if (text.startsWith('wasm streaming compile failed:') ||
149 +
            text.startsWith('falling back to ArrayBuffer instantiation') ||
150 +
            text.startsWith('Calling stub instead of sigaction')) {
151 +
            logOutput("[known behavior] " + text);
152 +
            return;
153 +
        }
154 +
155 +
        logError(text);
156 +
157 +
158 +
    },
159 +
    canvas: (function () {
160 +
161 +
        // As a default initial behavior, pop up an alert when webgl context is lost. To make your
162 +
        // application robust, you may want to override this behavior before shipping!
163 +
        // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
164 +
        canvas.addEventListener("webglcontextlost", function (e) {
165 +
            alert('WebGL context lost. You will need to reload the page.');
166 +
            e.preventDefault();
167 +
        }, false);
168 +
        return canvas;
169 +
    })(),
170 +
    setStatus: function (text) {
171 +
        if (!Module.setStatus.last) Module.setStatus.last = {
172 +
            time: Date.now(),
173 +
            text: ''
174 +
        };
175 +
        if (text === Module.setStatus.last.text) return;
176 +
        const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
177 +
        let now = Date.now();
178 +
        if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
179 +
        Module.setStatus.last.time = now;
180 +
        Module.setStatus.last.text = text;
181 +
        if (m) {
182 +
            text = m[1];
183 +
            progressElement.value = parseInt(m[2]) * 100;
184 +
            progressElement.max = parseInt(m[4]) * 100;
185 +
            progressElement.hidden = false;
186 +
            spinnerElement.hidden = false;
187 +
        } else {
188 +
            progressElement.value = null;
189 +
            progressElement.max = null;
190 +
            progressElement.hidden = true;
191 +
            if (!text) spinnerElement.hidden = true;
192 +
        }
193 +
        statusElement.innerHTML = text;
194 +
        logOutput(text);
195 +
    },
196 +
    totalDependencies: 0,
197 +
    monitorRunDependencies: function (left) {
198 +
        this.totalDependencies = Math.max(this.totalDependencies, left);
199 +
        Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies - left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
200 +
    }
201 +
};
202 +
203 +
Module.setStatus('Downloading file...');
204 +
logOutput('Downloading file...');
205 +
206 +
window.onerror = function () {
207 +
    // Module.setStatus('Exception thrown, see JavaScript console');
208 +
    spinnerElement.style.display = 'none';
209 +
    Module.setStatus = function (text) {
210 +
        if (text) Module.printErr('[post-exception status] ' + text);
211 +
    };
212 +
};
213 +
214 +
function loadManifestLink() {
215 +
    addRunDependency('load-manifest-link');
216 +
    console.log("Loading URL:", manifest_link);
217 +
    fetch(manifest_link)
218 +
        .then(function (response) {
219 +
            var disposition = response.headers.get('Content-Disposition');
220 +
            if (disposition) {
221 +
                var filename = parseDispositionFilename(disposition);
222 +
                if (filename) {
223 +
                    console.log("Loading filename:", filename);
224 +
                    if (filename.toLowerCase().endsWith('.bas')) {
225 +
                        console.log("Loading from BAS.");
226 +
                        loadBas(manifest_link, filename);
227 +
                    } else if (filename.toLowerCase().endsWith('.prg')) {
228 +
                        console.log("Loading from PRG.");
229 +
                        loadPrg(manifest_link, filename);
230 +
                    } else if (filename.toLowerCase().endsWith('.zip')) {
231 +
                        console.log("Loading from ZIP.");
232 +
                        loadZip(manifest_link);
233 +
                    }
234 +
                }
235 +
            } else {
236 +
                if (manifest_link.toLowerCase().endsWith('.bas')) {
237 +
                    var filename = manifest_link.replace(/^.*[\\\/]/, '');
238 +
                    console.log("Loading from BAS.");
239 +
                    loadBas(manifest_link, filename);
240 +
                } else if (manifest_link.toLowerCase().endsWith('.prg')) {
241 +
                    var filename = manifest_link.replace(/^.*[\\\/]/, '');
242 +
                    console.log("Loading from PRG.");
243 +
                    loadPrg(manifest_link, filename);
244 +
                } else if (manifest_link.toLowerCase().endsWith('.zip')) {
245 +
                    console.log("Loading from ZIP.");
246 +
                    loadZip(manifest_link);
247 +
                } else {
248 +
                    console.log("Loading from directory.");
249 +
                    if (!manifest_link.endsWith('/')) {
250 +
                        manifest_link = manifest_link + '/';
251 +
                    }
252 +
                    loadManifest();
253 +
                }
254 +
            }
255 +
        })
256 +
        .then(function () {
257 +
            removeRunDependency('load-manifest-link');
258 +
        });
259 +
}
260 +
261 +
function parseDispositionFilename(disposition) {
262 +
    const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i;
263 +
    const asciiFilenameRegex = /^filename=(["']?)(.*?[^\\])\1(?:; ?|$)/i;
264 +
265 +
    let fileName = null;
266 +
    if (utf8FilenameRegex.test(disposition)) {
267 +
        fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
268 +
    } else {
269 +
        // prevent ReDos attacks by anchoring the ascii regex to string start and
270 +
        // slicing off everything before 'filename='
271 +
        const filenameStart = disposition.toLowerCase().indexOf('filename=');
272 +
        if (filenameStart >= 0) {
273 +
            const partialDisposition = disposition.slice(filenameStart);
274 +
            const matches = asciiFilenameRegex.exec(partialDisposition);
275 +
            if (matches != null && matches[2]) {
276 +
                fileName = matches[2];
277 +
            }
278 +
        }
279 +
    }
280 +
    return fileName;
281 +
}
282 +
283 +
function loadBas(basFileUrl, filename) {
284 +
    console.log('Adding start BAS:', filename)
285 +
    emuArguments.push('-bas', filename, '-run');
286 +
    FS.createPreloadedFile('/', filename, basFileUrl, true, true);
287 +
    console.log("Starting Emulator...")
288 +
    console.log("Emulator arguments: ", emuArguments)
289 +
}
290 +
291 +
function loadPrg(prgFileUrl, filename) {
292 +
    console.log('Adding start PRG:', filename)
293 +
    emuArguments.push('-prg', filename, '-run');
294 +
    FS.createPreloadedFile('/', filename, prgFileUrl, true, true);
295 +
    console.log("Starting Emulator...")
296 +
    console.log("Emulator arguments: ", emuArguments)
297 +
}
298 +
299 +
function loadZip(zipFileUrl) {
300 +
    addRunDependency('load-zip');
301 +
    fetch(zipFileUrl)
302 +
        .then(function (response) {
303 +
            if (response.status === 200 || response.status === 0) {
304 +
                return Promise.resolve(response.blob());
305 +
            } else {
306 +
                return Promise.reject(new Error(response.statusText));
307 +
                // todo error handling here, display to user
308 +
            }
309 +
        })
310 +
        .then(JSZip.loadAsync)
311 +
        .then(extractManifestFromBuffer)
312 +
        .then(function () {
313 +
            console.log("Starting Emulator...")
314 +
            console.log("Emulator arguments: ", emuArguments)
315 +
            removeRunDependency('load-zip');
316 +
        });
317 +
}
318 +
319 +
function extractManifestFromBuffer(zip) {
320 +
    if (zip.file("manifest.json") == null) {
321 +
        console.log("Unable to find manifest.json file. Writing all files from zip.");
322 +
        const promises = [];
323 +
        writeAllFilesFromZip(zip, promises, []);
324 +
        return Promise.all(promises).then((value) => {
325 +
            console.log("Emulator filesystem loading complete.")
326 +
        });
327 +
    }
328 +
    else {
329 +
        return zip.file("manifest.json").async("uint8array")
330 +
            .then(function (content) {
331 +
                let manifestString = new TextDecoder("utf-8").decode(content);
332 +
                let manifestObject = JSON.parse(manifestString);
333 +
                console.log("Parsed manifest from zip:")
334 +
                console.log(manifestObject);
335 +
336 +
                if (manifestObject.ram) {
337 +
                    console.log('Found RAM amount: '+manifestObject.ram);
338 +
                    emuArguments.push('-ram', manifestObject.ram);
339 +
                }
340 +
341 +
                let gen1 = true;
342 +
343 +
                if (manifestObject.model) {
344 +
                    console.log('Found model: '+manifestObject.model);
345 +
                    if (manifestObject.model === 'gs') {
346 +
                        gen1 = false;
347 +
                        emuArguments.push('-gs');
348 +
                    }
349 +
                }
350 +
351 +
                if (gen1 === true && manifestObject.cpu) {
352 +
                    console.log('Found CPU type: '+manifestObject.cpu);
353 +
                    if (manifestObject.cpu == 'c816') {
354 +
                        emuArguments.push('-c816');
355 +
                    } else if (manifestObject.cpu == 'c02') {
356 +
                        emuArguments.push('-c02');
357 +
                    }
358 +
                }
359 +
                if (manifestObject.mhz) {
360 +
                    console.log('Found mhz variable: '+manifestObject.mhz);
361 +
                    emuArguments.push('-mhz', manifestObject.mhz);
362 +
                }
363 +
                if (manifestObject.keymap) {
364 +
                    console.log('Found keymap variable: '+manifestObject.keymap);
365 +
                    emuArguments.push('-keymap', manifestObject.keymap);
366 +
                }
367 +
                if (manifestObject.longpwron) {
368 +
                    console.log('Found longpwron variable');
369 +
                    emuArguments.push('-longpwron');
370 +
                }
371 +
                if (manifestObject.widescreen) {
372 +
                    console.log('Found widescreen variable');
373 +
                    emuArguments.push('-widescreen');
374 +
                }
375 +
                if (manifestObject.capture) {
376 +
                    console.log('Found capture variable');
377 +
                    emuArguments.push('-capture');
378 +
                }
379 +
                if (manifestObject.midlineeffects) {
380 +
                    console.log('Found midlineeffects variable');
381 +
                    emuArguments.push('-midline-effects');
382 +
                }
383 +
384 +
                const promises = [];
385 +
                if (manifestObject.resources) {
386 +
                    console.log('Found resources section in manifest.');
387 +
                    var startFiles = [];
388 +
                    manifestObject.resources.forEach(function (element) {
389 +
                        let fileName = element.replace(/^.*[\\\/]/, '');
390 +
                        if (fileName.toLowerCase().endsWith(".bas") || fileName.toLowerCase().endsWith(".prg")) {
391 +
                            startFiles.push(fileName);
392 +
                        }
393 +
                        if (zip.file(fileName) == null) {
394 +
                            logError("Unable to find resources entry: " + fileName);
395 +
                            logError("This is likely an error, check resources section in manifest.")
396 +
                        } else {
397 +
                            promises.push(zip.file(fileName).async("uint8array").then(function (content) {
398 +
                                console.log('Writing to emulator filesystem:', fileName);
399 +
                                try {
400 +
                                    FS.writeFile(fileName, content);
401 +
                                }
402 +
                                catch(e) {
403 +
                                    console.log('Error writing to emulator filesystem:', file.name);
404 +
                                }
405 +
                            }));
406 +
                        }
407 +
                        addStartFile(manifestObject, startFiles);
408 +
                    });
409 +
                } else {
410 +
                    console.log('Resources section not found in manifest. Writing all files from zip.');
411 +
                    writeAllFilesFromZip(zip, promises, manifestObject);
412 +
                }
413 +
                return Promise.all(promises);
414 +
            })
415 +
            .then((value) => {
416 +
                console.log("Emulator filesystem loading complete.")
417 +
            });
418 +
    }
419 +
}
420 +
421 +
function writeAllFilesFromZip(zip, promises, manifestObject) {
422 +
    var startFiles = [];
423 +
    const writeResources = (zip) => {
424 +
        zip.forEach((path, file) => {
425 +
            if(file.dir) {
426 +
                FS.mkdirTree(file.name);
427 +
                writeResources(zip.folder(path));
428 +
                return;
429 +
            } else {
430 +
                if (file.name.toLowerCase().endsWith(".bas") || file.name.toLowerCase().endsWith(".prg")) {
431 +
                    startFiles.push(file.name);
432 +
                }
433 +
            }
434 +
435 +
            promises.push(zip.file(path).async("uint8array")
436 +
                .then(function (content) {
437 +
                    console.log('Writing to emulator filesystem:', file.name);
438 +
                    try {
439 +
                        FS.writeFile(file.name, content);
440 +
                    }
441 +
                    catch(e) {
442 +
                        console.log('Error writing to emulator filesystem:', file.name);
443 +
                    }
444 +
                })
445 +
            );
446 +
        });
447 +
    };
448 +
    writeResources(zip);
449 +
    addStartFile(manifestObject, startFiles);
450 +
}
451 +
452 +
function loadManifest() {
453 +
    addRunDependency('load-manifest');
454 +
    fetch(manifest_link + 'manifest.json').then(function (response) {
455 +
        return response.json();
456 +
    }).then(function (manifest) {
457 +
        console.log("Loading from manifest:")
458 +
        console.log(manifest);
459 +
        var startFiles = [];
460 +
        manifest.resources.forEach(element => {
461 +
            element = manifest_link + element;
462 +
            let filename = element.replace(/^.*[\\\/]/, '')
463 +
            if (filename.toLowerCase().endsWith(".bas") || filename.toLowerCase().endsWith(".prg")) {
464 +
                startFiles.push(filename);
465 +
            }
466 +
            FS.createPreloadedFile('/', filename, element, true, true);
467 +
        });
468 +
        addStartFile(manifest, startFiles);
469 +
        console.log("Starting Emulator...")
470 +
        console.log("Emulator arguments: ", emuArguments)
471 +
        removeRunDependency('load-manifest');
472 +
    }).catch(function () {
473 +
        console.log("Unable to read manifest. Check the manifest http parameter");
474 +
    });
475 +
}
476 +
477 +
function addStartFile(manifestObject, startFiles) {
478 +
    if (manifestObject && manifestObject.start_bas && manifestObject.start_prg) {
479 +
        logError("start_bas and start_prg used in manifest");
480 +
        logError("This is likely an error, defaulting to start_bas")
481 +
    }
482 +
483 +
    if (manifestObject && manifestObject.start_bas) {
484 +
        console.log('Adding start BAS:', manifestObject.start_bas)
485 +
        emuArguments.push('-bas', manifestObject.start_bas, '-run');
486 +
    } else if (manifestObject && manifestObject.start_prg) {
487 +
        console.log('Adding start PRG:', manifestObject.start_prg)
488 +
        emuArguments.push('-prg', manifestObject.start_prg, '-run');
489 +
    } else if (startFiles) {
490 +
        if (startFiles.length === 1) {
491 +
            // If there is a single BAS or PRG file, execute it.
492 +
            var filename = startFiles[0];
493 +
            if (filename.toLowerCase().endsWith(".bas")) {
494 +
                console.log('Adding start BAS:', filename)
495 +
                emuArguments.push('-bas', filename, '-run');
496 +
            } else if (filename.toLowerCase().endsWith(".prg")) {
497 +
                console.log('Adding start PRG:', filename)
498 +
                emuArguments.push('-prg', filename, '-run');
499 +
            }
500 +
        } else {
501 +
            logOutput("Start files: " + startFiles);
502 +
            // Let the user decide which file to LOAD.
503 +
        }
504 +
    }
505 +
}
506 +
507 +
function toggleAudio() {
508 +
    if (audioContext && audioContext.state != "running") {
509 +
        audioContext.resume().then(() => {
510 +
            volumeElement.innerHTML = "volume_up";
511 +
            volumeElementFullScreen.innerHTML = "volume_up";
512 +
            console.log("Resumed Audio.")
513 +
            Module.ccall("j2c_start_audio", "void", ["bool"], [true]);
514 +
        });
515 +
    } else if (audioContext && audioContext.state == "running") {
516 +
        audioContext.suspend().then(function () {
517 +
            console.log("Stopped Audio.")
518 +
            volumeElement.innerHTML = "volume_off";
519 +
            volumeElementFullScreen.innerHTML = "volume_off";
520 +
            Module.ccall("j2c_start_audio", "void", ["bool"], [false]);
521 +
        });
522 +
    }
523 +
    canvas.focus();
524 +
}
525 +
526 +
function resetEmulator() {
527 +
    j2c_reset = Module.cwrap("j2c_reset", "void", []);
528 +
    j2c_reset();
529 +
    canvas.focus();
530 +
}
531 +
532 +
function runCode() {
533 +
    Module.ccall("j2c_paste", "void", ["string"], ['\nNEW\n' + code.value + '\nRUN\n']);
534 +
    canvas.focus();
535 +
}
536 +
537 +
function closeFs() {
538 +
    canvas.parentElement.classList.remove("fullscreen");
539 +
    canvas.focus();
540 +
}
541 +
542 +
function openFs() {
543 +
    canvas.parentElement.classList.add("fullscreen");
544 +
    canvas.focus();
545 +
}
546 +
547 +
function logOutput(text) {
548 +
    if (output) {
549 +
        output.innerHTML += text + "\n";
550 +
        output.parentElement.scrollTop = output.parentElement.scrollHeight; // focus on bottom
551 +
    }
552 +
    console.log(text);
553 +
}
554 +
555 +
function logError(text) {
556 +
    if (output) {
557 +
        output.innerHTML += "[error] " + text + "\n";
558 +
        output.parentElement.scrollTop = output.parentElement.scrollHeight; // focus on bottom
559 +
    }
560 +
    console.error(text);
561 +
}
562 +
563 +
564 +
565 +
function getFirstBrowserLanguage() {
566 +
    const nav = window.navigator,
567 +
        browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'];
568 +
    let i,
569 +
        language;
570 +
571 +
    // support for HTML 5.1 "navigator.languages"
572 +
    if (Array.isArray(nav.languages)) {
573 +
        for (i = 0; i < nav.languages.length; i++) {
574 +
            language = nav.languages[i];
575 +
            if (language && language.length) {
576 +
                return language;
577 +
            }
578 +
        }
579 +
    }
580 +
581 +
    // support for other well known properties in browsers
582 +
    for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
583 +
        language = nav[browserLanguagePropertyKeys[i]];
584 +
        if (language && language.length) {
585 +
            return language;
586 +
        }
587 +
    }
588 +
589 +
    return null;
590 +
}
added webassembly/styles.css +252 −0
1 +
body, html {
2 +
    padding:0;
3 +
    margin:0;
4 +
    height: 100%;
5 +
    overflow: hidden;
6 +
}
7 +
8 +
textarea, select, input, button, canvas { outline: none; }
9 +
10 +
body {
11 +
    background: #333;
12 +
    color: #ccc;
13 +
    font-family: 'ArialMT', 'Arial', sans-serif;
14 +
}
15 +
16 +
.container {
17 +
    display: flex;
18 +
    flex-direction: row-reverse;
19 +
    justify-content: space-between;
20 +
    flex-wrap: nowrap;
21 +
    height: 100%;
22 +
}
23 +
#editor {
24 +
    flex-grow: 100;
25 +
    padding:30px;
26 +
    padding-right:15px;
27 +
}
28 +
29 +
.editor-layout {
30 +
    width:100%;
31 +
    height: 100%;
32 +
    display: flex;
33 +
    flex-direction: column;
34 +
    justify-content: space-between;
35 +
    flex-wrap: nowrap;
36 +
}
37 +
38 +
.editor-top h1 {
39 +
    float:left;
40 +
    margin-top:0;
41 +
42 +
}
43 +
44 +
.editor-top h1 a {
45 +
    color: rgba(40, 116, 180,0.6);
46 +
    text-decoration: none;
47 +
    font-size: 14px;
48 +
}
49 +
50 +
.editor-top h1 a:hover {
51 +
    color: rgba(40, 116, 180,1);
52 +
}
53 +
.editor-top button {
54 +
    float:right;
55 +
    margin-left:15px;
56 +
    background: rgb(40, 116, 180);
57 +
    color: #fff;
58 +
    border:0;
59 +
    padding: 8px 10px;
60 +
}
61 +
62 +
.editor-middle {
63 +
    flex-grow: 100;
64 +
    display: flex;
65 +
}
66 +
67 +
68 +
#code {
69 +
    width: 100%;
70 +
    padding: 0;
71 +
    margin: 0;
72 +
    border: 0;
73 +
    background: #222;
74 +
    color: #999;
75 +
    resize: none;
76 +
    font-size: 16px;
77 +
    opacity: 0.8;
78 +
}
79 +
80 +
#code:focus {
81 +
    box-shadow: 0px 0px 5px 0px rgba(40, 116, 180,0.6);
82 +
    opacity: 1;
83 +
}
84 +
85 +
86 +
#emulator {
87 +
    padding:30px;
88 +
    padding-left:15px;
89 +
}
90 +
.emu-layout {
91 +
    width:100%;
92 +
    height: 100%;
93 +
    display: flex;
94 +
    flex-direction: column;
95 +
    justify-content: space-between;
96 +
    flex-wrap: nowrap;
97 +
}
98 +
99 +
100 +
.emu-container {
101 +
    width: 640px;
102 +
    height: 480px;
103 +
    background: #000;
104 +
    text-align: center;
105 +
}
106 +
107 +
108 +
#canvas {
109 +
    opacity: 0.8;
110 +
    min-width: 640px;
111 +
    min-height: 480px;
112 +
}
113 +
#canvas:focus {
114 +
    box-shadow: 0px 0px 5px 0px rgba(40, 116, 180,0.6);
115 +
    opacity: 1;
116 +
}
117 +
118 +
.fullscreen-audio {
119 +
    display:none;
120 +
}
121 +
122 +
.fullscreen .fullscreen-audio {
123 +
    display:block!important;
124 +
    position: absolute;
125 +
    left:10px;
126 +
    top:10px;
127 +
}
128 +
.fullscreen-audio a {
129 +
    color:#fff;
130 +
    margin-left: 10px;
131 +
}
132 +
133 +
134 +
135 +
.close-fullscreen {
136 +
    display:none;
137 +
}
138 +
.fullscreen .close-fullscreen {
139 +
    display:block!important;
140 +
    position: absolute;
141 +
    right:0;
142 +
    top:0;
143 +
}
144 +
.close-fullscreen a {
145 +
    color:#fff;
146 +
    text-decoration: none;
147 +
    font-size: 50px;
148 +
    margin-right: 10px;
149 +
}
150 +
151 +
.emu-container.fullscreen {
152 +
    position: absolute;
153 +
    top: 0;
154 +
    left: 0;
155 +
    height: 100%;
156 +
    width: 100%;
157 +
    display: flex;
158 +
    flex-direction: row;
159 +
    justify-content: center;
160 +
    align-items: center;
161 +
    background: #000000;
162 +
    z-index: 1;
163 +
}
164 +
165 +
.fullscreen #canvas {
166 +
    height: 90%;
167 +
    display: block;
168 +
    border: 0;
169 +
    opacity: 1;
170 +
}
171 +
172 +
.output-container {
173 +
    flex-grow: 100;
174 +
    margin-top: 30px;
175 +
    overflow-x: auto;
176 +
    overflow-y: auto;
177 +
    background: #000;
178 +
    color: #999;
179 +
    width: 640px;
180 +
181 +
}
182 +
#output {
183 +
184 +
185 +
    margin: 0;
186 +
    min-height: 100%;
187 +
188 +
189 +
190 +
191 +
}
192 +
193 +
#spinner {
194 +
    overflow:visible;
195 +
}
196 +
197 +
#spinner strong{
198 +
    display: block;
199 +
    margin-top: 15px;
200 +
}
201 +
202 +
.spinner {
203 +
    height: 50px;
204 +
    width: 50px;
205 +
    margin: 0px auto;
206 +
    -webkit-animation: rotation .8s linear infinite;
207 +
    -moz-animation: rotation .8s linear infinite;
208 +
    -o-animation: rotation .8s linear infinite;
209 +
    animation: rotation 0.8s linear infinite;
210 +
    border-left: 10px solid rgb(0, 150, 240);
211 +
    border-right: 10px solid rgb(0, 150, 240);
212 +
    border-bottom: 10px solid rgb(0, 150, 240);
213 +
    border-top: 10px solid rgb(100, 0, 200);
214 +
    border-radius: 100%;
215 +
    background-color: rgb(200, 100, 250);
216 +
}
217 +
218 +
@-webkit-keyframes rotation {
219 +
    from {
220 +
        -webkit-transform: rotate(0deg);
221 +
    }
222 +
    to {
223 +
        -webkit-transform: rotate(360deg);
224 +
    }
225 +
}
226 +
227 +
@-moz-keyframes rotation {
228 +
    from {
229 +
        -moz-transform: rotate(0deg);
230 +
    }
231 +
    to {
232 +
        -moz-transform: rotate(360deg);
233 +
    }
234 +
}
235 +
236 +
@-o-keyframes rotation {
237 +
    from {
238 +
        -o-transform: rotate(0deg);
239 +
    }
240 +
    to {
241 +
        -o-transform: rotate(360deg);
242 +
    }
243 +
}
244 +
245 +
@keyframes rotation {
246 +
    from {
247 +
        transform: rotate(0deg);
248 +
    }
249 +
    to {
250 +
        transform: rotate(360deg);
251 +
    }
252 +
}
added webassembly/x16emu-template.html +88 −0
1 +
<!doctype html>
2 +
<html lang="en-us">
3 +
4 +
<head>
5 +
    <meta charset="utf-8">
6 +
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
7 +
    <link rel="stylesheet" href="webassembly/styles.css">
8 +
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
9 +
    <title>Commander X16 Emulator</title>
10 +
</head>
11 +
12 +
<body>
13 +
14 +
    <div class="container">
15 +
        <section id="emulator">
16 +
17 +
            <div class="emu-layout">
18 +
19 +
                <div class="emu-container">
20 +
                    <figure id="spinner">
21 +
                        <div class="spinner"></div>
22 +
                        <strong>Loading</strong>
23 +
                    </figure>
24 +
                    <div class="emscripten" id="status">Downloading...</div>
25 +
                    <div class="emscripten">
26 +
                        <progress value="0" max="100" id="progress" hidden=1></progress>
27 +
                    </div>
28 +
                    <canvas onclick="this.focus();" tabindex="1" id="canvas" class=""
29 +
                        oncontextmenu="event.preventDefault()"></canvas>
30 +
                    <div class="close-fullscreen"><a href="javascript:closeFs();" title="Exit fullscreen">&times;</a>
31 +
                    </div>
32 +
                    <div class="fullscreen-audio"><a href="javascript:toggleAudio();" title="Toggle Audio"><i
33 +
                                id="fullscreen_volume_icon" class="material-icons"
34 +
                                style="font-size:22px;">volume_off</i></a>
35 +
                    </div>
36 +
                </div>
37 +
38 +
39 +
                <div class="output-container">
40 +
                    <pre id="output"></pre>
41 +
                </div>
42 +
            </div>
43 +
44 +
        </section>
45 +
        <section id="editor">
46 +
            <div class="editor-layout">
47 +
48 +
                <div class="editor-top">
49 +
                    <h1>Commander X16 Emulator <a href="http://commanderx16.com" target="_blank">visit
50 +
                            commanderx16.com</a> </h1>
51 +
                    <button type="button" onclick="openFs();">Fullscreen</button>
52 +
                    <button type="button" onclick="runCode();">Run</button>
53 +
                    <button type="button" onclick="resetEmulator();">Reset</button>
54 +
                    <button type="button" onclick="toggleAudio();"><i id="volume_icon" class="material-icons"
55 +
                            style="font-size:12px;">volume_off</i></button>
56 +
                </div>
57 +
                <div class="editor-middle">
58 +
59 +
                    <textarea id="code">100 REM SEE HTTPS://10PRINT.ORG/
60 +
105 REM ADAPTED BY GIOVANNI GIORGI
61 +
106 REM V 1.1
62 +
110 PRINT CHR$(147); "OLD SCHOOL MAZE DEMO"
63 +
115 FOR MC = 1 TO 3
64 +
118 PRINT "MAZE TYPE ", MC
65 +
120 FOR ROW = 1 TO 16
66 +
200 FOR I = 1 TO 32
67 +
300 IF MC <=1 THEN PRINT CHR$(205.50+RND(1));
68 +
310 IF MC =2 THEN PRINT CHR$(205.25+RND(1));
69 +
320 IF MC =3 THEN PRINT CHR$(204+(RND(1)+.5)*3);
70 +
400 NEXT I
71 +
500 PRINT
72 +
600 NEXT ROW
73 +
700 NEXT MC</textarea>
74 +
75 +
                </div>
76 +
77 +
78 +
            </div>
79 +
80 +
        </section>
81 +
    </div>
82 +
    <script src="webassembly/jszip.min.js"></script>
83 +
    <script src="webassembly/main.js"></script>
84 +
    {{{ SCRIPT }}}
85 +
86 +
</body>
87 +
88 +
</html>
added x16-emulator.code-workspace +7 −0
1 +
{
2 +
	"folders": [
3 +
		{
4 +
			"path": "."
5 +
		}
6 +
	]
7 +
}