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 | + | [](https://github.com/x16community/x16-emulator/actions/workflows/build.yml) |
|
| 6 | + | [](https://github.com/x16community/x16-emulator/releases) |
|
| 7 | + | [](./LICENSE) |
|
| 8 | + | [](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 |[`] ⇒ [←], [~] ⇒ [π], [\] ⇒ [£]| |
|
| 153 | + | |en-gb |United Kingdom |[`] ⇒ [←], [~] ⇒ [π]| |
|
| 154 | + | |de |German |[§] ⇒ [£], [´] ⇒ [^], [^] ⇒ [←], [°] ⇒ [π]| |
|
| 155 | + | |nordic|Nordic |key left of [1] ⇒ [←],[π]| |
|
| 156 | + | |it |Italian |[\] ⇒ [←], [|] ⇒ [π]| |
|
| 157 | + | |pl |Polish (Programmers) |[`] ⇒ [←], [~] ⇒ [π], [\] ⇒ [£]| |
|
| 158 | + | |hu |Hungarian |[\] ⇒ [←], [|] ⇒ [π], [§] ⇒ [£]| |
|
| 159 | + | |es |Spanish |[|] ⇒ π, \ ⇒ [←], Alt + [<] ⇒ [£]| |
|
| 160 | + | |fr |French |[²] ⇒ [←], [§] ⇒ [£]| |
|
| 161 | + | |de-ch |Swiss German |[^] ⇒ [←], [°] ⇒ [π]| |
|
| 162 | + | |fr-be |Belgian French |[²] ⇒ [←], [³] ⇒ [π]| |
|
| 163 | + | |fi |Finnish |[§] ⇒ [←], [½] ⇒ [π]| |
|
| 164 | + | |pt-br |Portuguese (Brazil ABNT)|[\] ⇒ [←], [|] ⇒ [π]| |
|
| 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 �. |
|
| 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 <[email protected]>, [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: <enter monitor> |
|
| 496 | + | F3: RUN |
|
| 497 | + | F4: <switch 40/80> |
|
| 498 | + | F5: LOAD |
|
| 499 | + | F6: SAVE" |
|
| 500 | + | F7: DOS"$ <doesn't work yet> |
|
| 501 | + | F8: DOS <doesn't work yet> |
|
| 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(®s.sp) |
|
| 112 | + | #define decsp() decrement_wrap_at_page_boundary(®s.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 ®s() { 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 ®s() { 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 ®s() 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 ®s() 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 ®s() { 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 ®s() { 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, ®s.a, sizeof(uint8_t), 1); |
|
| 311 | + | SDL_RWwrite(f, ®s.xl, sizeof(uint8_t), 1); |
|
| 312 | + | SDL_RWwrite(f, ®s.yl, sizeof(uint8_t), 1); |
|
| 313 | + | SDL_RWwrite(f, ®s.sp, sizeof(uint8_t), 1); |
|
| 314 | + | SDL_RWwrite(f, ®s.status, sizeof(uint8_t), 1); |
|
| 315 | + | SDL_RWwrite(f, ®s.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(®s.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(®s.sp); |
|
| 1623 | + | uint8_t low = debug_read6502(regs.sp, 0, USE_CURRENT_X16_BANK); |
|
| 1624 | + | increment_wrap_at_page_boundary(®s.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(®s.sp); |
|
| 240 | + | write6502(regs.sp, 0, (0xfffd - 1) & 255); |
|
| 241 | + | decrement_wrap_at_page_boundary(®s.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, ®_composer[0], sizeof(uint8_t), sizeof(reg_composer)); |
|
| 1343 | + | SDL_RWwrite(f, &palette[0], sizeof(uint8_t), sizeof(palette)); |
|
| 1344 | + | SDL_RWwrite(f, ®_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">×</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 | + | } |