Support for using Furnace as audio tracker. Icon support for JoeyLib apps. MANY bugs fixed.
This commit is contained in:
parent
71d1cb47a4
commit
d33d9c9c25
346 changed files with 25392 additions and 8104 deletions
98
LICENSES.md
98
LICENSES.md
|
|
@ -5,9 +5,10 @@ file inventories every component JoeyLib depends on but does not contain:
|
||||||
what is linked into or shipped with the programs it builds, what builds
|
what is linked into or shipped with the programs it builds, what builds
|
||||||
them, and what tests them. JoeyLib's own tree holds no third-party code or
|
them, and what tests them. JoeyLib's own tree holds no third-party code or
|
||||||
assets, apart from the patches in `installer/patches/`, which are under
|
assets, apart from the patches in `installer/patches/`, which are under
|
||||||
the licenses of the projects they patch (noted in those entries).
|
the licenses of the projects they patch (noted in those entries), and the
|
||||||
|
BeOS palette in `tools/iconBake.py` (Haiku's, MIT; noted below).
|
||||||
Components are grouped by role; each entry lists the platforms that use
|
Components are grouped by role; each entry lists the platforms that use
|
||||||
it, where it comes from, its license and where the installer puts it.
|
it, where it comes from, its license, and where the installer puts it.
|
||||||
|
|
||||||
When upstream license terms change, update the corresponding entry here.
|
When upstream license terms change, update the corresponding entry here.
|
||||||
This file summarises; the license text that comes with each component is
|
This file summarises; the license text that comes with each component is
|
||||||
|
|
@ -20,23 +21,13 @@ what counts.
|
||||||
These end up in what a JoeyLib program ships as, so their terms matter
|
These end up in what a JoeyLib program ships as, so their terms matter
|
||||||
most.
|
most.
|
||||||
|
|
||||||
### libxmp-lite 4.7.0
|
|
||||||
|
|
||||||
- **Used on:** DOS, Atari ST, X68000, SDL3, PSP, web, Win16, Win32, Haiku,
|
|
||||||
BeOS
|
|
||||||
- **URL:** <https://github.com/libxmp/libxmp>
|
|
||||||
- **License:** MIT
|
|
||||||
- **Role:** ProTracker / Amiga MOD replayer + SMIX SFX overlay. Each
|
|
||||||
port's makefile compiles it into AUDIO programs.
|
|
||||||
- **Installed at:** `toolchains/opt/libxmp/` (installer component `libxmp`)
|
|
||||||
|
|
||||||
### SDL 3.4.18
|
### SDL 3.4.18
|
||||||
|
|
||||||
- **Used on:** SDL3, PSP, web
|
- **Used on:** SDL3, PSP, web
|
||||||
- **URL:** <https://www.libsdl.org/>
|
- **URL:** <https://www.libsdl.org/>
|
||||||
- **License:** zlib
|
- **License:** zlib
|
||||||
- **Role:** window, input and audio for the hosted SDL3 code. Built as a
|
- **Role:** window, input, and audio for the hosted SDL3 code. Built as a
|
||||||
static library for the host, for the PSP with pspdev and for Emscripten
|
static library for the host, for the PSP with pspdev, and for Emscripten
|
||||||
(with JoeyLib's patches, `installer/patches/sdl3Web/`, which are under
|
(with JoeyLib's patches, `installer/patches/sdl3Web/`, which are under
|
||||||
SDL's zlib license), and linked into each program.
|
SDL's zlib license), and linked into each program.
|
||||||
- **Installed at:** `toolchains/opt/sdl3/`, `toolchains/opt/sdl3Psp/`,
|
- **Installed at:** `toolchains/opt/sdl3/`, `toolchains/opt/sdl3Psp/`,
|
||||||
|
|
@ -60,12 +51,15 @@ most.
|
||||||
|
|
||||||
### PTPlayer 6.4
|
### PTPlayer 6.4
|
||||||
|
|
||||||
- **Used on:** Amiga
|
- **Used on:** Amiga; and, translated to C, DOS, Atari ST, X68000, SDL3,
|
||||||
|
PSP, web, Win16, Win32, Haiku, BeOS
|
||||||
- **URL:** <http://aminet.net/mus/play/ptplayer.lha>
|
- **URL:** <http://aminet.net/mus/play/ptplayer.lha>
|
||||||
(author Frank Wille, <http://aminet.net/package/mus/play/ptplayer>)
|
(author Frank Wille, <http://aminet.net/package/mus/play/ptplayer>)
|
||||||
- **License:** Public Domain (the archive's `license` file: the Unlicense)
|
- **License:** Public Domain (the archive's `license` file: the Unlicense)
|
||||||
- **Role:** ProTracker MOD replayer + `mt_playfx` SFX channel for the
|
- **Role:** ProTracker MOD replayer + `mt_playfx` SFX channel for the
|
||||||
Paula audio HAL.
|
Paula audio HAL. Its replay, translated to C (with its period table), is
|
||||||
|
JoeyLib's module player, `src/core/modPlayer.c`, which every port that
|
||||||
|
mixes modules in software links.
|
||||||
- **Installed at:** `toolchains/opt/ptplayer/` (installer component `ptplayer`)
|
- **Installed at:** `toolchains/opt/ptplayer/` (installer component `ptplayer`)
|
||||||
|
|
||||||
### CWSDPMI r7
|
### CWSDPMI r7
|
||||||
|
|
@ -84,7 +78,7 @@ most.
|
||||||
|
|
||||||
### The toolchains' C libraries and start files
|
### The toolchains' C libraries and start files
|
||||||
|
|
||||||
Each toolchain below also links its own C library, start files and
|
Each toolchain below also links its own C library, start files, and
|
||||||
compiler runtime into the programs it builds. What a shipped program owes
|
compiler runtime into the programs it builds. What a shipped program owes
|
||||||
them, in brief (the entries below have the detail):
|
them, in brief (the entries below have the detail):
|
||||||
|
|
||||||
|
|
@ -92,7 +86,7 @@ them, in brief (the entries below have the detail):
|
||||||
source if its users are told how to get DJGPP (`copying.dj`). DVX
|
source if its users are told how to get DJGPP (`copying.dj`). DVX
|
||||||
programs are modules DVX loads, built with DVX's SDK.
|
programs are modules DVX loads, built with DVX's SDK.
|
||||||
- **Atari ST:** MiNTLib, file by file under its own license (COPYMINT),
|
- **Atari ST:** MiNTLib, file by file under its own license (COPYMINT),
|
||||||
the LGPL 2 or later, BSD, Henry Spencer's and musl's MIT terms; a linked
|
the LGPL 2 or later, BSD, and Henry Spencer's and musl's MIT terms; a linked
|
||||||
program meets all of them (MiNTLib's `LICENSES`).
|
program meets all of them (MiNTLib's `LICENSES`).
|
||||||
- **Amiga:** libnix (public domain).
|
- **Amiga:** libnix (public domain).
|
||||||
- **X68000:** newlib (a collection of permissive licenses,
|
- **X68000:** newlib (a collection of permissive licenses,
|
||||||
|
|
@ -177,7 +171,7 @@ above.
|
||||||
(a fork of llvm-mos, <https://github.com/llvm-mos/llvm-mos>)
|
(a fork of llvm-mos, <https://github.com/llvm-mos/llvm-mos>)
|
||||||
- **License:** Apache License 2.0 with LLVM Exceptions, llvm-mos's (and
|
- **License:** Apache License 2.0 with LLVM Exceptions, llvm-mos's (and
|
||||||
LLVM's) license, for all of it: llvm-mos's code and the W65816 backend,
|
LLVM's) license, for all of it: llvm-mos's code and the W65816 backend,
|
||||||
link816, omfEmit and the runtime (its `LICENSE.TXT`). Its README lists
|
link816, omfEmit, and the runtime (its `LICENSE.TXT`). Its README lists
|
||||||
the third-party code it holds under other licenses (the C++ runtime's
|
the third-party code it holds under other licenses (the C++ runtime's
|
||||||
Embedded Template Library, MIT; test and demo sources), none of which
|
Embedded Template Library, MIT; test and demo sources), none of which
|
||||||
reaches a JoeyLib program.
|
reaches a JoeyLib program.
|
||||||
|
|
@ -256,7 +250,7 @@ above.
|
||||||
(Vincent Riviere's builds; mintlib: <https://github.com/freemint/mintlib>)
|
(Vincent Riviere's builds; mintlib: <https://github.com/freemint/mintlib>)
|
||||||
- **License:** gcc 15.2 and binutils 2.45: GNU GPL v3, with the GCC
|
- **License:** gcc 15.2 and binutils 2.45: GNU GPL v3, with the GCC
|
||||||
Runtime Library Exception for libgcc. MiNTLib 0.60.1: per file, under
|
Runtime Library Exception for libgcc. MiNTLib 0.60.1: per file, under
|
||||||
the MiNTLib license (COPYMINT), the LGPL 2 or later, BSD, Henry
|
the MiNTLib license (COPYMINT), the LGPL 2 or later, BSD, or Henry
|
||||||
Spencer's or musl's MIT terms; a program linked with it meets all of
|
Spencer's or musl's MIT terms; a program linked with it meets all of
|
||||||
them (MiNTLib's `LICENSES`).
|
them (MiNTLib's `LICENSES`).
|
||||||
- **Role:** GEMDOS-native gcc + binutils + mintlib used to compile
|
- **Role:** GEMDOS-native gcc + binutils + mintlib used to compile
|
||||||
|
|
@ -322,9 +316,9 @@ above.
|
||||||
|
|
||||||
- **Used on:** Win32
|
- **Used on:** Win32
|
||||||
- **URL:** <https://github.com/mstorsjo/llvm-mingw>
|
- **URL:** <https://github.com/mstorsjo/llvm-mingw>
|
||||||
- **License:** clang, lld and the LLVM runtimes: Apache License 2.0 with
|
- **License:** clang, lld, and the LLVM runtimes: Apache License 2.0 with
|
||||||
LLVM Exceptions. mingw-w64's headers and runtime: Zope Public License
|
LLVM Exceptions. mingw-w64's headers and runtime: Zope Public License
|
||||||
2.1, with parts marked public domain, BSD or LGPL.
|
2.1, with parts marked public domain, BSD, or LGPL.
|
||||||
- **Role:** i686 cross compiler for the win32 port's programs, which link
|
- **Role:** i686 cross compiler for the win32 port's programs, which link
|
||||||
Windows' MSVCRT.DLL.
|
Windows' MSVCRT.DLL.
|
||||||
- **Installed at:** `toolchains/opt/llvmMingw/` (installer component
|
- **Installed at:** `toolchains/opt/llvmMingw/` (installer component
|
||||||
|
|
@ -338,7 +332,7 @@ above.
|
||||||
Library Exception for libgcc. The PSPSDK: BSD. newlib: its collection
|
Library Exception for libgcc. The PSPSDK: BSD. newlib: its collection
|
||||||
of permissive licenses. Every library pspdev carries has its license
|
of permissive licenses. Every library pspdev carries has its license
|
||||||
under `psp/share/licenses/`.
|
under `psp/share/licenses/`.
|
||||||
- **Role:** psp-gcc, the PSPSDK and the tools that make an EBOOT.PBP.
|
- **Role:** psp-gcc, the PSPSDK, and the tools that make an EBOOT.PBP.
|
||||||
- **Installed at:** `toolchains/opt/pspdev/` (installer component `pspdev`)
|
- **Installed at:** `toolchains/opt/pspdev/` (installer component `pspdev`)
|
||||||
|
|
||||||
### Emscripten 6.0.11 (emsdk)
|
### Emscripten 6.0.11 (emsdk)
|
||||||
|
|
@ -376,10 +370,45 @@ above.
|
||||||
distribution, keeping its notice).
|
distribution, keeping its notice).
|
||||||
- **Role:** `package` unpacks the `.hpkg` packages the sysroots are made
|
- **Role:** `package` unpacks the `.hpkg` packages the sysroots are made
|
||||||
from; `bfs_shell` reads and writes the BFS disks the VMs take their jobs
|
from; `bfs_shell` reads and writes the BFS disks the VMs take their jobs
|
||||||
on.
|
on; `xres` puts every Be API program's icons into its resources.
|
||||||
- **Installed at:** `toolchains/opt/haikuTools/` (installer component
|
- **Installed at:** `toolchains/opt/haikuTools/` (installer component
|
||||||
`haikuTools`)
|
`haikuTools`)
|
||||||
|
|
||||||
|
### The BeOS palette (in `tools/iconBake.py`)
|
||||||
|
|
||||||
|
- **Used on:** Haiku, Haiku x86, BeOS
|
||||||
|
- **URL:** <https://github.com/haiku/haiku> (`headers/private/interface/Palette.h`)
|
||||||
|
- **License:** MIT (Haiku).
|
||||||
|
- **Role:** the system's 256 colours, copied into `tools/iconBake.py` as
|
||||||
|
`BE_PALETTE`, which the Be API programs' icons are baked to (an icon is
|
||||||
|
indices into it; the palette itself is not in the program).
|
||||||
|
- **Installed at:** nowhere; it is part of `tools/iconBake.py`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### libxmp-lite 4.7.0
|
||||||
|
|
||||||
|
- **Used on:** none. It played modules on DOS, the ST, the X68000 and the
|
||||||
|
hosted ports until 2026-10-14, when JoeyLib's own module player replaced
|
||||||
|
it; the installer still installs it (component `libxmp`).
|
||||||
|
- **URL:** <https://github.com/libxmp/libxmp>
|
||||||
|
- **License:** MIT
|
||||||
|
- **Installed at:** `toolchains/opt/libxmp/` (installer component `libxmp`)
|
||||||
|
|
||||||
|
## Content tools (optional; not shipped, not linked)
|
||||||
|
|
||||||
|
### Furnace 0.6.8.3
|
||||||
|
|
||||||
|
- **Used on:** every port (chip-tracker music, through `tools/vgm2song.py`)
|
||||||
|
- **URL:** <https://github.com/tildearrow/furnace>
|
||||||
|
- **License:** GPLv2 or later. Its demo songs are not under the GPL: they are
|
||||||
|
their authors', all rights reserved, and nothing made from them is kept or
|
||||||
|
shipped (`scripts/check-vgm.sh` converts them in a scratch folder).
|
||||||
|
- **Role:** the recommended tracker for writing chip-tracker songs
|
||||||
|
(docs/Assets.adoc); its VGM export is what `tools/vgm2song.py` converts.
|
||||||
|
- **Installed at:** `toolchains/opt/furnace/` (installer component `furnace`,
|
||||||
|
optional: `./install.sh install furnace`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Test-time emulators (not shipped, not linked)
|
## Test-time emulators (not shipped, not linked)
|
||||||
|
|
@ -478,13 +507,13 @@ Installed from the Linux distribution by the components named below
|
||||||
numpy (BSD), installed by hand with pip. The script holds none of
|
numpy (BSD), installed by hand with pip. The script holds none of
|
||||||
potracer's code, and the logo it made is not covered by potracer's
|
potracer's code, and the logo it made is not covered by potracer's
|
||||||
license.
|
license.
|
||||||
- Building MAME, QEMU, PPSSPP and SDL 3 also takes the distribution's
|
- Building MAME, QEMU, PPSSPP, and SDL 3 also takes the distribution's
|
||||||
development packages (SDL2, X11, Wayland, PulseAudio, ALSA, GLib and the
|
development packages (SDL2, X11, Wayland, PulseAudio, ALSA, GLib, and the
|
||||||
rest), each under its own license.
|
rest), each under its own license.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## System ROMs, operating systems and disk images (used for testing only)
|
## System ROMs, operating systems, and disk images (used for testing only)
|
||||||
|
|
||||||
These are **not** part of JoeyLib distribution. `./install.sh`
|
These are **not** part of JoeyLib distribution. `./install.sh`
|
||||||
places them in `toolchains/firmware/` or `toolchains/opt/` for
|
places them in `toolchains/firmware/` or `toolchains/opt/` for
|
||||||
|
|
@ -539,10 +568,23 @@ project hosts (`JOEY_BUNDLE_URL`); matched by the checksums in
|
||||||
Trio64V+ display driver** (its `.zip`): Microsoft and S3 proprietary.
|
Trio64V+ display driver** (its `.zip`): Microsoft and S3 proprietary.
|
||||||
Installed unattended into the DOSBox PC every win16 run uses. A Win16
|
Installed unattended into the DOSBox PC every win16 run uses. A Win16
|
||||||
program's users bring their own Windows. Component `win311`.
|
program's users bring their own Windows. Component `win311`.
|
||||||
|
- **Microsoft WinG 1.0 SDK** (`WING10.EXE`): Microsoft proprietary, under
|
||||||
|
its Microsoft Development License (`LICENSE.TXT`, installed beside the
|
||||||
|
runtime): the SDK may be used to design, develop, and test software; its
|
||||||
|
runtime (`WING.DLL`, `WING32.DLL`, `WINGDE.DLL`, `WINGDIB.DRV`,
|
||||||
|
`WINGPAL.WND`, `SETUPHLP.DLL`, `DVA.386`) may be redistributed only as part
|
||||||
|
of your own application, not for further redistribution, and with an
|
||||||
|
indemnity to Microsoft. Microsoft no longer offers it; archive.org keeps a
|
||||||
|
copy (<https://archive.org/details/WING10>). The win16 port uses WinG when
|
||||||
|
it is installed on the machine and draws through GDI without it, so
|
||||||
|
JoeyLib never ships it; a game that wants WinG on its users' machines
|
||||||
|
ships the runtime under those terms. The installer takes the runtime out
|
||||||
|
of the SDK into `toolchains/opt/wing/` and the win16 gates install it in
|
||||||
|
their Windows. Component `wing`.
|
||||||
- **BeOS 5 Professional CD** (its image): Be, Inc. proprietary; the terms
|
- **BeOS 5 Professional CD** (its image): Be, Inc. proprietary; the terms
|
||||||
for its start files are Unknown. Its boot floppy and x86 system become
|
for its start files are Unknown. Its boot floppy and x86 system become
|
||||||
the BeOS R5 VM (component `beosVm`), and its Development package's
|
the BeOS R5 VM (component `beosVm`), and its Development package's
|
||||||
headers, start files and system libraries the beos port's sysroot
|
headers, start files, and system libraries the beos port's sysroot
|
||||||
(component `beosR5`). Every beos program links the start files
|
(component `beosR5`). Every beos program links the start files
|
||||||
(`start_dyn.o`, `init_term_dyn.o`, `crti.o`, `crtn.o`), as every program
|
(`start_dyn.o`, `init_term_dyn.o`, `crti.o`, `crtn.o`), as every program
|
||||||
built with Be's own tools did; check their terms before shipping one.
|
built with Be's own tools did; check their terms before shipping one.
|
||||||
|
|
|
||||||
14
Makefile
14
Makefile
|
|
@ -24,6 +24,7 @@
|
||||||
# make docs renders the manual to build/docs (HTML + PDF)
|
# make docs renders the manual to build/docs (HTML + PDF)
|
||||||
# make verify builds every port, then runs every gate (needs emulators)
|
# make verify builds every port, then runs every gate (needs emulators)
|
||||||
# make verify-audio runs every port's audio gate (needs emulators)
|
# make verify-audio runs every port's audio gate (needs emulators)
|
||||||
|
# make verify-conform grades every recording port against the audio contract
|
||||||
# make verify-serial runs every port's serial loopback gate
|
# make verify-serial runs every port's serial loopback gate
|
||||||
# make clean removes all build outputs
|
# make clean removes all build outputs
|
||||||
#
|
#
|
||||||
|
|
@ -38,7 +39,7 @@ include $(dir $(firstword $(MAKEFILE_LIST)))make/common.mk
|
||||||
|
|
||||||
ALL_TARGETS := $(JOEY_PORTS_INSTALLED)
|
ALL_TARGETS := $(JOEY_PORTS_INSTALLED)
|
||||||
|
|
||||||
.PHONY: all iigs iigs-disk iigs-verify amiga atarist ste dos x68000 dvx sdl3 web win32 win16 psp haiku haiku32 beos blank-check tools clean help status verify verify-audio verify-serial docs
|
.PHONY: all iigs iigs-disk iigs-verify amiga atarist ste dos x68000 dvx sdl3 web win32 win16 psp haiku haiku32 beos blank-check tools clean help status verify verify-audio verify-conform verify-serial docs
|
||||||
|
|
||||||
all: $(ALL_TARGETS) tools
|
all: $(ALL_TARGETS) tools
|
||||||
ifeq ($(ALL_TARGETS),)
|
ifeq ($(ALL_TARGETS),)
|
||||||
|
|
@ -127,6 +128,12 @@ verify:
|
||||||
verify-audio:
|
verify-audio:
|
||||||
@$(REPO_DIR)/scripts/verify-all.sh audio $(PORTS)
|
@$(REPO_DIR)/scripts/verify-all.sh audio $(PORTS)
|
||||||
|
|
||||||
|
# Every recording port against the one audio contract (AUDPAR, graded by
|
||||||
|
# tools/gradeAudioConformance.py), sequentially, with the same exit codes.
|
||||||
|
# Takes a subset: make verify-conform PORTS="sdl3 dos"
|
||||||
|
verify-conform:
|
||||||
|
@$(REPO_DIR)/scripts/verify-all.sh conform $(PORTS)
|
||||||
|
|
||||||
# The host check of the shared serial layer, then every port's loopback gate
|
# The host check of the shared serial layer, then every port's loopback gate
|
||||||
# (banner out, probe echoed back), sequentially, with the same exit codes.
|
# (banner out, probe echoed back), sequentially, with the same exit codes.
|
||||||
# Takes a subset: make verify-serial PORTS="host dos"
|
# Takes a subset: make verify-serial PORTS="host dos"
|
||||||
|
|
@ -159,7 +166,8 @@ help:
|
||||||
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
||||||
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
||||||
@echo " verify (build every port, then run every gate; PORTS= subset)"
|
@echo " verify (build every port, then run every gate; PORTS= subset)"
|
||||||
@echo " verify-audio (all 5 audio gates; PORTS=\"atarist amiga\" for a subset)"
|
@echo " verify-audio (every port's audio gate; PORTS=\"atarist amiga\" for a subset)"
|
||||||
@echo " verify-serial (host check + all 5 serial loopback gates; PORTS= subset)"
|
@echo " verify-conform (every recording port against the audio contract; PORTS= subset)"
|
||||||
|
@echo " verify-serial (host check + every port's serial loopback gate; PORTS= subset)"
|
||||||
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
@echo " docs (manual to build/docs; FORMAT=html for HTML only)"
|
||||||
@echo "Install toolchains first: ./install.sh install toolchains"
|
@echo "Install toolchains first: ./install.sh install toolchains"
|
||||||
|
|
|
||||||
132
README.md
132
README.md
|
|
@ -28,8 +28,9 @@ and the PSP, from a single codebase:
|
||||||
under QEMU)
|
under QEMU)
|
||||||
- PSP (homebrew for custom firmware: the SDL3 port built with pspdev, each
|
- PSP (homebrew for custom firmware: the SDL3 port built with pspdev, each
|
||||||
program an `EBOOT.PBP` in a folder for `PSP/GAME/`, the stage at 1:1 in
|
program an `EBOOT.PBP` in a folder for `PSP/GAME/`, the stage at 1:1 in
|
||||||
the 480x272 screen; tested under PPSSPP with every memory access checked
|
the 480x272 screen, a keyboard drawn on it for the keys a PSP lacks;
|
||||||
for alignment as a PSP checks it)
|
tested under PPSSPP with every memory access checked for alignment as a
|
||||||
|
PSP checks it)
|
||||||
|
|
||||||
The Apple IIgs defines the capability ceiling. Stronger platforms coast.
|
The Apple IIgs defines the capability ceiling. Stronger platforms coast.
|
||||||
Hot paths are hand-written assembly per port; the public API is C.
|
Hot paths are hand-written assembly per port; the public API is C.
|
||||||
|
|
@ -97,7 +98,7 @@ Your own program does not go into this tree: it lives in a directory of its
|
||||||
own whose Makefile describes it and includes `make/project.mk` and
|
own whose Makefile describes it and includes `make/project.mk` and
|
||||||
`make/programs.mk` from a JoeyLib checkout (found through `JOEYLIB`, by
|
`make/programs.mk` from a JoeyLib checkout (found through `JOEYLIB`, by
|
||||||
default `../joeylib`). `make` there builds it for every installed target with
|
default `../joeylib`). `make` there builds it for every installed target with
|
||||||
the same rules, flags and packaging as the examples, into the project's own
|
the same rules, flags, and packaging as the examples, into the project's own
|
||||||
`build/`; the manual's "Building Your Program" chapter
|
`build/`; the manual's "Building Your Program" chapter
|
||||||
(`docs/BuildIntegration.adoc`) has the details. Two complete games are built
|
(`docs/BuildIntegration.adoc`) has the details. Two complete games are built
|
||||||
this way as separate projects: Space Taxi, a port of the 1984 Commodore 64
|
this way as separate projects: Space Taxi, a port of the 1984 Commodore 64
|
||||||
|
|
@ -127,9 +128,10 @@ the emulators and their firmware:
|
||||||
A few files are never downloaded -- the Amiga Kickstart, the Amiga
|
A few files are never downloaded -- the Amiga Kickstart, the Amiga
|
||||||
`serial.device` and `lowlevel.library` from Workbench 3.1, the X68000 font
|
`serial.device` and `lowlevel.library` from Workbench 3.1, the X68000 font
|
||||||
ROM, the Windows for Workgroups 3.11 install disc and S3 Trio64V+ driver
|
ROM, the Windows for Workgroups 3.11 install disc and S3 Trio64V+ driver
|
||||||
the Win16 port runs on, and the BeOS 5 Professional CD -- so supply your
|
the Win16 port runs on (and, optionally, Microsoft's WinG 1.0 SDK,
|
||||||
|
`WING10.EXE`), and the BeOS 5 Professional CD -- so supply your
|
||||||
copies once with `./install.sh import PATH` (files, folders, `.zip` or
|
copies once with `./install.sh import PATH` (files, folders, `.zip` or
|
||||||
`.7z` archives or Amiga `.adf` disk images; the Windows disc as its `.iso`,
|
`.7z` archives, or Amiga `.adf` disk images; the Windows disc as its `.iso`,
|
||||||
the BeOS CD as its CloneCD `.img`).
|
the BeOS CD as its CloneCD `.img`).
|
||||||
The manual's Getting Started chapter covers `joey-run`'s options and the
|
The manual's Getting Started chapter covers `joey-run`'s options and the
|
||||||
private-file bundle.
|
private-file bundle.
|
||||||
|
|
@ -148,8 +150,9 @@ make dvx
|
||||||
```
|
```
|
||||||
|
|
||||||
`make atarist` builds programs for every ST; `make ste` builds the STe
|
`make atarist` builds programs for every ST; `make ste` builds the STe
|
||||||
variant, which uses the STe's BLiTTER, DMA sound and 4-bit palette and runs
|
variant, which uses the STe's BLiTTER, DMA sound, and 4-bit palette, reads
|
||||||
only on an STe or Mega STe.
|
Jaguar pads in a 1040 STe's enhanced joystick ports, and runs only on an STe
|
||||||
|
or Mega STe.
|
||||||
|
|
||||||
Every fragment also answers to a bare `clean`, and every fragment that builds
|
Every fragment also answers to a bare `clean`, and every fragment that builds
|
||||||
through `make/common.mk` -- all of them except `make/iigs.mk`, which assembles
|
through `make/common.mk` -- all of them except `make/iigs.mk`, which assembles
|
||||||
|
|
@ -169,9 +172,9 @@ compares two `joeylog.txt` captures and exits non-zero on any mismatch. The
|
||||||
frozen references live in `tests/goldens/uber/`. All five ports currently agree
|
frozen references live in `tests/goldens/uber/`. All five ports currently agree
|
||||||
on all 51 hashes; the X68000 has no golden of its own and is diffed against
|
on all 51 hashes; the X68000 has no golden of its own and is diffed against
|
||||||
`tests/goldens/uber/iigs.txt`, which it matches exactly, and DVX, SDL3, the
|
`tests/goldens/uber/iigs.txt`, which it matches exactly, and DVX, SDL3, the
|
||||||
web and the PSP, which share the DOS stage, are diffed against
|
web, and the PSP, which share the DOS stage, are diffed against
|
||||||
`tests/goldens/uber/dos.txt` (`scripts/bench-dvx.sh`, `scripts/bench-sdl3.sh`,
|
`tests/goldens/uber/dos.txt` (`scripts/bench-dvx.sh`, `scripts/bench-sdl3.sh`,
|
||||||
`scripts/bench-web.sh` or `scripts/bench-psp.sh`, then
|
`scripts/bench-web.sh`, or `scripts/bench-psp.sh`, then
|
||||||
`tools/diff-uber-hashes`). SDL3's run needs no emulator and no X server
|
`tools/diff-uber-hashes`). SDL3's run needs no emulator and no X server
|
||||||
(SDL's dummy drivers) and takes about fifteen seconds; the web's runs UBER's
|
(SDL's dummy drivers) and takes about fifteen seconds; the web's runs UBER's
|
||||||
page in headless Chromium; the PSP's runs UBER under headless PPSSPP in a few
|
page in headless Chromium; the PSP's runs UBER under headless PPSSPP in a few
|
||||||
|
|
@ -227,6 +230,8 @@ tools/assetbake/ PNG -> native .tbk / .spr baker (Python)
|
||||||
tools/joeymod/ Protracker .MOD converter (passthrough or .NTP)
|
tools/joeymod/ Protracker .MOD converter (passthrough or .NTP)
|
||||||
tools/spritebake/ offline .spr -> .spc pre-compiled sprite baker
|
tools/spritebake/ offline .spr -> .spc pre-compiled sprite baker
|
||||||
tools/songbake/ chip-tracker text notation -> JYM1 stream
|
tools/songbake/ chip-tracker text notation -> JYM1 stream
|
||||||
|
tools/vgm2song.py a tracker's VGM export (Furnace) -> chip-tracker .song
|
||||||
|
tools/iconBake.py one picture -> every system's program icon
|
||||||
tools/xdftool.py Human68k FAT12 .XDF disk image reader / writer
|
tools/xdftool.py Human68k FAT12 .XDF disk image reader / writer
|
||||||
tools/diff-uber-hashes UBER golden-hash comparator
|
tools/diff-uber-hashes UBER golden-hash comparator
|
||||||
examples/ example programs
|
examples/ example programs
|
||||||
|
|
@ -264,6 +269,7 @@ is a quick reference. Every entry point is plain C, no C++ extensions.
|
||||||
```c
|
```c
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t codegenBytes; // runtime compiled-sprite cache size
|
uint32_t codegenBytes; // runtime compiled-sprite cache size
|
||||||
|
bool noAudio; // true: no audio; else jlInit starts it
|
||||||
} jlConfigT;
|
} jlConfigT;
|
||||||
|
|
||||||
bool jlInit (const jlConfigT *config);
|
bool jlInit (const jlConfigT *config);
|
||||||
|
|
@ -290,18 +296,18 @@ void jlRandomSeed (uint32_t seed);
|
||||||
|
|
||||||
All surfaces are 320x200 16-color images with a 200-entry SCB table
|
All surfaces are 320x200 16-color images with a 200-entry SCB table
|
||||||
and 16 palettes of 16 `$0RGB` colors. In-memory storage is
|
and 16 palettes of 16 `$0RGB` colors. In-memory storage is
|
||||||
target-native: chunky 4bpp packed on IIgs, DOS, DVX, SDL3, the web and the PSP, native planar
|
target-native: chunky 4bpp packed on IIgs, DOS, DVX, SDL3, the web, and the PSP, native planar
|
||||||
(separate bitplanes on Amiga and Sharp X68000, word-interleaved planes
|
(separate bitplanes on Amiga and Sharp X68000, word-interleaved planes
|
||||||
on Atari ST) on the 68k ports. The public API speaks in color indices
|
on Atari ST) on the 68k ports. The public API speaks in color indices
|
||||||
(0..15) and hides the storage format.
|
(0..15) and hides the storage format.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#define SURFACE_WIDTH 320
|
#define JL_SURFACE_WIDTH 320
|
||||||
#define SURFACE_HEIGHT 200
|
#define JL_SURFACE_HEIGHT 200
|
||||||
#define SURFACE_BYTES_PER_ROW 160
|
#define JL_SURFACE_BYTES_PER_ROW 160
|
||||||
#define SURFACE_PIXELS_SIZE (SURFACE_BYTES_PER_ROW * SURFACE_HEIGHT)
|
#define JL_SURFACE_PIXELS_SIZE (JL_SURFACE_BYTES_PER_ROW * JL_SURFACE_HEIGHT)
|
||||||
#define SURFACE_PALETTE_COUNT 16
|
#define JL_SURFACE_PALETTE_COUNT 16
|
||||||
#define SURFACE_COLORS_PER_PALETTE 16
|
#define JL_SURFACE_COLORS_PER_PALETTE 16
|
||||||
|
|
||||||
typedef struct jlSurfaceT jlSurfaceT; // opaque
|
typedef struct jlSurfaceT jlSurfaceT; // opaque
|
||||||
|
|
||||||
|
|
@ -377,14 +383,14 @@ A "tile" is just an 8x8-aligned region of any surface. The API moves
|
||||||
type so callers can stash a copy without allocating a scratch surface.
|
type so callers can stash a copy without allocating a scratch surface.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#define TILE_PIXELS_PER_SIDE 8
|
#define JL_TILE_PIXELS_PER_SIDE 8
|
||||||
#define TILE_BYTES_PER_ROW 4
|
#define JL_TILE_BYTES_PER_ROW 4
|
||||||
#define TILE_BYTES (TILE_BYTES_PER_ROW * TILE_PIXELS_PER_SIDE)
|
#define JL_TILE_BYTES (JL_TILE_BYTES_PER_ROW * JL_TILE_PIXELS_PER_SIDE)
|
||||||
#define TILE_BLOCKS_PER_ROW (SURFACE_WIDTH / TILE_PIXELS_PER_SIDE) // 40
|
#define JL_TILE_BLOCKS_PER_ROW (JL_SURFACE_WIDTH / JL_TILE_PIXELS_PER_SIDE) // 40
|
||||||
#define TILE_BLOCKS_PER_COL (SURFACE_HEIGHT / TILE_PIXELS_PER_SIDE) // 25
|
#define JL_TILE_BLOCKS_PER_COL (JL_SURFACE_HEIGHT / JL_TILE_PIXELS_PER_SIDE) // 25
|
||||||
#define TILE_NO_GLYPH ((uint16_t)0xFFFFu)
|
#define JL_TILE_NO_GLYPH ((uint16_t)0xFFFFu)
|
||||||
|
|
||||||
typedef struct jlTileT { uint8_t pixels[TILE_BYTES]; } jlTileT;
|
typedef struct jlTileT { uint8_t pixels[JL_TILE_BYTES]; } jlTileT;
|
||||||
|
|
||||||
void jlTileCopy (jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
void jlTileCopy (jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
||||||
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
||||||
|
|
@ -453,7 +459,6 @@ bool jlSpriteBankSavePrecompiled(const char *path, jlSpriteT **cels,
|
||||||
uint16_t count, const uint16_t *palette);
|
uint16_t count, const uint16_t *palette);
|
||||||
|
|
||||||
bool jlSpriteCompile (jlSpriteT *sp); // build per-shift fast path
|
bool jlSpriteCompile (jlSpriteT *sp); // build per-shift fast path
|
||||||
void jlSpritePrewarm (jlSpriteT *sp); // hint: compile if not already
|
|
||||||
uint32_t jlSpriteCompiledSize (const jlSpriteT *sp);
|
uint32_t jlSpriteCompiledSize (const jlSpriteT *sp);
|
||||||
|
|
||||||
void jlSpriteDraw (jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
void jlSpriteDraw (jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
||||||
|
|
@ -517,6 +522,24 @@ For runtime-extracted content (sprites peeled out of a procedural
|
||||||
or captured surface), use `jlSpriteCreateFromSurface` -- the
|
or captured surface), use `jlSpriteCreateFromSurface` -- the
|
||||||
on-the-fly path stays alongside the baked load API.
|
on-the-fly path stays alongside the baked load API.
|
||||||
|
|
||||||
|
### Program icons (baked at build time)
|
||||||
|
|
||||||
|
Every program gets an icon on each system that shows programs by one --
|
||||||
|
the IIgs Finder, Workbench, the Program Managers of DVX and Windows 3.1,
|
||||||
|
Explorer, Tracker, the PSP's menu, the SDL3 window, and the web page --
|
||||||
|
baked by `tools/iconBake.py` from one picture: the program's `ID_ICON` (a
|
||||||
|
PNG), or JoeyLib's kangaroo (`assets/joeyIcon.png`). `docs/BuildIntegration.adoc`
|
||||||
|
(Icons) says what each system gets, and `scripts/verify-all.sh icon` checks
|
||||||
|
that each one shows it.
|
||||||
|
|
||||||
|
### Content creation tools
|
||||||
|
|
||||||
|
`docs/Assets.adoc` (Content Creation Tools) recommends a tool for each kind
|
||||||
|
of asset: GrafX2 or LibreSprite for tile and sprite sheets, Furnace for
|
||||||
|
chip-tracker music (`./install.sh install furnace`; its VGM exports become
|
||||||
|
`.song` files through `tools/vgm2song.py`), MilkyTracker or OpenMPT for
|
||||||
|
`.MOD` music, and sfxr, jfxr, Audacity, or SoX for sound effects.
|
||||||
|
|
||||||
|
|
||||||
### Present (`joey/present.h`)
|
### Present (`joey/present.h`)
|
||||||
|
|
||||||
|
|
@ -544,17 +567,17 @@ every port. Refilled by the same `jlInputPoll`; see `claudeDocs/input.md`
|
||||||
for per-port sources and limitations.
|
for per-port sources and limitations.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
typedef enum { /* KEY_NONE, KEY_A..KEY_Z, KEY_0..KEY_9, KEY_SPACE,
|
typedef enum { /* JL_KEY_NONE, JL_KEY_A..JL_KEY_Z, JL_KEY_0..JL_KEY_9, JL_KEY_SPACE,
|
||||||
KEY_ESCAPE, KEY_RETURN, KEY_TAB, KEY_BACKSPACE,
|
JL_KEY_ESCAPE, JL_KEY_RETURN, JL_KEY_TAB, JL_KEY_BACKSPACE,
|
||||||
KEY_UP/DOWN/LEFT/RIGHT, KEY_LSHIFT/RSHIFT/LCTRL/LALT,
|
JL_KEY_UP/DOWN/LEFT/RIGHT, JL_KEY_LSHIFT/RSHIFT/LCTRL/LALT,
|
||||||
KEY_F1..KEY_F10, KEY_COUNT */ } jlKeyE;
|
JL_KEY_F1..JL_KEY_F10, JL_KEY_COUNT */ } jlKeyE;
|
||||||
typedef enum { MOUSE_BUTTON_NONE, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT,
|
typedef enum { JL_MOUSE_BUTTON_NONE, JL_MOUSE_BUTTON_LEFT, JL_MOUSE_BUTTON_RIGHT,
|
||||||
MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_COUNT } jlMouseButtonE;
|
JL_MOUSE_BUTTON_MIDDLE, JL_MOUSE_BUTTON_COUNT } jlMouseButtonE;
|
||||||
typedef enum { JOYSTICK_0, JOYSTICK_1, JOYSTICK_COUNT } jlJoystickE;
|
typedef enum { JL_JOYSTICK_0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE;
|
||||||
typedef enum { JOY_BUTTON_0, JOY_BUTTON_1, JOY_BUTTON_COUNT } jlJoyButtonE;
|
typedef enum { JL_JOY_BUTTON_0, JL_JOY_BUTTON_1, JL_JOY_BUTTON_COUNT } jlJoyButtonE;
|
||||||
|
|
||||||
#define JOYSTICK_AXIS_MAX 127
|
#define JL_JOYSTICK_AXIS_MAX 127
|
||||||
#define JOYSTICK_AXIS_MIN (-127)
|
#define JL_JOYSTICK_AXIS_MIN (-127)
|
||||||
|
|
||||||
#define JL_CHAR_BACKSPACE 0x08
|
#define JL_CHAR_BACKSPACE 0x08
|
||||||
#define JL_CHAR_TAB 0x09
|
#define JL_CHAR_TAB 0x09
|
||||||
|
|
@ -588,20 +611,19 @@ void jlJoystickReset (jlJoystickE js, uint8_t deadZone);
|
||||||
### Audio (`joey/audio.h`)
|
### Audio (`joey/audio.h`)
|
||||||
|
|
||||||
4-channel Protracker-style music plus five one-shot SFX slots, three
|
4-channel Protracker-style music plus five one-shot SFX slots, three
|
||||||
PSG-style tone voices and a noise channel. Module data must be the
|
PSG-style tone voices, and a noise channel. Module data must be the
|
||||||
platform-native form produced by `tools/joeymod` (`.mod` for
|
platform-native form produced by `tools/joeymod` (`.mod` for
|
||||||
Amiga/DOS/ST/X68000; `.ntp` for IIgs; `.amod` if you want loop=false on
|
DOS/ST/X68000; `.amod` for the Amiga; `.ntp` for the IIgs). The Amiga
|
||||||
Amiga). Amiga plays `.mod` through PTPlayer and the IIgs through the
|
plays `.mod` through PTPlayer and the IIgs through the NinjaTrackerPlus
|
||||||
NinjaTrackerPlus replayer; DOS, Atari ST and X68000 decode with
|
replayer; DOS, the Atari ST, the X68000, and the hosted ports mix it
|
||||||
libxmp-lite. A failed `jlAudioInit` is non-fatal; the rest of the API
|
with JoeyLib's module player, PTPlayer's replay in C. `jlInit` starts
|
||||||
stays callable as no-ops.
|
audio (`jlConfigT.noAudio` leaves it off) and `jlShutdown` stops it;
|
||||||
|
sound that does not start is non-fatal, and the rest of the API stays
|
||||||
|
callable as no-ops.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#define JOEY_AUDIO_SFX_SLOTS 5
|
#define JL_AUDIO_SFX_SLOTS 5
|
||||||
#define JOEY_AUDIO_VOICES 3
|
#define JL_AUDIO_VOICES 3
|
||||||
|
|
||||||
bool jlAudioInit (void);
|
|
||||||
void jlAudioShutdown (void);
|
|
||||||
|
|
||||||
void jlAudioPlayMod (const uint8_t *data, uint32_t length, bool loop);
|
void jlAudioPlayMod (const uint8_t *data, uint32_t length, bool loop);
|
||||||
void jlAudioStopMod (void);
|
void jlAudioStopMod (void);
|
||||||
|
|
@ -613,7 +635,9 @@ void jlAudioPlaySfxStream (uint8_t slot, jlAudioStreamFillT fill, void *ctx,
|
||||||
uint16_t rateHz);
|
uint16_t rateHz);
|
||||||
void jlAudioStopSfx (uint8_t slot);
|
void jlAudioStopSfx (uint8_t slot);
|
||||||
|
|
||||||
// Portable chip-tracker music: a JYM1 event stream baked by tools/songbake,
|
// Portable chip-tracker music: a JYM1 event stream baked by tools/songbake
|
||||||
|
// (from text notation, or from a tracker: write it in Furnace for the
|
||||||
|
// SN76489 or AY-3-8910, export VGM, convert with tools/vgm2song.py),
|
||||||
// played through the tone/noise layer at register-write cost on every port.
|
// played through the tone/noise layer at register-write cost on every port.
|
||||||
// No CPU mixing, so it is viable where jlAudioPlayMod is not.
|
// No CPU mixing, so it is viable where jlAudioPlayMod is not.
|
||||||
bool jlMusicPlay (const uint8_t *data, uint32_t length, bool loop);
|
bool jlMusicPlay (const uint8_t *data, uint32_t length, bool loop);
|
||||||
|
|
@ -754,13 +778,13 @@ conditionally compile on these:
|
||||||
JOEYLIB_PLATFORM_IIGS / _AMIGA / _ATARIST / _DOS / _X68000 / _DVX / _HOSTED // exactly one defined
|
JOEYLIB_PLATFORM_IIGS / _AMIGA / _ATARIST / _DOS / _X68000 / _DVX / _HOSTED // exactly one defined
|
||||||
JOEYLIB_HOSTED_SDL3 / _WIN32 / _WIN16 // the hosted backend
|
JOEYLIB_HOSTED_SDL3 / _WIN32 / _WIN16 // the hosted backend
|
||||||
JOEYLIB_ATARI_STE // with _ATARIST: the STe build (make ste)
|
JOEYLIB_ATARI_STE // with _ATARIST: the STe build (make ste)
|
||||||
JOEYLIB_SPRITE_INTERPRETED // no sprite code emitter (hosted, blank)
|
JL_SPRITE_INTERPRETED // no sprite code emitter (hosted but Win16, blank)
|
||||||
JOEYLIB_CPU_65816 / _68000 / _X86
|
JL_CPU_65816 / _68000 / _X86
|
||||||
JOEYLIB_ENDIAN_LITTLE / _BIG
|
JL_ENDIAN_LITTLE / _BIG
|
||||||
JOEYLIB_NATIVE_CHUNKY / _NATIVE_PLANAR
|
JL_NATIVE_CHUNKY / _NATIVE_PLANAR
|
||||||
JOEYLIB_HAS_BLITTER / _HAS_COPPER // Amiga only
|
JL_HAS_BLITTER / _HAS_COPPER // Amiga only
|
||||||
JOEYLIB_PLATFORM_NAME // human-readable string
|
JL_PLATFORM_NAME // human-readable string
|
||||||
JOEYLIB_VERSION_MAJOR / _MINOR / _PATCH / _STRING
|
JL_VERSION_MAJOR / _MINOR / _PATCH / _STRING
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -772,6 +796,6 @@ copyright and permission notice, in its documentation for instance. Each
|
||||||
of JoeyLib's source files opens with a short header that says so
|
of JoeyLib's source files opens with a short header that says so
|
||||||
(`SPDX-License-Identifier: MIT`); a new file gets the same.
|
(`SPDX-License-Identifier: MIT`); a new file gets the same.
|
||||||
|
|
||||||
The components JoeyLib depends on, their licenses and what a shipped
|
The components JoeyLib depends on, their licenses, and what a shipped
|
||||||
program owes them are in [LICENSES.md](LICENSES.md). The patches in
|
program owes them are in [LICENSES.md](LICENSES.md). The patches in
|
||||||
`installer/patches/` are under the licenses of the projects they patch.
|
`installer/patches/` are under the licenses of the projects they patch.
|
||||||
|
|
|
||||||
BIN
assets/joeyIcon.png
(Stored with Git LFS)
Normal file
BIN
assets/joeyIcon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
|
|
@ -2,25 +2,32 @@
|
||||||
|
|
||||||
Declared in `<joey/audio.h>`.
|
Declared in `<joey/audio.h>`.
|
||||||
|
|
||||||
There are three layers, and on two platforms they share physical hardware:
|
There are three layers, and on some platforms they share physical hardware:
|
||||||
|
|
||||||
* *Module playback* -- `jlAudioPlayMod`, four-channel Protracker `.MOD`.
|
* *Module playback* -- `jlAudioPlayMod`, four-channel Protracker `.MOD`.
|
||||||
* *Chip tracker* -- `jlMusicPlay`, a portable event stream driving three tone
|
* *Chip tracker* -- `jlMusicPlay`, a portable event stream driving three tone
|
||||||
voices plus noise at register-write cost, with no CPU mixing. The recommended
|
voices plus noise at register-write cost, with no CPU mixing. The recommended
|
||||||
music path.
|
music path.
|
||||||
* *Voices and effects* -- `jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx` and
|
* *Voices and effects* -- `jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and
|
||||||
the streaming forms.
|
the streaming forms.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
On the Atari ST and Amiga the tone voices *are* the hardware the module player
|
On the Amiga the tone voices and the noise *are* the hardware the module
|
||||||
drives. While a `.MOD` plays, `jlAudioVoice` writes fight the player's register
|
player drives: Paula's four channels. While a `.MOD` plays, `jlAudioVoice` and
|
||||||
writes and lose. Choose the module layer or the voice and tracker layer per
|
`jlAudioNoise` writes fight the player's register writes and lose. On the
|
||||||
scene, never both at once.
|
plain ST (not the STe build) digital sound -- a module, an effect, or a
|
||||||
|
stream -- plays through the sound chip's channel A, so tone voice 0 is silent
|
||||||
|
while it plays and sounds again when it stops; voices 1 and 2 play on. Choose
|
||||||
|
the module layer or the voice and tracker layer per scene, never both at once.
|
||||||
====
|
====
|
||||||
|
|
||||||
A failed `jlAudioInit` is not fatal: `jlInit` still succeeds and every audio
|
`jlInit` starts audio and `jlShutdown` stops it; a program that wants none
|
||||||
call becomes a silent no-op.
|
sets `jlConfigT.noAudio`, and every audio call is then silent. Sound that does
|
||||||
|
not start is not fatal: `jlInit` still succeeds, modules and sound effects are
|
||||||
|
silent, `jlAudioTickRegister` refuses, and the log says so. The tone and noise
|
||||||
|
voices, and so `jlMusicPlay`, still play where they drive a sound chip of
|
||||||
|
their own: on the Amiga, the ST, DOS, and the X68000.
|
||||||
|
|
||||||
=== Constants
|
=== Constants
|
||||||
|
|
||||||
|
|
@ -28,8 +35,8 @@ call becomes a silent no-op.
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|
|
||||||
|`JOEY_AUDIO_SFX_SLOTS` |5 |Sound effect slots, 0..4.
|
|`JL_AUDIO_SFX_SLOTS` |5 |Sound effect slots, 0..4.
|
||||||
|`JOEY_AUDIO_VOICES` |3 |PSG-style tone voices, 0..2.
|
|`JL_AUDIO_VOICES` |3 |PSG-style tone voices, 0..2.
|
||||||
|`JL_SFX_RAW_SIGNED8` |0 |Raw stream format: `int8_t` PCM.
|
|`JL_SFX_RAW_SIGNED8` |0 |Raw stream format: `int8_t` PCM.
|
||||||
|`JL_SFX_RAW_UNSIGNED8_NOZERO` |1 |Raw stream format: `uint8_t` biased 0x80,
|
|`JL_SFX_RAW_UNSIGNED8_NOZERO` |1 |Raw stream format: `uint8_t` biased 0x80,
|
||||||
and 0x00 is forbidden (it is the DOC stop byte).
|
and 0x00 is forbidden (it is the DOC stop byte).
|
||||||
|
|
@ -37,34 +44,15 @@ call becomes a silent no-op.
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
Prefer *slot 4* for game sound effects when music is playing. The IIgs maps
|
Prefer *slot 4* for game sound effects when music is playing. The IIgs plays
|
||||||
tracker tone voices onto slots 0..2 and the noise voice onto slot 3. On the
|
tone voices 0..2 on sound effect slots 0..2 and the noise on slot 3: an effect
|
||||||
other ports the slots are independent of the voice layer.
|
and a voice on the same slot cut each other off, and a voice still being held
|
||||||
|
takes its slot back at the next `jlAudioFrameTick`. On the Amiga effects, the
|
||||||
|
module, the voices, and the noise share Paula's four channels, so an effect can
|
||||||
|
take a channel a voice is using. On the other ports the slots are independent
|
||||||
|
of the voice layer.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioInit
|
|
||||||
|
|
||||||
[source,c]
|
|
||||||
----
|
|
||||||
bool jlAudioInit(void);
|
|
||||||
----
|
|
||||||
|
|
||||||
*Parameters:* none.
|
|
||||||
|
|
||||||
*Returns:* `true` if the platform has a working audio engine and started it;
|
|
||||||
`false` silently otherwise, with the rest of the API still callable but silent.
|
|
||||||
|
|
||||||
=== jlAudioShutdown
|
|
||||||
|
|
||||||
[source,c]
|
|
||||||
----
|
|
||||||
void jlAudioShutdown(void);
|
|
||||||
----
|
|
||||||
|
|
||||||
Tears the engine down. Safe when audio was never initialised.
|
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
|
||||||
|
|
||||||
=== jlAudioFrameTick
|
=== jlAudioFrameTick
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -72,10 +60,12 @@ Tears the engine down. Safe when audio was never initialised.
|
||||||
void jlAudioFrameTick(void);
|
void jlAudioFrameTick(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Hooks the engine into your game loop. Most platforms drive their engines from a
|
Hooks the engine into your game loop. Every port does its sound work here: DOS,
|
||||||
hardware interrupt and ignore this, but it is required for any port that mixes
|
the ST, the X68000, DVX, and the hosted ports mix or refill their sound buffers
|
||||||
in user context, it advances tracker playback, and on IIgs and Amiga it is what
|
in it, so without it modules and effects stop or repeat; the IIgs and Amiga
|
||||||
fires the next chunk of a streaming sound effect.
|
feed each streaming sound effect its next chunk, notice a module's end, and
|
||||||
|
(on the IIgs) keep held notes sounding; and on every port it advances
|
||||||
|
`jlMusicPlay`.
|
||||||
|
|
||||||
Call it once per frame, unconditionally.
|
Call it once per frame, unconditionally.
|
||||||
|
|
||||||
|
|
@ -93,8 +83,9 @@ Begins module playback. If a module is already playing it is replaced.
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`data` |`const uint8_t *` |Platform-native module blob: `.mod` on most ports,
|
|`data` |`const uint8_t *` |Platform-native module blob: `.mod` on every port
|
||||||
`.amod` on Amiga, `.ntp` on IIgs. The asset pipeline produces the right form.
|
but the IIgs (an `.amod`, with its end marker, plays on the Amiga too), `.ntp`
|
||||||
|
on the IIgs. The asset pipeline produces the right form.
|
||||||
|`length` |`uint32_t` |Blob size in bytes.
|
|`length` |`uint32_t` |Blob size in bytes.
|
||||||
|`loop` |`bool` |`true` plays forever.
|
|`loop` |`bool` |`true` plays forever.
|
||||||
|===
|
|===
|
||||||
|
|
@ -103,9 +94,15 @@ Begins module playback. If a module is already playing it is replaced.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
`loop = false` needs help on the Amiga: PTPlayer has no native song-end signal,
|
`loop = false` plays the song once and stops it. The IIgs's replayer has a
|
||||||
so the module must contain an `E8FF` marker at song end. The `joeymod` tool's
|
play-once mode; the ports that mix modules in software stop where the song
|
||||||
`.amod` output injects it. A plain `.mod` with no marker loops anyway on Amiga.
|
comes back to a row it has played (its end, or a jump back), at an `F00`, or
|
||||||
|
at the Amiga's `E8F` stop marker; and the Amiga adds that marker as it copies
|
||||||
|
the module to chip RAM, sending the song's jumps back to it
|
||||||
|
(<<_music_protracker_modules>>). A module with no room for one, all 128 song
|
||||||
|
positions or all 128 patterns in use, loops there anyway, and so does one
|
||||||
|
whose jump back sits in a pattern it also plays where that jump goes
|
||||||
|
forward.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioStopMod
|
=== jlAudioStopMod
|
||||||
|
|
@ -130,7 +127,8 @@ bool jlAudioIsPlayingMod(void);
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* `true` while a module is producing output. `false` after a stop,
|
*Returns:* `true` while a module is producing output. `false` after a stop,
|
||||||
before the first play, and on platforms where audio init failed.
|
once a song played with `loop = false` has ended, before the first play, and on
|
||||||
|
platforms where audio init failed.
|
||||||
|
|
||||||
=== jlAudioPlaySfx
|
=== jlAudioPlaySfx
|
||||||
|
|
||||||
|
|
@ -146,7 +144,7 @@ new sample replaces it.
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`slot` |`uint8_t` |Slot 0..`JOEY_AUDIO_SFX_SLOTS`-1. Prefer 4 alongside music.
|
|`slot` |`uint8_t` |Slot 0..`JL_AUDIO_SFX_SLOTS`-1. Prefer 4 alongside music.
|
||||||
|`sample` |`const uint8_t *` |Raw signed 8-bit PCM.
|
|`sample` |`const uint8_t *` |Raw signed 8-bit PCM.
|
||||||
|`length` |`uint32_t` |Sample count in bytes.
|
|`length` |`uint32_t` |Sample count in bytes.
|
||||||
|`rateHz` |`uint16_t` |The rate the sample was recorded at; the engine
|
|`rateHz` |`uint16_t` |The rate the sample was recorded at; the engine
|
||||||
|
|
@ -155,6 +153,12 @@ new sample replaces it.
|
||||||
|
|
||||||
*Returns:* nothing.
|
*Returns:* nothing.
|
||||||
|
|
||||||
|
Most ports read the sample where it is while it plays, so keep it valid and
|
||||||
|
unchanged until it has finished or the slot has been stopped. The IIgs also
|
||||||
|
remembers the last sample each slot played by its address, length, and rate,
|
||||||
|
and plays its own copy when they match: changed bytes at the same address,
|
||||||
|
length, and rate are not heard there.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
The full -128..127 range is accepted everywhere, but the IIgs plays -128 as
|
The full -128..127 range is accepted everywhere, but the IIgs plays -128 as
|
||||||
|
|
@ -175,9 +179,9 @@ deactivates.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
This is called from the audio engine's refill context -- the main thread on DOS,
|
On every port it is called in program time, from the call that starts the
|
||||||
ST and SDL3 (on DVX, DVX's own sound pump, while the program is yielded), but the
|
stream and then from `jlAudioFrameTick` (on DVX, also while the desktop runs).
|
||||||
*audio interrupt* on Amiga and IIgs. It must not block and must not allocate.
|
It must not block, and it should be quick: it runs inside those calls.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioPlaySfxStream
|
=== jlAudioPlaySfxStream
|
||||||
|
|
@ -206,10 +210,11 @@ with no caller-side allocation.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
On IIgs and Amiga the engine plays one chunk at a time and fires the next from
|
Every port feeds streams from `jlAudioFrameTick`, so *you must be calling
|
||||||
`jlAudioFrameTick`, so *you must be calling `jlAudioFrameTick` every frame* or
|
`jlAudioFrameTick` every frame* or the stream stops. On the IIgs and Amiga the
|
||||||
the stream stops after its first chunk. A chunk seam on those ports can carry
|
engine plays one chunk at a time (7 KB on the IIgs, 4 KB on the Amiga) and
|
||||||
up to one frame of jitter.
|
fires the next from it, so a chunk seam on those ports can carry up to one
|
||||||
|
frame of jitter.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioSfxRawFormat / jlAudioPlaySfxStreamRaw
|
=== jlAudioSfxRawFormat / jlAudioPlaySfxStreamRaw
|
||||||
|
|
@ -226,10 +231,10 @@ void jlAudioPlaySfxStreamRaw(uint8_t slot, jlAudioStreamFillT fill,
|
||||||
|
|
||||||
`jlAudioPlaySfxStreamRaw` is `jlAudioPlaySfxStream` except your callback
|
`jlAudioPlaySfxStreamRaw` is `jlAudioPlaySfxStream` except your callback
|
||||||
delivers bytes *already in that format*, so the port skips its per-byte
|
delivers bytes *already in that format*, so the port skips its per-byte
|
||||||
conversion of every chunk. On the IIgs that conversion is a 65816 byte loop
|
conversion of every chunk. On the IIgs that conversion costs about 7
|
||||||
over each 3.5 KB chunk, and it is enough to stall a game for a third of a
|
microseconds a byte, some 50 ms for each 7 KB chunk. Every other port's raw
|
||||||
second per chunk of speech. Every other port streams signed PCM natively, so
|
format is signed PCM, so there this is simply the plain call (DVX still flips
|
||||||
there this is simply the plain call.
|
each byte for its sound library).
|
||||||
|
|
||||||
If your producer cannot make the port's format, use `jlAudioPlaySfxStream`.
|
If your producer cannot make the port's format, use `jlAudioPlaySfxStream`.
|
||||||
|
|
||||||
|
|
@ -240,7 +245,7 @@ If your producer cannot make the port's format, use `jlAudioPlaySfxStream`.
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
typedef enum {
|
typedef enum {
|
||||||
AUDIO_PAN_LEFT = 0, AUDIO_PAN_CENTER, AUDIO_PAN_RIGHT, AUDIO_PAN_COUNT
|
JL_AUDIO_PAN_LEFT = 0, JL_AUDIO_PAN_CENTER, JL_AUDIO_PAN_RIGHT, JL_AUDIO_PAN_COUNT
|
||||||
} jlAudioPanE;
|
} jlAudioPanE;
|
||||||
|
|
||||||
void jlAudioSetPan(uint8_t slot, jlAudioPanE pan);
|
void jlAudioSetPan(uint8_t slot, jlAudioPanE pan);
|
||||||
|
|
@ -268,14 +273,16 @@ What each port can actually do:
|
||||||
|IIgs |All three. The DOC pans per oscillator, so a centred sound plays on two
|
|IIgs |All three. The DOC pans per oscillator, so a centred sound plays on two
|
||||||
oscillators, one per side. A stock IIgs sums to mono, so this shows up only on
|
oscillators, one per side. A stock IIgs sums to mono, so this shows up only on
|
||||||
a stereo card or an emulator.
|
a stereo card or an emulator.
|
||||||
|Amiga |Left and right only, by choosing the Paula channel side. Paula cannot
|
|Amiga |Left and right by choosing the Paula channel side; a Paula channel plays
|
||||||
place one voice in both ears, so `AUDIO_PAN_CENTER` leaves PTPlayer's own
|
on one side only. A centred sound plays on a free channel each side when both
|
||||||
channel choice alone.
|
sides have one, so it reaches both ears; otherwise on the one channel
|
||||||
|
PTPlayer picks.
|
||||||
|X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move
|
|X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move
|
||||||
all audio at once.
|
all audio at once.
|
||||||
|DOS, ST |Ignored. Output is mono.
|
|DOS, ST, STe |Ignored. Output is mono.
|
||||||
|DVX |All three, through DVX's stereo mixer (on a stereo card).
|
|DVX |All three, through DVX's stereo mixer (on a stereo card).
|
||||||
|SDL3 |All three: the port mixes in stereo.
|
|SDL3, web, Win32, PSP, Haiku, BeOS |All three: the port mixes in stereo.
|
||||||
|
|Win16 |Ignored. Output is mono.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== jlAudioStopSfx
|
=== jlAudioStopSfx
|
||||||
|
|
@ -300,13 +307,12 @@ void jlAudioStopSfx(uint8_t slot);
|
||||||
void jlAudioTone(uint16_t freqHz);
|
void jlAudioTone(uint16_t freqHz);
|
||||||
----
|
----
|
||||||
|
|
||||||
Direct hardware tone generator: plays a square wave continuously until the next
|
A single tone that plays until the next call: `jlAudioVoice(0, freqHz, 0)`,
|
||||||
call. Designed for AGI-era music where the engine programs a divisor and lets
|
tone voice 0 at full volume, on every port. Designed for AGI-era music where
|
||||||
the chip play, so CPU cost during playback is zero. Single voice; uses voice 0
|
the engine programs a divisor and lets the chip play, so CPU cost during
|
||||||
on platforms with several oscillators. On DOS this programs PIT counter 2 and
|
playback is zero. A program that mixes it with `jlAudioVoice` or `jlMusicPlay`
|
||||||
the speaker gate; on DVX it is a square tone of DVX's sound library, on the FM
|
shares voice 0 with them. On DOS without an OPL2 (AdLib) chip, voice 0, and so
|
||||||
chip when there is one; on SDL3 it is voice 0 of the port's mixer at full
|
this tone, is the PC speaker.
|
||||||
volume.
|
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -314,8 +320,7 @@ volume.
|
||||||
|`freqHz` |`uint16_t` |Frequency in Hz. 0 silences the generator.
|
|`freqHz` |`uint16_t` |Frequency in Hz. 0 silences the generator.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing. A no-op on platforms with no reachable tone generator --
|
*Returns:* nothing. Silent wherever voice 0 is (see `jlAudioVoice`).
|
||||||
fall back to `jlAudioPlaySfxStream` there if you need synthesised sound.
|
|
||||||
|
|
||||||
=== jlAudioVoice
|
=== jlAudioVoice
|
||||||
|
|
||||||
|
|
@ -329,33 +334,32 @@ Three-voice PSG-style polyphonic tone generator.
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`voice` |`uint8_t` |Voice 0, 1 or 2.
|
|`voice` |`uint8_t` |Voice 0, 1, or 2.
|
||||||
|`freqHz` |`uint16_t` |Frequency in Hz. 0 silences this voice.
|
|`freqHz` |`uint16_t` |Frequency in Hz. 0 silences this voice.
|
||||||
|`atten` |`uint8_t` |AGI / SN76489-style 4-bit attenuation: 0 loudest,
|
|`atten` |`uint8_t` |AGI / SN76489-style 4-bit attenuation: 0 loudest,
|
||||||
15 silent, about 2 dB per step.
|
15 silent, 2 dB per step as near as the chip comes (the ST's YM2149 has
|
||||||
|
sixteen levels about 3.5 dB apart, so a step there is within 1.6 dB).
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing.
|
*Returns:* nothing.
|
||||||
|
|
||||||
Mapping to hardware: DOS uses AdLib OPL2 melodic channels 0..2; the ST uses
|
Mapping to hardware: DOS uses AdLib OPL2 melodic channels 0..2 (with no OPL2,
|
||||||
YM2149 channels A, B and C; the Amiga uses Paula channels 0..2 looping a
|
voice 0 is the PC speaker and voices 1 and 2 are silent); the ST uses YM2149
|
||||||
two-byte square sample; the IIgs uses Ensoniq DOC oscillators playing a
|
channels A, B, and C (on the plain ST voice 0 is silent while digital sound
|
||||||
resident square; DVX plays square tones of its sound library, which puts them
|
plays); the Amiga uses Paula channels 0..2 looping a two-byte square sample;
|
||||||
on the FM chip when there is a free voice and mixes them otherwise; SDL3 mixes
|
the IIgs uses Ensoniq DOC oscillators playing a resident square on sound
|
||||||
square waves in software.
|
effect slots 0..2; the X68000 uses YM2151 channels 0..2, one operator each,
|
||||||
|
so its tones are sine waves; DVX plays square tones of its sound library,
|
||||||
Voice state is sticky -- a voice keeps its last frequency until you change it
|
which puts them on the FM chip when there is a free voice and mixes them
|
||||||
-- and CPU cost during sustained playback is zero, because the hardware
|
otherwise; the hosted ports mix square waves in software. The OPL2's waves
|
||||||
oscillates on its own.
|
are not square either.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
The IIgs is the exception to stickiness: each call is an approximately 80 ms
|
A note sounds until the next call for that voice. The IIgs plays each note as
|
||||||
retriggered one-shot, so a note held longer must be re-asserted about once a
|
a sample a fraction of a second long and restarts the notes being held from
|
||||||
frame. It also requires `jlAudioInit`, because the DOC path rides the tracker
|
`jlAudioFrameTick`, so call that every frame, as every program should; a frame
|
||||||
engine. On DOS, ST and Amiga this is direct hardware programming and works
|
much longer than 80 ms can leave a gap in a held note there.
|
||||||
without `jlAudioInit`; on DVX it opens the program's sound on first use. On
|
|
||||||
SDL3 the voices are mixed in software and sound only after `jlAudioInit`.
|
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioNoise
|
=== jlAudioNoise
|
||||||
|
|
@ -378,13 +382,17 @@ layer.
|
||||||
|
|
||||||
*Returns:* nothing.
|
*Returns:* nothing.
|
||||||
|
|
||||||
Mapping: the ST uses the YM2149 noise generator mixed onto channel C; DOS uses
|
The noise runs at 125000 / `pitch` Hz (`pitch` 0 as 1), the YM2149's rate, on
|
||||||
the OPL2 rhythm-mode snare, leaving the three tone channels untouched; the
|
every port as far as its chip goes. Mapping: the ST uses the YM2149 noise
|
||||||
Amiga uses Paula channel 3 with an LFSR noise sample; the IIgs uses a resident
|
generator mixed onto channel C; the X68000 the YM2151's noise, at the same
|
||||||
noise sample on slot 3 with the same retriggered one-shot model as its tone
|
rates exactly; the Amiga Paula channel 3 looping an LFSR noise sample, whose
|
||||||
voices; DVX uses a noise tone of its sound library; SDL3 mixes the YM2149's
|
fastest rate, 28.6 kHz, pitches 0 to 4 all play at; the IIgs a resident noise
|
||||||
own noise in software (its 17-bit generator, clocked at 125 kHz divided by
|
sample on slot 3, up to 26 kHz, likewise for pitches 0 to 4; DVX a noise tone
|
||||||
the pitch), so it sounds as on the ST.
|
of its sound library; and the hosted ports mix the YM2149's own noise in
|
||||||
|
software (its 17-bit generator), so it sounds as on the ST. DOS uses the OPL2
|
||||||
|
rhythm-mode snare, leaving the three tone channels untouched: a tuned drum,
|
||||||
|
not free-running noise, whose pitch spreads over the snare's range rather than
|
||||||
|
following the YM2149's rate.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
|
|
@ -406,7 +414,7 @@ is no CPU mixing, this is fully viable on the ST and cheap on DOS, which
|
||||||
`.MOD` playback is not.
|
`.MOD` playback is not.
|
||||||
|
|
||||||
Playback is pumped by `jlAudioFrameTick` and paced by `jlMillisElapsed`, so
|
Playback is pumped by `jlAudioFrameTick` and paced by `jlMillisElapsed`, so
|
||||||
tempo holds across 50, 60 and 70 Hz ports.
|
tempo holds across 50, 60, and 70 Hz ports.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -419,13 +427,8 @@ tempo holds across 50, 60 and 70 Hz ports.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` on success; `false` on a malformed stream, in which case
|
*Returns:* `true` on success; `false` on a malformed stream, in which case
|
||||||
nothing plays.
|
nothing plays. With `jlConfigT.noAudio` the song runs silently, its time and
|
||||||
|
its end as usual.
|
||||||
[NOTE]
|
|
||||||
====
|
|
||||||
Call `jlAudioInit` first for the song to be audible on the IIgs -- see
|
|
||||||
`jlAudioVoice`.
|
|
||||||
====
|
|
||||||
|
|
||||||
=== jlMusicStop
|
=== jlMusicStop
|
||||||
|
|
||||||
|
|
@ -507,35 +510,55 @@ frame, so the chip only hears the last write and short notes vanish.
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`fn` |`jlAudioTickFnT` |Callback, fired from interrupt context. Pass `NULL` to
|
|`fn` |`jlAudioTickFnT` |Callback, which may be fired from an interrupt. Pass
|
||||||
uninstall.
|
`NULL` to uninstall.
|
||||||
|`hz` |`uint16_t` |Firings per second.
|
|`hz` |`uint16_t` |Firings per second.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` if installed. Ports without a suitable timer source return
|
*Returns:* `true` if installed. `false` when `hz` is 0, when audio is off or
|
||||||
`false`.
|
did not start, or when the port cannot take the rate (above 1000 Hz on DVX, or
|
||||||
|
above the mixer's rate on the hosted ports). Uninstalling always returns
|
||||||
|
`true`.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
`fn` runs in interrupt context. Keep it to memory reads and direct hardware
|
`fn` may run in an interrupt: it does on DOS, the Amiga, the ST, and the
|
||||||
register writes -- no allocation, no `printf`, nothing blocking.
|
X68000. Keep it to memory reads and direct hardware register writes -- no
|
||||||
|
allocation, no `printf`, nothing blocking -- and share data with it through
|
||||||
|
`jlAudioCriticalEnter` and `jlAudioCriticalExit`.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
On every port the calls average exactly `hz` a second; where they fall
|
||||||
|
differs. DOS, the Amiga, and the hosted ports place each one at its own time.
|
||||||
|
The ST and X68000 deliver them from the system's own timer, 200 and 100 times a
|
||||||
|
second, so the calls due between two of its ticks come together. The IIgs and
|
||||||
|
DVX deliver them in program time from `jlAudioFrameTick`, so a frame's calls
|
||||||
|
come together at it.
|
||||||
|
|
||||||
On DOS this reprograms PIT counter 0 and hooks INT 8, keeping the BIOS tick
|
On DOS this reprograms PIT counter 0 and hooks INT 8, keeping the BIOS tick
|
||||||
counter advancing at the canonical 18.2 Hz through an accumulator so `time`
|
counter advancing at the canonical 18.2 Hz through an accumulator so `time`
|
||||||
stays correct. `jlMillisElapsed` carries on from where it was when the tick
|
stays correct. `jlMillisElapsed` carries on from where it was when the tick
|
||||||
starts or stops; it never jumps back.
|
starts or stops; it never jumps back.
|
||||||
|
|
||||||
|
On the Amiga a `timer.device` request on the E-clock raises a software
|
||||||
|
interrupt at each tick. On the ST the tick rides TOS's 200 Hz Timer C
|
||||||
|
interrupt, and on the X68000 Human68k's 100 Hz Timer C interrupt.
|
||||||
|
|
||||||
|
On the IIgs `fn` runs in program time, at `jlAudioFrameTick`, as many times as
|
||||||
|
the display's beats since the last frame have made due, so the average is
|
||||||
|
exact whatever the program's frame rate.
|
||||||
|
|
||||||
On DVX a timer of DVX's (at most 1000 Hz) counts the ticks, and `fn` runs in
|
On DVX a timer of DVX's (at most 1000 Hz) counts the ticks, and `fn` runs in
|
||||||
program time, once per counted tick, at the next `jlAudioFrameTick`: DVX's
|
program time, once per counted tick, at the next `jlAudioFrameTick`: DVX's
|
||||||
sound library, which `jlAudioVoice` and the rest go through there, may not be
|
sound library, which `jlAudioVoice` and the rest go through there, may not be
|
||||||
called from an interrupt. `fn` may therefore do anything, but what it does
|
called from an interrupt. `fn` may therefore do anything, but what it does
|
||||||
lands on the frame, not between frames.
|
lands on the frame, not between frames.
|
||||||
|
|
||||||
On SDL3 the sound is mixed in program time, a few frames ahead, and `fn` is
|
On the hosted ports the sound is mixed in program time, a few frames ahead,
|
||||||
called by the mixer between exactly the two samples where its tick falls: in
|
and `fn` is called by the mixer between exactly the two samples where its tick
|
||||||
program time, so it may do anything, yet a note it starts begins at the right
|
falls: in program time, so it may do anything, yet a note it starts begins at
|
||||||
place in the sound. `hz` may be anything up to the mixer's 44100.
|
the right place in the sound. `hz` may be anything up to the mixer's rate,
|
||||||
|
44100 (22050 on Win16).
|
||||||
|
|
||||||
=== jlAudioCriticalEnter / jlAudioCriticalExit
|
=== jlAudioCriticalEnter / jlAudioCriticalExit
|
||||||
|
|
||||||
|
|
@ -547,8 +570,10 @@ void jlAudioCriticalExit(void);
|
||||||
|
|
||||||
Critical section against the tick interrupt. If you mutate state that the
|
Critical section against the tick interrupt. If you mutate state that the
|
||||||
registered tick callback reads, wrap the mutation in these so the interrupt
|
registered tick callback reads, wrap the mutation in these so the interrupt
|
||||||
cannot observe a half-written value. On DOS this is `cli` and `sti`; on ports
|
cannot observe a half-written value. On DOS, the Amiga, the ST, and the
|
||||||
with no audio interrupt, and on DVX and SDL3, where the callback runs in
|
X68000 a section holds every interrupt off, so keep it short. Sections nest:
|
||||||
program time, both are no-ops.
|
only the outermost pair does anything, and its Exit puts back the interrupt
|
||||||
|
state its Enter found. On the IIgs, DVX, and the hosted ports, whose
|
||||||
|
callbacks run in program time, they do nothing, and need not.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Declared in `<joey/core.h>`, included by `<joey/joey.h>`.
|
||||||
----
|
----
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t codegenBytes;
|
uint32_t codegenBytes;
|
||||||
|
bool noAudio;
|
||||||
} jlConfigT;
|
} jlConfigT;
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -18,13 +19,26 @@ typedef struct {
|
||||||
|`codegenBytes`
|
|`codegenBytes`
|
||||||
|`uint32_t`
|
|`uint32_t`
|
||||||
|Size of the runtime compiled-sprite cache. `0` selects a per-platform
|
|Size of the runtime compiled-sprite cache. `0` selects a per-platform
|
||||||
default: 8 KB on IIgs, 32 KB on Amiga, ST, DOS and DVX (SDL3 interprets every
|
default: 8 KB on the IIgs, 64 KB on the Amiga and ST, and 32 KB on the
|
||||||
sprite and leaves the cache unused). On the IIgs the cache is
|
X68000, DOS, DVX, and Win16. The other hosted ports interpret every sprite
|
||||||
one Memory Manager block that may not span a 64 KB bank boundary, so values
|
and never use the cache, but still allocate it (32 KB by default). The
|
||||||
above 65536 are clamped to 65536, and even a clamped request can fail if no
|
cache is one block, and `jlInit` fails if it cannot be had -- except on the
|
||||||
free bank exists. Size it against `jlSpriteCompiledSize()`.
|
IIgs, where it is up to four Memory Manager blocks of at most 64 KB, each
|
||||||
|
inside one 64 KB bank, so at most 256 KB; a bank that is refused is tried
|
||||||
|
again at 60 KB, and `jlInit` takes whatever part of the request it was
|
||||||
|
granted. Size it against `jlSpriteCompiledSize()`.
|
||||||
|
|
||||||
|
|`noAudio`
|
||||||
|
|`bool`
|
||||||
|
|`true` leaves audio off: every audio call is silent, as on a machine without
|
||||||
|
sound, and the program pays nothing for audio (on the IIgs no replayer read
|
||||||
|
from disk, on DOS and the Amiga no interrupt hooked). `false` has `jlInit`
|
||||||
|
start audio and `jlShutdown` stop it.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
Set every field: a `jlConfigT` is read whole, so a field left unset is
|
||||||
|
whatever the memory held.
|
||||||
|
|
||||||
=== jlInit
|
=== jlInit
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -33,8 +47,11 @@ bool jlInit(const jlConfigT *config);
|
||||||
----
|
----
|
||||||
|
|
||||||
Initialises the library: takes over the display, allocates the stage and the
|
Initialises the library: takes over the display, allocates the stage and the
|
||||||
codegen arena, and brings up input. Must be called before any other JoeyLib
|
codegen arena, brings up input, and last starts audio (unless `noAudio`). Must
|
||||||
function.
|
be called before any other JoeyLib function. Sound that does not start is not
|
||||||
|
a failure: modules and sound effects are then silent, and the log says so
|
||||||
|
(<<_debug_logging>>). The tone and noise voices still play on the ports whose
|
||||||
|
sound chip needs no engine: the Amiga, the ST, DOS, and the X68000.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -43,7 +60,9 @@ function.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` on success. On failure returns `false` and `jlLastError()`
|
*Returns:* `true` on success. On failure returns `false` and `jlLastError()`
|
||||||
describes the reason.
|
describes the reason. Where the port's own start-up fails without saying
|
||||||
|
why, as the IIgs, Amiga, ST, X68000, and DVX ports' can, the reason is just
|
||||||
|
`jlpInit failed`.
|
||||||
|
|
||||||
=== jlShutdown
|
=== jlShutdown
|
||||||
|
|
||||||
|
|
@ -52,8 +71,10 @@ describes the reason.
|
||||||
void jlShutdown(void);
|
void jlShutdown(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Shuts the library down, restoring the display mode, releasing all library
|
Shuts the library down: stops audio first, then restores the display mode,
|
||||||
resources and un-hooking any interrupt vectors the port installed. Call it on
|
releases the library's own resources, and un-hooks any interrupt vectors the
|
||||||
|
port installed. Surfaces and sprites the program created are not released:
|
||||||
|
destroy them with `jlSurfaceDestroy` and `jlSpriteDestroy` first. Call it on
|
||||||
every exit path; skipping it can leave the machine unusable.
|
every exit path; skipping it can leave the machine unusable.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
@ -104,9 +125,10 @@ void *jlAlloc(uint32_t bytes);
|
||||||
|
|
||||||
Allocates a large or persistent buffer from the platform's native allocator
|
Allocates a large or persistent buffer from the platform's native allocator
|
||||||
rather than the small C heap. *Use this instead of `malloc` for anything
|
rather than the small C heap. *Use this instead of `malloc` for anything
|
||||||
substantial.* On the IIgs the C heap lives in bank 0 and is tiny, with a single
|
substantial.* On the IIgs the whole C heap is one Memory Manager block of
|
||||||
`malloc` capped around 32 KB, so multi-kilobyte caches and frame buffers must
|
32 KB to 48 KB (and, if that block cannot be had, only what the program's link
|
||||||
come from the Memory Manager instead.
|
left in its bank, which can be a few hundred bytes), so multi-kilobyte caches
|
||||||
|
and frame buffers must come from the Memory Manager instead.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -124,7 +146,9 @@ zero-filled.
|
||||||
void jlFree(void *p);
|
void jlFree(void *p);
|
||||||
----
|
----
|
||||||
|
|
||||||
Releases a buffer from `jlAlloc`.
|
Releases a buffer from `jlAlloc`. Never mix `jlFree` and `free`: on most
|
||||||
|
ports they are the same thing, but on the IIgs a `jlAlloc` block is a Memory
|
||||||
|
Manager handle, and either call given the other's block corrupts memory.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -164,8 +188,11 @@ void jlWaitVBL(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Blocks until the next display vertical blank, pacing a game loop to the
|
Blocks until the next display vertical blank, pacing a game loop to the
|
||||||
display's native refresh rate. Cheap on every port, because the underlying
|
display's native refresh rate. On the machine ports it waits for the
|
||||||
mechanism is always a hardware-level wait rather than a software timer.
|
display's own vertical blank. The hosted ports, and DVX in a window, have no
|
||||||
|
vertical blank to wait for: there it waits for the next tick of a virtual
|
||||||
|
frame clock, 60 Hz on the hosted ports and 70 Hz on DVX, and nothing stops a
|
||||||
|
present from tearing.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
|
@ -178,13 +205,15 @@ uint16_t jlFrameCount(void);
|
||||||
|
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* a monotonic 16-bit frame counter.
|
*Returns:* a monotonic 16-bit frame counter. An interrupt or a clock keeps it,
|
||||||
|
so it counts every frame however seldom you read it -- except on an X68000
|
||||||
|
whose system refuses JoeyLib's frame interrupt, where it counts only while
|
||||||
|
`jlWaitVBL`, `jlFrameCount`, or `jlStagePresent` is being called.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
You must poll faster than twice `jlFrameHz()` or edges are missed, and the
|
The counter wraps roughly every 18 minutes at 60 Hz, so compare two readings
|
||||||
counter wraps roughly every 18 minutes at 60 Hz. For elapsed-time work prefer
|
by subtracting them. For elapsed-time work prefer `jlMillisElapsed`.
|
||||||
`jlMillisElapsed`.
|
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlFrameHz
|
=== jlFrameHz
|
||||||
|
|
@ -196,13 +225,16 @@ uint16_t jlFrameHz(void);
|
||||||
|
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* the nominal display frame rate in Hz -- 50 on Amiga PAL, 60 on IIgs
|
*Returns:* the nominal display frame rate in Hz -- 60 on the IIgs, or 50 on a
|
||||||
and ST NTSC, 55 on the X68000, 70 on VGA mode 13h and on DVX, whose window has
|
PAL machine; 50 on the Amiga, whose screen is PAL, or 60 on an NTSC Amiga that
|
||||||
a virtual 70 Hz (and whose full screen is mode 13h's, or a VBE mode's, with the
|
cannot show PAL; 50 or 60 on the ST, as the machine is set; 55 on the X68000;
|
||||||
same frame count), and 60 under SDL3, whose window has a virtual 60 Hz. The X68000 times its display at `jlInit` and rounds to the
|
70 on VGA mode 13h and on DVX, whose window has a virtual 70 Hz (and whose full
|
||||||
nearest whole number: 55.46 Hz on the machine, 56.07 Hz under MAME, which
|
screen is mode 13h's, or a VBE mode's, with the same frame count); and 60 on
|
||||||
therefore reports 56. The real cadence may drift slightly; this is the figure
|
every hosted port, whose window has a virtual 60 Hz. The X68000 times its
|
||||||
to divide by when converting frames to seconds.
|
display at `jlInit` and rounds to the nearest whole number: 55.46 Hz on the
|
||||||
|
machine, 56.07 Hz under MAME, which therefore reports 56. The real cadence may
|
||||||
|
drift slightly; this is the figure to divide by when converting frames to
|
||||||
|
seconds.
|
||||||
|
|
||||||
=== jlMillisElapsed
|
=== jlMillisElapsed
|
||||||
|
|
||||||
|
|
@ -211,10 +243,9 @@ to divide by when converting frames to seconds.
|
||||||
uint32_t jlMillisElapsed(void);
|
uint32_t jlMillisElapsed(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Monotonic millisecond counter since `jlInit`. DOS, ST, X68000, DVX and SDL3 read a
|
Monotonic millisecond counter since `jlInit`. It may advance a frame at a
|
||||||
real timer; IIgs and Amiga derive it from the frame counter, so the
|
time. Two calls separated by a long render report the true elapsed time, not
|
||||||
value steps at frame granularity there. Two calls separated by a long render report the true
|
the number of vertical blanks that fired.
|
||||||
elapsed time, not the number of vertical blanks that fired.
|
|
||||||
|
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
|
|
@ -222,8 +253,8 @@ elapsed time, not the number of vertical blanks that fired.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
On the frame-derived ports you must poll at least once every 65535 frames --
|
Call it at least once every 15 minutes, or the time between two calls can be
|
||||||
about 18 minutes at 60 Hz -- or wrapped frames are lost.
|
lost.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlRandom
|
=== jlRandom
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,40 @@
|
||||||
|
|
||||||
Declared in `<joey/debug.h>`.
|
Declared in `<joey/debug.h>`.
|
||||||
|
|
||||||
A crash-tracing logger that writes to a file beside the executable (on DVX,
|
A crash-tracing logger that writes `joeylog.txt` in the current directory
|
||||||
in the program's settings folder, `CONFIG\APPS\JOEYLIB\NAME\JOEYLOG.TXT`).
|
(on DVX, in the program's settings folder,
|
||||||
Debugging a program on an emulated 1988 machine is awkward, and this is
|
`CONFIG\APPS\JOEYLIB\NAME\JOEYLOG.TXT`). Debugging a program on an emulated
|
||||||
usually the fastest way to find out what it did before it stopped doing it.
|
1988 machine is awkward, and this is usually the fastest way to find out what
|
||||||
The headless run scripts retrieve the log automatically.
|
it did before it stopped doing it. The headless run scripts retrieve the log
|
||||||
|
automatically. The library itself writes to it only when something fails, such
|
||||||
|
as sound that does not start.
|
||||||
|
|
||||||
|
How the file is written differs:
|
||||||
|
|
||||||
|
* *Every port but the IIgs* opens the file on the first line and appends to
|
||||||
|
it, writing each line out as it is logged, so a crash or hang leaves every
|
||||||
|
line before it on disk. The file grows across runs until `jlLogReset`.
|
||||||
|
* *The IIgs* keeps the lines in an 8 KB ring in memory, because a write per
|
||||||
|
line costs seconds of floppy time there. Nothing reaches the disk until
|
||||||
|
`jlLogFlush`, which replaces the file with the last 8 KB logged. A program
|
||||||
|
that never calls it leaves no log.
|
||||||
|
* *The web* writes the file into the page's memory, where it is lost when the
|
||||||
|
page closes.
|
||||||
|
* *The hosted ports* make the program's folder the current directory in
|
||||||
|
`jlInit`: a line logged before that goes to a log in the directory the
|
||||||
|
program was started from, and the rest of the run's lines follow it there.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
The logger needs a multi-kilobyte ring buffer, which the tightest targets
|
The logger's buffer is linked into every program, since the library logs its
|
||||||
cannot spare. Space Taxi compiles it out entirely on the IIgs and X68000 for
|
own failures: the 8 KB ring on the IIgs, and a 16 KB file buffer on every
|
||||||
that reason. If you are debugging a memory-tight program, expect to do the
|
other port but DVX. A memory-tight program can still leave its own log calls
|
||||||
same and to observe hardware state through the emulator instead.
|
out and observe hardware state through the emulator instead.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
On DOS the library also creates `CRASH.LOG` in the current directory at
|
||||||
|
`jlInit`, and writes the processor's registers there if the program faults.
|
||||||
|
|
||||||
=== jlLog
|
=== jlLog
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -41,13 +61,14 @@ void jlLogF(const char *fmt, ...);
|
||||||
----
|
----
|
||||||
|
|
||||||
Appends a formatted message, `printf` style, as one line: like `jlLog`, the
|
Appends a formatted message, `printf` style, as one line: like `jlLog`, the
|
||||||
library ends the line, so the format needs no trailing `\n`.
|
library ends the line, so the format needs no trailing `\n`. On the IIgs a
|
||||||
|
line is cut at 199 characters.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`fmt` |`const char *` |Format string.
|
|`fmt` |`const char *` |Format string.
|
||||||
|`...` | |Arguments matching the format string.
|
|`+...+` | |Arguments matching the format string.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing.
|
*Returns:* nothing.
|
||||||
|
|
@ -66,8 +87,10 @@ time.
|
||||||
void jlLogFlush(void);
|
void jlLogFlush(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Flushes buffered log output to disk. Call it before anything that might crash
|
Flushes buffered log output to disk. On the IIgs it is the only way the log
|
||||||
or exit, or the last and most interesting lines may never be written.
|
reaches the disk: it rewrites the whole file with the last 8 KB logged, so call
|
||||||
|
it before anything that might crash or exit, or the last and most interesting
|
||||||
|
lines are never written. Every other port has already written each line.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
|
@ -79,7 +102,8 @@ void jlLogReset(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Clears the log, discarding previous contents. Call once at startup so each run
|
Clears the log, discarding previous contents. Call once at startup so each run
|
||||||
produces a fresh log rather than appending to the last one.
|
produces a fresh log rather than appending to the last one. On the IIgs it
|
||||||
|
clears the ring only; the file on disk changes at the next `jlLogFlush`.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
|
@ -89,6 +113,7 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ no-ops, never errors, and there are no unclipped variants in the public API --
|
||||||
you cannot corrupt memory by drawing off the edge. Every primitive marks the
|
you cannot corrupt memory by drawing off the edge. Every primitive marks the
|
||||||
rows it touched, so `jlStagePresent` knows what to copy.
|
rows it touched, so `jlStagePresent` knows what to copy.
|
||||||
|
|
||||||
Primitives work in colour indices. Per-scanline palette resolution happens only
|
Primitives work in colour indices, and use only the low 4 bits of one.
|
||||||
at display time, so drawing does not care which palette a line uses.
|
Per-scanline palette resolution happens only at display time, so drawing does
|
||||||
|
not care which palette a line uses.
|
||||||
|
|
||||||
=== jlSurfaceClear
|
=== jlSurfaceClear
|
||||||
|
|
||||||
|
|
@ -185,8 +186,10 @@ void jlFloodFill(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor);
|
||||||
----
|
----
|
||||||
|
|
||||||
Flood-fills a 4-connected region, replacing every pixel of the original colour
|
Flood-fills a 4-connected region, replacing every pixel of the original colour
|
||||||
reachable by north, south, east and west steps. A no-op if the start point is
|
reachable by north, south, east, and west steps. A no-op if the start point is
|
||||||
off-surface or already `newColor`.
|
off-surface or already `newColor`. The fill keeps a 512-entry stack of spans
|
||||||
|
still to visit, and a region complex enough to need more is left partly
|
||||||
|
filled, the same way on every port.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -208,7 +211,9 @@ void jlFloodFillBounded(jlSurfaceT *s, int16_t x, int16_t y,
|
||||||
|
|
||||||
Flood-fills a 4-connected region, stopping at pixels of `boundaryColor` rather
|
Flood-fills a 4-connected region, stopping at pixels of `boundaryColor` rather
|
||||||
than at pixels differing from the seed colour. Every reachable pixel that is
|
than at pixels differing from the seed colour. Every reachable pixel that is
|
||||||
not the boundary colour becomes `newColor`.
|
not the boundary colour becomes `newColor`. Pixels that are `newColor` already
|
||||||
|
stop the fill too, so a region behind them is not reached, and a start on
|
||||||
|
either colour does nothing. The 512-entry span stack of `jlFloodFill` applies.
|
||||||
|
|
||||||
This is the primitive for vector-art playback, such as Sierra-style picture
|
This is the primitive for vector-art playback, such as Sierra-style picture
|
||||||
drawing: outline a closed region with `jlDrawLine` in the boundary colour, then
|
drawing: outline a closed region with `jlDrawLine` in the boundary colour, then
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,47 @@ Declared in `<joey/file.h>`.
|
||||||
Two directories sit beside the executable, staged there by each platform's disk
|
Two directories sit beside the executable, staged there by each platform's disk
|
||||||
packager:
|
packager:
|
||||||
|
|
||||||
* `DATA/` -- read-only game content: levels, sprites, audio.
|
* `DATA/` -- game content: levels, sprites, audio.
|
||||||
* `SAVES/` -- writable saves and preferences.
|
* `SAVES/` -- writable saves and preferences.
|
||||||
|
|
||||||
DVX is the exception, because its programs share one current directory:
|
Where the calls look for them:
|
||||||
there `DATA/` is beside the program, in its folder under DVX's `APPS`, and
|
|
||||||
`SAVES/` is in the program's own settings folder, `CONFIG\APPS\JOEYLIB\NAME`,
|
* *The hosted ports* make the program's own folder the current directory in
|
||||||
which DVX keeps apart from the program files so a reinstall never overwrites
|
`jlInit`, so both are found beside the program wherever it was started
|
||||||
it. The calls below find both there by themselves.
|
from.
|
||||||
|
* *DOS, the ST, the Amiga, and the X68000* look in the current directory. A
|
||||||
|
program started from its own folder (as a desktop starts it) finds them;
|
||||||
|
one started from a command line elsewhere finds no `DATA/` and makes its
|
||||||
|
`SAVES/` there.
|
||||||
|
* *The IIgs* looks for `DATA/` under GS/OS prefix 0 and puts `SAVES/` in the
|
||||||
|
application's folder (prefix 1). When the two differ, data and saves come
|
||||||
|
from different folders.
|
||||||
|
* *DVX* is the exception, because its programs share one current directory:
|
||||||
|
there `DATA/` is beside the program, in its folder under DVX's `APPS`, and
|
||||||
|
`SAVES/` is in the program's own settings folder,
|
||||||
|
`CONFIG\APPS\JOEYLIB\NAME`, which DVX keeps apart from the program files so
|
||||||
|
a reinstall never overwrites it. The calls below find both there by
|
||||||
|
themselves.
|
||||||
|
|
||||||
Both are addressed by a *bare name relative to their directory*. The prefix is
|
Both are addressed by a *bare name relative to their directory*. The prefix is
|
||||||
added for you, so never hard-code `"DATA/"` or `"SAVES/"` yourself.
|
added for you, so never hard-code `"DATA/"` or `"SAVES/"` yourself.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
Keep names portable across the retro filesystems: at most 8 characters plus a
|
Keep names portable across the retro filesystems: at most 8 characters plus
|
||||||
short extension, letters and digits only, so they satisfy both ProDOS and DOS
|
an extension of up to 3, letters and digits only, starting with a letter, so
|
||||||
8.3. Do not depend on case being preserved.
|
they satisfy both ProDOS (whose names must start with a letter) and DOS 8.3.
|
||||||
|
The save calls refuse any other name on every port, as if the file could not
|
||||||
|
be opened; names in `DATA/`, which ship with the program, are passed through
|
||||||
|
as they are. Do not depend on case being preserved, and use the same case
|
||||||
|
every time: on Linux, Haiku, BeOS, and the web, `HERO.SAV` and `hero.sav` are
|
||||||
|
different files.
|
||||||
====
|
====
|
||||||
|
|
||||||
A failed open or write is non-fatal and simply reports failure, so save code
|
A failed open or write is non-fatal and simply reports failure, so save code
|
||||||
can degrade gracefully on a platform or volume where `SAVES/` is unavailable.
|
can degrade gracefully on a platform or volume where `SAVES/` is unavailable.
|
||||||
|
On the IIgs at most 21 files can be open at once, and saves are created as
|
||||||
|
text files (type TXT).
|
||||||
|
|
||||||
=== jlDataOpen
|
=== jlDataOpen
|
||||||
|
|
||||||
|
|
@ -34,7 +54,10 @@ can degrade gracefully on a platform or volume where `SAVES/` is unavailable.
|
||||||
FILE *jlDataOpen(const char *name, const char *mode);
|
FILE *jlDataOpen(const char *name, const char *mode);
|
||||||
----
|
----
|
||||||
|
|
||||||
Opens a read-only data file. The `DATA/` prefix is prepended automatically.
|
Opens a data file. The `DATA/` prefix is prepended automatically. The mode is
|
||||||
|
passed to `fopen` as it is, so a writing mode works where the medium allows it,
|
||||||
|
but `DATA/` is meant to be read: on the web, for instance, what is written
|
||||||
|
there is lost when the page closes.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -74,7 +97,9 @@ whole-file blob prefer `jlSaveWrite` and `jlSaveRead`.
|
||||||
bool jlSaveWrite(const char *name, const void *buf, uint32_t len);
|
bool jlSaveWrite(const char *name, const void *buf, uint32_t len);
|
||||||
----
|
----
|
||||||
|
|
||||||
Writes a whole save file, replacing any existing contents.
|
Writes a whole save file, replacing any existing contents. The write is not
|
||||||
|
atomic: if it fails part way (a full disk, say) the save is left short. A
|
||||||
|
`NULL` `buf` with a non-zero `len` returns `false` and leaves the save alone.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -145,8 +170,11 @@ uint32_t jlDiskFree(void);
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* free space in bytes on the volume holding `SAVES/`. Returns 0 if it
|
*Returns:* free space in bytes on the volume holding `SAVES/`. Returns 0 if it
|
||||||
cannot be determined -- some ports have no query for it -- and saturates at
|
cannot be determined, and saturates at `UINT32_MAX` on volumes larger than
|
||||||
`UINT32_MAX` on volumes larger than 4 GB.
|
4 GB. On DOS and Win16 the query (DOS's own) stops near 2 GB on a larger
|
||||||
|
volume, and on the web it is the browser's own estimate of what the site may
|
||||||
|
still store (its quota less what it uses), which the browser may round or
|
||||||
|
pad.
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
Declared in `<joey/input.h>`.
|
Declared in `<joey/input.h>`.
|
||||||
|
|
||||||
Call `jlInputPoll()` exactly once per frame to refresh keyboard, mouse and
|
Call `jlInputPoll()` exactly once per frame to refresh keyboard, mouse, and
|
||||||
joystick state. Everything else in this section reads the state that poll
|
joystick state. Everything else in this section reads the state that poll
|
||||||
captured.
|
captured.
|
||||||
|
|
||||||
Two parallel keyboard interfaces share that one poll. The *predicates* answer
|
Two parallel keyboard interfaces share that one poll. The *predicates* answer
|
||||||
"is the fire key held?", which is the right shape for games. The *character
|
"is the fire key held?", which is the right shape for games. The *character
|
||||||
queue* answers "which character did the user type?", with shift, caps lock and
|
queue* answers "which character did the user type?", with shift, caps lock, and
|
||||||
the machine's keyboard layout already applied, which is the right shape for
|
the machine's keyboard layout already applied, which is the right shape for
|
||||||
text entry. They coexist freely.
|
text entry. They coexist freely.
|
||||||
|
|
||||||
|
|
@ -20,32 +20,33 @@ transition happened.
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
typedef enum {
|
typedef enum {
|
||||||
KEY_NONE = 0,
|
JL_KEY_NONE = 0,
|
||||||
KEY_A .. KEY_Z,
|
JL_KEY_A .. JL_KEY_Z,
|
||||||
KEY_0 .. KEY_9,
|
JL_KEY_0 .. JL_KEY_9,
|
||||||
KEY_SPACE, KEY_ESCAPE, KEY_RETURN, KEY_TAB, KEY_BACKSPACE,
|
JL_KEY_SPACE, JL_KEY_ESCAPE, JL_KEY_RETURN, JL_KEY_TAB, JL_KEY_BACKSPACE,
|
||||||
KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT,
|
JL_KEY_UP, JL_KEY_DOWN, JL_KEY_LEFT, JL_KEY_RIGHT,
|
||||||
KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_LALT,
|
JL_KEY_LSHIFT, JL_KEY_RSHIFT, JL_KEY_LCTRL, JL_KEY_LALT,
|
||||||
KEY_F1 .. KEY_F10,
|
JL_KEY_F1 .. JL_KEY_F10,
|
||||||
KEY_COUNT
|
JL_KEY_COUNT
|
||||||
} jlKeyE;
|
} jlKeyE;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MOUSE_BUTTON_NONE = 0, MOUSE_BUTTON_LEFT,
|
JL_MOUSE_BUTTON_NONE = 0, JL_MOUSE_BUTTON_LEFT,
|
||||||
MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_COUNT
|
JL_MOUSE_BUTTON_RIGHT, JL_MOUSE_BUTTON_MIDDLE, JL_MOUSE_BUTTON_COUNT
|
||||||
} jlMouseButtonE;
|
} jlMouseButtonE;
|
||||||
|
|
||||||
typedef enum { JOYSTICK_0 = 0, JOYSTICK_1, JOYSTICK_COUNT } jlJoystickE;
|
typedef enum { JL_JOYSTICK_0 = 0, JL_JOYSTICK_1, JL_JOYSTICK_COUNT } jlJoystickE;
|
||||||
typedef enum { JOY_BUTTON_0 = 0, JOY_BUTTON_1, JOY_BUTTON_COUNT } jlJoyButtonE;
|
typedef enum { JL_JOY_BUTTON_0 = 0, JL_JOY_BUTTON_1, JL_JOY_BUTTON_COUNT } jlJoyButtonE;
|
||||||
----
|
----
|
||||||
|
|
||||||
Constants: `JOYSTICK_AXIS_MAX` (127) and `JOYSTICK_AXIS_MIN` (-127);
|
Constants: `JL_JOYSTICK_AXIS_MAX` (127) and `JL_JOYSTICK_AXIS_MIN` (-127);
|
||||||
`JL_CHAR_QUEUE_SIZE` (32).
|
`JL_CHAR_QUEUE_SIZE` (32).
|
||||||
|
|
||||||
Control codes delivered through the character queue: `JL_CHAR_BACKSPACE`
|
Control codes delivered through the character queue: `JL_CHAR_BACKSPACE`
|
||||||
(0x08), `JL_CHAR_TAB` (0x09), `JL_CHAR_RETURN` (0x0D), `JL_CHAR_ESCAPE` (0x1B)
|
(0x08), `JL_CHAR_TAB` (0x09), `JL_CHAR_RETURN` (0x0D), `JL_CHAR_ESCAPE` (0x1B),
|
||||||
and `JL_CHAR_DELETE` (0x7F). That last one is *forward* delete, the key marked
|
and `JL_CHAR_DELETE` (0x7F). That last one is *forward* delete, the key marked
|
||||||
Del -- a text editor needs both it and backspace.
|
Del -- a text editor needs both it and backspace. The IIgs never delivers it:
|
||||||
|
its Delete key is the backspace key, and types `JL_CHAR_BACKSPACE`.
|
||||||
|
|
||||||
=== jlInputPoll
|
=== jlInputPoll
|
||||||
|
|
||||||
|
|
@ -55,7 +56,9 @@ void jlInputPoll(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Refreshes all input state and refills the typed-character queue. Call once per
|
Refreshes all input state and refills the typed-character queue. Call once per
|
||||||
frame, typically just before drawing.
|
frame, typically just before drawing. A key pressed and let go between two
|
||||||
|
polls still reads down for one poll, so `jlKeyPressed` sees a quick tap
|
||||||
|
(except on DVX, which reads its keyboard only as it is at the poll).
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
||||||
|
|
@ -72,11 +75,13 @@ not polling means nothing ever changes.
|
||||||
int jlInputGetChar(void);
|
int jlInputGetChar(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Pops the next typed character. Shift, caps lock and the keyboard layout have
|
Pops the next typed character. Shift, caps lock, and the keyboard layout have
|
||||||
already been applied by the backend, so punctuation and shifted symbols arrive
|
already been applied by the backend, so punctuation and shifted symbols arrive
|
||||||
correctly. The layout is the machine's own on IIgs, Amiga, ST and X68000,
|
correctly. The layout is the machine's own on the IIgs, Amiga, ST, and
|
||||||
DVX's own on DVX and the operating system's on SDL3; the DOS port translates
|
X68000. It is DVX's own on DVX, and the operating system's on SDL3, the web,
|
||||||
with a fixed US layout.
|
Win32, Win16, Haiku, and BeOS. DOS, and the PSP's on-screen keyboard, use a
|
||||||
|
fixed US layout. On the ST a key held with Alt types nothing, so the
|
||||||
|
characters a national TOS puts on Alt cannot be typed.
|
||||||
|
|
||||||
Arrow keys are not characters and stay on the predicate API.
|
Arrow keys are not characters and stay on the predicate API.
|
||||||
|
|
||||||
|
|
@ -88,9 +93,16 @@ Arrow keys are not characters and stay on the predicate API.
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
The queue holds `JL_CHAR_QUEUE_SIZE - 1` characters, so 31. When full, further
|
The queue holds `JL_CHAR_QUEUE_SIZE - 1` characters, so 31. When full, further
|
||||||
characters are dropped until you pop some. Auto-repeat may deliver repeats on
|
characters are dropped until you pop some. A chord types nothing on any port:
|
||||||
ports whose keyboard services repeat; repeat policy is yours to manage. 7-bit
|
Ctrl+M is not Return and Ctrl+1 is not 1, and Alt chords type nothing where
|
||||||
ASCII only -- no Unicode, no input methods.
|
Alt is a command key (DOS, DVX, Windows, the Be ports' Command key, the PSP's
|
||||||
|
on-screen keyboard); the Amiga's Alt and Windows' AltGr type their layout's
|
||||||
|
characters, and so does the ST's Alt where TOS keeps its keyboard's Alt
|
||||||
|
layer in its tables (TOS 4.00 and later, and EmuTOS: a German keyboard's
|
||||||
|
@ [ ] { } \), and otherwise nothing (<<portsDifferInput>>). A held key
|
||||||
|
repeats its character on every port (the ST at the rate TOS is set to);
|
||||||
|
repeat policy is yours to manage. 7-bit ASCII only -- no Unicode, no input
|
||||||
|
methods.
|
||||||
|
|
||||||
The return type is plain `int`, not a stdint type, deliberately: this is the
|
The return type is plain `int`, not a stdint type, deliberately: this is the
|
||||||
`getchar()`-style "byte or -1" contract.
|
`getchar()`-style "byte or -1" contract.
|
||||||
|
|
@ -103,9 +115,12 @@ The return type is plain `int`, not a stdint type, deliberately: this is the
|
||||||
void jlWaitForAnyKey(void);
|
void jlWaitForAnyKey(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Blocks until the user presses any key, polling internally so per-port input
|
Blocks until the user presses a key -- one JoeyLib names (with a `jlKeyE`),
|
||||||
machinery -- including interrupt-driven audio samplers -- keeps running during
|
or one that types a character, punctuation for instance -- polling internally
|
||||||
the wait.
|
so per-port input machinery, including interrupt-driven audio samplers, keeps
|
||||||
|
running during the wait. The mouse and joystick buttons do not end it, and a
|
||||||
|
key already held when the wait starts must be let go and pressed again (its
|
||||||
|
auto-repeat does not count).
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing. The character queue is emptied on
|
*Parameters:* none. *Returns:* nothing. The character queue is emptied on
|
||||||
return, so the dismissing keystroke is consumed rather than delivered as text.
|
return, so the dismissing keystroke is consumed rather than delivered as text.
|
||||||
|
|
@ -129,6 +144,16 @@ bool jlKeyReleased(jlKeyE key);
|
||||||
`true` only in the frame the key went down; `jlKeyReleased` only in the frame
|
`true` only in the frame the key went down; `jlKeyReleased` only in the frame
|
||||||
it came up.
|
it came up.
|
||||||
|
|
||||||
|
A named key that two physical keys give is down while either is held, and
|
||||||
|
comes up with the last of them: `JL_KEY_RETURN` is Return and the keypad's
|
||||||
|
Enter; `JL_KEY_UP`, `JL_KEY_DOWN`, `JL_KEY_LEFT`, and `JL_KEY_RIGHT` are the
|
||||||
|
arrow keys and the keypad's 8, 2, 4, and 6; and `JL_KEY_LCTRL` and
|
||||||
|
`JL_KEY_LALT` are the left and right Ctrl and Alt, where the keyboard has
|
||||||
|
both (on the X68000, OPT.1 and OPT.2 are Alt). The keypad's keys still type
|
||||||
|
what the machine's keypad types (`jlInputGetChar`). The IIgs knows keys by
|
||||||
|
their characters, so its keypad reads as the digits
|
||||||
|
(<<portsDifferInput>>).
|
||||||
|
|
||||||
=== jlMousePresent
|
=== jlMousePresent
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -139,13 +164,10 @@ bool jlMousePresent(void);
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* `true` if the platform confirmed a mouse at init. On DOS that means
|
*Returns:* `true` if the platform confirmed a mouse at init. On DOS that means
|
||||||
the INT 33h driver reset successfully; on Amiga, ST, IIgs, X68000, DVX and
|
the INT 33h driver reset successfully; on the Amiga, that JoeyLib's input
|
||||||
SDL3 the OS mouse is always wired. On DVX the position is the pointer's over
|
window opened; on Win32 and Win16, that Windows reports a mouse; on SDL3 and
|
||||||
the program's window, divided by the window's scale; it keeps its last value
|
the web, that SDL reports one. The IIgs, ST, X68000, DVX, Haiku, and BeOS
|
||||||
while the pointer is elsewhere. On DVX's full screen it is the pointer's
|
always report one, and the PSP never does.
|
||||||
place on the stage (DVX draws no pointer there). On SDL3 it is the pointer's
|
|
||||||
place on the stage however the window scales it, held to the stage's edge
|
|
||||||
while the pointer is beside it; the OS pointer is hidden over the window.
|
|
||||||
|
|
||||||
=== jlMouseX / jlMouseY
|
=== jlMouseX / jlMouseY
|
||||||
|
|
||||||
|
|
@ -161,6 +183,14 @@ int16_t jlMouseY(void);
|
||||||
Until the platform reports the pointer, it is the middle of the stage,
|
Until the platform reports the pointer, it is the middle of the stage,
|
||||||
(160, 100), on every port.
|
(160, 100), on every port.
|
||||||
|
|
||||||
|
On DVX the position is the pointer's over the program's window, divided by
|
||||||
|
the window's scale; it keeps its last value while the pointer is elsewhere. On
|
||||||
|
DVX's full screen it is the pointer's place on the stage (DVX draws no pointer
|
||||||
|
there). On the hosted ports it is the pointer's place on the stage however the
|
||||||
|
window scales it, held to the stage's edge while the pointer is beside it; the
|
||||||
|
OS pointer is hidden over the window. On DOS the mouse driver keeps it in
|
||||||
|
range.
|
||||||
|
|
||||||
=== jlMouseDown / jlMousePressed / jlMouseReleased
|
=== jlMouseDown / jlMousePressed / jlMouseReleased
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -178,6 +208,10 @@ bool jlMouseReleased(jlMouseButtonE button);
|
||||||
|
|
||||||
*Returns:* held state, rising edge, and falling edge respectively.
|
*Returns:* held state, rising edge, and falling edge respectively.
|
||||||
|
|
||||||
|
The IIgs mouse has one button, `JL_MOUSE_BUTTON_LEFT`. The ST and X68000 have
|
||||||
|
left and right. The other ports report left, right, and middle, as far as the
|
||||||
|
mouse has them. The PSP has no mouse.
|
||||||
|
|
||||||
=== jlJoystickConnected
|
=== jlJoystickConnected
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -188,13 +222,20 @@ bool jlJoystickConnected(jlJoystickE js);
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`js` |`jlJoystickE` |Which stick, `JOYSTICK_0` or `JOYSTICK_1`.
|
|`js` |`jlJoystickE` |Which stick, `JL_JOYSTICK_0` or `JL_JOYSTICK_1`.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` if the platform reports a stick on that port. On the ST and
|
*Returns:* `true` if the platform reports a stick on that port. On the ST and
|
||||||
X68000 both ports always report one: an empty port cannot be told from a stick
|
X68000 both ports always report one: an empty port cannot be told from a stick
|
||||||
at rest. The Amiga (`lowlevel.library`) has the same blind spot the other
|
at rest. The Amiga (`lowlevel.library`) has the same blind spot the other
|
||||||
way: it reports a stick only once it has moved, and never a mouse.
|
way: it reports a stick only once it has moved, and never a mouse. The IIgs
|
||||||
|
has one stick, `JL_JOYSTICK_0`, and stops looking for it after about a second
|
||||||
|
with none seen, until `jlJoystickReset`. On DOS a stick is connected while the
|
||||||
|
game port times both its axes. SDL3, the web, and the PSP see SDL's gamepads
|
||||||
|
and joysticks both, a joystick SDL has no gamepad mapping for read by its first
|
||||||
|
two axes, its first hat, and its first two buttons; Win32, Win16, Haiku, and
|
||||||
|
BeOS see the system's joysticks, and Haiku and BeOS notice a new one within a
|
||||||
|
second.
|
||||||
|
|
||||||
=== jlJoystickX / jlJoystickY
|
=== jlJoystickX / jlJoystickY
|
||||||
|
|
||||||
|
|
@ -210,8 +251,11 @@ int8_t jlJoystickY(jlJoystickE js);
|
||||||
|`js` |`jlJoystickE` |Which stick.
|
|`js` |`jlJoystickE` |Which stick.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* axis value, -127 to +127. Digital sticks snap to the extremes;
|
*Returns:* axis value, -127 to +127. Digital sticks (the Amiga, ST, and
|
||||||
analog sticks return the raw centred value. Test against a threshold rather
|
X68000) snap to the extremes; analog sticks (the IIgs, DOS, DVX, and the
|
||||||
|
hosted ports) return their offset from the centre, scaled to that range, with
|
||||||
|
the dead zone applied. On the IIgs and DOS the axes read 0 until the stick's
|
||||||
|
centre has been read (<<_jljoystickreset>>). Test against a threshold rather
|
||||||
than for exact values, so the same code works with both.
|
than for exact values, so the same code works with both.
|
||||||
|
|
||||||
=== jlJoyDown / jlJoyPressed / jlJoyReleased
|
=== jlJoyDown / jlJoyPressed / jlJoyReleased
|
||||||
|
|
@ -232,6 +276,15 @@ bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button);
|
||||||
|
|
||||||
*Returns:* held state, rising edge, and falling edge respectively.
|
*Returns:* held state, rising edge, and falling edge respectively.
|
||||||
|
|
||||||
|
Every stick has two buttons. On the ST and STe's own joystick ports, and on
|
||||||
|
the Amiga until a stick's second button is first pressed, a stick is taken to
|
||||||
|
have one: a quick press of it is `JL_JOY_BUTTON_0`, down for the one poll after
|
||||||
|
it is let go, and a press held for half a second is `JL_JOY_BUTTON_1`, down
|
||||||
|
from then until it is let go. So act on button 0 with `jlJoyPressed`, and do
|
||||||
|
not count on it staying down while the button is held. Elsewhere the two
|
||||||
|
buttons are the stick's own, and a one-button stick never reports
|
||||||
|
`JL_JOY_BUTTON_1`.
|
||||||
|
|
||||||
=== jlJoystickReset
|
=== jlJoystickReset
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -239,34 +292,35 @@ bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button);
|
||||||
void jlJoystickReset(jlJoystickE js, uint8_t deadZone);
|
void jlJoystickReset(jlJoystickE js, uint8_t deadZone);
|
||||||
----
|
----
|
||||||
|
|
||||||
Re-enables joystick polling and recalibrates the resting position.
|
Sets the stick's dead zone and, where the port can, re-centres it and looks
|
||||||
|
for it again:
|
||||||
|
|
||||||
This matters on the IIgs. That port *auto-disables* joystick polling after a
|
* *IIgs*: polling starts again if it had stopped for want of a stick, and the
|
||||||
short window with no stick detected, because polling costs about 3 ms a frame
|
stick is re-centred at its next reading.
|
||||||
there -- and it does not re-probe on its own. If a user plugs a stick in while
|
* *DOS*: the game port is probed again, and the stick is re-centred at its
|
||||||
your program runs, you must call this to resume polling.
|
next reading.
|
||||||
|
* *Amiga*: the port goes back to `lowlevel.library`'s own detection.
|
||||||
|
* *Elsewhere* only the dead zone changes.
|
||||||
|
|
||||||
|
On the IIgs polling for a stick that is not there stops. If a user may plug a
|
||||||
|
stick in while your program runs, call this, with the stick centred, to pick
|
||||||
|
it up.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`js` |`jlJoystickE` |Which stick.
|
|`js` |`jlJoystickE` |Which stick.
|
||||||
|`deadZone` |`uint8_t` |Raw readings within this many units of centre clamp
|
|`deadZone` |`uint8_t` |Axis values within this many units of 0 (on the
|
||||||
to 0. Pass 0 to disable the dead zone.
|
-127..127 scale) read 0, on the ports with analog sticks: the IIgs, DOS, DVX,
|
||||||
|
and the hosted ports. Each stick starts with 8, so a stick left alone reads 0
|
||||||
|
though its pots jitter. Pass 0 to disable the dead zone.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing.
|
*Returns:* nothing.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
The next poll after this call captures the stick's *current* raw position as
|
On the IIgs and DOS the next reading after this call is taken as the stick's
|
||||||
the new centre, so the user must be holding the stick centred when you call it.
|
centre, so the user must be holding the stick centred when you call it. The
|
||||||
|
call is safe to make unconditionally.
|
||||||
On Amiga, ST and DOS the sticks are genuinely digital and already report -1, 0
|
|
||||||
or +1, so recalibration is a no-op and `deadZone` is ignored. DVX's sticks are
|
|
||||||
analog but calibrated, dead zone included, in DVX's own Control Panel, so the
|
|
||||||
call is a no-op there too. SDL3's sticks are gamepads the OS calibrates; there
|
|
||||||
the call only sets the dead zone, inside which the stick reads as centred.
|
|
||||||
The call still
|
|
||||||
clears any auto-disconnect state, so it is safe and correct to make it
|
|
||||||
unconditionally.
|
|
||||||
====
|
====
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,18 @@ which palette each scanline uses. That is how a JoeyLib program shows more than
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
Colour 0 of every palette is black, always. `jlPaletteSet` silently masks entry
|
Colour 0 of every palette is black, always. `jlPaletteSet` silently masks entry
|
||||||
0 to `$000` whatever you pass. This is deliberate: the Amiga and ST hardware
|
0 to `$000` whatever you pass, and `jlSurfaceLoadFile` does the same to every
|
||||||
border follows colour 0 of the active palette, and colour 0 is the transparent
|
palette it loads. This is deliberate: the Amiga and ST hardware border follows
|
||||||
colour for sprite drawing. You have 15 usable colours per palette.
|
colour 0 of the active palette, and colour 0 is the transparent colour for
|
||||||
|
sprite drawing. You have 15 usable colours per palette.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
Palette and SCB changes to the stage reach the display at the next
|
||||||
|
`jlStagePresent`, not before. Out-of-range arguments are ignored
|
||||||
|
the same way on every port: a palette index above 15 or a line above 199 makes
|
||||||
|
the setters do nothing (`jlScbSetRange` stops its range at line 199), and
|
||||||
|
`jlScbGet` returns 0 for a line off the surface.
|
||||||
|
|
||||||
=== jlPaletteSet
|
=== jlPaletteSet
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -42,7 +49,9 @@ Loads all 16 colours of one palette.
|
||||||
void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
|
void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
|
||||||
----
|
----
|
||||||
|
|
||||||
Reads all 16 colours of one palette.
|
Reads all 16 colours of one palette, as they were set: the values passed to
|
||||||
|
`jlPaletteSet` (with entry 0 forced to `$000`, and bits 12 to 15 kept as
|
||||||
|
passed), not what the display can show of them.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -100,7 +109,7 @@ screen, whether that is one palette everywhere or several horizontal bands.
|
||||||
----
|
----
|
||||||
// Sky on the top third, ground below, using two palettes.
|
// Sky on the top third, ground below, using two palettes.
|
||||||
jlScbSetRange(stage, 0, 65, 0);
|
jlScbSetRange(stage, 0, 65, 0);
|
||||||
jlScbSetRange(stage, 66, SURFACE_HEIGHT - 1, 1);
|
jlScbSetRange(stage, 66, JL_SURFACE_HEIGHT - 1, 1);
|
||||||
----
|
----
|
||||||
|
|
||||||
=== jlScbGet
|
=== jlScbGet
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,15 @@ the ST goes through the BIOS Aux channel; the X68000 transmits through IOCS
|
||||||
and receives on its own SCC interrupt; DVX goes through DVX's serial library,
|
and receives on its own SCC interrupt; DVX goes through DVX's serial library,
|
||||||
which owns the COM ports for every program on the desktop (a port another
|
which owns the COM ports for every program on the desktop (a port another
|
||||||
program has open cannot be opened); Win32 and Win16 go through Windows' own
|
program has open cannot be opened); Win32 and Win16 go through Windows' own
|
||||||
communications calls, whose driver owns the ports the same way. SDL3, the web
|
communications calls, whose driver owns the ports the same way. SDL3, the web,
|
||||||
and the PSP have no serial port: `jlSerialOpen` fails there.
|
the PSP, Haiku, and BeOS have no serial port: `jlSerialOpen` fails there.
|
||||||
|
|
||||||
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
||||||
background ring -- a hardware interrupt on DOS, the X68000 and the IIgs, the
|
background ring -- a hardware interrupt on DOS, the X68000, and the IIgs, the
|
||||||
OS on Amiga, ST, DVX and Windows -- and reads never block. Call `jlSerialPoll` once per
|
OS on Amiga, ST, DVX, and Windows -- and reads never block. Call `jlSerialPoll`
|
||||||
frame: it is next to a no-op where the interrupt or OS already fills the ring,
|
once per frame. (The X68000 takes its interrupt only when the port is opened in
|
||||||
and the hardware drain where nothing does, so calling it every frame is always
|
supervisor mode, which `jlInit` puts every program in; opened without it, the
|
||||||
correct. On DVX it also gives the other programs their turn
|
port is drained from RSDRV.SYS's own 64-byte buffer by the serial calls.)
|
||||||
(<<_dvx_programs>>).
|
|
||||||
|
|
||||||
On the IIgs a Super Serial Card only interrupts with its interrupt switch
|
On the IIgs a Super Serial Card only interrupts with its interrupt switch
|
||||||
(SW2-6) on; with it off the card still works, drained by `jlSerialPoll`, but
|
(SW2-6) on; with it off the card still works, drained by `jlSerialPoll`, but
|
||||||
|
|
@ -71,6 +70,10 @@ at 9600 baud, so:
|
||||||
|
|
||||||
At 19200 baud each lasts half as long, and so on.
|
At 19200 baud each lasts half as long, and so on.
|
||||||
|
|
||||||
|
A byte that arrives with a framing, parity, or overrun error is dropped on the
|
||||||
|
X68000 and delivered as it came on the IIgs and DOS; on the other ports the
|
||||||
|
system's driver decides.
|
||||||
|
|
||||||
JoeyLib's own work is safe while a port is open, with one exception on the
|
JoeyLib's own work is safe while a port is open, with one exception on the
|
||||||
X68000 (below). On the IIgs, where the
|
X68000 (below). On the IIgs, where the
|
||||||
display and sound code masks interrupts for long stretches, opening a port
|
display and sound code masks interrupts for long stretches, opening a port
|
||||||
|
|
@ -89,7 +92,8 @@ These can still lose bytes, and are the program's to schedule around:
|
||||||
* *Disk access.* On the IIgs, GS/OS holds interrupts off while it reads or
|
* *Disk access.* On the IIgs, GS/OS holds interrupts off while it reads or
|
||||||
writes a 3.5-inch floppy: 65 to 220 ms at a time, measured. That is every
|
writes a 3.5-inch floppy: 65 to 220 ms at a time, measured. That is every
|
||||||
file call -- `jlFile*`, `jlSave*`, loading assets and sprite banks, and
|
file call -- `jlFile*`, `jlSave*`, loading assets and sprite banks, and
|
||||||
`jlAudioInit`, which loads the replayer from disk. The other ports' disk
|
`jlInit` unless `jlConfigT.noAudio` is set, since starting audio loads the
|
||||||
|
replayer from disk. The other ports' disk
|
||||||
access goes through the operating system and has not been measured; treat
|
access goes through the operating system and has not been measured; treat
|
||||||
it as a risk there too.
|
it as a risk there too.
|
||||||
* *Starting a song on the IIgs.* `jlAudioPlayMod` loads the song's
|
* *Starting a song on the IIgs.* `jlAudioPlayMod` loads the song's
|
||||||
|
|
@ -100,13 +104,13 @@ These can still lose bytes, and are the program's to schedule around:
|
||||||
* *A Super Serial Card above 2400 baud.* The IIgs's own interrupt handling can
|
* *A Super Serial Card above 2400 baud.* The IIgs's own interrupt handling can
|
||||||
outlast the 6551's single byte, so the card runs loss-free at 2400 and above
|
outlast the 6551's single byte, so the card runs loss-free at 2400 and above
|
||||||
that relies on the protocol to recover. The built-in modem and printer ports
|
that relies on the protocol to recover. The built-in modem and printer ports
|
||||||
stay loss-free at 9600 through full-screen presents, clears, joystick reads
|
stay loss-free at 9600 through full-screen presents, clears, joystick reads,
|
||||||
and sound effects.
|
and sound effects.
|
||||||
* *Per-scanline palettes on the X68000.* The port reloads its one hardware
|
* *Per-scanline palettes on the X68000.* The port reloads its one hardware
|
||||||
palette as the beam scans, and a run of palette changes on consecutive lines
|
palette as the beam scans, and a run of palette changes on consecutive lines
|
||||||
holds serial receive off for the whole run, about 32 microseconds a line.
|
holds serial receive off for the whole run, about 32 microseconds a line.
|
||||||
Receive stays loss-free while such runs are under about 90 lines at 9600
|
Receive stays loss-free while such runs are under about 90 lines at 9600
|
||||||
baud, 40 at 19200 and 15 at 38400; palette changes three or more lines apart
|
baud, 40 at 19200, and 15 at 38400; palette changes three or more lines apart
|
||||||
release it between bands.
|
release it between bands.
|
||||||
* *The program's own code*, wherever it masks interrupts.
|
* *The program's own code*, wherever it masks interrupts.
|
||||||
|
|
||||||
|
|
@ -121,6 +125,48 @@ Ways to live with them:
|
||||||
* For data that must arrive intact, use a protocol with checksums and
|
* For data that must arrive intact, use a protocol with checksums and
|
||||||
retransmission; XMODEM and its descendants exist for exactly this.
|
retransmission; XMODEM and its descendants exist for exactly this.
|
||||||
|
|
||||||
|
=== Flow Control
|
||||||
|
|
||||||
|
`flow` in `jlSerialConfigT` picks the handshake, and every port with a serial
|
||||||
|
port honours it:
|
||||||
|
|
||||||
|
* `JL_SERIAL_FLOW_RTSCTS`: transmit waits while the peer holds CTS down, and
|
||||||
|
the port drops RTS when its receive buffer is three quarters full, raising
|
||||||
|
it again once reads have it down to a quarter.
|
||||||
|
* `JL_SERIAL_FLOW_XONXOFF`: the peer's XOFF and XON (0x13 and 0x11) pause and
|
||||||
|
resume transmit and never reach `jlSerialRead`, and the port sends XOFF
|
||||||
|
when its receive buffer is three quarters full and XON at a quarter.
|
||||||
|
|
||||||
|
The receive buffer, which also decides how long a program may go without
|
||||||
|
reading when there is no flow control:
|
||||||
|
|
||||||
|
[cols="3,2,2", options="header"]
|
||||||
|
|===
|
||||||
|
|Port |Receive buffer |Lasts at 9600 baud
|
||||||
|
|IIgs |512 bytes |~0.5 s
|
||||||
|
|DOS, DVX |2 KB |~2 s
|
||||||
|
|Amiga (`serial.device`) |4 KB |~4 s
|
||||||
|
|Atari ST (TOS's, enlarged while the port is open) |4 KB |~4 s
|
||||||
|
|X68000 |4 KB |~4 s
|
||||||
|
|Win32, Win16 (Windows' driver) |4 KB |~4 s
|
||||||
|
|===
|
||||||
|
|
||||||
|
Where they differ:
|
||||||
|
|
||||||
|
* The IIgs modem and printer ports' connector (mini-DIN-8) has no RTS line:
|
||||||
|
its one handshake output is DTR. RTS/CTS there makes transmit wait for CTS,
|
||||||
|
but nothing can tell the peer to pause, so receive needs XON/XOFF or a
|
||||||
|
protocol that paces itself. A Super Serial Card has RTS, and its 6551 also
|
||||||
|
stops its own transmitter while RTS is down.
|
||||||
|
* On the ST, the Amiga, Win32, and Win16 the operating system's driver does
|
||||||
|
the handshaking, and under RTS/CTS it may change RTS after
|
||||||
|
`jlSerialSetRts` did. DVX's library holds the peer off at four fifths full
|
||||||
|
and lets it go at a fifth.
|
||||||
|
* The X68000 drops RTS from its receive interrupt, but sends XOFF and XON
|
||||||
|
from `jlSerialPoll`, `jlSerialRead`, and `jlSerialWrite`: under XON/XOFF, a
|
||||||
|
program that calls none of them for longer than the last quarter of the
|
||||||
|
buffer lasts (about a second at 9600 baud) can still lose bytes.
|
||||||
|
|
||||||
=== Types
|
=== Types
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -143,7 +189,7 @@ typedef enum {
|
||||||
} jlSerialFlowE;
|
} jlSerialFlowE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t baud; // 300 .. 115200, clamped per port
|
uint32_t baud; // 300 .. 115200; each port runs its nearest
|
||||||
uint8_t dataBits; // 5 .. 8
|
uint8_t dataBits; // 5 .. 8
|
||||||
uint8_t stopBits; // 1 or 2
|
uint8_t stopBits; // 1 or 2
|
||||||
jlSerialParityE parity;
|
jlSerialParityE parity;
|
||||||
|
|
@ -152,10 +198,54 @@ typedef struct {
|
||||||
} jlSerialConfigT;
|
} jlSerialConfigT;
|
||||||
----
|
----
|
||||||
|
|
||||||
|
A field left 0 takes a default: `baud` 9600, `dataBits` 8, and `stopBits` 1.
|
||||||
|
`jlSerialOpen` fails for `dataBits` outside 5 to 8, `stopBits` other than 1 or
|
||||||
|
2, or a `parity` or `flow` the enumerations do not name.
|
||||||
|
|
||||||
|
`baud` is the rate asked for. Each port runs at the nearest rate it has:
|
||||||
|
|
||||||
|
[cols="2,5", options="header"]
|
||||||
|
|===
|
||||||
|
|Port |Rates
|
||||||
|
|
||||||
|
|IIgs modem and printer ports
|
||||||
|
|115200 divided by a whole number, up to 57600; a rate between two of those
|
||||||
|
runs at the one above it, and anything faster at 57600.
|
||||||
|
|
||||||
|
|IIgs Super Serial Card
|
||||||
|
|The 6551's fifteen rates, 50 to 19200; any other runs at the fastest one
|
||||||
|
below it.
|
||||||
|
|
||||||
|
|DOS
|
||||||
|
|115200 divided by a whole number, up to 115200; a rate between two of those
|
||||||
|
runs at the one above it.
|
||||||
|
|
||||||
|
|DVX
|
||||||
|
|DVX's library's rates, 50 to 115200; any other runs at the fastest one below
|
||||||
|
it.
|
||||||
|
|
||||||
|
|Amiga
|
||||||
|
|The rate as asked, handed to `serial.device`.
|
||||||
|
|
||||||
|
|Atari ST
|
||||||
|
|The MFP's sixteen rates, 50 to 19200; any other runs at the fastest one below
|
||||||
|
it.
|
||||||
|
|
||||||
|
|X68000
|
||||||
|
|75 to 38400, each double the last (38400 is really 39062); any other runs at
|
||||||
|
the fastest one below it.
|
||||||
|
|
||||||
|
|Win32
|
||||||
|
|The rate as asked, handed to Windows' driver.
|
||||||
|
|
||||||
|
|Win16
|
||||||
|
|The rate as asked up to 57600; above that, see below.
|
||||||
|
|===
|
||||||
|
|
||||||
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
||||||
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
||||||
the IIgs modem port, COM1 on DOS, DVX, Win32 and Win16, the ST Modem port,
|
the IIgs modem port; COM1 on DOS, DVX, Win32, and Win16; the ST Modem port;
|
||||||
Amiga unit 0 or the X68000's RS-232C port.
|
Amiga unit 0; or the X68000's RS-232C port.
|
||||||
|
|
||||||
On Win16 a `baud` above 57600 runs at 115200 through KPCOMM.DRV, the
|
On Win16 a `baud` above 57600 runs at 115200 through KPCOMM.DRV, the
|
||||||
communications driver JoeyLib's Windows runs with in place of Windows 3.1's
|
communications driver JoeyLib's Windows runs with in place of Windows 3.1's
|
||||||
|
|
@ -167,14 +257,13 @@ RSDRV.SYS. Every Human68k system disk has it and the stock CONFIG.SYS loads
|
||||||
it (`DEVICE = \SYS\RSDRV.SYS`), as do JoeyLib's own X68000 boot disks; a
|
it (`DEVICE = \SYS\RSDRV.SYS`), as do JoeyLib's own X68000 boot disks; a
|
||||||
game shipped to boot on its own must keep it. Without it `jlSerialOpen`
|
game shipped to boot on its own must keep it. Without it `jlSerialOpen`
|
||||||
returns `false` and `jlLastError` says the driver is missing. With it the
|
returns `false` and `jlLastError` says the driver is missing. With it the
|
||||||
port runs at 75 to 38400 bps, and `JL_SERIAL_FLOW_RTSCTS` makes transmit wait
|
port runs at 75 to 38400 bps. 38400 is really 39062 bps, 1.7% fast, within
|
||||||
for CTS; RTS stays up, since JoeyLib's receive interrupt, not the driver's,
|
the usual tolerance of a UART at the far end.
|
||||||
fills the receive ring. 38400 is really 39062 bps, 1.7% fast, within the
|
|
||||||
usual tolerance of a UART at the far end.
|
|
||||||
|
|
||||||
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
||||||
Serial Card slot 1..7, a DOS or DVX COM number 1..4, a Win16 COM number
|
Serial Card slot 1..7, a DOS or DVX COM number 1..4, a Win16 COM number
|
||||||
1..9, a Win32 COM number 1..255, or an Amiga `serial.device` unit.
|
1..9, a Win32 COM number 1..255, or an Amiga `serial.device` unit. The ST and
|
||||||
|
X68000 have one port, and open it for every device, `JL_SERIAL_SLOT` included.
|
||||||
|
|
||||||
=== jlSerialOpen
|
=== jlSerialOpen
|
||||||
|
|
||||||
|
|
@ -194,9 +283,10 @@ open without an intervening close fails.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` on success. `false` if the port cannot be opened or the
|
*Returns:* `true` on success. `false` if the port cannot be opened or the
|
||||||
platform has no serial support -- and then every other call no-ops. On the
|
platform has no serial support -- and then every other call no-ops. It is
|
||||||
X68000 it is also `false` when RSDRV.SYS is not loaded, and `jlLastError`
|
also `false`, with `jlLastError` saying why, when no UART answers at the DOS
|
||||||
says so.
|
COM port, when no Super Serial Card answers in the IIgs slot, and when
|
||||||
|
RSDRV.SYS is not loaded on the X68000.
|
||||||
|
|
||||||
=== jlSerialClose
|
=== jlSerialClose
|
||||||
|
|
||||||
|
|
@ -207,9 +297,12 @@ void jlSerialClose(void);
|
||||||
|
|
||||||
Closes the port and releases its resources. Safe when not open. A port still
|
Closes the port and releases its resources. Safe when not open. A port still
|
||||||
open when the program exits is closed then: the first successful
|
open when the program exits is closed then: the first successful
|
||||||
`jlSerialOpen` registers an `atexit` handler, because on DOS, the X68000 and
|
`jlSerialOpen` registers an `atexit` handler, because on DOS, the X68000, and
|
||||||
the IIgs the port's receive interrupt vector points into the program until it
|
the IIgs the port's receive interrupt vector points into the program until it
|
||||||
is closed.
|
is closed. Bytes `jlSerialWrite` accepted are still sent, so a program can
|
||||||
|
write a farewell and close straight away. DOS, the ST, and the X68000 put back
|
||||||
|
the speed and framing the port had before the open; the IIgs (whose serial
|
||||||
|
chip's settings cannot be read back) and DVX leave it as JoeyLib set it.
|
||||||
|
|
||||||
Closing does *not* hang up: `jlSerialClose` leaves DTR exactly as it is, so
|
Closing does *not* hang up: `jlSerialClose` leaves DTR exactly as it is, so
|
||||||
a modem call or a BBS caller survives the program handing the line back: a
|
a modem call or a BBS caller survives the program handing the line back: a
|
||||||
|
|
@ -291,7 +384,11 @@ uint16_t jlSerialRead(uint8_t *buf, uint16_t max);
|
||||||
uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len);
|
uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len);
|
||||||
----
|
----
|
||||||
|
|
||||||
Sends bytes. Transmit is a bounded poll.
|
Sends bytes without waiting long. Most ports feed the bytes to the line as it
|
||||||
|
takes them and give up after a bounded wait when it stalls (a peer holding
|
||||||
|
transmit off, say); the Amiga queues them in two 1 KB buffers it sends from in
|
||||||
|
the background, and Win16 in Windows' driver's queue, so neither waits at
|
||||||
|
all.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -386,7 +483,11 @@ Asserts or drops RTS, for a caller doing its own receive-side flow control.
|
||||||
`JL_SERIAL_FLOW_RTSCTS` asks the *port* to handshake; this is the other half --
|
`JL_SERIAL_FLOW_RTSCTS` asks the *port* to handshake; this is the other half --
|
||||||
the application saying "my buffer is nearly full, stop sending", and later "go
|
the application saying "my buffer is nearly full, stop sending", and later "go
|
||||||
ahead". That is what a client with a small ring and a peer that can be told to
|
ahead". That is what a client with a small ring and a peer that can be told to
|
||||||
pause actually needs.
|
pause actually needs. Under `JL_SERIAL_FLOW_RTSCTS` on DOS, the X68000, and a
|
||||||
|
IIgs Super Serial Card, RTS is up only while both the port's handshake and
|
||||||
|
this call want it; elsewhere the driver may change it again (see Flow
|
||||||
|
Control). The IIgs modem and printer ports have no RTS line on their
|
||||||
|
connector, so nothing reaches the peer there.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,16 @@ colour-0 transparency. Sprite pixel data is `widthTiles * heightTiles * 32`
|
||||||
bytes of 4bpp packed data, laid out as a flat blob of tiles in row-major order.
|
bytes of 4bpp packed data, laid out as a flat blob of tiles in row-major order.
|
||||||
|
|
||||||
The distinguishing feature is that *sprite blitters are compiled at run time*.
|
The distinguishing feature is that *sprite blitters are compiled at run time*.
|
||||||
The library emits native 65816, 68000 or x86 code for each sprite, including
|
On the IIgs, Amiga, ST, X68000, DOS, DVX, and Win16 the library emits native
|
||||||
pre-shifted variants so smooth horizontal motion needs no runtime bit shifting.
|
65816, 68000, or x86 code for each sprite, including pre-shifted variants so
|
||||||
|
smooth horizontal motion needs no runtime bit shifting. SDL3, the web, Win32,
|
||||||
|
the PSP, Haiku, and BeOS have no code emitter and always use the interpreter.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
`jlSpriteDraw` does *not* compile on demand. An uncompiled sprite draws through
|
`jlSpriteDraw` does *not* compile on demand. An uncompiled sprite draws through
|
||||||
an interpreter roughly *forty times slower*, and nothing reports this. Always
|
an interpreter *two to six times slower*, depending on the machine, and
|
||||||
call `jlSpriteCompile` or `jlSpritePrewarm` during loading.
|
nothing reports this. Always call `jlSpriteCompile` during loading.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlSpriteT and jlSpriteBackupT
|
=== jlSpriteT and jlSpriteBackupT
|
||||||
|
|
@ -35,33 +37,42 @@ typedef struct {
|
||||||
----
|
----
|
||||||
|
|
||||||
You supply `bytes` and its capacity; the library fills in the rest to describe
|
You supply `bytes` and its capacity; the library fills in the rest to describe
|
||||||
what it actually captured. On planar ports the captured region is the
|
what it actually captured, in `x`, `y`, `width`, `height`, and `sizeBytes`.
|
||||||
*16-pixel-aligned window* containing the sprite, not the sprite rectangle, and
|
That is often not the sprite rectangle:
|
||||||
the `x`, `width`, `height` and `sizeBytes` fields report that true geometry. It
|
|
||||||
is the recorded window you must not overwrite before restoring.
|
|
||||||
|
|
||||||
=== JOEY_SPRITE_BACKUP_BYTES
|
* `x` is rounded down to an even pixel, and `width` grows to match, on every
|
||||||
|
port.
|
||||||
|
* A compiled save on the Amiga, ST, and X68000 captures the *16-pixel-aligned
|
||||||
|
region* containing the sprite.
|
||||||
|
* A compiled save on the IIgs captures only the rows that hold an opaque
|
||||||
|
pixel, so `y` can be lower and `height` shorter than the sprite's.
|
||||||
|
* A save of a sprite partly off the surface captures only the visible part.
|
||||||
|
|
||||||
|
It is the recorded region you must not overwrite before restoring.
|
||||||
|
|
||||||
|
=== jlSpriteBackupLength
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
#define JOEY_SPRITE_BACKUP_BYTES(_wT, _hT) /* ... */
|
#define jlSpriteBackupLength(_type, _wT, _hT) /* ... */
|
||||||
----
|
----
|
||||||
|
|
||||||
Worst-case backup capacity for a `widthTiles` by `heightTiles` sprite at any x
|
How many `_type` elements a backup buffer needs for a `widthTiles` by
|
||||||
position, on every port. Use it to size your backing array.
|
`heightTiles` sprite at any x position, on every port, rounded up. Use it to
|
||||||
|
size your backing array; with `uint8_t` it is the size in bytes.
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
// 2-byte aligned by construction -- see the warning below.
|
static uint16_t gBacking[jlSpriteBackupLength(uint16_t, 4, 4)];
|
||||||
static uint16_t gBacking[JOEY_SPRITE_BACKUP_BYTES(4, 4) / 2];
|
|
||||||
----
|
----
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
Give the backing array *at least 2-byte alignment*. An odd buffer address
|
Give the backing array *at least 2-byte alignment*. On the Amiga, ST, and
|
||||||
routes save and restore onto slow interpreted paths on the planar ports,
|
X68000 an odd buffer address sends the save and restore calls down a much
|
||||||
because the 68000 cannot do word accesses at odd addresses. Declaring it
|
slower path, and on the Amiga and ST it makes the trusted variants crash; the
|
||||||
`uint16_t`, as above, guarantees this.
|
other ports do not mind. Declaring it `uint16_t`, as above, guarantees the
|
||||||
|
alignment.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlSpriteBankLoad
|
=== jlSpriteBankLoad
|
||||||
|
|
@ -109,8 +120,10 @@ replacement for `jlSpriteBankLoad` plus a compile pass.
|
||||||
|
|
||||||
*Parameters:* as `jlSpriteBankLoad`.
|
*Parameters:* as `jlSpriteBankLoad`.
|
||||||
|
|
||||||
*Returns:* the number of cels written, or 0 if the file is missing or its
|
*Returns:* the number of cels written, or 0 if the file is missing, its
|
||||||
magic, target or shift count does not match this build.
|
magic, target, or shift count does not match this build, its cel size is
|
||||||
|
out of range, or the 64 KB the loader reads each cel's code into cannot be
|
||||||
|
allocated (on the ports that interpret every sprite too).
|
||||||
|
|
||||||
=== jlSpriteBankSavePrecompiled
|
=== jlSpriteBankSavePrecompiled
|
||||||
|
|
||||||
|
|
@ -129,14 +142,16 @@ cache the large per-platform compiled form on first run.
|
||||||
|===
|
|===
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`path` |`const char *` |Destination path.
|
|`path` |`const char *` |Destination path.
|
||||||
|`cels` |`jlSpriteT **` |The sprites to cache. Uncompiled ones are cached
|
|`cels` |`jlSpriteT **` |The sprites to cache, all of one size. Uncompiled
|
||||||
as-is and will compile again next run.
|
ones are cached uncompiled, and nothing compiles them when the cache is
|
||||||
|
loaded: call `jlSpriteCompile` on any cel whose `jlSpriteCompiledSize` is 0.
|
||||||
|`count` |`uint16_t` |How many sprites.
|
|`count` |`uint16_t` |How many sprites.
|
||||||
|`palette` |`const uint16_t *` |Optional, may be `NULL`. Embedded if given.
|
|`palette` |`const uint16_t *` |Optional, may be `NULL`. Embedded if given.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` on success; `false` if the destination is not writable, which
|
*Returns:* `true` on success; `false` if the cels are not all one size or the
|
||||||
is harmless -- the program simply keeps compiling at startup.
|
destination is not writable, which is harmless -- the program simply keeps
|
||||||
|
compiling at startup.
|
||||||
|
|
||||||
=== jlSpriteCreate
|
=== jlSpriteCreate
|
||||||
|
|
||||||
|
|
@ -157,8 +172,8 @@ Wraps a tile-data blob in a sprite.
|
||||||
|`heightTiles` |`uint8_t` |Height in tiles. Must be non-zero.
|
|`heightTiles` |`uint8_t` |Height in tiles. Must be non-zero.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* the sprite, or `NULL` if either dimension is 0 or the codegen arena
|
*Returns:* the sprite, or `NULL` if `tileData` is `NULL`, either dimension is
|
||||||
cannot fit a placeholder entry.
|
0, or memory runs out. Creating a sprite does not touch the codegen arena.
|
||||||
|
|
||||||
=== jlSpriteDestroy
|
=== jlSpriteDestroy
|
||||||
|
|
||||||
|
|
@ -185,8 +200,11 @@ built from is *not* freed -- that buffer is yours.
|
||||||
bool jlSpriteCompile(jlSpriteT *sp);
|
bool jlSpriteCompile(jlSpriteT *sp);
|
||||||
----
|
----
|
||||||
|
|
||||||
Compiles the sprite's draw routines into the codegen arena. Idempotent --
|
Compiles the sprite's draw, save, and restore routines into the codegen arena,
|
||||||
compiling an already-compiled sprite is a no-op that returns `true`.
|
right away. No draw ever compiles on demand, so call it while loading, not in
|
||||||
|
the frame loop (a bank loaded with `jlSpriteBankLoadPrecompiled` arrives
|
||||||
|
compiled already). Idempotent -- compiling an already-compiled sprite is a
|
||||||
|
no-op that returns `true`.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -194,8 +212,20 @@ compiling an already-compiled sprite is a no-op that returns `true`.
|
||||||
|`sp` |`jlSpriteT *` |Sprite to compile.
|
|`sp` |`jlSpriteT *` |Sprite to compile.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` if the sprite is compiled. `false` if the arena is full (you
|
*Returns:* `true` if the sprite is compiled. `false` if it will be drawn by the
|
||||||
may call `jlSpriteCompact` and retry) or the sprite has no source tile data.
|
interpreter instead: the arena is full even after compacting it (`jlLastError`
|
||||||
|
says so; raise `codegenBytes` or destroy sprites you no longer need), the
|
||||||
|
sprite has no source tile data, the 64 KB the compiler works in cannot be
|
||||||
|
allocated, no routine fits the compiler's limits (16 KB a routine), or the
|
||||||
|
port has no code emitter (the Sprites row of
|
||||||
|
<<_per_platform_behaviour_summary>>). Only the full arena sets `jlLastError`.
|
||||||
|
A `false` is never fatal: the sprite still draws, only slower.
|
||||||
|
|
||||||
|
`true` means at least part of the sprite compiled, not all of it. When the
|
||||||
|
arena is short, the Amiga, ST, and X68000 keep only the draws for x on an
|
||||||
|
8-pixel boundary, and a routine larger than 16 KB is left out on every port.
|
||||||
|
A draw the compiled routines do not cover, and every draw of a sprite partly
|
||||||
|
off the surface, goes through the interpreter.
|
||||||
|
|
||||||
=== jlSpriteCompiledSize
|
=== jlSpriteCompiledSize
|
||||||
|
|
||||||
|
|
@ -216,24 +246,6 @@ the current platform, rather than guessing.
|
||||||
*Returns:* bytes occupied in the codegen arena, summed over all compiled shift
|
*Returns:* bytes occupied in the codegen arena, summed over all compiled shift
|
||||||
variants. Returns 0 if the sprite was never compiled.
|
variants. Returns 0 if the sprite was never compiled.
|
||||||
|
|
||||||
=== jlSpritePrewarm
|
|
||||||
|
|
||||||
[source,c]
|
|
||||||
----
|
|
||||||
void jlSpritePrewarm(jlSpriteT *sp);
|
|
||||||
----
|
|
||||||
|
|
||||||
Hints that a sprite will be drawn soon. A wrapper around `jlSpriteCompile` that
|
|
||||||
ignores the return value, for callers that do not need to handle failure.
|
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
|
||||||
|===
|
|
||||||
|Parameter |Type |Description
|
|
||||||
|`sp` |`jlSpriteT *` |Sprite to compile.
|
|
||||||
|===
|
|
||||||
|
|
||||||
*Returns:* nothing.
|
|
||||||
|
|
||||||
=== jlSpriteDraw
|
=== jlSpriteDraw
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -242,7 +254,8 @@ void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
||||||
----
|
----
|
||||||
|
|
||||||
Draws the sprite at a pixel position. Source pixels of colour 0 are skipped.
|
Draws the sprite at a pixel position. Source pixels of colour 0 are skipped.
|
||||||
Off-surface portions are clipped.
|
Off-surface portions are clipped. Compiled routines do not clip, so a sprite
|
||||||
|
partly off the surface draws through the interpreter, compiled or not.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -272,12 +285,15 @@ would write, so it can be restored later.
|
||||||
|`sp` |`jlSpriteT *` |Sprite whose footprint to capture.
|
|`sp` |`jlSpriteT *` |Sprite whose footprint to capture.
|
||||||
|`x`, `y` |`int16_t` |Position the sprite will be drawn at.
|
|`x`, `y` |`int16_t` |Position the sprite will be drawn at.
|
||||||
|`backup` |`jlSpriteBackupT *` |Receives the capture. You must set `bytes` to
|
|`backup` |`jlSpriteBackupT *` |Receives the capture. You must set `bytes` to
|
||||||
a buffer of at least `JOEY_SPRITE_BACKUP_BYTES(w, h)` capacity for fully
|
a buffer of at least `jlSpriteBackupLength(uint8_t, w, h)` bytes for fully
|
||||||
in-bounds draws. For clipped draws only the visible bytes are stored.
|
in-bounds draws. For clipped draws only the visible bytes are stored. On the
|
||||||
|
IIgs the buffer must not cross a 64 KB bank boundary; `jlAlloc`, the C heap,
|
||||||
|
and static arrays never give one that does. With `bytes` `NULL` nothing is
|
||||||
|
copied: the call only fills in the region a save would record.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing. The captured geometry is reported in the backup's `x`,
|
*Returns:* nothing. The captured geometry is reported in the backup's `x`,
|
||||||
`width`, `height` and `sizeBytes` fields.
|
`y`, `width`, `height`, and `sizeBytes` fields (see <<_jlspritet_and_jlspritebackupt>>).
|
||||||
|
|
||||||
=== jlSpriteRestoreUnder
|
=== jlSpriteRestoreUnder
|
||||||
|
|
||||||
|
|
@ -293,7 +309,7 @@ Repaints the destination from a previous capture.
|
||||||
|Parameter |Type |Description
|
|Parameter |Type |Description
|
||||||
|`s` |`jlSurfaceT *` |Destination surface.
|
|`s` |`jlSurfaceT *` |Destination surface.
|
||||||
|`backup` |`const jlSpriteBackupT *` |A backup from `jlSpriteSaveUnder`, not
|
|`backup` |`const jlSpriteBackupT *` |A backup from `jlSpriteSaveUnder`, not
|
||||||
invalidated by other writes overlapping its recorded window.
|
invalidated by other writes overlapping its recorded region.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing.
|
*Returns:* nothing.
|
||||||
|
|
@ -339,12 +355,15 @@ corrupting -- but the guarantees below are still yours to keep.
|
||||||
Behaviour is *undefined* -- out-of-bounds writes, crashes -- unless all of the
|
Behaviour is *undefined* -- out-of-bounds writes, crashes -- unless all of the
|
||||||
following hold:
|
following hold:
|
||||||
|
|
||||||
* `s`, `backup`, `backup->sprite` and `backup->bytes` are non-`NULL`;
|
* `s`, `backup`, `+backup->sprite+`, and `+backup->bytes+` are non-`NULL`;
|
||||||
* the sprite is fully on-surface at `(x, y)`, because *no clipping happens*;
|
* the sprite is fully on-surface at `(x, y)`, because *no clipping happens*;
|
||||||
* for the restore, the backup came from a save that took the compiled path:
|
* for the restore, the backup came from a save that took the compiled path:
|
||||||
16-pixel-aligned x and width on planar ports, even on chunky, height equal
|
16-pixel-aligned x and width on the Amiga, ST, and X68000, even on the
|
||||||
to the sprite's pixel height, and a buffer at least 2-byte aligned;
|
other ports, and a height equal to the sprite's pixel height (on the IIgs,
|
||||||
* the recorded window is fully on the destination surface.
|
to the rows the compiled save keeps, those holding an opaque pixel);
|
||||||
|
* on the Amiga and ST, `+backup->bytes+` is at least 2-byte aligned, for the
|
||||||
|
trusted save and save-and-draw as well as the restore;
|
||||||
|
* the recorded region is fully on the destination surface.
|
||||||
|
|
||||||
Use the plain calls anywhere those invariants are not statically guaranteed.
|
Use the plain calls anywhere those invariants are not statically guaranteed.
|
||||||
====
|
====
|
||||||
|
|
@ -371,7 +390,9 @@ copied into a sprite-owned buffer, so the source may be modified afterwards.
|
||||||
|`widthTiles`, `heightTiles` |`uint8_t` |Size in *tiles*, each 8x8 pixels.
|
|`widthTiles`, `heightTiles` |`uint8_t` |Size in *tiles*, each 8x8 pixels.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* the new sprite, or `NULL` if the coordinates are not tile-aligned.
|
*Returns:* the new sprite, or `NULL` if `src` is `NULL`, a dimension is 0,
|
||||||
|
`x` or `y` is not a multiple of 8, the region is not wholly on the surface, or
|
||||||
|
memory runs out.
|
||||||
|
|
||||||
=== jlSpriteCompact
|
=== jlSpriteCompact
|
||||||
|
|
||||||
|
|
@ -403,4 +424,9 @@ Arena introspection, for judging whether to compact or to raise
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* bytes in use, and total arena size. Free space is the difference,
|
*Returns:* bytes in use, and total arena size. Free space is the difference,
|
||||||
though it may be fragmented across holes until `jlSpriteCompact` runs.
|
though it may be fragmented across holes until `jlSpriteCompact` runs. On the
|
||||||
|
IIgs the total is what `jlInit` was granted, which can be less than
|
||||||
|
`codegenBytes` asked for, and it is split into blocks of at most 64 KB that
|
||||||
|
compacting never merges: one sprite's code must fit in one block. On the
|
||||||
|
ports that interpret every sprite the total is the arena they allocated
|
||||||
|
anyway, and the bytes in use stay 0.
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
Declared in `<joey/surface.h>` and `<joey/present.h>`.
|
Declared in `<joey/surface.h>` and `<joey/present.h>`.
|
||||||
|
|
||||||
A surface is a 320x200 16-colour image with its own 200-entry SCB table and its
|
A surface is a 320x200 16-colour image with its own 200-entry SCB table and its
|
||||||
own 16x16 palette set. Storage is target-native -- chunky 4bpp on IIgs and DOS,
|
own 16x16 palette set. Storage is target-native -- chunky 4bpp on the IIgs,
|
||||||
planar on Amiga, ST and X68000 -- but every entry point speaks in colour
|
DOS, DVX, and the hosted ports, planar on the Amiga, ST, and X68000 -- but every
|
||||||
indices, so your code never sees the difference.
|
entry point speaks in colour indices, so your code never sees the difference.
|
||||||
|
A new surface starts with the 16-colour EGA palette in all 16 of its palettes.
|
||||||
|
|
||||||
=== Constants
|
=== Constants
|
||||||
|
|
||||||
|
|
@ -13,13 +14,13 @@ indices, so your code never sees the difference.
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|
|
||||||
|`SURFACE_WIDTH` |320 |Surface width in pixels.
|
|`JL_SURFACE_WIDTH` |320 |Surface width in pixels.
|
||||||
|`SURFACE_HEIGHT` |200 |Surface height in pixels.
|
|`JL_SURFACE_HEIGHT` |200 |Surface height in pixels.
|
||||||
|`SURFACE_BYTES_PER_ROW` |160 |Bytes per row in chunky 4bpp form.
|
|`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row in chunky 4bpp form.
|
||||||
|`SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|
|`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|
||||||
|`SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|
|`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|
||||||
|`SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette.
|
|`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette.
|
||||||
|`SURFACE_PALETTE_ENTRIES` |256 |Total palette entries per surface.
|
|`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries per surface.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== jlSurfaceT
|
=== jlSurfaceT
|
||||||
|
|
@ -29,7 +30,7 @@ indices, so your code never sees the difference.
|
||||||
typedef struct jlSurfaceT jlSurfaceT;
|
typedef struct jlSurfaceT jlSurfaceT;
|
||||||
----
|
----
|
||||||
|
|
||||||
Opaque. Obtain one from `jlSurfaceCreate`, `jlStageGet` or a loader.
|
Opaque. Obtain one from `jlSurfaceCreate`, `jlStageGet`, or a loader.
|
||||||
|
|
||||||
=== jlSurfaceCreate
|
=== jlSurfaceCreate
|
||||||
|
|
||||||
|
|
@ -38,7 +39,9 @@ Opaque. Obtain one from `jlSurfaceCreate`, `jlStageGet` or a loader.
|
||||||
jlSurfaceT *jlSurfaceCreate(void);
|
jlSurfaceT *jlSurfaceCreate(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Allocates a new offscreen surface.
|
Allocates a new offscreen surface. Each one costs about 32 KB. On the Amiga
|
||||||
|
that comes from chip RAM, because the blitter works on the planes there, so on
|
||||||
|
a 512 KB machine, beside the display's own pages, only a few fit.
|
||||||
|
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
|
|
@ -83,7 +86,7 @@ the displayed framebuffer until the next present.
|
||||||
void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||||
----
|
----
|
||||||
|
|
||||||
Copies pixels, SCBs and palettes from `src` into `dst`.
|
Copies pixels, SCBs, and palettes from `src` into `dst`.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -128,10 +131,10 @@ multiples of 16. Two consequences you own:
|
||||||
|
|
||||||
* up to 15 extra pixels each side are copied, so a snapped erase can clobber
|
* up to 15 extra pixels each side are copied, so a snapped erase can clobber
|
||||||
on-screen content outside the rectangle you asked for;
|
on-screen content outside the rectangle you asked for;
|
||||||
* it silently invalidates any live `jlSpriteBackupT` whose recorded window it
|
* it silently invalidates any live `jlSpriteBackupT` whose recorded region it
|
||||||
touches.
|
touches.
|
||||||
|
|
||||||
Erase *all* windows, then draw *all* sprites. Never mix this with per-sprite
|
Erase *all* regions, then draw *all* sprites. Never mix this with per-sprite
|
||||||
save and restore in the same region.
|
save and restore in the same region.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
@ -180,7 +183,10 @@ nothing is reallocated.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* `true` on success; `false` if the file is missing, the wrong size,
|
*Returns:* `true` on success; `false` if the file is missing, the wrong size,
|
||||||
or unreadable.
|
has an SCB byte above 15, or unreadable. A read that fails part way can leave
|
||||||
|
the pixels partly overwritten; the SCBs and palettes are then untouched. Colour
|
||||||
|
0 of every palette loaded is set to black, as `jlPaletteSet` does. Neither file
|
||||||
|
call sets `jlLastError`.
|
||||||
|
|
||||||
=== jlSurfaceHash
|
=== jlSurfaceHash
|
||||||
|
|
||||||
|
|
@ -213,8 +219,10 @@ void jlStagePresent(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Flips the dirty regions of the stage to the display and clears the dirty state.
|
Flips the dirty regions of the stage to the display and clears the dirty state.
|
||||||
On chunky platforms this is a direct copy; on planar platforms it is a
|
Where the stage already has the display's layout this is a copy (the IIgs's
|
||||||
chunky-to-planar conversion.
|
4bpp pixels; the planes of the Amiga, ST, and X68000); DOS, DVX, and the hosted
|
||||||
|
ports expand each 4-bit pixel to their display's 8-bit or RGB pixels. Palette
|
||||||
|
and SCB changes reach the display here too.
|
||||||
|
|
||||||
Drawing primitives mark per-row dirty ranges as a side effect, so you only need
|
Drawing primitives mark per-row dirty ranges as a side effect, so you only need
|
||||||
to call this at the end of a frame and everything you drew appears. Rows
|
to call this at the end of a frame and everything you drew appears. Rows
|
||||||
|
|
@ -224,6 +232,6 @@ nothing touched since the last present are skipped.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
If you modify pixels by a route other than the library's primitives, the dirty
|
Every call that changes the stage's pixels marks the rows it touched, and the
|
||||||
tracking does not know, and those rows will not be presented.
|
API has no other way to write them, so there is nothing to mark by hand.
|
||||||
====
|
====
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ surfaces or fills them.
|
||||||
The consequence is that everything else works on tiles too. You can paint tile
|
The consequence is that everything else works on tiles too. You can paint tile
|
||||||
content with `jlDrawPixel` and `jlFillRect`, generate it procedurally, or edit
|
content with `jlDrawPixel` and `jlFillRect`, generate it procedurally, or edit
|
||||||
it at run time, because a "tileset" is just a surface you agree to treat as
|
it at run time, because a "tileset" is just a surface you agree to treat as
|
||||||
one. Fonts, terrain sets and spritesheets are all the same thing.
|
one. Fonts, terrain sets, and spritesheets are all the same thing.
|
||||||
|
|
||||||
=== Constants and Types
|
=== Constants and Types
|
||||||
|
|
||||||
|
|
@ -18,19 +18,19 @@ one. Fonts, terrain sets and spritesheets are all the same thing.
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|
|
||||||
|`TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|
|`JL_TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|
||||||
|`TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp).
|
|`JL_TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp).
|
||||||
|`TILE_BYTES` |32 |Bytes in one tile.
|
|`JL_TILE_BYTES` |32 |Bytes in one tile.
|
||||||
|`TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface.
|
|`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface.
|
||||||
|`TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface.
|
|`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface.
|
||||||
|`TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character".
|
|`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character".
|
||||||
|`TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone".
|
|`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone".
|
||||||
|===
|
|===
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
typedef struct jlTileT {
|
typedef struct jlTileT {
|
||||||
uint8_t pixels[TILE_BYTES];
|
uint8_t pixels[JL_TILE_BYTES];
|
||||||
} jlTileT;
|
} jlTileT;
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -39,11 +39,12 @@ need a whole scratch surface.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
A `jlTileT` is *target-native*: plane-major on the planar ports, chunky on the
|
A `jlTileT` is *target-native*: plane-major on the Amiga and X68000, row by row
|
||||||
others. Treat it as opaque. In particular, do not feed a tile captured by
|
with each row's four plane bytes together on the ST, and chunky on the others.
|
||||||
`jlTileSnap` to `jlTilePasteMono`, which wants a chunky silhouette -- that is a
|
Treat it as opaque. In particular, do not feed a tile from `jlTileSnap` or
|
||||||
format mismatch and draws the wrong shape. Use `jlTilePasteGlyph` for 1-bit
|
`jlTileBankLoad` to `jlTilePasteMono`, which wants a chunky silhouette -- on the
|
||||||
glyph data instead.
|
Amiga, ST, and X68000 that is a format mismatch and draws the wrong shape. Use
|
||||||
|
`jlTilePasteGlyph` for 1-bit glyph data instead.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlTileCopy
|
=== jlTileCopy
|
||||||
|
|
@ -166,7 +167,7 @@ this needs only a small internal scratch buffer and one pass over the file.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* the number of tiles pasted. Blocks beyond the file's tile count keep
|
*Returns:* the number of tiles pasted. Blocks beyond the file's tile count keep
|
||||||
their previous content.
|
their previous content, and tiles past the surface's 1000 blocks are ignored.
|
||||||
|
|
||||||
=== jlTileSnap
|
=== jlTileSnap
|
||||||
|
|
||||||
|
|
@ -233,10 +234,10 @@ comes from a separate map, as in C64-style colour RAM. Opaque.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
This call wants a *chunky* silhouette, but a `jlTileT` from `jlTileSnap` is
|
This call wants a *chunky* silhouette, but a `jlTileT` from `jlTileSnap` or
|
||||||
target-native. Feeding one to this function on a planar port is a format
|
`jlTileBankLoad` is target-native. Feeding one to this function on the Amiga,
|
||||||
mismatch. For glyphs, use `jlTilePasteGlyph`, which takes unambiguous 1-bit
|
ST, or X68000 is a format mismatch. For glyphs, use `jlTilePasteGlyph`, which
|
||||||
input.
|
takes unambiguous 1-bit input.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlTilePasteGlyph
|
=== jlTilePasteGlyph
|
||||||
|
|
@ -278,7 +279,10 @@ void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
||||||
|
|
||||||
Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph
|
Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph
|
||||||
colour 0 is transparent, so the background shows through. The cursor wraps at
|
colour 0 is transparent, so the background shows through. The cursor wraps at
|
||||||
the right edge and truncates at the bottom.
|
the right edge and truncates at the bottom. A start off the 40x25 grid uses up
|
||||||
|
characters without drawing them as the cursor moves on: a start column past
|
||||||
|
39 drops the first character and carries on at the start of the next row, and
|
||||||
|
a start row past 24 draws nothing.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -288,7 +292,8 @@ the right edge and truncates at the bottom.
|
||||||
|`fontSurface` |`const jlSurfaceT *` |Surface holding the glyphs.
|
|`fontSurface` |`const jlSurfaceT *` |Surface holding the glyphs.
|
||||||
|`asciiMap` |`const uint16_t *` |256 entries mapping ASCII code to glyph
|
|`asciiMap` |`const uint16_t *` |256 entries mapping ASCII code to glyph
|
||||||
position: low byte is source block X, high byte is source block Y. An entry of
|
position: low byte is source block X, high byte is source block Y. An entry of
|
||||||
`TILE_NO_GLYPH` skips that character, advancing the cursor without drawing.
|
`JL_TILE_NO_GLYPH`, or one off the font surface's 40x25 grid, skips that
|
||||||
|
character, advancing the cursor without drawing.
|
||||||
|`str` |`const char *` |NUL-terminated string.
|
|`str` |`const char *` |NUL-terminated string.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
@ -303,7 +308,7 @@ void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
||||||
const jlTileT *tiles, const uint8_t *map);
|
const jlTileT *tiles, const uint8_t *map);
|
||||||
----
|
----
|
||||||
|
|
||||||
Pastes a whole rectangle of tiles in one call, so validation, dispatch and the
|
Pastes a whole rectangle of tiles in one call, so validation, dispatch, and the
|
||||||
dirty-rectangle union are paid once for the rectangle instead of once per tile.
|
dirty-rectangle union are paid once for the rectangle instead of once per tile.
|
||||||
For anything larger than a few tiles this is substantially faster than looping
|
For anything larger than a few tiles this is substantially faster than looping
|
||||||
over `jlTilePaste`.
|
over `jlTilePaste`.
|
||||||
|
|
@ -316,7 +321,7 @@ over `jlTilePaste`.
|
||||||
|`wTiles`, `hTiles` |`uint8_t` |Rectangle size in tiles. Zero is a no-op.
|
|`wTiles`, `hTiles` |`uint8_t` |Rectangle size in tiles. Zero is a no-op.
|
||||||
|`tiles` |`const jlTileT *` |The tileset.
|
|`tiles` |`const jlTileT *` |The tileset.
|
||||||
|`map` |`const uint8_t *` |`wTiles * hTiles` tileset indices, row-major, so
|
|`map` |`const uint8_t *` |`wTiles * hTiles` tileset indices, row-major, so
|
||||||
`map[row * wTiles + col]`. `TILE_MAP_SKIP` leaves that cell untouched.
|
`map[row * wTiles + col]`. `JL_TILE_MAP_SKIP` leaves that cell untouched.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
*Returns:* nothing. A rectangle whose edge crosses the surface boundary is
|
*Returns:* nothing. A rectangle whose edge crosses the surface boundary is
|
||||||
|
|
@ -324,7 +329,7 @@ rejected as a no-op.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
Any map index other than `TILE_MAP_SKIP` must be a valid tileset index. The
|
Any map index other than `JL_TILE_MAP_SKIP` must be a valid tileset index. The
|
||||||
batch does not know the tileset length and does *not* range-check it -- an
|
batch does not know the tileset length and does *not* range-check it -- an
|
||||||
out-of-range index reads past the end of your array.
|
out-of-range index reads past the end of your array.
|
||||||
====
|
====
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@
|
||||||
[cols="3,1,4", options="header"]
|
[cols="3,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|`SURFACE_WIDTH` |320 |Surface width in pixels.
|
|`JL_SURFACE_WIDTH` |320 |Surface width in pixels.
|
||||||
|`SURFACE_HEIGHT` |200 |Surface height in pixels.
|
|`JL_SURFACE_HEIGHT` |200 |Surface height in pixels.
|
||||||
|`SURFACE_BYTES_PER_ROW` |160 |Bytes per row, chunky 4bpp.
|
|`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row, chunky 4bpp.
|
||||||
|`SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|
|`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|
||||||
|`SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|
|`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|
||||||
|`SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette.
|
|`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette.
|
||||||
|`SURFACE_PALETTE_ENTRIES` |256 |Total palette entries.
|
|`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== Tiles
|
=== Tiles
|
||||||
|
|
@ -20,13 +20,13 @@
|
||||||
[cols="3,1,4", options="header"]
|
[cols="3,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|`TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|
|`JL_TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels.
|
||||||
|`TILE_BYTES_PER_ROW` |4 |Bytes per tile row.
|
|`JL_TILE_BYTES_PER_ROW` |4 |Bytes per tile row.
|
||||||
|`TILE_BYTES` |32 |Bytes per tile.
|
|`JL_TILE_BYTES` |32 |Bytes per tile.
|
||||||
|`TILE_BLOCKS_PER_ROW` |40 |Tile columns per surface.
|
|`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns per surface.
|
||||||
|`TILE_BLOCKS_PER_COL` |25 |Tile rows per surface.
|
|`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows per surface.
|
||||||
|`TILE_NO_GLYPH` |0xFFFF |`jlDrawText`: skip this character.
|
|`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText`: skip this character.
|
||||||
|`TILE_MAP_SKIP` |0xFF |`jlTileMapPaste`: leave this cell untouched.
|
|`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste`: leave this cell untouched.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== Input
|
=== Input
|
||||||
|
|
@ -34,8 +34,8 @@
|
||||||
[cols="3,1,4", options="header"]
|
[cols="3,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|`JOYSTICK_AXIS_MAX` |127 |Maximum axis value.
|
|`JL_JOYSTICK_AXIS_MAX` |127 |Maximum axis value.
|
||||||
|`JOYSTICK_AXIS_MIN` |-127 |Minimum axis value.
|
|`JL_JOYSTICK_AXIS_MIN` |-127 |Minimum axis value.
|
||||||
|`JL_CHAR_QUEUE_SIZE` |32 |Queue capacity; 31 characters can be pending.
|
|`JL_CHAR_QUEUE_SIZE` |32 |Queue capacity; 31 characters can be pending.
|
||||||
|`JL_CHAR_BACKSPACE` |0x08 |Backspace, deletes before the caret.
|
|`JL_CHAR_BACKSPACE` |0x08 |Backspace, deletes before the caret.
|
||||||
|`JL_CHAR_TAB` |0x09 |Tab.
|
|`JL_CHAR_TAB` |0x09 |Tab.
|
||||||
|
|
@ -49,8 +49,8 @@
|
||||||
[cols="3,1,4", options="header"]
|
[cols="3,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|Name |Value |Meaning
|
|Name |Value |Meaning
|
||||||
|`JOEY_AUDIO_SFX_SLOTS` |5 |Sound effect slots, 0..4.
|
|`JL_AUDIO_SFX_SLOTS` |5 |Sound effect slots, 0..4.
|
||||||
|`JOEY_AUDIO_VOICES` |3 |Tone voices, 0..2.
|
|`JL_AUDIO_VOICES` |3 |Tone voices, 0..2.
|
||||||
|`JL_SFX_RAW_SIGNED8` |0 |Raw stream format: signed 8-bit PCM.
|
|`JL_SFX_RAW_SIGNED8` |0 |Raw stream format: signed 8-bit PCM.
|
||||||
|`JL_SFX_RAW_UNSIGNED8_NOZERO` |1 |Raw stream format: unsigned biased 0x80.
|
|`JL_SFX_RAW_UNSIGNED8_NOZERO` |1 |Raw stream format: unsigned biased 0x80.
|
||||||
|===
|
|===
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
|`sprite.h`
|
|`sprite.h`
|
||||||
|`jlSpriteBankLoad`, `jlSpriteBankLoadPrecompiled`,
|
|`jlSpriteBankLoad`, `jlSpriteBankLoadPrecompiled`,
|
||||||
`jlSpriteBankSavePrecompiled`, `jlSpriteCreate`, `jlSpriteDestroy`,
|
`jlSpriteBankSavePrecompiled`, `jlSpriteCreate`, `jlSpriteDestroy`,
|
||||||
`jlSpriteCompile`, `jlSpriteCompiledSize`, `jlSpritePrewarm`, `jlSpriteDraw`,
|
`jlSpriteCompile`, `jlSpriteCompiledSize`, `jlSpriteDraw`,
|
||||||
`jlSpriteDrawTrusted`, `jlSpriteSaveUnder`, `jlSpriteSaveUnderTrusted`,
|
`jlSpriteDrawTrusted`, `jlSpriteSaveUnder`, `jlSpriteSaveUnderTrusted`,
|
||||||
`jlSpriteRestoreUnder`, `jlSpriteRestoreUnderTrusted`, `jlSpriteSaveAndDraw`,
|
`jlSpriteRestoreUnder`, `jlSpriteRestoreUnderTrusted`, `jlSpriteSaveAndDraw`,
|
||||||
`jlSpriteSaveAndDrawTrusted`, `jlSpriteCreateFromSurface`, `jlSpriteCompact`,
|
`jlSpriteSaveAndDrawTrusted`, `jlSpriteCreateFromSurface`, `jlSpriteCompact`,
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
`jlJoystickReset`
|
`jlJoystickReset`
|
||||||
|
|
||||||
|`audio.h`
|
|`audio.h`
|
||||||
|`jlAudioInit`, `jlAudioShutdown`, `jlAudioPlayMod`, `jlAudioStopMod`,
|
|`jlAudioPlayMod`, `jlAudioStopMod`,
|
||||||
`jlAudioIsPlayingMod`, `jlAudioPlaySfx`, `jlAudioPlaySfxStream`,
|
`jlAudioIsPlayingMod`, `jlAudioPlaySfx`, `jlAudioPlaySfxStream`,
|
||||||
`jlAudioSfxRawFormat`, `jlAudioPlaySfxStreamRaw`, `jlAudioSetPan`,
|
`jlAudioSfxRawFormat`, `jlAudioPlaySfxStreamRaw`, `jlAudioSetPan`,
|
||||||
`jlAudioStopSfx`, `jlAudioTone`, `jlAudioTickRegister`,
|
`jlAudioStopSfx`, `jlAudioTone`, `jlAudioTickRegister`,
|
||||||
|
|
@ -120,7 +120,7 @@
|
||||||
|`serial.h` (opt-in)
|
|`serial.h` (opt-in)
|
||||||
|`jlSerialOpen`, `jlSerialClose`, `jlSerialPoll`, `jlSerialAvailable`,
|
|`jlSerialOpen`, `jlSerialClose`, `jlSerialPoll`, `jlSerialAvailable`,
|
||||||
`jlSerialRead`, `jlSerialWrite`, `jlSerialWriteByte`, `jlSerialReadByte`,
|
`jlSerialRead`, `jlSerialWrite`, `jlSerialWriteByte`, `jlSerialReadByte`,
|
||||||
`jlSerialFlush`, `jlSerialSetRts`
|
`jlSerialFlush`, `jlSerialSetDtr`, `jlSerialSetRts`
|
||||||
|
|
||||||
|`debug.h`
|
|`debug.h`
|
||||||
|`jlLog`, `jlLogF`, `jlLogFlush`, `jlLogReset`
|
|`jlLog`, `jlLogF`, `jlLogFlush`, `jlLogReset`
|
||||||
|
|
@ -139,7 +139,7 @@
|
||||||
smoke-tests what is installed.
|
smoke-tests what is installed.
|
||||||
|
|
||||||
|Sprites draw but the game crawls
|
|Sprites draw but the game crawls
|
||||||
|The sprites were never compiled. Call `jlSpritePrewarm` during loading. Check
|
|The sprites were never compiled. Call `jlSpriteCompile` during loading. Check
|
||||||
`jlSpriteCompiledSize` returns non-zero.
|
`jlSpriteCompiledSize` returns non-zero.
|
||||||
|
|
||||||
|Sprites compile on some machines and not others
|
|Sprites compile on some machines and not others
|
||||||
|
|
@ -147,7 +147,8 @@
|
||||||
eight times the IIgs figure. Measure with `jlSpriteCompiledSize`.
|
eight times the IIgs figure. Measure with `jlSpriteCompiledSize`.
|
||||||
|
|
||||||
|Works everywhere except the IIgs, failing at startup
|
|Works everywhere except the IIgs, failing at startup
|
||||||
|A `malloc` too large for the bank-0 C heap. Use `jlAlloc`.
|
|A `malloc` too large for the IIgs's C heap, which is 32 KB to 48 KB in all.
|
||||||
|
Use `jlAlloc`.
|
||||||
|
|
||||||
|Crashes on IIgs deep in a call chain
|
|Crashes on IIgs deep in a call chain
|
||||||
|A large function-local array overflowing the soft stack. Make it `static`.
|
|A large function-local array overflowing the soft stack. Make it `static`.
|
||||||
|
|
@ -156,10 +157,6 @@
|
||||||
|`jlStagePresent` was never called, or drawing went to a surface that is not
|
|`jlStagePresent` was never called, or drawing went to a surface that is not
|
||||||
the stage.
|
the stage.
|
||||||
|
|
||||||
|Part of the screen never updates
|
|
||||||
|Pixels were written by something other than the library primitives, so the
|
|
||||||
dirty tracking did not see them.
|
|
||||||
|
|
||||||
|Runs at different speeds on different machines
|
|Runs at different speeds on different machines
|
||||||
|Per-frame steps are hard-coded. Scale by `jlFrameHz()` or use
|
|Per-frame steps are hard-coded. Scale by `jlFrameHz()` or use
|
||||||
`jlMillisElapsed()`.
|
`jlMillisElapsed()`.
|
||||||
|
|
@ -171,23 +168,24 @@
|
||||||
|The character queue holds 31 entries and drops the rest. Pop it every frame.
|
|The character queue holds 31 entries and drops the rest. Pop it every frame.
|
||||||
|
|
||||||
|Music plays but `jlAudioVoice` does nothing
|
|Music plays but `jlAudioVoice` does nothing
|
||||||
|On ST and Amiga the module player owns those channels (in the ST build; the
|
|On the Amiga the module player owns those channels, and on the plain ST
|
||||||
STe build plays modules through the DMA sound chip). Use one layer per scene.
|
voice 0 is silent while digital sound plays. Use one layer per scene.
|
||||||
|
|
||||||
|A note sounds for only a fraction of a second on the IIgs
|
|A note sounds for only a fraction of a second
|
||||||
|`jlAudioVoice` is a roughly 80 ms one-shot there. Re-assert it about once a
|
|`jlAudioFrameTick` is not being called every frame. The IIgs plays each note
|
||||||
frame.
|
as a short sample and restarts the notes being held from it.
|
||||||
|
|
||||||
|A streaming sound effect stops after its first chunk
|
|A streaming sound effect stops after its first chunk
|
||||||
|`jlAudioFrameTick` is not being called every frame. IIgs and Amiga fire the
|
|`jlAudioFrameTick` is not being called every frame. Every port feeds streams
|
||||||
next chunk from it.
|
from it.
|
||||||
|
|
||||||
|Sprite trails or corruption around moving objects
|
|Sprite trails or corruption around moving objects
|
||||||
|Save-under and clean-buffer erasing are being mixed in one region, or a
|
|Save-under and clean-buffer erasing are being mixed in one region, or a
|
||||||
`jlSurfaceCopyRect` invalidated a live backup by snapping outward to the
|
`jlSurfaceCopyRect` invalidated a live backup by snapping outward to the
|
||||||
16-pixel grid.
|
16-pixel grid.
|
||||||
|
|
||||||
|Save and restore are unexpectedly slow on Amiga or ST
|
|Save and restore are unexpectedly slow on the Amiga, ST, or X68000, or the
|
||||||
|
trusted variants crash on the Amiga or ST
|
||||||
|The backup buffer is at an odd address. Declare it `uint16_t`.
|
|The backup buffer is at an odd address. Declare it `uint16_t`.
|
||||||
|
|
||||||
|A tile bank refuses to load
|
|A tile bank refuses to load
|
||||||
|
|
|
||||||
152
docs/Assets.adoc
152
docs/Assets.adoc
|
|
@ -1,6 +1,6 @@
|
||||||
== Creating Assets
|
== Creating Assets
|
||||||
|
|
||||||
JoeyLib does not load PNGs, WAVs or MODs at run time. Every asset is converted
|
JoeyLib does not load PNGs, WAVs, or MODs at run time. Every asset is converted
|
||||||
on the build host into a compact binary the target can use directly, which is
|
on the build host into a compact binary the target can use directly, which is
|
||||||
what keeps loading fast and the runtime small. This chapter covers each asset
|
what keeps loading fast and the runtime small. This chapter covers each asset
|
||||||
type: what you author, which tool bakes it, and which API call loads it.
|
type: what you author, which tool bakes it, and which API call loads it.
|
||||||
|
|
@ -31,6 +31,11 @@ type: what you author, which tool bakes it, and which API call loads it.
|
||||||
|`.jym` or a C header
|
|`.jym` or a C header
|
||||||
|`jlMusicPlay`
|
|`jlMusicPlay`
|
||||||
|
|
||||||
|
|A tracker song (Furnace) for the SN76489 or AY-3-8910, exported as VGM
|
||||||
|
|`vgm2song.py`, then `songbake.py`
|
||||||
|
|`.jym` or a C header
|
||||||
|
|`jlMusicPlay`
|
||||||
|
|
||||||
|Protracker `.MOD`
|
|Protracker `.MOD`
|
||||||
|`joeymod`
|
|`joeymod`
|
||||||
|`.mod`, `.amod`, `.ntp`
|
|`.mod`, `.amod`, `.ntp`
|
||||||
|
|
@ -45,6 +50,52 @@ type: what you author, which tool bakes it, and which API call loads it.
|
||||||
Baked assets are staged into `DATA/` beside the executable, and the program
|
Baked assets are staged into `DATA/` beside the executable, and the program
|
||||||
opens them with bare names.
|
opens them with bare names.
|
||||||
|
|
||||||
|
=== Content Creation Tools
|
||||||
|
|
||||||
|
These are the tools we recommend for making each kind of asset. All of them
|
||||||
|
run on Linux, and all but OpenMPT are native there.
|
||||||
|
|
||||||
|
[cols="2,4,3", options="header"]
|
||||||
|
|===
|
||||||
|
|You make |With |Notes
|
||||||
|
|
||||||
|
|Tile and sprite sheets
|
||||||
|
|*GrafX2* (GPLv2), a Deluxe Paint-style editor built around an indexed
|
||||||
|
palette. *LibreSprite* (GPLv2), for sprite sheets drawn as layers and
|
||||||
|
animation frames. *GIMP* (GPLv3), to bring existing art down to an indexed
|
||||||
|
PNG.
|
||||||
|
|In GrafX2, set the palette's RGB scale to 16 (right-click the palette
|
||||||
|
button for the secondary palette menu): every colour you pick is then one a
|
||||||
|
JoeyLib palette holds exactly, 4 bits a gun. The sheet rules are in
|
||||||
|
<<_authoring_the_png>>.
|
||||||
|
|
||||||
|
|Program icons
|
||||||
|
|Any of the above, saving an RGBA PNG with transparency.
|
||||||
|
|One picture for every system (<<_icons>>).
|
||||||
|
|
||||||
|
|Chip-tracker music
|
||||||
|
|*Furnace* (GPLv2 or later), a chiptune tracker that emulates the chips it
|
||||||
|
writes for. `./install.sh install furnace` puts it in
|
||||||
|
`toolchains/opt/furnace`.
|
||||||
|
|Write for the TI SN76489 or the AY-3-8910, export VGM, and convert it
|
||||||
|
(<<_writing_songs_in_furnace>>).
|
||||||
|
|
||||||
|
|`.MOD` music
|
||||||
|
|*MilkyTracker* (GPLv3), the *ProTracker 2 clone* (open source), or
|
||||||
|
*OpenMPT* (BSD; Windows, runs under Wine).
|
||||||
|
|Four channels, then `joeymod` (<<_music_protracker_modules>>).
|
||||||
|
|
||||||
|
|Sound effects
|
||||||
|
|*sfxr* (MIT) or *jfxr* (open source, in a browser) for generated game
|
||||||
|
sounds; *Audacity* (GPLv3) to edit recordings; *SoX* to convert.
|
||||||
|
|Save a WAV, then convert it to raw signed 8-bit PCM
|
||||||
|
(<<_sound_effects>>).
|
||||||
|
|===
|
||||||
|
|
||||||
|
None of these is needed to build a program: the baked files are what the
|
||||||
|
build uses. Only Furnace has an installer component, because
|
||||||
|
`scripts/check-vgm.sh` uses it to test the converter on real songs.
|
||||||
|
|
||||||
=== Graphics: Tiles and Sprites
|
=== Graphics: Tiles and Sprites
|
||||||
|
|
||||||
==== Authoring the PNG
|
==== Authoring the PNG
|
||||||
|
|
@ -75,11 +126,11 @@ $ python3 tools/assetbake/assetbake.py --type tile --target atarist \
|
||||||
|
|
||||||
Tile banks are *per-target*, because the 8x8 block layout is the platform's
|
Tile banks are *per-target*, because the 8x8 block layout is the platform's
|
||||||
native pixel format -- plane-major on Amiga and X68000, row-major-with-planes-
|
native pixel format -- plane-major on Amiga and X68000, row-major-with-planes-
|
||||||
per-row on ST, chunky 4bpp on DOS and IIgs. You must bake one `.tbk` per platform, and the
|
per-row on ST, and chunky 4bpp on DOS and IIgs. You must bake one `.tbk` per platform, and the
|
||||||
loader refuses a file baked for a different target rather than drawing garbage.
|
loader refuses a file baked for a different target rather than drawing garbage.
|
||||||
|
|
||||||
`--target` is required for tiles, and accepts `amiga`, `atarist`, `dos`,
|
`--target` is required for tiles, and accepts `amiga`, `atarist`, `dos`,
|
||||||
`iigs` or `x68000`.
|
`iigs`, or `x68000`.
|
||||||
|
|
||||||
==== Baking Sprites
|
==== Baking Sprites
|
||||||
|
|
||||||
|
|
@ -93,8 +144,8 @@ $ python3 tools/assetbake/assetbake.py --type sprite --cell 2x3 \
|
||||||
pixels. The sheet is cut into cels row by row.
|
pixels. The sheet is cut into cels row by row.
|
||||||
|
|
||||||
Sprite `.spr` banks are *cross-target*: the same file works on every platform,
|
Sprite `.spr` banks are *cross-target*: the same file works on every platform,
|
||||||
because the sprite walker reads chunky 4bpp and converts at draw time. Bake
|
because it holds chunky 4bpp tiles and each port converts a sprite to its own
|
||||||
once, ship everywhere.
|
form when the sprite is created. Bake once, ship everywhere.
|
||||||
|
|
||||||
Both formats can embed the 16-entry `$0RGB` palette, and both loaders will hand
|
Both formats can embed the 16-entry `$0RGB` palette, and both loaders will hand
|
||||||
it back through their `outPalette` parameter so artwork carries its own colours
|
it back through their `outPalette` parameter so artwork carries its own colours
|
||||||
|
|
@ -192,33 +243,88 @@ tone voice 2. Author noise hits and voice-2 notes so they do not sound at the
|
||||||
same time; `songbake` warns when they overlap.
|
same time; `songbake` warns when they overlap.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
==== Writing Songs in Furnace
|
||||||
|
|
||||||
|
A tracker is easier than a text file for anything longer than a jingle. Write
|
||||||
|
the song in Furnace, export it as VGM (a log of the chip's register writes as
|
||||||
|
the song plays), and `tools/vgm2song.py` turns that into a `.song` file, which
|
||||||
|
is still plain text you can read and touch up:
|
||||||
|
|
||||||
|
. Start the song on a system with a *TI SN76489* (the Sega Master System, BBC
|
||||||
|
Micro, or TI-99/4A) or an *AY-3-8910 / YM2149* (the ZX Spectrum, MSX, or
|
||||||
|
Atari ST's chip). The SN76489 is the closer match: three square tone voices
|
||||||
|
and a noise channel with its own volume, attenuated in 2 dB steps exactly as
|
||||||
|
JoeyLib's are. Use that one chip only; another chip's part (a Master System
|
||||||
|
FM unit, say) is left out.
|
||||||
|
. Export: *File > Export > VGM*, or `furnace -vgmout song.vgm song.fur` from a
|
||||||
|
shell.
|
||||||
|
. Convert and bake:
|
||||||
|
+
|
||||||
|
[source,console]
|
||||||
|
----
|
||||||
|
$ python3 tools/vgm2song.py song.vgm song.song
|
||||||
|
$ python3 tools/songbake/songbake.py -c gSong song.song song.h
|
||||||
|
----
|
||||||
|
|
||||||
|
What comes across:
|
||||||
|
|
||||||
|
* *Tones* at their exact frequencies (`@Hz` cells) and volumes; an AY volume
|
||||||
|
`v` becomes attenuation `15 - v`, exact on the ST.
|
||||||
|
* *Noise* at the nearest JoeyLib noise pitch. The SN76489's periodic noise (a
|
||||||
|
pitched buzz, the Master System's bass trick) plays as a square at its
|
||||||
|
pitch on a tone voice that is free, and as noise when all three are busy.
|
||||||
|
* *The AY's volume envelope*, followed row by row. An envelope repeating
|
||||||
|
faster than two rows is a timbre (the "buzzer"), not a volume: it plays at
|
||||||
|
full volume, and a channel whose tone is off sounds at the envelope's
|
||||||
|
frequency.
|
||||||
|
* *Timing.* One `.song` row a tick, by default the song's update interval
|
||||||
|
rounded down (16 ms at 60 Hz, 20 ms at 50 Hz), placed by the VGM's own clock,
|
||||||
|
so the length and the loop point are right to within a tick.
|
||||||
|
* *The loop point*, if the song has one.
|
||||||
|
|
||||||
|
A baked song must stay under 64 KB. A long, busy song that would not fit gets
|
||||||
|
a longer tick, a whole multiple of the default, which merges changes faster
|
||||||
|
than it (vibrato, arpeggios); `--tick MS` sets the tick yourself. The
|
||||||
|
converter warns about everything it approximates.
|
||||||
|
|
||||||
=== Music: Protracker Modules
|
=== Music: Protracker Modules
|
||||||
|
|
||||||
For richer music, author a four-channel `.MOD` in any tracker (ProTracker,
|
For richer music, author a four-channel `.MOD` in any tracker (ProTracker,
|
||||||
MilkyTracker, OpenMPT). Then convert per target:
|
MilkyTracker, OpenMPT) -- one whose signature is `M.K.`, `M!K!`, `FLT4`, or
|
||||||
|
`4CHN`; the other module formats and the 15-sample Soundtracker files are not
|
||||||
|
played. Every port plays its effects as Protracker 2.3 does. Then convert
|
||||||
|
per target:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ tools/joeymod/joeymod song.mod song.mod # DOS and ST: validated copy
|
$ tools/joeymod/joeymod song.mod song.mod # every port but the IIgs
|
||||||
$ tools/joeymod/joeymod song.mod song.amod # Amiga
|
$ tools/joeymod/joeymod song.mod song.amod # Amiga, with an end marker
|
||||||
$ tools/joeymod/joeymod song.mod song.ntp # Apple IIgs
|
$ tools/joeymod/joeymod song.mod song.ntp # Apple IIgs
|
||||||
----
|
----
|
||||||
|
|
||||||
The output extension selects the conversion:
|
The output extension selects the conversion:
|
||||||
|
|
||||||
* *`.mod`* -- a passthrough copy with the signature validated. DOS and ST
|
* *`.mod`* -- a passthrough copy with the signature validated, played as it
|
||||||
detect song end inside libxmp-lite, so the file is used as-is.
|
is on every port but the IIgs. The ports that mix modules in software
|
||||||
* *`.amod`* -- the same, plus an `E8FF` stop marker injected at song end. The
|
(DOS, the ST, the X68000, and the hosted ports) find the song's end
|
||||||
Amiga replayer, PTPlayer, has no native song-end signal, so without this
|
themselves, and the Amiga adds an end marker as it copies the module to
|
||||||
marker `jlAudioPlayMod(..., loop=false)` loops anyway. Other replayers ignore
|
chip RAM, so `+jlAudioPlayMod(..., loop=false)+` plays the song once.
|
||||||
`E8FF`, so an `.amod` is harmless if it reaches the wrong port.
|
* *`.amod`* -- the same, plus an `E8F` stop marker added at song end ahead of
|
||||||
|
time. The Amiga replayer, PTPlayer, has no song-end signal of its own and
|
||||||
|
needs the marker, but the Amiga adds it itself when a song is played with
|
||||||
|
`loop=false`, so a plain `.mod` works there too. A module with all 128 song
|
||||||
|
positions or all 128 patterns in use has no room for the marker either way,
|
||||||
|
and loops on the Amiga. The other ports' module player stops a song that
|
||||||
|
does not loop at the marker too, and the IIgs's replayer ignores `E8F`, so
|
||||||
|
an `.amod` plays the same anywhere (looping, with the marker's pattern of
|
||||||
|
silence at its end).
|
||||||
* *`.ntp`* -- NinjaTrackerPlus format for the IIgs, produced by the
|
* *`.ntp`* -- NinjaTrackerPlus format for the IIgs, produced by the
|
||||||
NinjaTrackerPlus converter the installer's `ntp` component provides (part of
|
NinjaTrackerPlus converter the installer's `ntp` component provides (part of
|
||||||
`toolchain:iigs`; it needs the PHP command-line interpreter, which the
|
`toolchain:iigs`; it needs the PHP command-line interpreter, which the
|
||||||
installer's host packages include).
|
installer's host packages include).
|
||||||
|
|
||||||
Keep modules modest. A four-channel mix is real CPU work on DOS and the ST, and
|
Keep modules modest. A four-channel mix is real CPU work on DOS, the ST, and
|
||||||
the sample data has to fit in memory alongside your game.
|
the X68000, and the sample data has to fit in memory alongside your game.
|
||||||
|
|
||||||
=== Sound Effects
|
=== Sound Effects
|
||||||
|
|
||||||
|
|
@ -260,7 +366,7 @@ jlSurfaceSaveFile(surface, "title.sur");
|
||||||
jlSurfaceLoadFile(surface, "title.sur");
|
jlSurfaceLoadFile(surface, "title.sur");
|
||||||
----
|
----
|
||||||
|
|
||||||
The file is 32000 bytes of pixels, 200 SCB bytes and 512 bytes of palette. It
|
The file is 32000 bytes of pixels, 200 SCB bytes, and 512 bytes of palette. It
|
||||||
is *not* portable between platforms -- no format conversion happens, so the
|
is *not* portable between platforms -- no format conversion happens, so the
|
||||||
machine that wrote it must be the machine that reads it. It is best used as a
|
machine that wrote it must be the machine that reads it. It is best used as a
|
||||||
runtime cache (a generated title screen, say), not as a shipped asset. For
|
runtime cache (a generated title screen, say), not as a shipped asset. For
|
||||||
|
|
@ -292,10 +398,12 @@ $(call programDataDir,MYGAME)/font.tbk: assets/font.png
|
||||||
python3 $(REPO_DIR)/tools/assetbake/assetbake.py --type tile --target $(ASSET_PLATFORM) $< $@
|
python3 $(REPO_DIR)/tools/assetbake/assetbake.py --type tile --target $(ASSET_PLATFORM) $< $@
|
||||||
----
|
----
|
||||||
|
|
||||||
Tile banks and `.spc` files differ per platform, while `.spr`, `.jym` and
|
Tile banks and `.spc` files differ per platform, while `.spr`, `.jym`, and
|
||||||
`.sfx` files are shared: `ASSET_PLATFORM` names the target a bake is for (DVX,
|
`.sfx` files are shared: `ASSET_PLATFORM` names the target a bake is for (DVX
|
||||||
SDL3 and the web take the DOS bakes; the last two use only a `.spc`'s cels, not
|
and every hosted port -- SDL3, the web, Win32, Win16, the PSP, Haiku, and
|
||||||
their code) and `SPRITEBAKE` the matching host `spritebake`.
|
BeOS -- take the DOS bakes; DVX and Win16 run the x86 code of a `.spc`, the
|
||||||
|
others use only its cels; the STe build takes the ST's) and `SPRITEBAKE` the
|
||||||
|
matching host `spritebake`.
|
||||||
|
|
||||||
`examples/tilebank` is a working reference: one rule in `make/examples.mk`
|
`examples/tilebank` is a working reference: one rule in `make/examples.mk`
|
||||||
bakes the same PNG for each target into its `DATA/tilebank` (`TILEBANK_DATA`),
|
bakes the same PNG for each target into its `DATA/tilebank` (`TILEBANK_DATA`),
|
||||||
|
|
@ -313,7 +421,7 @@ per-level banks marked optional, and speech baked with per-target flags.
|
||||||
// Tiles into a caller-owned array.
|
// Tiles into a caller-owned array.
|
||||||
jlTileT tiles[256];
|
jlTileT tiles[256];
|
||||||
bool valid[256];
|
bool valid[256];
|
||||||
uint16_t palette[SURFACE_COLORS_PER_PALETTE];
|
uint16_t palette[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t n = jlTileBankLoad("font.tbk", tiles, 256, valid, palette);
|
uint16_t n = jlTileBankLoad("font.tbk", tiles, 256, valid, palette);
|
||||||
|
|
||||||
// Or stream a big bank straight onto a surface, with no caller buffer.
|
// Or stream a big bank straight onto a surface, with no caller buffer.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
== Building Your Program
|
== Building Your Program
|
||||||
|
|
||||||
JoeyLib does not install as a system library: each target needs its own cross
|
JoeyLib does not install as a system library: each target needs its own cross
|
||||||
compiler, link step, packaging and disk image, and JoeyLib's makefiles own all
|
compiler, link step, packaging, and disk image; JoeyLib's makefiles own all
|
||||||
of that. Your program lives in a directory of its own -- its own repository,
|
of that. Your program lives in a directory of its own -- its own repository,
|
||||||
usually -- next to a JoeyLib checkout. Its Makefile describes the program and
|
usually -- next to a JoeyLib checkout. Its Makefile describes the program and
|
||||||
includes two of JoeyLib's makefiles, and that is enough to build it for every
|
includes two of JoeyLib's makefiles, and that is enough to build it for every
|
||||||
target with exactly the flags, link order and packaging JoeyLib's own examples
|
target with exactly the flags, link order, and packaging JoeyLib's own examples
|
||||||
get. JoeyLib's examples are described the same way and built by the same
|
get. JoeyLib's examples are described the same way and built by the same
|
||||||
rules; there is one set of them.
|
rules; there is one set of them.
|
||||||
|
|
||||||
|
|
@ -62,19 +62,20 @@ The project's outputs land in its own `build/`, laid out like JoeyLib's:
|
||||||
|Target |Output
|
|Target |Output
|
||||||
|
|
||||||
|Apple IIgs |`build/iigs/bin/MYGAME` (an S16 OMF), its data staged in
|
|Apple IIgs |`build/iigs/bin/MYGAME` (an S16 OMF), its data staged in
|
||||||
`build/iigs/data/MYGAME/`, and with `MYGAME_IIGS_GAME_DISK` the disks
|
`build/iigs/data/MYGAME/`, its Finder icon in `build/iigs/icon/MYGAME/`, and
|
||||||
`build/iigs/bin/MYGAME.2mg` and `MYGAME-data.2mg`
|
with `MYGAME_IIGS_GAME_DISK` the disks `build/iigs/bin/MYGAME.2mg` and
|
||||||
|Amiga |`build/amiga/bin/Mygame`
|
`MYGAME-data.2mg`
|
||||||
|
|Amiga |`build/amiga/bin/Mygame` and its Workbench icon, `Mygame.info`
|
||||||
|Atari ST |`build/atarist/bin/MYGAME.PRG`, which runs on every ST
|
|Atari ST |`build/atarist/bin/MYGAME.PRG`, which runs on every ST
|
||||||
|Atari STe |`build/ste/bin/MYGAME.PRG`, the STe build: STe and Mega STe only
|
|Atari STe |`build/ste/bin/MYGAME.PRG`, the STe build: STe and Mega STe only
|
||||||
(<<_the_atari_st_family>>); same toolchain and baked data as the ST
|
(<<_the_atari_st_family>>); same toolchain and baked data as the ST
|
||||||
|MS-DOS |`build/dos/bin/MYGAME.EXE`, with the DPMI server built in
|
|MS-DOS |`build/dos/bin/MYGAME.EXE`, with the DPMI server built in
|
||||||
|Sharp X68000 |`build/x68000/bin/MYGAME.X`
|
|Sharp X68000 |`build/x68000/bin/MYGAME.X`
|
||||||
|DVX |`build/dvx/bin/apps/joeylib/mygame/`: `mygame.app`, `mygame.hlp` and its
|
|DVX |`build/dvx/bin/apps/joeylib/mygame/`: `mygame.app`, `mygame.hlp`, and its
|
||||||
`DATA/`
|
`DATA/`
|
||||||
|SDL3 |`build/sdl3/bin/MYGAME`, a native program with SDL 3 linked in
|
|SDL3 |`build/sdl3/bin/MYGAME`, a native program with SDL 3 linked in
|
||||||
|Web |`build/web/bin/MYGAME/`: `MYGAME.html`, `.js`, `.wasm`, and `.data`
|
|Web |`build/web/bin/MYGAME/`: `MYGAME.html`, `.js`, `.wasm`, and `.data`
|
||||||
(its `DATA/`, packed in)
|
(its `DATA/`, packed in), and `icon.png`
|
||||||
|Win32 |`build/win32/bin/MYGAME.EXE`, a 32-bit Windows program
|
|Win32 |`build/win32/bin/MYGAME.EXE`, a 32-bit Windows program
|
||||||
|Win16 |`build/win16/bin/MYGAME.EXE`, a Windows 3.x program
|
|Win16 |`build/win16/bin/MYGAME.EXE`, a Windows 3.x program
|
||||||
|PSP |`build/psp/bin/MYGAME/`: `EBOOT.PBP` and its `DATA/`, a folder for the
|
|PSP |`build/psp/bin/MYGAME/`: `EBOOT.PBP` and its `DATA/`, a folder for the
|
||||||
|
|
@ -84,7 +85,8 @@ memory stick's `PSP/GAME/`; the ELF it is packed from in
|
||||||
|
|
||||||
A program with `MYGAME_FOLDER` set ships in a folder of its own with its
|
A program with `MYGAME_FOLDER` set ships in a folder of its own with its
|
||||||
`DATA/` beside it (`build/dos/bin/MYGAME/MYGAME.EXE`); otherwise programs share
|
`DATA/` beside it (`build/dos/bin/MYGAME/MYGAME.EXE`); otherwise programs share
|
||||||
`bin/DATA/`.
|
`bin/DATA/`. A program's icon (<<_icons>>) is baked in
|
||||||
|
`build/<target>/icon/MYGAME/` on its way into the program.
|
||||||
|
|
||||||
JoeyLib's library for each target, and the host tools such as `spritebake`, are
|
JoeyLib's library for each target, and the host tools such as `spritebake`, are
|
||||||
built on demand where JoeyLib keeps them, in the checkout's `build/`, and shared
|
built on demand where JoeyLib keeps them, in the checkout's `build/`, and shared
|
||||||
|
|
@ -132,13 +134,16 @@ probes for gates).
|
||||||
|`ID_PORTS` |The targets it builds for. Every target when unset.
|
|`ID_PORTS` |The targets it builds for. Every target when unset.
|
||||||
|`ID_NAME` |Its file name without the extension (see <<_naming>>).
|
|`ID_NAME` |Its file name without the extension (see <<_naming>>).
|
||||||
|`ID_FOLDER` |Set: it ships in a folder of its own with its `DATA/` (Amiga,
|
|`ID_FOLDER` |Set: it ships in a folder of its own with its `DATA/` (Amiga,
|
||||||
ST, DOS, X68000, SDL3, Win32, Win16; on the IIgs, DVX, the web and the PSP
|
ST, DOS, X68000, SDL3, Win32, Win16; on the IIgs, DVX, the web, and the PSP
|
||||||
every program is packaged apart anyway).
|
every program is packaged apart anyway).
|
||||||
|`ID_BINDIR` |Build it there instead of `bin/`.
|
|`ID_BINDIR` |Build it there instead of `bin/`.
|
||||||
|`ID_DATA` |Its data files (<<_data_files>>).
|
|`ID_DATA` |Its data files (<<_data_files>>).
|
||||||
|`ID_TITLE` |DVX: its window title. PSP: its title in the PSP's menu (by
|
|`ID_TITLE` |DVX: its window title. PSP: its title in the PSP's menu (by
|
||||||
default its name).
|
default its name).
|
||||||
|`ID_HELP` |DVX: the line its help file says about it. No apostrophes.
|
|`ID_ICON` |Its icon: a picture (a PNG, best square, transparent around the
|
||||||
|
figure), baked for every target whose system shows programs by an icon
|
||||||
|
(<<_icons>>). JoeyLib's kangaroo by default.
|
||||||
|
|`ID_HELP` |DVX: the line its help file says about it.
|
||||||
|`ID_IIGS_OVERFLOW_CAP` |IIgs: link large overflow segments (`0xF000`), which
|
|`ID_IIGS_OVERFLOW_CAP` |IIgs: link large overflow segments (`0xF000`), which
|
||||||
a big program needs to leave the Memory Manager room (see
|
a big program needs to leave the Memory Manager room (see
|
||||||
`make/support/iigs/clang-build.sh`).
|
`make/support/iigs/clang-build.sh`).
|
||||||
|
|
@ -174,11 +179,12 @@ set up:
|
||||||
|`$(call programDataDir,ID)` |Where its `DATA/` files go on this target.
|
|`$(call programDataDir,ID)` |Where its `DATA/` files go on this target.
|
||||||
|`$(call programOptionalDir,ID)` |Where the files go that it can run without
|
|`$(call programOptionalDir,ID)` |Where the files go that it can run without
|
||||||
(<<_disk_images_and_packaging>>).
|
(<<_disk_images_and_packaging>>).
|
||||||
|`PLATFORM` |The target: `iigs`, `amiga`, `atarist`, `ste`, `dos`, `x68000`, `dvx`, `sdl3`, `web`, `win32`, `win16`, `psp`.
|
|`PLATFORM` |The target: `iigs`, `amiga`, `atarist`, `ste`, `dos`, `x68000`, `dvx`, `sdl3`, `web`, `win32`, `win16`, `psp`, `haiku`, `haiku32`, `beos`.
|
||||||
|`ASSET_PLATFORM` |The target its bakes are for. DVX shows the DOS stage and
|
|`ASSET_PLATFORM` |The target its bakes are for. DVX shows the DOS stage and
|
||||||
runs x86 code, so it takes the DOS bakes; the hosted ports' stage (SDL3, the
|
runs x86 code, so it takes the DOS bakes; the hosted ports' stage (SDL3, the
|
||||||
web, Win32, Win16, the PSP) is the same chunky one, so they take them too
|
web, Win32, Win16, the PSP, Haiku, BeOS) is the same chunky one, so they take
|
||||||
(the cels of a pre-compiled sprite bank, without their code).
|
them too: Win16 runs a pre-compiled sprite bank's x86 code as DVX does, and
|
||||||
|
the others take its cels without their code. The STe build takes the ST's.
|
||||||
|`SPRITEBAKE` |The host `spritebake` for this target (its rule is
|
|`SPRITEBAKE` |The host `spritebake` for this target (its rule is
|
||||||
JoeyLib's).
|
JoeyLib's).
|
||||||
|`PROJECT_DIR`, `PROJECT_BUILD` |The project and its `build/`.
|
|`PROJECT_DIR`, `PROJECT_BUILD` |The project and its `build/`.
|
||||||
|
|
@ -186,7 +192,7 @@ JoeyLib's).
|
||||||
|`HOST_CC`, `HOST_CFLAGS` |For a host tool of the project's own.
|
|`HOST_CC`, `HOST_CFLAGS` |For a host tool of the project's own.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
A copy, a tile-bank bake and a pre-compiled sprite bank:
|
A copy, a tile-bank bake, and a pre-compiled sprite bank:
|
||||||
|
|
||||||
[source,makefile]
|
[source,makefile]
|
||||||
----
|
----
|
||||||
|
|
@ -252,7 +258,8 @@ off a CFFA2 card. `make iigs-game-disk GAME=NAME` builds one for any program,
|
||||||
`GAME_DISK_SIZE=2048KB` forces the size, and `make iigs-verify-game-disk`
|
`GAME_DISK_SIZE=2048KB` forces the size, and `make iigs-verify-game-disk`
|
||||||
boots it headlessly and checks it draws. The data the program needs goes on with
|
boots it headlessly and checks it draws. The data the program needs goes on with
|
||||||
it; files listed under `programOptionalDir` (a program's optional data, which
|
it; files listed under `programOptionalDir` (a program's optional data, which
|
||||||
it can run without) go on last, only while the volume has room.
|
it can run without) go on last, only while the volume has room, and the
|
||||||
|
programs' Finder icons last of all (<<_icons>>).
|
||||||
|
|
||||||
*Sharp X68000.* Programs ship on a Human68k floppy; `joey-run` builds one for
|
*Sharp X68000.* Programs ship on a Human68k floppy; `joey-run` builds one for
|
||||||
each run from the program and its `DATA/`. A floppy is 1232 KB, so a large
|
each run from the program and its `DATA/`. A floppy is 1232 KB, so a large
|
||||||
|
|
@ -266,6 +273,7 @@ seems to vanish.
|
||||||
needs to be on the disk.
|
needs to be on the disk.
|
||||||
|
|
||||||
*Amiga.* `joey-run` writes an `s/startup-sequence` that invokes your binary.
|
*Amiga.* `joey-run` writes an `s/startup-sequence` that invokes your binary.
|
||||||
|
`Name.info` beside it is its Workbench icon, from which it starts too.
|
||||||
|
|
||||||
*DVX.* Every program is a folder laid out as it installs in DVX:
|
*DVX.* Every program is a folder laid out as it installs in DVX:
|
||||||
`build/dvx/bin/apps/joeylib/mygame/` holds `mygame.app`, its help file
|
`build/dvx/bin/apps/joeylib/mygame/` holds `mygame.app`, its help file
|
||||||
|
|
@ -287,16 +295,64 @@ DLLs, so its folder, with its `DATA/`, is all it needs; it ships the way a
|
||||||
DOS program does.
|
DOS program does.
|
||||||
|
|
||||||
*Win16.* A program is one `.EXE`, its 32-bit code bound to Open Watcom's
|
*Win16.* A program is one `.EXE`, its 32-bit code bound to Open Watcom's
|
||||||
Win386 supervisor and calling only what Windows 3.1 has (USER, GDI, KERNEL
|
Win386 supervisor and calling only what Windows 3.1 has (USER, GDI, KERNEL,
|
||||||
and MMSYSTEM), so its folder, with its `DATA/`, is all it needs; it ships the
|
and MMSYSTEM), so its folder, with its `DATA/`, is all it needs; it ships the
|
||||||
way a DOS program does.
|
way a DOS program does.
|
||||||
|
|
||||||
*PSP.* A program is its folder, `build/psp/bin/MYGAME/`: `EBOOT.PBP` (the
|
*PSP.* A program is its folder, `build/psp/bin/MYGAME/`: `EBOOT.PBP` (the
|
||||||
program, SDL 3 linked in, packed as the PSPSDK packs it, with no icon or
|
program, SDL 3 linked in, packed as the PSPSDK packs it, with its icon and no
|
||||||
pictures) and its `DATA/`. Copy the folder into `PSP/GAME/` on a memory
|
other pictures) and its `DATA/`. Copy the folder into `PSP/GAME/` on a memory
|
||||||
stick; the PSP shows it by its `ID_TITLE`. The unstripped ELF stays in
|
stick; the PSP shows it by its icon and its `ID_TITLE`. The unstripped ELF stays in
|
||||||
`build/psp/elf/`, for `psp-addr2line` on an address a crash reports.
|
`build/psp/elf/`, for `psp-addr2line` on an address a crash reports.
|
||||||
|
|
||||||
|
=== Icons
|
||||||
|
|
||||||
|
A program has an icon on every system that shows programs by one. `ID_ICON`
|
||||||
|
names a picture, a PNG, best square and transparent around the figure, and
|
||||||
|
`make` bakes it for each target with `tools/iconBake.py`; a program without
|
||||||
|
one gets JoeyLib's kangaroo, `assets/joeyIcon.png`. A picture of 64 pixels or
|
||||||
|
less is taken for pixel art and enlarged by whole pixels; a bigger one is
|
||||||
|
resampled smoothly. Where an icon has few colours, each pixel takes the
|
||||||
|
nearest.
|
||||||
|
|
||||||
|
[cols="1,5", options="header"]
|
||||||
|
|===
|
||||||
|
|Target |The icon
|
||||||
|
|
||||||
|
|Apple IIgs |The Finder's: an icon file (type `$CA`) for each program, which
|
||||||
|
the disk scripts put in the volume's `Icons` folder as `NAME.ICONS`, where the
|
||||||
|
Finder reads icon files when it starts. 24x20 and 12x10 colour icons, which
|
||||||
|
the Finder shows as they are: in its 640 mode each icon pixel is two screen
|
||||||
|
pixels, black, red, green, or white beside black, blue, yellow, or white, seen
|
||||||
|
together as one of 16 colours (an icon drawn at an odd screen column would
|
||||||
|
pair them with the other halves of the palette; the Finder drew every one at
|
||||||
|
an even column). A data disk takes the icons
|
||||||
|
last, while there is room; a game disk keeps its program's at its root. A
|
||||||
|
program copied to another volume takes its icon file along, into that
|
||||||
|
volume's `Icons` folder.
|
||||||
|
|Amiga |Workbench's: `Name.info` beside the program, a tool's icon in
|
||||||
|
Workbench's four colours, 48x24 hires pixels, which starts it with
|
||||||
|
`AMIGA_ICON_STACK` (32768) bytes of stack. Started from it, the program runs
|
||||||
|
in its own folder, as it does from a shell.
|
||||||
|
|DVX |Program Manager's: the program's `icon32` resource, 32x32 on DVX's grey.
|
||||||
|
|Win16 |Program Manager's and the minimised window's: 32x32 in the 16 VGA
|
||||||
|
colours, bound into the `.EXE`.
|
||||||
|
|Win32 |Explorer's and the window's: 16, 32, and 48 pixels, each in 256
|
||||||
|
colours and in 32-bit, linked into the `.EXE`.
|
||||||
|
|Haiku, BeOS |Tracker's and the Deskbar's: 32x32 and 16x16 in the BeOS
|
||||||
|
palette, in the program's resources. Tracker shows a file by the icons in its
|
||||||
|
attributes, which a program built on another machine does not have; the
|
||||||
|
program copies them across the first time it runs, so Tracker shows it by its
|
||||||
|
icon from then on (or at once, after `mimeset -F NAME`).
|
||||||
|
|PSP |The menu's: `ICON0` in the EBOOT, 144x80.
|
||||||
|
|SDL3 |The window's, 64x64, which the desktop shows where it shows one (title
|
||||||
|
bar, task bar, dock).
|
||||||
|
|Web |The page's: `icon.png`, 32x32, beside it.
|
||||||
|
|===
|
||||||
|
|
||||||
|
MS-DOS, the Atari ST, and the X68000 show no program by an icon of its own,
|
||||||
|
so their programs get none.
|
||||||
|
|
||||||
=== Verifying It Actually Runs
|
=== Verifying It Actually Runs
|
||||||
|
|
||||||
Build success is weak evidence on retro targets. Run it with `joey-run` (see
|
Build success is weak evidence on retro targets. Run it with `joey-run` (see
|
||||||
|
|
@ -353,7 +409,7 @@ Human68k floppies, environment checks) takes paths.
|
||||||
|`make verify-audio`
|
|`make verify-audio`
|
||||||
|Every port's audio gate: the IIgs DOC and X68000 chips are read directly,
|
|Every port's audio gate: the IIgs DOC and X68000 chips are read directly,
|
||||||
the ST's YM2149 register writes are traced (and for the STe build the
|
the ST's YM2149 register writes are traced (and for the STe build the
|
||||||
samples the DMA sound chip plays), the Amiga, DOS and DVX output
|
samples the DMA sound chip plays), the Amiga, DOS, and DVX output
|
||||||
is recorded and graded, SDL3's mix is written to a file by SDL's disk
|
is recorded and graded, SDL3's mix is written to a file by SDL's disk
|
||||||
driver and graded, and the web page's sound is recorded in headless
|
driver and graded, and the web page's sound is recorded in headless
|
||||||
Chromium and graded, as is the Win32 program's under Wine, the Win16
|
Chromium and graded, as is the Win32 program's under Wine, the Win16
|
||||||
|
|
@ -365,7 +421,7 @@ if a gate had to be skipped.
|
||||||
|The IIgs actually reached the program and drew something.
|
|The IIgs actually reached the program and drew something.
|
||||||
|
|
||||||
|`make iigs-verify-game-disk`
|
|`make iigs-verify-game-disk`
|
||||||
|A program's bootable IIgs volume boots, launches the program and draws.
|
|A program's bootable IIgs volume boots, launches the program, and draws.
|
||||||
|
|
||||||
|`scripts/verify-<port>-tilebank.sh`
|
|`scripts/verify-<port>-tilebank.sh`
|
||||||
|The port's own tile-bank bake loads pixel-exact both ways, with the same
|
|The port's own tile-bank bake loads pixel-exact both ways, with the same
|
||||||
|
|
@ -373,6 +429,13 @@ if a gate had to be skipped.
|
||||||
for the other machines are refused. `scripts/verify-all.sh tilebank` runs
|
for the other machines are refused. `scripts/verify-all.sh tilebank` runs
|
||||||
every port's.
|
every port's.
|
||||||
|
|
||||||
|
|`scripts/verify-all.sh icon`
|
||||||
|
|Every program carries its icon where its system looks
|
||||||
|
(`scripts/check-icons.sh`, no emulator), and each system shows it: the IIgs
|
||||||
|
Finder's window, the Amiga's icon.library (and a Workbench start), DVX's
|
||||||
|
Program Manager, Windows 3.11's desktop with the program minimised, the
|
||||||
|
Win32 and SDL3 windows' icons on X, and Tracker on Haiku and BeOS.
|
||||||
|
|
||||||
|`scripts/bench-<port>.sh`
|
|`scripts/bench-<port>.sh`
|
||||||
|Per-operation throughput for that port.
|
|Per-operation throughput for that port.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ Toolchain availability:
|
||||||
|
|
||||||
==== Installer reference
|
==== Installer reference
|
||||||
|
|
||||||
`./install.sh [options] COMMAND [TARGETS...]`, run from the top of the
|
`+./install.sh [options] COMMAND [TARGETS...]+`, run from the top of the
|
||||||
repository.
|
repository.
|
||||||
|
|
||||||
[cols="1,3"]
|
[cols="1,3"]
|
||||||
|
|
@ -91,9 +91,9 @@ libraries, for example). It reports anything out of date, any component
|
||||||
whose files have gone, and any part of a port you installed only partly,
|
whose files have gone, and any part of a port you installed only partly,
|
||||||
such as FS-UAE without a Kickstart.
|
such as FS-UAE without a Kickstart.
|
||||||
|
|
||||||
| `import PATH...`
|
| `+import PATH...+`
|
||||||
| Installs the files that are not downloaded (see <<Firmware that is not
|
| Installs the files that are not downloaded (see <<Firmware that is not
|
||||||
downloaded>>) from files, folders, `.zip` archives or Amiga `.adf` disk
|
downloaded>>) from files, folders, `.zip` archives, or Amiga `.adf` disk
|
||||||
images, matched by checksum.
|
images, matched by checksum.
|
||||||
|
|
||||||
| `bundle FILE`
|
| `bundle FILE`
|
||||||
|
|
@ -198,7 +198,7 @@ trees.
|
||||||
Nothing in `toolchains/` is part of the repository: delete the whole folder
|
Nothing in `toolchains/` is part of the repository: delete the whole folder
|
||||||
to return the machine to a clean state, and `./install.sh` re-creates it. The
|
to return the machine to a clean state, and `./install.sh` re-creates it. The
|
||||||
installer itself lives in `install.sh` and `installer/` (its component
|
installer itself lives in `install.sh` and `installer/` (its component
|
||||||
recipes, patches and package lists), and the build helpers the makefiles call
|
recipes, patches, and package lists), and the build helpers the makefiles call
|
||||||
live in `make/support/`.
|
live in `make/support/`.
|
||||||
|
|
||||||
=== Building
|
=== Building
|
||||||
|
|
@ -251,6 +251,7 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -266,10 +267,13 @@ int main(void) {
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
Three things to notice, because they apply to every JoeyLib program:
|
Four things to notice, because they apply to every JoeyLib program:
|
||||||
|
|
||||||
. *One header.* `#include <joey/joey.h>` pulls in everything except the
|
. *One header.* `#include <joey/joey.h>` pulls in everything except the
|
||||||
optional serial add-on.
|
optional serial add-on.
|
||||||
|
. *Set every field of `jlConfigT`.* It is read whole, so a field left unset
|
||||||
|
holds whatever was in memory. `noAudio` says whether the program wants sound:
|
||||||
|
`jlInit` starts audio unless it is `true`.
|
||||||
. *`jlInit` can fail*, and when it does `jlLastError()` tells you why. Always
|
. *`jlInit` can fail*, and when it does `jlLastError()` tells you why. Always
|
||||||
check it.
|
check it.
|
||||||
. *`jlShutdown` is not optional.* It restores the display mode, releases the
|
. *`jlShutdown` is not optional.* It restores the display mode, releases the
|
||||||
|
|
@ -289,10 +293,11 @@ graphics on screen and waits for a keypress:
|
||||||
int main(void) {
|
int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
jlSurfaceT *stage;
|
jlSurfaceT *stage;
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +305,7 @@ int main(void) {
|
||||||
stage = jlStageGet(); // the library-owned back buffer
|
stage = jlStageGet(); // the library-owned back buffer
|
||||||
|
|
||||||
// Build palette 0. Entry 0 is forced black whatever we put here.
|
// Build palette 0. Entry 0 is forced black whatever we put here.
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = 0x000;
|
colors[i] = 0x000;
|
||||||
}
|
}
|
||||||
colors[1] = 0xFFF; // white
|
colors[1] = 0xFFF; // white
|
||||||
|
|
@ -309,12 +314,12 @@ int main(void) {
|
||||||
jlPaletteSet(stage, 0, colors);
|
jlPaletteSet(stage, 0, colors);
|
||||||
|
|
||||||
// Every scanline uses palette 0 (this is also the default).
|
// Every scanline uses palette 0 (this is also the default).
|
||||||
jlScbSetRange(stage, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(stage, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
|
|
||||||
jlSurfaceClear(stage, 0);
|
jlSurfaceClear(stage, 0);
|
||||||
jlFillRect(stage, 40, 30, 240, 140, 3);
|
jlFillRect(stage, 40, 30, 240, 140, 3);
|
||||||
jlDrawRect(stage, 40, 30, 240, 140, 1);
|
jlDrawRect(stage, 40, 30, 240, 140, 1);
|
||||||
jlDrawLine(stage, 0, 0, SURFACE_WIDTH - 1, SURFACE_HEIGHT - 1, 2);
|
jlDrawLine(stage, 0, 0, JL_SURFACE_WIDTH - 1, JL_SURFACE_HEIGHT - 1, 2);
|
||||||
|
|
||||||
jlStagePresent(); // nothing is visible until this runs
|
jlStagePresent(); // nothing is visible until this runs
|
||||||
jlWaitForAnyKey();
|
jlWaitForAnyKey();
|
||||||
|
|
@ -375,7 +380,7 @@ the next. Nothing is written to your home directory.
|
||||||
| The machine, when naming an example rather than a file.
|
| The machine, when naming an example rather than a file.
|
||||||
|
|
||||||
| `--data DIR`
|
| `--data DIR`
|
||||||
| Copy `DIR`'s files into the program's `DATA` folder for this run, for
|
| Copy the files in `DIR` into the program's `DATA` folder for this run, for
|
||||||
example the game an interpreter plays.
|
example the game an interpreter plays.
|
||||||
|
|
||||||
| `--headless`
|
| `--headless`
|
||||||
|
|
@ -392,7 +397,7 @@ example the game an interpreter plays.
|
||||||
| Apple IIgs: the ROM 01 machine instead of ROM 03.
|
| Apple IIgs: the ROM 01 machine instead of ROM 03.
|
||||||
|
|
||||||
| `--machine st`
|
| `--machine st`
|
||||||
| Atari: Hatari's machine, `st`, `megast`, `ste` or `megaste`. By default an
|
| Atari: Hatari's machine, `st`, `megast`, `ste`, or `megaste`. By default an
|
||||||
ST-build program (`make atarist`) gets a plain ST and an STe-build one
|
ST-build program (`make atarist`) gets a plain ST and an STe-build one
|
||||||
(`make ste`) an STe; the STe build runs only on `ste` and `megaste`.
|
(`make ste`) an STe; the STe build runs only on `ste` and `megaste`.
|
||||||
|
|
||||||
|
|
@ -419,7 +424,7 @@ Kickstarts (`./install.sh install emulator:aga`).
|
||||||
default is 9600.
|
default is 9600.
|
||||||
|
|
||||||
| `-- ARGS`
|
| `-- ARGS`
|
||||||
| Pass `ARGS` to the program. DOS, SDL3, Win32 and Win16 only: the other
|
| Pass `ARGS` to the program. DOS, SDL3, Win32, and Win16 only: the other
|
||||||
machines' autostart cannot pass arguments, and a page and a PSP program have
|
machines' autostart cannot pass arguments, and a page and a PSP program have
|
||||||
none.
|
none.
|
||||||
|===
|
|===
|
||||||
|
|
@ -440,7 +445,7 @@ desktop.
|
||||||
An SDL3 program is a native program, so `joey-run` simply starts it, in a
|
An SDL3 program is a native program, so `joey-run` simply starts it, in a
|
||||||
window; running the file yourself does the same. `--headless` runs it on a
|
window; running the file yourself does the same. `--headless` runs it on a
|
||||||
private virtual display at one window pixel per stage pixel, so a
|
private virtual display at one window pixel per stage pixel, so a
|
||||||
`--snapshot` is exactly the 320x200 stage. The window's size, shape and full
|
`--snapshot` is exactly the 320x200 stage. The window's size, shape, and full
|
||||||
screen are set by environment variables (<<_sdl3_programs>>). The SDL3 port
|
screen are set by environment variables (<<_sdl3_programs>>). The SDL3 port
|
||||||
has no serial port.
|
has no serial port.
|
||||||
|
|
||||||
|
|
@ -457,7 +462,7 @@ in when it is built, so `--data` does not apply.
|
||||||
A Windows program runs under Wine: in a window on your display, with its
|
A Windows program runs under Wine: in a window on your display, with its
|
||||||
Wine settings kept in the project's `build/win32/prefix` so they are set up
|
Wine settings kept in the project's `build/win32/prefix` so they are set up
|
||||||
once (nothing goes in `~/.wine`; a run with `--serial` uses a private one, so
|
once (nothing goes in `~/.wine`; a run with `--serial` uses a private one, so
|
||||||
the kept COM1 is left alone). The window's size, shape and full screen are
|
the kept COM1 is left alone). The window's size, shape, and full screen are
|
||||||
set by the same environment variables as SDL3's, and Alt+Enter switches. With
|
set by the same environment variables as SDL3's, and Alt+Enter switches. With
|
||||||
`--headless` it runs on a private virtual display at one pixel per stage
|
`--headless` it runs on a private virtual display at one pixel per stage
|
||||||
pixel, so a `--snapshot` is the stage.
|
pixel, so a `--snapshot` is the stage.
|
||||||
|
|
@ -474,7 +479,7 @@ up to 115200 baud). Their components, `kpjoy` and `kpcomm`, build them from
|
||||||
the kpjoy and mscomm projects, checkouts beside JoeyLib's (`../kpjoy`,
|
the kpjoy and mscomm projects, checkouts beside JoeyLib's (`../kpjoy`,
|
||||||
`../mscomm/drv`; `KPJOY_DIR` and `KPCOMM_DIR` name others). The program is
|
`../mscomm/drv`; `KPJOY_DIR` and `KPCOMM_DIR` name others). The program is
|
||||||
Windows' shell, so Windows ends when the program does, and its folder is
|
Windows' shell, so Windows ends when the program does, and its folder is
|
||||||
drive D:, so what it saves stays there. The window's size, shape and full
|
drive D:, so what it saves stays there. The window's size, shape, and full
|
||||||
screen are set by the same environment variables as SDL3's: `joey-run` puts
|
screen are set by the same environment variables as SDL3's: `joey-run` puts
|
||||||
them in the DOS environment Windows starts from. Alt+Enter is DOSBox's own
|
them in the DOS environment Windows starts from. Alt+Enter is DOSBox's own
|
||||||
full-screen key, so the program switches with Alt and the keypad's Enter.
|
full-screen key, so the program switches with Alt and the keypad's Enter.
|
||||||
|
|
@ -496,11 +501,11 @@ serial port.
|
||||||
==== Haiku and BeOS
|
==== Haiku and BeOS
|
||||||
|
|
||||||
A Haiku (`haiku`, `haiku32`) or BeOS R5 (`beos`) program runs in that port's
|
A Haiku (`haiku`, `haiku32`) or BeOS R5 (`beos`) program runs in that port's
|
||||||
VM under QEMU (`./install.sh install emulator:haiku`, `emulator:haiku32` or
|
VM under QEMU (`./install.sh install emulator:haiku`, `emulator:haiku32`, or
|
||||||
`emulator:beos`), in QEMU's window, from a copy of its folder on a second
|
`emulator:beos`), in QEMU's window, from a copy of its folder on a second
|
||||||
disk. The machine boots from a disk it never writes to, runs the program,
|
disk. The machine boots from a disk it never writes to, runs the program,
|
||||||
and powers off when the program ends; what the program wrote in its folder
|
and powers off when the program ends; what the program wrote in its folder
|
||||||
(saves, its log) is then copied back. The window's size, shape and full
|
(saves, its log) is then copied back. The window's size, shape, and full
|
||||||
screen are set by the same environment variables as SDL3's. Haiku's VM has
|
screen are set by the same environment variables as SDL3's. Haiku's VM has
|
||||||
sound; BeOS's has none, and is taken through R5's boot loader menu to its
|
sound; BeOS's has none, and is taken through R5's boot loader menu to its
|
||||||
fail-safe video mode (1024x768, 16-bit colour), as R5 has no driver for any
|
fail-safe video mode (1024x768, 16-bit colour), as R5 has no driver for any
|
||||||
|
|
@ -585,7 +590,7 @@ arrives. To connect out, `socat` joins a pseudo-terminal to the network.
|
||||||
| As on the ST: Wine's COM1 can only be a device, so `socat` joins a
|
| As on the ST: Wine's COM1 can only be a device, so `socat` joins a
|
||||||
pseudo-terminal to the network both ways.
|
pseudo-terminal to the network both ways.
|
||||||
|
|
||||||
| SDL3, Web, PSP
|
| SDL3, Web, PSP, Haiku, BeOS
|
||||||
| None
|
| None
|
||||||
| These ports have no serial port; `jlSerialOpen` fails there.
|
| These ports have no serial port; `jlSerialOpen` fails there.
|
||||||
|===
|
|===
|
||||||
|
|
@ -604,14 +609,14 @@ rate the program sets.
|
||||||
and X68000, where MAME prints `Unable to load image` and runs with the port
|
and X68000, where MAME prints `Unable to load image` and runs with the port
|
||||||
unplugged.
|
unplugged.
|
||||||
* While a port is listening, what the program sends waits for the connection,
|
* While a port is listening, what the program sends waits for the connection,
|
||||||
except on DOS, DVX and Win16, where DOSBox drops it just as a cable with
|
except on DOS, DVX, and Win16, where DOSBox drops it just as a cable with
|
||||||
nothing on the other end would. `examples/sertest` sends its banner at once,
|
nothing on the other end would. `examples/sertest` sends its banner at once,
|
||||||
so there a client that connects late sees only the echo.
|
so there a client that connects late sees only the echo.
|
||||||
|
|
||||||
==== Firmware that is not downloaded
|
==== Firmware that is not downloaded
|
||||||
|
|
||||||
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST,
|
Almost everything the emulators need is downloaded: EmuTOS for the Atari ST;
|
||||||
the Apple IIgs ROMs, GS/OS 6.0.4 and the CFFA2 card's firmware, and Sharp's own
|
the Apple IIgs ROMs, GS/OS 6.0.4, and the CFFA2 card's firmware; and Sharp's own
|
||||||
free release of the X68000 IPL-ROM and Human68k 3.02. A few files are not,
|
free release of the X68000 IPL-ROM and Human68k 3.02. A few files are not,
|
||||||
because they were never released for free: the Amiga Kickstart 3.1 ROM, the
|
because they were never released for free: the Amiga Kickstart 3.1 ROM, the
|
||||||
Amiga `serial.device` and `lowlevel.library` from Workbench 3.1 (Kickstart
|
Amiga `serial.device` and `lowlevel.library` from Workbench 3.1 (Kickstart
|
||||||
|
|
@ -621,7 +626,10 @@ Kickstart 3.1 (optional: only for running the Amiga programs on those
|
||||||
machines), the X68000 CG (font) ROM,
|
machines), the X68000 CG (font) ROM,
|
||||||
for the Win16 port the Windows for Workgroups 3.11 install disc (as an
|
for the Win16 port the Windows for Workgroups 3.11 install disc (as an
|
||||||
`.iso`) and the S3 Trio64V+ 1.70.04 Windows 3.1 display driver, from which
|
`.iso`) and the S3 Trio64V+ 1.70.04 Windows 3.1 display driver, from which
|
||||||
the installer sets Windows up unattended, and for the BeOS port the BeOS 5
|
the installer sets Windows up unattended (and, optionally, Microsoft's WinG
|
||||||
|
1.0 SDK, `WING10.EXE`, whose runtime the Win16 port draws through when it is
|
||||||
|
installed: import it, then `./install.sh install wing`; archive.org keeps a
|
||||||
|
copy), and for the BeOS port the BeOS 5
|
||||||
Professional CD (its CloneCD `.img`, or the `.7z` holding it), from which
|
Professional CD (its CloneCD `.img`, or the `.7z` holding it), from which
|
||||||
the installer takes R5's headers and libraries and makes the VM. Supply your
|
the installer takes R5's headers and libraries and makes the VM. Supply your
|
||||||
copies once:
|
copies once:
|
||||||
|
|
@ -631,7 +639,7 @@ copies once:
|
||||||
$ ./install.sh import ~/my-roms
|
$ ./install.sh import ~/my-roms
|
||||||
----
|
----
|
||||||
|
|
||||||
`import` takes files, folders, `.zip` and `.7z` archives and Amiga `.adf` disk images
|
`import` takes files, folders, `.zip` and `.7z` archives, and Amiga `.adf` disk images
|
||||||
(point it at your Workbench 3.1 disk for `serial.device` and
|
(point it at your Workbench 3.1 disk for `serial.device` and
|
||||||
`lowlevel.library`, and at the S3
|
`lowlevel.library`, and at the S3
|
||||||
driver's `.zip` for its three files), identifies each file by its checksum
|
driver's `.zip` for its three files), identifies each file by its checksum
|
||||||
|
|
@ -655,9 +663,9 @@ built for every platform:
|
||||||
|`draw` |Every drawing primitive, laid out in four quadrants.
|
|`draw` |Every drawing primitive, laid out in four quadrants.
|
||||||
|`pattern` |Palette and per-scanline palette (SCB) work.
|
|`pattern` |Palette and per-scanline palette (SCB) work.
|
||||||
|`keys` |Typed-character input and the key predicates.
|
|`keys` |Typed-character input and the key predicates.
|
||||||
|`joy` |Joystick axes, buttons and recalibration.
|
|`joy` |Joystick axes, buttons, and recalibration.
|
||||||
|`sprite` |Sprite loading, compiling, save-under and restore.
|
|`sprite` |Sprite loading, compiling, save-under, and restore.
|
||||||
|`audio` |Tracker music, `.MOD` playback, digital sound effects, a streamed sound and the noise generator.
|
|`audio` |Tracker music, `.MOD` playback, digital sound effects, a streamed sound, and the noise generator.
|
||||||
|`save` |Save files and the disk-space query.
|
|`save` |Save files and the disk-space query.
|
||||||
|`tilebank` |Loading a baked tile bank both ways, and a bank for another machine refused.
|
|`tilebank` |Loading a baked tile bank both ways, and a bank for another machine refused.
|
||||||
|`serial` |The optional RS-232 add-on.
|
|`serial` |The optional RS-232 add-on.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
=== What JoeyLib Is
|
=== What JoeyLib Is
|
||||||
|
|
||||||
JoeyLib is a single-codebase C game library for five 16-bit and 32-bit retro
|
JoeyLib is a single-codebase C game library for five 16-bit and 32-bit retro
|
||||||
machines, for DVX, a DOS desktop whose programs run in windows, and for
|
machines, for DVX, a DOS desktop whose programs run in windows, for
|
||||||
today's desktops, browsers and the PSP through SDL3, and for Windows from
|
today's desktops, browsers, and the PSP through SDL3, and for Windows from
|
||||||
3.1 on. You write one program against one API, and the same source compiles
|
3.1 on. You write one program against one API, and the same source compiles
|
||||||
and runs on all of them:
|
and runs on all of them:
|
||||||
|
|
||||||
|
|
@ -28,8 +28,9 @@ and runs on all of them:
|
||||||
|512 KB
|
|512 KB
|
||||||
|Word-interleaved planar. Shares 68000 assembly with the Amiga port. Two
|
|Word-interleaved planar. Shares 68000 assembly with the Amiga port. Two
|
||||||
builds: the ST build runs on every ST and never uses STe hardware; the STe
|
builds: the ST build runs on every ST and never uses STe hardware; the STe
|
||||||
build (`make ste`) uses the STe's 4-bit palette, DMA sound and BLiTTER, and a
|
build (`make ste`) uses the STe's 4-bit palette, DMA sound, BLiTTER,
|
||||||
Mega STe's 16 MHz, and runs only there. See <<_the_atari_st_family>>.
|
enhanced joystick ports, and a Mega STe's 16 MHz, and runs only there. See
|
||||||
|
<<_the_atari_st_family>>.
|
||||||
|
|
||||||
|MS-DOS
|
|MS-DOS
|
||||||
|386 or later
|
|386 or later
|
||||||
|
|
@ -73,16 +74,17 @@ and runs on all of them:
|
||||||
|what Windows 3.1 in 386 enhanced mode needs
|
|what Windows 3.1 in 386 enhanced mode needs
|
||||||
|A Windows 3.x program: the hosted core with a backend written to the
|
|A Windows 3.x program: the hosted core with a backend written to the
|
||||||
Windows 3.1 API, in 32-bit code through Open Watcom's Win386 extender; 8-bit
|
Windows 3.1 API, in 32-bit code through Open Watcom's Win386 extender; 8-bit
|
||||||
indexed, so every palette is exact on a 256-colour display. See
|
indexed, so every palette is exact on a 256-colour display, and sprites
|
||||||
<<_windows_programs>>.
|
compiled as on DOS. See <<_windows_programs>>.
|
||||||
|
|
||||||
|PSP
|
|PSP
|
||||||
|MIPS Allegrex at 222 MHz
|
|MIPS Allegrex at 222 MHz
|
||||||
|32 MB (PSP-1000)
|
|32 MB (PSP-1000)
|
||||||
|PSP homebrew, for custom firmware and PPSSPP: the SDL3 port built with
|
|PSP homebrew, for custom firmware and PPSSPP: the SDL3 port built with
|
||||||
pspdev. The stage sits at 1:1 in the middle of the 480x272 screen; the
|
pspdev. The stage sits at 1:1 in the middle of the 480x272 screen; the
|
||||||
d-pad, nub and buttons are joystick 0, Start is Return and Select Escape.
|
d-pad, nub, and buttons are joystick 0; Start is Return, Select is Escape,
|
||||||
See <<_psp_programs>>.
|
and Square opens a keyboard on the screen for every other key. See
|
||||||
|
<<_psp_programs>>.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The Apple IIgs is deliberately the slowest target and the design reference. If
|
The Apple IIgs is deliberately the slowest target and the design reference. If
|
||||||
|
|
@ -116,7 +118,7 @@ that out of your program:
|
||||||
|===
|
|===
|
||||||
|Platform |In-memory pixel storage
|
|Platform |In-memory pixel storage
|
||||||
|
|
||||||
|Apple IIgs, MS-DOS, DVX, SDL3, Web, PSP
|
|Apple IIgs, MS-DOS, DVX, SDL3, Web, PSP, Win32, Win16, Haiku, BeOS
|
||||||
|Chunky 4bpp packed: two pixels per byte, high nibble is the left pixel.
|
|Chunky 4bpp packed: two pixels per byte, high nibble is the left pixel.
|
||||||
|
|
||||||
|Commodore Amiga
|
|Commodore Amiga
|
||||||
|
|
@ -126,7 +128,8 @@ that out of your program:
|
||||||
|Native planar, word-interleaved: four plane words per 16 pixels, per row.
|
|Native planar, word-interleaved: four plane words per 16 pixels, per row.
|
||||||
|
|
||||||
|Sharp X68000
|
|Sharp X68000
|
||||||
|Text-plane planes, four planes 128 KB apart.
|
|Native planar: four separate bitplanes, as on the Amiga. The display they are
|
||||||
|
copied to is the text plane, four planes 128 KB apart.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
Every public entry point speaks in *colour indices, 0 to 15*. Your code never
|
Every public entry point speaks in *colour indices, 0 to 15*. Your code never
|
||||||
|
|
@ -135,11 +138,12 @@ documented and flagged in this manual -- see <<_writing_portable_code>>.
|
||||||
|
|
||||||
=== What You Get
|
=== What You Get
|
||||||
|
|
||||||
* Surfaces, palettes and per-scanline palette selection
|
* Surfaces, palettes, and per-scanline palette selection
|
||||||
* Drawing primitives: pixels, lines, rectangles, circles, flood fill
|
* Drawing primitives: pixels, lines, rectangles, circles, flood fill
|
||||||
* 8x8 tiles, tile banks, tile maps and bitmap text
|
* 8x8 tiles, tile banks, tile maps, and bitmap text
|
||||||
* Sprites with colour-0 transparency and *runtime-compiled blitters* -- the
|
* Sprites with colour-0 transparency and *runtime-compiled blitters* -- the
|
||||||
library emits native 65816, 68000 or x86 code for each sprite at run time
|
library emits native 65816, 68000, or x86 code for each sprite at run time
|
||||||
|
(on the hosted ports other than Win16 every sprite is interpreted)
|
||||||
* Keyboard (both game-style predicates and a typed-character queue), mouse
|
* Keyboard (both game-style predicates and a typed-character queue), mouse
|
||||||
and joystick
|
and joystick
|
||||||
* Four-channel Protracker `.MOD` music, a portable chip-tracker music format,
|
* Four-channel Protracker `.MOD` music, a portable chip-tracker music format,
|
||||||
|
|
@ -155,8 +159,8 @@ documented and flagged in this manual -- see <<_writing_portable_code>>.
|
||||||
JoeyLib is distributed under the MIT License (`LICENSE.md`). A program built
|
JoeyLib is distributed under the MIT License (`LICENSE.md`). A program built
|
||||||
with it has JoeyLib compiled in, so it carries JoeyLib's copyright and
|
with it has JoeyLib compiled in, so it carries JoeyLib's copyright and
|
||||||
permission notice, in its documentation for instance. A program also carries
|
permission notice, in its documentation for instance. A program also carries
|
||||||
code from the libraries each port links (libxmp-lite for music on most
|
code from the libraries each port links (PTPlayer for music on the Amiga,
|
||||||
ports, the toolchains' C libraries and start files), whose terms
|
the toolchains' C libraries and start files), whose terms
|
||||||
`LICENSES.md` lists with what a shipped program owes them. On the Atari ST
|
`LICENSES.md` lists with what a shipped program owes them. On the Atari ST
|
||||||
that includes MiNTLib's LGPL parts: a closed-source game lets its users
|
that includes MiNTLib's LGPL parts: a closed-source game lets its users
|
||||||
relink it against a changed MiNTLib (by shipping its object files, for
|
relink it against a changed MiNTLib (by shipping its object files, for
|
||||||
|
|
@ -169,7 +173,7 @@ If you are new to JoeyLib, read <<_getting_started>>, then
|
||||||
<<_anatomy_of_a_joeylib_program>>, then <<_writing_portable_code>>. Those three
|
<<_anatomy_of_a_joeylib_program>>, then <<_writing_portable_code>>. Those three
|
||||||
chapters are the whole programming model.
|
chapters are the whole programming model.
|
||||||
|
|
||||||
<<_creating_assets>> covers getting graphics, music and sound into a form the
|
<<_creating_assets>> covers getting graphics, music, and sound into a form the
|
||||||
library can load.
|
library can load.
|
||||||
|
|
||||||
<<_building_your_program>> explains how to get your own program, in a
|
<<_building_your_program>> explains how to get your own program, in a
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
== Writing Portable Code
|
== Writing Portable Code
|
||||||
|
|
||||||
A JoeyLib program that follows the rules in this chapter compiles and behaves
|
A JoeyLib program that follows the rules in this chapter compiles and behaves
|
||||||
the same on all five machines, on DVX, under SDL3, in a browser and on Windows. Most of the rules exist because one
|
the same on all five machines, on DVX, under SDL3, in a browser, and on Windows. Most of the rules exist because one
|
||||||
particular target cannot do something the others can, and the Apple IIgs is
|
particular target cannot do something the others can, and the Apple IIgs is
|
||||||
usually that target.
|
usually that target.
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ usually that target.
|
||||||
. Compile your sprites before you draw them.
|
. Compile your sprites before you draw them.
|
||||||
. Keep large objects out of function-local variables on the IIgs.
|
. Keep large objects out of function-local variables on the IIgs.
|
||||||
. Use `jlRandom`, not `rand`, when identical behaviour across ports matters.
|
. Use `jlRandom`, not `rand`, when identical behaviour across ports matters.
|
||||||
. Keep file names short, uppercase-safe and 8.3-compatible.
|
. Keep file names short, uppercase-safe, and 8.3-compatible.
|
||||||
. Do not mix save-under with clean-buffer erasing in the same screen region.
|
. Do not mix save-under with clean-buffer erasing in the same screen region.
|
||||||
. Pick one of the audio layers per scene, not two.
|
. Pick one of the audio layers per scene, not two.
|
||||||
|
|
||||||
|
|
@ -21,11 +21,12 @@ The rest of this chapter explains each one.
|
||||||
|
|
||||||
=== Memory
|
=== Memory
|
||||||
|
|
||||||
This is the rule that catches everyone. *The C heap on the IIgs is tiny.* It
|
This is the rule that catches everyone. *The C heap on the IIgs is tiny.* The
|
||||||
lives in bank 0, and a single `malloc` is capped at roughly 32 KB -- often far
|
whole heap is one Memory Manager block of 32 KB to 48 KB, and if that block
|
||||||
less in practice, because the clang libc heap on that port can be a few hundred
|
cannot be had at start-up it is only what the program's link left in its
|
||||||
bytes. A program that `malloc`s a 32000-byte buffer works on four targets and
|
bank, which can be a few hundred bytes. A program that `malloc`s a
|
||||||
fails on the reference one.
|
32000-byte buffer works on every other target and can fail on the reference
|
||||||
|
one.
|
||||||
|
|
||||||
Use the library allocator, which is backed by each platform's native allocator
|
Use the library allocator, which is backed by each platform's native allocator
|
||||||
(the Memory Manager on the IIgs):
|
(the Memory Manager on the IIgs):
|
||||||
|
|
@ -40,7 +41,10 @@ if (buffer == NULL) {
|
||||||
jlFree(buffer);
|
jlFree(buffer);
|
||||||
----
|
----
|
||||||
|
|
||||||
`jlAlloc` does *not* zero the memory it returns.
|
`jlAlloc` does *not* zero the memory it returns. Never free a `jlAlloc` block
|
||||||
|
with `free`, or a `malloc` block with `jlFree`: on most ports they are the
|
||||||
|
same, but on the IIgs a `jlAlloc` block is a Memory Manager handle, and mixing
|
||||||
|
them corrupts memory.
|
||||||
|
|
||||||
For bulk copies, `jlMemCopy` and `memcpy` are the same thing: `jlMemCopy` is
|
For bulk copies, `jlMemCopy` and `memcpy` are the same thing: `jlMemCopy` is
|
||||||
`memcpy` with a 32-bit count. On the IIgs both are `MVN` block moves at 7
|
`memcpy` with a 32-bit count. On the IIgs both are `MVN` block moves at 7
|
||||||
|
|
@ -71,12 +75,11 @@ void renderScene(void) {
|
||||||
=== Timing
|
=== Timing
|
||||||
|
|
||||||
Covered in <<_frame_rate_independence>>, and worth repeating because it is the
|
Covered in <<_frame_rate_independence>>, and worth repeating because it is the
|
||||||
second most common portability bug: the targets run at 50, 60 and 70 Hz. Scale
|
second most common portability bug: the targets run at 50, 55, 60, and 70 Hz.
|
||||||
by `jlFrameHz()` or work from `jlMillisElapsed()`.
|
Scale by `jlFrameHz()` or work from `jlMillisElapsed()`.
|
||||||
|
|
||||||
`jlFrameCount()` is a 16-bit counter, so you must poll it faster than twice the
|
`jlFrameCount()` is a 16-bit counter that wraps roughly every 18 minutes at
|
||||||
frame rate or you will miss edges, and it wraps roughly every 18 minutes at
|
60 Hz, so compare two readings by subtracting them.
|
||||||
60 Hz.
|
|
||||||
|
|
||||||
=== Colour and the Palette
|
=== Colour and the Palette
|
||||||
|
|
||||||
|
|
@ -94,28 +97,31 @@ have both opaque and masked forms.
|
||||||
Two rules.
|
Two rules.
|
||||||
|
|
||||||
*Compile before drawing.* `jlSpriteDraw` never compiles on demand. An
|
*Compile before drawing.* `jlSpriteDraw` never compiles on demand. An
|
||||||
uncompiled sprite runs through the interpreter at roughly 1/40th the speed, and
|
uncompiled sprite runs through the interpreter, two to six times slower
|
||||||
nothing warns you.
|
depending on the machine, and nothing warns you.
|
||||||
|
|
||||||
*Align your backup buffers.* Give any `jlSpriteBackupT` backing array 2-byte
|
*Align your backup buffers.* Give any `jlSpriteBackupT` backing array 2-byte
|
||||||
alignment, by declaring it `uint16_t` or with an alignment attribute. An odd
|
alignment, by declaring it `uint16_t` or with an alignment attribute. On the
|
||||||
buffer address routes save and restore onto slow interpreted paths on the
|
Amiga, ST, and X68000 an odd buffer address sends save and restore down a much
|
||||||
planar ports, because the 68000 cannot perform word accesses at odd addresses.
|
slower path, and on the Amiga and ST it crashes the trusted variants.
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
// Right: 2-byte aligned by construction.
|
// Right: 2-byte aligned by construction.
|
||||||
static uint16_t gBacking[JOEY_SPRITE_BACKUP_BYTES(4, 4) / 2];
|
static uint16_t gBacking[jlSpriteBackupLength(uint16_t, 4, 4)];
|
||||||
|
|
||||||
// Risky: may land on an odd address.
|
// Risky: may land on an odd address.
|
||||||
static uint8_t gBacking[JOEY_SPRITE_BACKUP_BYTES(4, 4)];
|
static uint8_t gBacking[jlSpriteBackupLength(uint8_t, 4, 4)];
|
||||||
----
|
----
|
||||||
|
|
||||||
Note also that on the planar ports the region a save-under actually captures is
|
Note also that the region a save-under actually captures is often not the
|
||||||
the *16-pixel-aligned window* containing the sprite, not the sprite rectangle.
|
sprite rectangle: it can be the *16-pixel-aligned region* containing the
|
||||||
`jlSpriteSaveUnder` reports the true geometry in the backup structure's
|
sprite (the Amiga, ST, and X68000), only the rows holding an opaque pixel (the
|
||||||
`x`, `width`, `height` and `sizeBytes` fields. It is that recorded window you
|
IIgs), or only the visible part of a sprite partly off the surface (every
|
||||||
must not overwrite before restoring.
|
port). `jlSpriteSaveUnder` reports the true geometry in the backup structure's
|
||||||
|
`x`, `y`, `width`, `height`, and `sizeBytes` fields. It is that recorded region
|
||||||
|
you must not overwrite before restoring. On the IIgs the buffer must not cross
|
||||||
|
a 64 KB bank boundary, which a `jlAlloc` block and the C heap never do.
|
||||||
|
|
||||||
=== Randomness
|
=== Randomness
|
||||||
|
|
||||||
|
|
@ -142,19 +148,24 @@ you:
|
||||||
|
|
||||||
Pass bare names: `jlDataOpen("levels/title.dat", "rb")`, never
|
Pass bare names: `jlDataOpen("levels/title.dat", "rb")`, never
|
||||||
`"DATA/levels/title.dat"`. On DVX the two are not in the current directory
|
`"DATA/levels/title.dat"`. On DVX the two are not in the current directory
|
||||||
at all (<<_dvx_programs>>), so a program that opens `"DATA/..."` itself finds
|
at all (<<_dvx_programs>>), so a program that opens `+"DATA/..."+` itself finds
|
||||||
nothing there.
|
nothing there. DOS, the ST, the Amiga, and the X68000 look in the current
|
||||||
|
directory, so start a program there from its own folder; the IIgs looks for
|
||||||
|
`DATA/` under GS/OS prefix 0 but keeps `SAVES/` in the application's folder
|
||||||
|
(<<_files_and_saves>>).
|
||||||
|
|
||||||
Keep names portable across the retro filesystems this library targets: *at most
|
Keep names portable across the retro filesystems this library targets: *at most
|
||||||
8 characters plus a short extension, letters and digits only*, so they satisfy
|
8 characters plus a short extension, letters and digits only, starting with a
|
||||||
both ProDOS and DOS 8.3. Do not rely on case being preserved.
|
letter*, so they satisfy both ProDOS and DOS 8.3. Nothing checks them for you.
|
||||||
|
Do not rely on case being preserved, and spell a name the same way every
|
||||||
|
time: on Linux, Haiku, BeOS, and the web, case matters.
|
||||||
|
|
||||||
A failed save is not fatal -- the calls report failure and your program should
|
A failed save is not fatal -- the calls report failure and your program should
|
||||||
degrade gracefully, because on some volumes `SAVES/` simply is not writable.
|
degrade gracefully, because on some volumes `SAVES/` simply is not writable.
|
||||||
|
|
||||||
=== Audio
|
=== Audio
|
||||||
|
|
||||||
JoeyLib gives you three sound layers, and on two platforms they share physical
|
JoeyLib gives you three sound layers, and on some platforms they share physical
|
||||||
hardware:
|
hardware:
|
||||||
|
|
||||||
[cols="2,5", options="header"]
|
[cols="2,5", options="header"]
|
||||||
|
|
@ -162,41 +173,48 @@ hardware:
|
||||||
|Layer |What it is
|
|Layer |What it is
|
||||||
|
|
||||||
|Module playback
|
|Module playback
|
||||||
|`jlAudioPlayMod` -- four-channel Protracker `.MOD`, mixed in software on
|
|`jlAudioPlayMod` -- four-channel Protracker `.MOD`, mixed in software by
|
||||||
DOS and ST, hardware-replayed on Amiga and IIgs.
|
JoeyLib's module player on DOS, the ST, the X68000, and the hosted ports,
|
||||||
|
played by DVX's music library on DVX, and replayed by the sound hardware on
|
||||||
|
the Amiga and IIgs. The module player is the Amiga's replayer, PTPlayer,
|
||||||
|
translated to C, so those ports play a module as the Amiga does, at a PAL
|
||||||
|
Amiga's pitch, Protracker 2.3's quirks and all.
|
||||||
|
|
||||||
|Chip tracker
|
|Chip tracker
|
||||||
|`jlMusicPlay` -- a portable event stream driving three tone voices plus
|
|`jlMusicPlay` -- a portable event stream driving three tone voices plus
|
||||||
noise, at register-write cost with no CPU mixing.
|
noise, at register-write cost with no CPU mixing.
|
||||||
|
|
||||||
|Voices and effects
|
|Voices and effects
|
||||||
|`jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx` and the streaming forms.
|
|`jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and the streaming forms.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
On the Atari ST and Amiga, the tone voices *are* the same hardware the module
|
On the Amiga the tone voices are the hardware the module player drives, and
|
||||||
player drives. While a `.MOD` is playing, `jlAudioVoice` writes fight the
|
while a `.MOD` is playing, `jlAudioVoice` writes fight the player's register
|
||||||
player's register writes and lose. Choose the module layer or the voice and
|
writes and lose. On the plain ST, digital sound takes the chip channel tone
|
||||||
tracker layer *per scene*, not both at once. (The STe build plays modules and
|
voice 0 plays on, so voice 0 is silent while a module or effect plays. On the
|
||||||
sound effects through the STe's DMA sound chip instead, so there the voices
|
IIgs the voices share sound effect slots 0 to 2 and the noise slot 3. Choose
|
||||||
keep the YM2149 to themselves -- but a program that also ships the ST build
|
the module layer or the voice and tracker layer *per scene*, not both at once.
|
||||||
still has to follow the rule.)
|
|
||||||
====
|
====
|
||||||
|
|
||||||
The chip tracker is the portable choice for music. It costs almost nothing on
|
The chip tracker is the portable choice for music. It costs almost nothing on
|
||||||
every port -- unlike `.MOD` playback, which needs real CPU mixing and is
|
every port -- unlike `.MOD` playback, which needs real CPU mixing where the
|
||||||
marginal on the ST.
|
sound hardware cannot replay a module. On a plain 8 MHz ST a four-channel
|
||||||
|
module takes about three quarters of the machine (measured under Hatari: a
|
||||||
|
third for the interrupt that plays each sample through the YM2149, the rest
|
||||||
|
the mix), leaving a quarter for the game; the STe's DMA sound needs no such
|
||||||
|
interrupt.
|
||||||
|
|
||||||
On the IIgs, `jlAudioVoice` and `jlAudioNoise` require `jlAudioInit` (they ride
|
A `jlAudioVoice` or `jlAudioNoise` note sounds until the next call for it, as
|
||||||
the tracker engine's DOC path) and behave as retriggered one-shots of about
|
long as `jlAudioFrameTick` runs every frame: the IIgs plays each note as a
|
||||||
80 ms, so a note held longer must be re-asserted roughly once a frame. On DOS,
|
short sample and restarts the held ones from there.
|
||||||
ST and Amiga those two calls are direct hardware programming and work without
|
|
||||||
`jlAudioInit`.
|
|
||||||
|
|
||||||
A failed `jlAudioInit` is *not* fatal. `jlInit` still succeeds and every audio
|
`jlInit` starts audio (`jlConfigT.noAudio` leaves it off) and `jlShutdown`
|
||||||
call becomes a silent no-op, so a program need not treat missing sound as an
|
stops it. Sound that does not start is *not* fatal: `jlInit` still succeeds,
|
||||||
error.
|
modules and sound effects become silent no-ops (the tone and noise voices
|
||||||
|
still play on the Amiga, the ST, DOS, and the X68000), so a program need not
|
||||||
|
treat missing sound as an error.
|
||||||
|
|
||||||
=== Per-Platform Behaviour Summary
|
=== Per-Platform Behaviour Summary
|
||||||
|
|
||||||
|
|
@ -207,9 +225,9 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|Feature |IIgs |Amiga |Atari ST |MS-DOS |X68000 |DVX |SDL3, Web, Win32, PSP, Haiku, BeOS |Win16
|
|Feature |IIgs |Amiga |Atari ST |MS-DOS |X68000 |DVX |SDL3, Web, Win32, PSP, Haiku, BeOS |Win16
|
||||||
|
|
||||||
|Frame rate
|
|Frame rate
|
||||||
|60 Hz
|
|60 Hz (50 on a PAL machine)
|
||||||
|50 Hz (PAL)
|
|50 Hz (PAL; 60 on an NTSC machine without PAL)
|
||||||
|60 Hz (NTSC)
|
|50 or 60 Hz, as the machine is set
|
||||||
|70 Hz
|
|70 Hz
|
||||||
|55 Hz (56 under MAME)
|
|55 Hz (56 under MAME)
|
||||||
|70 Hz (virtual; the real retrace on the whole screen)
|
|70 Hz (virtual; the real retrace on the whole screen)
|
||||||
|
|
@ -221,7 +239,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|planar
|
|planar
|
||||||
|planar, interleaved
|
|planar, interleaved
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|text-plane planar
|
|planar (shown through the text plane)
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|
|
@ -234,11 +252,11 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|compiled
|
|compiled
|
||||||
|compiled
|
|compiled
|
||||||
|interpreted
|
|interpreted
|
||||||
|interpreted
|
|compiled
|
||||||
|
|
||||||
|Stereo pan
|
|Stereo pan
|
||||||
|all three
|
|all three
|
||||||
|left and right
|
|all three (centred on a free channel each side)
|
||||||
|ignored (mono)
|
|ignored (mono)
|
||||||
|ignored (mono)
|
|ignored (mono)
|
||||||
|ignored
|
|ignored
|
||||||
|
|
@ -246,24 +264,36 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|all three
|
|all three
|
||||||
|ignored (mono)
|
|ignored (mono)
|
||||||
|
|
||||||
|`jlAudioTone`
|
|Program icon (<<_icons>>)
|
||||||
|DOC oscillator
|
|the Finder's (`Icons` folder)
|
||||||
|Paula
|
|Workbench's (`Name.info`)
|
||||||
|YM2149
|
|none
|
||||||
|PIT counter 2
|
|none
|
||||||
|-
|
|none
|
||||||
|a DVX tone (FM chip or mixer)
|
|Program Manager's
|
||||||
|voice 0 of the mixer
|
|the window's (SDL3), the page's (web), Explorer's (Win32), the menu's
|
||||||
|voice 0 of the mixer
|
(PSP), Tracker's (Haiku, BeOS)
|
||||||
|
|Program Manager's
|
||||||
|
|
||||||
|
|Tone voices (`jlAudioTone` is voice 0)
|
||||||
|
|DOC oscillators on sound effect slots 0 to 2
|
||||||
|
|Paula channels 0 to 2
|
||||||
|
|YM2149 (voice 0 silent while digital sound plays)
|
||||||
|
|OPL2 (with none, voice 0 on the PC speaker)
|
||||||
|
|YM2151 (sine waves)
|
||||||
|
|DVX tones (FM chip or mixer)
|
||||||
|
|square waves in the mixer
|
||||||
|
|square waves in the mixer
|
||||||
|
|
||||||
|Joystick
|
|Joystick
|
||||||
|analog, auto-disables
|
|one, analog, auto-disables
|
||||||
|digital
|
|
||||||
|digital
|
|digital
|
||||||
|digital
|
|digital
|
||||||
|
|analog
|
||||||
|digital
|
|digital
|
||||||
|analog (DVX calibrates)
|
|analog (DVX calibrates)
|
||||||
|the OS's gamepads or joysticks (OS calibrates); the PSP's own controls
|
|SDL's gamepads and joysticks (SDL3, web); Windows' joysticks (Win32); the system's joysticks
|
||||||
|
(Haiku, BeOS); the PSP's own controls
|
||||||
|Windows' joysticks (Control Panel calibrates)
|
|Windows' joysticks (Control Panel calibrates)
|
||||||
|
|
||||||
|Keyboard layout
|
|Keyboard layout
|
||||||
|
|
@ -273,7 +303,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|fixed US
|
|fixed US
|
||||||
|machine's own
|
|machine's own
|
||||||
|DVX's
|
|DVX's
|
||||||
|the OS's; the PSP has no keyboard
|
|the OS's; on the PSP, a US one on the screen
|
||||||
|Windows'
|
|Windows'
|
||||||
|
|
||||||
|Serial ports
|
|Serial ports
|
||||||
|
|
@ -281,28 +311,35 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|`serial.device`
|
|`serial.device`
|
||||||
|Modem port
|
|Modem port
|
||||||
|COM1..COM4
|
|COM1..COM4
|
||||||
|-
|
|RS-232C (needs RSDRV.SYS)
|
||||||
|COM1..COM4 (DVX's library)
|
|COM1..COM4 (DVX's library)
|
||||||
|Win32: COM1 and up; the others -
|
|Win32: COM1 and up; SDL3, web, PSP, Haiku, BeOS: none
|
||||||
|COM1..COM9
|
|COM1..COM9
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The joystick row deserves a note: the IIgs port *auto-disables* joystick
|
The joystick row deserves a note: the IIgs port stops polling for a stick
|
||||||
polling after a short window with no stick detected, because polling costs
|
after about a second (60 polls) with none detected, because polling for an
|
||||||
about 3 ms per frame there. It does not re-probe on its own. If a user plugs a
|
absent stick costs about 1 ms a frame there; `jlJoystickReset` starts it again
|
||||||
stick in while the program is running, you must call `jlJoystickReset` to
|
(<<_jljoystickreset>>).
|
||||||
resume polling.
|
|
||||||
|
|
||||||
On the X68000 both joystick ports always report a stick, as on the ST: an
|
On the X68000 both joystick ports always report a stick, as on the ST: an
|
||||||
empty port cannot be told from a stick at rest. `JOY_BUTTON_0` is a stick's
|
empty port cannot be told from a stick at rest. `JL_JOY_BUTTON_0` is a stick's
|
||||||
trigger A and `JOY_BUTTON_1` its trigger B. Holding both directions of an axis
|
trigger A and `JL_JOY_BUTTON_1` its trigger B. Holding both directions of an axis
|
||||||
reads as the positive one (right, down), as on the Amiga and ST.
|
reads as the positive one (right, down), as on the Amiga and ST.
|
||||||
|
|
||||||
On the ST, `JOYSTICK_0` is port 1, the joystick port, and `JOYSTICK_1` port 0.
|
On the ST, `JL_JOYSTICK_0` is port 1, the joystick port, and `JL_JOYSTICK_1` port 0.
|
||||||
JoeyLib keeps TOS's keyboard-processor mode, in which port 0 is the mouse's:
|
JoeyLib keeps TOS's keyboard-processor mode, in which port 0 is the mouse's:
|
||||||
the ST reports no stick movement there, so `JOYSTICK_1` stays at rest whatever
|
the ST reports no stick movement there, so `JL_JOYSTICK_1` stays at rest whatever
|
||||||
is plugged in. Port 1's fire button is the same line as the mouse's right
|
is plugged in. A stick there has one button, so a quick press is
|
||||||
button, so a press reads as both `JOY_BUTTON_0` and `MOUSE_BUTTON_RIGHT`.
|
`JL_JOY_BUTTON_0` and a held one `JL_JOY_BUTTON_1` (<<_jljoydown_jljoypressed_jljoyreleased>>).
|
||||||
|
Port 1's fire button is the same line as the mouse's right button, so a press
|
||||||
|
reads as both the stick's button and `JL_MOUSE_BUTTON_RIGHT`.
|
||||||
|
|
||||||
|
The 1040 STe also has two enhanced joystick ports, and the STe build reads an
|
||||||
|
Atari Jaguar pad in each. A pad in enhanced port A is `JL_JOYSTICK_0` whenever
|
||||||
|
it was used more recently than port 1's stick, so either one plays, and a pad
|
||||||
|
in enhanced port B is `JL_JOYSTICK_1`. A pad's A and B buttons are
|
||||||
|
`JL_JOY_BUTTON_0` and `JL_JOY_BUTTON_1`. The Mega STe has no enhanced ports.
|
||||||
|
|
||||||
On the Amiga the sticks are read through `lowlevel.library`, which Kickstart
|
On the Amiga the sticks are read through `lowlevel.library`, which Kickstart
|
||||||
3.1 does not have in ROM: AmigaOS loads it from `LIBS:`, so the boot drive must
|
3.1 does not have in ROM: AmigaOS loads it from `LIBS:`, so the boot drive must
|
||||||
|
|
@ -312,12 +349,15 @@ on its own must do the same. `lowlevel.library` reports a stick only once it
|
||||||
has moved, and never a mouse. It also checks each port again now and then, and
|
has moved, and never a mouse. It also checks each port again now and then, and
|
||||||
a two-button stick with both buttons held can read as nothing at that moment,
|
a two-button stick with both buttons held can read as nothing at that moment,
|
||||||
so once a port has shown a stick JoeyLib keeps it one; `jlJoystickReset` hands
|
so once a port has shown a stick JoeyLib keeps it one; `jlJoystickReset` hands
|
||||||
the port back to `lowlevel.library`'s own detection.
|
the port back to the library's own detection. `lowlevel.library` reports
|
||||||
|
a one-button stick and a two-button one alike, so a stick is taken for a
|
||||||
|
one-button one, a held button being `JL_JOY_BUTTON_1`, until its second button
|
||||||
|
is first pressed; a CD32 pad has both from the start.
|
||||||
|
|
||||||
On DOS the game port's counts depend on the stick and on the CPU's speed, so
|
On DOS the game port's counts depend on the stick and on the CPU's speed, so a
|
||||||
the first reading after `jlInit` or `jlJoystickReset` is taken as the stick's
|
stick's first reading -- after `jlInit`, after `jlJoystickReset`, or when it is
|
||||||
rest, and a push past half that value either way reads as a full push. Call
|
plugged in -- is taken as its centre, and its axes are read in proportion to
|
||||||
`jlJoystickReset` with the stick centred if it was not at the start.
|
that: a count of 0 or twice the centre is a full push.
|
||||||
|
|
||||||
The X68000's display refreshes at 55.46 Hz. `jlFrameHz` times it when the
|
The X68000's display refreshes at 55.46 Hz. `jlFrameHz` times it when the
|
||||||
program starts and rounds to the nearest whole number, so it reports 55 --
|
program starts and rounds to the nearest whole number, so it reports 55 --
|
||||||
|
|
@ -325,18 +365,238 @@ and 56 under MAME, whose X68000 display runs slightly fast (56.07 Hz).
|
||||||
`jlMillisElapsed` does not depend on it: it reads the machine's 100 Hz system
|
`jlMillisElapsed` does not depend on it: it reads the machine's 100 Hz system
|
||||||
timer to the millisecond.
|
timer to the millisecond.
|
||||||
|
|
||||||
Per-scanline palettes cost something on the X68000, and only there. Its
|
Per-scanline palettes cost CPU time on the ST and the X68000, whose displays
|
||||||
display has one 16-colour palette, so the port reloads it as the beam scans: an
|
have one 16-colour palette, and a stall on the Amiga when the bands move. The
|
||||||
interrupt stores the next band's colours in the horizontal blanking before the
|
ST takes a timer interrupt at every change of palette, every frame, and copies
|
||||||
band's first line. A screen with one palette costs nothing and arms no
|
the next band's 16 colours there. The X68000 reloads its palette as the beam
|
||||||
interrupt. Each change of palette between one line and the next costs about
|
scans: an interrupt stores the next band's colours in the horizontal blanking
|
||||||
one line of CPU time (32 microseconds); a band more than two lines from the
|
before the band's first line. A screen with one palette costs nothing on
|
||||||
one before costs about two. A screen that changes palette on every line
|
either and arms no interrupt. On the X68000 each change of palette between one
|
||||||
therefore spends a third of each frame in that interrupt, during which serial
|
line and the next costs about one line of CPU time (32 microseconds); a band
|
||||||
and mouse interrupts wait (see the serial chapter for how long a run can be
|
more than two lines from the one before costs about two. A screen that changes
|
||||||
before received bytes are lost). Palette and SCB changes take effect at the
|
palette on every line therefore spends a third of each frame in that
|
||||||
start of the next frame rather than mid-frame, so a fade can lag its pixels by
|
interrupt, during which serial and mouse interrupts wait (see the serial
|
||||||
one frame but never tears.
|
chapter for how long a run can be before received bytes are lost). On the
|
||||||
|
Amiga the copper changes the palettes at no CPU cost, but an SCB change that
|
||||||
|
moves where a band starts or ends rebuilds the copper list, which holds up
|
||||||
|
that `jlStagePresent` for about three frames on an A500; a change of colours,
|
||||||
|
or of which palette an unmoved band uses, costs nothing. On the ST a
|
||||||
|
present's colours and pixels show from the same frame: it hands the
|
||||||
|
interrupts their new colours with the page it flips to. On the X68000, a
|
||||||
|
screen of two or more bands takes palette and SCB changes at the start of the
|
||||||
|
next frame rather than mid-frame, so a fade can lag its pixels by one frame
|
||||||
|
but never tears; a screen of one palette has its colours written at the
|
||||||
|
present.
|
||||||
|
|
||||||
|
=== Where the Ports Still Differ
|
||||||
|
|
||||||
|
The API is the same on every port, and where a port behaved differently from
|
||||||
|
the rest because of the library, the library has been changed. What is left is
|
||||||
|
set by the machines and by the systems the ports run on, and stays. Each entry
|
||||||
|
names the ports, says how they differ, and gives the reason.
|
||||||
|
|
||||||
|
[[portsDifferDisplay]]
|
||||||
|
==== Display
|
||||||
|
|
||||||
|
* *IIgs: a big present can tear.* The Super Hi-Res display reads one fixed
|
||||||
|
buffer, with no second page to flip to, and a full-screen present takes
|
||||||
|
several frames, so the screen can show part of the old picture and part of
|
||||||
|
the new.
|
||||||
|
* *Win16, and DVX in a window: no vertical blank.* Windows 3.1 has no call that
|
||||||
|
waits for the display's retrace, and DVX syncs to it only on its full screen,
|
||||||
|
so `jlWaitVBL` keeps a virtual frame clock there and nothing stops a present
|
||||||
|
from tearing.
|
||||||
|
* *X68000: a small, wide picture.* The machine has no 320x200 mode, so the stage
|
||||||
|
sits in the middle of a 512x512 screen, a picture about 2.1 times as wide as
|
||||||
|
it is tall. That screen is a 31 kHz mode, so the monitor must take 31 kHz.
|
||||||
|
* *ST: eight levels a channel.* The plain ST's video chip has 3 bits per colour
|
||||||
|
channel, so `$0RGB` values one apart can look the same. The STe build shows
|
||||||
|
all 4 bits.
|
||||||
|
* *X68000: one intensity bit for three channels.* Its colours have 5 bits per
|
||||||
|
channel and an intensity bit the three share. JoeyLib sets that bit for every
|
||||||
|
colour but black, so white is full white, and a channel that is 0 in any
|
||||||
|
other colour shows at 1/63 rather than 0: `$F00` carries a trace of green and
|
||||||
|
blue.
|
||||||
|
* *Windows and DVX on a 256-colour display.* The display has one palette for
|
||||||
|
everything on it. Windows keeps 20 of its entries for itself, and DVX shares
|
||||||
|
it among all its programs, so Win32 and a DVX window show each colour as the
|
||||||
|
nearest one the display has, and Win16 shows them exactly only while the 236
|
||||||
|
entries left are enough (<<_windows_programs>>).
|
||||||
|
* *Amiga and X68000: colours a frame off their pixels.* A present's new
|
||||||
|
colours and new pixels show from the same frame on the other ports (the IIgs
|
||||||
|
and DOS change both at once, and can tear). The Amiga's pixels change at the
|
||||||
|
next frame, when the copper loads the new bitplane pointers, but the bands
|
||||||
|
below the beam take their new colours in the frame the present runs: the
|
||||||
|
copper list is graphics.library's, one per screen, and JoeyLib patches its
|
||||||
|
colours in place. The X68000 draws straight into the displayed text plane,
|
||||||
|
which has no second page, while a screen of two or more bands takes its new
|
||||||
|
colours at the start of the next frame, so there the colours come a frame
|
||||||
|
after the pixels.
|
||||||
|
* *ST and X68000: per-scanline palettes cost time.* Both displays have one
|
||||||
|
16-colour palette at a time, so the CPU loads each band's colours as the beam
|
||||||
|
reaches it: the ST in a timer interrupt at every change of palette, every
|
||||||
|
frame, and the X68000 in a raster interrupt. A screen with one palette costs
|
||||||
|
nothing (<<_per_platform_behaviour_summary>>).
|
||||||
|
|
||||||
|
[[portsDifferInput]]
|
||||||
|
==== Input
|
||||||
|
|
||||||
|
* *IIgs keyboard: one key at a time, by character.* The keyboard reports only
|
||||||
|
the last character typed and whether any key is down. So JoeyLib sees one key
|
||||||
|
besides the modifiers at a time, knows a key by the character it types rather
|
||||||
|
than by its place on the keyboard, and reads it released only when every key
|
||||||
|
is up. With Ctrl held, Ctrl+H, I, J, K, M, and U read as those letters, not as
|
||||||
|
the left arrow, Tab, the down and up arrows, Return, and the right arrow,
|
||||||
|
whose codes they share; a shifted digit key reads as its digit only on
|
||||||
|
the US layout; and the keypad's keys read as the characters they type, so
|
||||||
|
its 8, 2, 4, and 6 are the digits, not the arrows as on the other ports.
|
||||||
|
* *IIgs keyboard: the keys it lacks.* It has no F1 to F10, no forward Delete
|
||||||
|
(Delete is the backspace key), and one Shift as far as JoeyLib can tell:
|
||||||
|
`JL_KEY_LSHIFT` and `JL_KEY_RSHIFT` both read either. Option is
|
||||||
|
`JL_KEY_LALT`.
|
||||||
|
* *ST keyboard: the Alt layer on TOS before 4.00.* A national keyboard types
|
||||||
|
some ASCII with Alt (a German one's @ [ ] { } \). TOS 4.00 and later, and
|
||||||
|
EmuTOS, keep that layer in the keyboard tables JoeyLib reads, so Alt types
|
||||||
|
it; older TOS keeps it in the keyboard code JoeyLib replaces, so there Alt
|
||||||
|
with a key types nothing, as on the ports where Alt is a command key.
|
||||||
|
* *X68000 keyboard: one Shift.* Both Shift keys send the same code, so
|
||||||
|
`JL_KEY_LSHIFT` and `JL_KEY_RSHIFT` both read either. OPT.1 and OPT.2 are
|
||||||
|
`JL_KEY_LALT`.
|
||||||
|
* *IIgs joystick: one stick, sharing two keys.* The game port takes one stick,
|
||||||
|
and its buttons are wired to the same lines as the Command and Option keys,
|
||||||
|
so holding either key reads as a button.
|
||||||
|
* *Mouse buttons.* The IIgs mouse has one button, and the ST and X68000 mice
|
||||||
|
have two; the other ports report three where the mouse has them.
|
||||||
|
* *ST and X68000: a stick is always connected.* An empty joystick port reads
|
||||||
|
exactly as a stick at rest, so `jlJoystickConnected` is true for both.
|
||||||
|
* *ST: the mouse port's stick.* The keyboard processor reports port 0 as the
|
||||||
|
mouse in the mode JoeyLib keeps it in, so `JL_JOYSTICK_1` never moves, and
|
||||||
|
port 1's fire button is the mouse's right button too.
|
||||||
|
* *Amiga: one button or two.* `lowlevel.library` reports a one-button and a
|
||||||
|
two-button stick alike, so a stick counts as a one-button one until its
|
||||||
|
second button is first pressed.
|
||||||
|
* *PSP: no mouse, one key at a time.* The PSP has no mouse, and its on-screen
|
||||||
|
keyboard has one cursor, so it presses one key at a time (Shift, Ctrl, and
|
||||||
|
Alt stay down until the next key is let go).
|
||||||
|
|
||||||
|
[[portsDifferAudio]]
|
||||||
|
==== Audio
|
||||||
|
|
||||||
|
* *Plain ST: voice 0 is silent while digital sound plays.* The ST has no
|
||||||
|
digital sound chip, so JoeyLib plays modules and effects by changing the
|
||||||
|
sound chip's channel A volume 12288 times a second, and channel A is tone
|
||||||
|
voice 0's. Voice 0 sounds again when the digital sound stops, and voices 1
|
||||||
|
and 2 play on. The STe build uses the STe's DMA sound and keeps all three.
|
||||||
|
* *ST: noise shares voice 2's volume.* The YM2149 has one volume per channel,
|
||||||
|
and its noise is mixed into channel C, voice 2's. Author noise hits and
|
||||||
|
voice-2 notes apart; `songbake` warns when they overlap.
|
||||||
|
* *ST: volume steps.* The YM2149 has 16 volume levels about 3.5 dB apart, so a
|
||||||
|
2 dB attenuation step is matched to within 1.6 dB.
|
||||||
|
* *Amiga: four channels for everything.* Paula has four sound channels, two
|
||||||
|
on each side, shared by the module, the effects, the three tone voices, and
|
||||||
|
the noise. At most four sounds play at once, an effect can take a channel a
|
||||||
|
voice was using, and a module plays on all four. A channel plays on one side
|
||||||
|
only, so a centred effect needs a free channel on each side, and without
|
||||||
|
them it plays on one channel, from one side; and the tone voices, one
|
||||||
|
channel each, are heard from one side: voice 0 (and `jlAudioTone`) on the
|
||||||
|
left, voices 1 and 2 on the right.
|
||||||
|
* *IIgs: voices on the effect slots.* By design the IIgs plays tone voices 0 to
|
||||||
|
2 on sound effect slots 0 to 2 and the noise on slot 3, so an effect on one
|
||||||
|
of those slots and a voice cut each other off. Use slot 4 for effects while
|
||||||
|
voices or `jlMusicPlay` sound.
|
||||||
|
* *IIgs: mono.* A stock IIgs mixes its stereo channels into one speaker, so
|
||||||
|
panning is heard only with a stereo card.
|
||||||
|
* *Plain ST and X68000: no panning.* Each has one output for digital sound:
|
||||||
|
the ST its channel A, the X68000 its one ADPCM channel, so `jlAudioSetPan`
|
||||||
|
is ignored.
|
||||||
|
* *DOS: the noise is a drum.* The OPL2 has no free-running noise generator; its
|
||||||
|
rhythm-mode snare is the nearest, a tuned drum whose pitch cannot follow the
|
||||||
|
YM2149's noise rate.
|
||||||
|
* *DOS and X68000: tones are not square.* The OPL2 and the YM2151 make their
|
||||||
|
waves from sines and have no square wave, so their tone voices (DOS's when it
|
||||||
|
has an OPL2) are not the square waves the other ports play.
|
||||||
|
* *Amiga and IIgs: the brightest noise.* Paula plays a sample no faster than
|
||||||
|
28.6 kHz and the IIgs's DOC no faster than 26 kHz, so noise pitches 0 to 4
|
||||||
|
sound alike there.
|
||||||
|
* *Where tick callbacks fall.* The calls average exactly `hz` a second on every
|
||||||
|
port. The ST and X68000 deliver them from the system's own timer, at 200 and
|
||||||
|
100 Hz, so the calls due between two of its interrupts come together. DVX
|
||||||
|
delivers them at `jlAudioFrameTick`, because its sound library may not be
|
||||||
|
called from an interrupt, and so does the IIgs, counting them against the
|
||||||
|
display's beats.
|
||||||
|
* *DOS, the ST, and the X68000 mix in program time.* The module player and
|
||||||
|
the SFX overlay run too long for an interrupt, so their sound buffers are
|
||||||
|
refilled at `jlAudioFrameTick`: a program that stops calling it for longer
|
||||||
|
than the buffer lasts stops or repeats its sound.
|
||||||
|
* *Win16 with a Sound Blaster 2.0: 8-bit sound.* Its driver, SNDBLST2.DRV, takes
|
||||||
|
only 8-bit samples, so the quietest attenuation steps are coarser there.
|
||||||
|
|
||||||
|
[[portsDifferSerial]]
|
||||||
|
==== Serial
|
||||||
|
|
||||||
|
* *Top speeds.* The ST runs at 19200 baud at most (its MFP's rate table), a
|
||||||
|
IIgs Super Serial Card at 19200 (the 6551's), the X68000 at 38400 (RSDRV.SYS's
|
||||||
|
table; really 39062), and Win16 with Windows' own COMM.DRV at 57600.
|
||||||
|
* *One port.* The base ST has one RS-232 port, and so has the X68000, whose
|
||||||
|
second serial channel is the mouse's.
|
||||||
|
* *IIgs modem and printer ports: no RTS.* Their mini-DIN-8 connector's one
|
||||||
|
handshake output is DTR, so under RTS/CTS nothing can tell the peer to
|
||||||
|
pause: receive needs XON/XOFF or a protocol that paces itself. The printer
|
||||||
|
port's channel is also AppleTalk's.
|
||||||
|
* *IIgs Super Serial Card: lines tied together.* The 6551's DTR is also its
|
||||||
|
receiver's enable, and dropping its RTS stops its transmitter.
|
||||||
|
* *X68000: DTR rises at every line setup.* The ROM's and RSDRV.SYS's line setup
|
||||||
|
raise DTR whenever it runs.
|
||||||
|
* *Windows: DTR drops at close.* Win32's driver, and Win16 with Windows' own
|
||||||
|
COMM.DRV, drop DTR when the port is closed, and neither has a call that
|
||||||
|
prevents it.
|
||||||
|
* *IIgs: disk access stops interrupts.* GS/OS holds interrupts off for 65 to
|
||||||
|
220 ms at a time while it reads or writes a 3.5-inch floppy, which can lose
|
||||||
|
received bytes (<<_what_can_lose_received_bytes>>).
|
||||||
|
|
||||||
|
[[portsDifferFiles]]
|
||||||
|
==== Files and Saves
|
||||||
|
|
||||||
|
* *Name rules.* ProDOS names on the IIgs are at most 15 characters and start
|
||||||
|
with a letter; DOS, the ST, and Win16 use 8.3 names; and Linux, Haiku, BeOS,
|
||||||
|
and the web tell upper and lower case apart. Keep to the portable rule
|
||||||
|
(<<_files>>).
|
||||||
|
* *Pre-compiled sprites per processor.* A `.spc` holds machine code, so it is
|
||||||
|
baked per port: DVX and Win16 run DOS's x86 code, the STe the ST's, and the
|
||||||
|
ports that interpret sprites use only its pixels.
|
||||||
|
* *Web saves are the browser's.* A browser may clear a site's stored data to
|
||||||
|
make room, and a private window keeps nothing once it closes.
|
||||||
|
|
||||||
|
[[portsDifferMemory]]
|
||||||
|
==== Sprites and Memory
|
||||||
|
|
||||||
|
* *IIgs: code and copies stay inside 64 KB banks.* The 65816 runs code within
|
||||||
|
one 64 KB bank and its block copy stays within one, so each sprite's code
|
||||||
|
must fit in one bank of the codegen arena, the arena is at most four banks,
|
||||||
|
compacting never merges free space across banks, and a save-under buffer
|
||||||
|
must not cross a bank.
|
||||||
|
* *Code size differs by processor.* The 68000 and x86 emitters make code four
|
||||||
|
to eight times larger than the 65816 one, so with the same `codegenBytes` a
|
||||||
|
large sprite can compile on the IIgs and fall back to the interpreter
|
||||||
|
elsewhere.
|
||||||
|
* *IIgs: a small C heap, and `jlFree` apart from `free`.* The C library's heap
|
||||||
|
is one Memory Manager block inside one bank, 32 KB to 48 KB, and a `jlAlloc`
|
||||||
|
block is a Memory Manager handle, so `jlFree` and `free` must never be given
|
||||||
|
each other's blocks.
|
||||||
|
|
||||||
|
[[portsDifferTiming]]
|
||||||
|
==== Timing
|
||||||
|
|
||||||
|
* *Frame rates.* The displays run at 50, 55, 60, and 70 Hz, as the table above
|
||||||
|
shows; the hosted ports and DVX keep a virtual 60 and 70 Hz.
|
||||||
|
* *IIgs: the clock stops while interrupts are off.* The IIgs has no free-running
|
||||||
|
timer JoeyLib can read, so its clock counts the display's vertical blanks,
|
||||||
|
and blanks that come while interrupts are held off are lost: a long stretch
|
||||||
|
with interrupts off, a floppy access for one, leaves the clock behind.
|
||||||
|
* *DOS: frames counted by the clock.* VGA has no dependable retrace interrupt,
|
||||||
|
so `jlFrameCount` counts 70ths of a second of the millisecond clock while
|
||||||
|
`jlWaitVBL` waits for the real retrace (70.086 Hz), and the two slowly drift
|
||||||
|
apart.
|
||||||
|
|
||||||
=== A Portability Checklist
|
=== A Portability Checklist
|
||||||
|
|
||||||
|
|
@ -365,7 +625,7 @@ audience (or both):
|
||||||
|
|
||||||
* *The ST build* (`make atarist`, programs in `build/atarist/bin`) is the
|
* *The ST build* (`make atarist`, programs in `build/atarist/bin`) is the
|
||||||
baseline. It is pure 68000 and *never uses STe hardware*: it runs on every
|
baseline. It is pure 68000 and *never uses STe hardware*: it runs on every
|
||||||
ST -- STF, STFM, Mega ST, STe and Mega STe alike -- exactly as on a plain
|
ST -- STF, STFM, Mega ST, STe, and Mega STe alike -- exactly as on a plain
|
||||||
ST, at no cost for the STe's existence.
|
ST, at no cost for the STe's existence.
|
||||||
* *The STe build* (`make ste`, programs in `build/ste/bin`) is the same port
|
* *The STe build* (`make ste`, programs in `build/ste/bin`) is the same port
|
||||||
compiled with `JOEYLIB_ATARI_STE`. It uses the STe's hardware
|
compiled with `JOEYLIB_ATARI_STE`. It uses the STe's hardware
|
||||||
|
|
@ -406,7 +666,7 @@ Nothing in the API differs between them:
|
||||||
The pixels are identical: UBER's 51 surface hashes match the ST's golden for
|
The pixels are identical: UBER's 51 surface hashes match the ST's golden for
|
||||||
the STe build on an STe and a Mega STe, and the gates check that under Hatari.
|
the STe build on an STe and a Mega STe, and the gates check that under Hatari.
|
||||||
`./joey-run` gives an ST-build program a plain ST and an STe-build program an
|
`./joey-run` gives an ST-build program a plain ST and an STe-build program an
|
||||||
STe; `--machine st`, `megast`, `ste` or `megaste` picks another. A project
|
STe; `--machine st`, `megast`, `ste`, or `megaste` picks another. A project
|
||||||
builds the STe variant with `make ste`, like any other port; it takes the ST's
|
builds the STe variant with `make ste`, like any other port; it takes the ST's
|
||||||
baked assets.
|
baked assets.
|
||||||
|
|
||||||
|
|
@ -420,7 +680,7 @@ expect less on memory-bound operations there.
|
||||||
=== DVX Programs
|
=== DVX Programs
|
||||||
|
|
||||||
DVX is a DOS desktop: a VESA graphics GUI with windows, cooperative
|
DVX is a DOS desktop: a VESA graphics GUI with windows, cooperative
|
||||||
multitasking and its own libraries for sound, music, joysticks and serial
|
multitasking, and its own libraries for sound, music, joysticks, and serial
|
||||||
ports. On DVX a JoeyLib program is one of its programs, a `.app` module
|
ports. On DVX a JoeyLib program is one of its programs, a `.app` module
|
||||||
running in a window of its own beside the others. The graphics are the
|
running in a window of its own beside the others. The graphics are the
|
||||||
MS-DOS port's -- the same chunky stage and the same x86 code, so UBER's
|
MS-DOS port's -- the same chunky stage and the same x86 code, so UBER's
|
||||||
|
|
@ -430,7 +690,7 @@ hashes are DOS's -- and the rest behaves like this:
|
||||||
at the largest whole scale, up to 3x, that fits the desktop: 1x at
|
at the largest whole scale, up to 3x, that fits the desktop: 1x at
|
||||||
640x480, 2x at 800x600, 3x at 1024x768. The stage reaches the desktop
|
640x480, 2x at 800x600, 3x at 1024x768. The stage reaches the desktop
|
||||||
through one colour table per palette, so SCB palettes are exact on 15-,
|
through one colour table per palette, so SCB palettes are exact on 15-,
|
||||||
16- and 32-bit desktops. On a 256-colour desktop every colour is the
|
16-, and 32-bit desktops. On a 256-colour desktop every colour is the
|
||||||
nearest one in DVX's palette, which all programs share: gradients band.
|
nearest one in DVX's palette, which all programs share: gradients band.
|
||||||
* *Full screen.* Game > Full Screen, or Alt+Enter, puts the program on the
|
* *Full screen.* Game > Full Screen, or Alt+Enter, puts the program on the
|
||||||
whole screen in DVX's full-screen mode, 320x200 in 256 colours (VGA mode
|
whole screen in DVX's full-screen mode, 320x200 in 256 colours (VGA mode
|
||||||
|
|
@ -453,18 +713,18 @@ hashes are DOS's -- and the rest behaves like this:
|
||||||
does not tear. The frame count is the same clock in both, so switching
|
does not tear. The frame count is the same clock in both, so switching
|
||||||
never makes it jump.
|
never makes it jump.
|
||||||
* *Keys DVX keeps.* While the window is active, or the program has the whole
|
* *Keys DVX keeps.* While the window is active, or the program has the whole
|
||||||
screen, every key goes to the program, F1, F10 and Alt included, except
|
screen, every key goes to the program, F1, F10, and Alt included, except
|
||||||
Alt+Tab (switch programs), Ctrl+Esc (Task Manager) and Alt+F4 (close the
|
Alt+Tab (switch programs), Ctrl+Esc (Task Manager) and Alt+F4 (close the
|
||||||
window), and Alt+Enter, which the port keeps for full screen. The window's
|
window), and Alt+Enter, which the port keeps for full screen. The window's
|
||||||
Game menu has Full Screen, Help (the program's help file) and Exit. Typed
|
Game menu has Full Screen, Help (the program's help file) and Exit. Typed
|
||||||
characters come through DVX's keyboard layout.
|
characters come through DVX's keyboard layout.
|
||||||
* *Closing ends the program.* Alt+F4, the close box and Game > Exit end the
|
* *Closing ends the program.* Alt+F4, the close box, and Game > Exit end the
|
||||||
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent` or
|
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent`, or
|
||||||
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
|
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
|
||||||
`jlShutdown`, and its log is closed.
|
`jlShutdown`, and its log is closed.
|
||||||
* *Sharing the machine.* DVX multitasks cooperatively: the other programs,
|
* *Sharing the machine.* DVX multitasks cooperatively: the other programs,
|
||||||
and this one's own window events, run only while it yields. `jlWaitVBL`
|
and this one's own window events, run only while it yields. `jlWaitVBL`
|
||||||
yields, and so do `jlInputPoll`, `jlStagePresent` and `jlSerialPoll`, at
|
yields, and so do `jlInputPoll`, `jlStagePresent`, and `jlSerialPoll`, at
|
||||||
most once a frame. A loop that calls none of them freezes the desktop until
|
most once a frame. A loop that calls none of them freezes the desktop until
|
||||||
it ends.
|
it ends.
|
||||||
* *Files.* DVX's programs share one current directory, so JoeyLib roots its
|
* *Files.* DVX's programs share one current directory, so JoeyLib roots its
|
||||||
|
|
@ -518,32 +778,36 @@ this:
|
||||||
program presents.
|
program presents.
|
||||||
* *Sprites are interpreted.* There is no code emitter for the host's CPU, so
|
* *Sprites are interpreted.* There is no code emitter for the host's CPU, so
|
||||||
`jlSpriteCompile` returns false and every sprite is drawn by the
|
`jlSpriteCompile` returns false and every sprite is drawn by the
|
||||||
interpreter -- far faster than any of the old machines all the same.
|
interpreter -- far faster than any of the old machines all the same. (The
|
||||||
`joey/platform.h` defines `JOEYLIB_SPRITE_INTERPRETED`, for a program that
|
Win16 port, below, compiles them as DOS does.)
|
||||||
|
`joey/platform.h` defines `JL_SPRITE_INTERPRETED`, for a program that
|
||||||
checks how many of its sprites compiled. A pre-compiled bank (`.spc`)
|
checks how many of its sprites compiled. A pre-compiled bank (`.spc`)
|
||||||
loads its cels' pixels and leaves their code: the DOS bake is used.
|
loads its cels' pixels and leaves their code: the DOS bake is used.
|
||||||
* *Input.* Keys by where they are on the keyboard, so another layout does not
|
* *Input.* Keys by where they are on the keyboard, so another layout does not
|
||||||
move the arrow keys; typed characters through the OS's keyboard layout. The
|
move the arrow keys; typed characters through the OS's keyboard layout. The
|
||||||
mouse is the pointer over the stage, in stage pixels (the OS pointer is
|
mouse is the pointer over the stage, in stage pixels (the OS pointer is
|
||||||
hidden over the window: JoeyLib programs draw their own). The first two
|
hidden over the window: JoeyLib programs draw their own). The first two
|
||||||
gamepads are joysticks 0 and 1: the left stick or the d-pad, the south
|
gamepads or joysticks are joysticks 0 and 1, coming and going as they are
|
||||||
button (A on an Xbox pad) as `JOY_BUTTON_0` and the east button (B) as
|
plugged in. A gamepad SDL knows how to map gives its left stick or its
|
||||||
`JOY_BUTTON_1`; they come and go as they are plugged in.
|
d-pad, its south button (A on an Xbox pad) as `JL_JOY_BUTTON_0`, and its
|
||||||
|
east button (B) as `JL_JOY_BUTTON_1`; any other joystick its first two axes
|
||||||
|
or its first hat, and its first two buttons.
|
||||||
* *Closing ends the program.* Closing the window ends the program at its next
|
* *Closing ends the program.* Closing the window ends the program at its next
|
||||||
`jlWaitVBL`, `jlInputPoll` or `jlStagePresent`, as `exit()` does anywhere:
|
`jlWaitVBL`, `jlInputPoll`, or `jlStagePresent`, as `exit()` does anywhere:
|
||||||
its `atexit` handlers run and the window and the sound are closed.
|
its `atexit` handlers run and the window and the sound are closed.
|
||||||
* *Files.* The program's own folder is the current directory from `jlInit`
|
* *Files.* The program's own folder is the current directory from `jlInit`
|
||||||
on, as on the machine ports: `DATA/`, `SAVES/` and `joeylog.txt` are beside
|
on, as on the machine ports: `DATA/`, `SAVES/`, and `joeylog.txt` are beside
|
||||||
the program.
|
the program.
|
||||||
* *Sound* is mixed in software into 16-bit stereo at 44.1 kHz: `.MOD` music
|
* *Sound* is mixed in software into 16-bit stereo at 44.1 kHz: `.MOD` music
|
||||||
through libxmp-lite (as on DOS), the SFX slots in stereo, the three tone
|
through the module player (as on DOS), Paula's channels hard left and
|
||||||
voices as square waves and the noise voice as the ST's YM2149 makes it. It
|
right as on the Amiga, the SFX slots in stereo, the three tone
|
||||||
|
voices as square waves, and the noise voice as the ST's YM2149 makes it. It
|
||||||
is mixed in program time by `jlAudioFrameTick`, `jlWaitVBL`,
|
is mixed in program time by `jlAudioFrameTick`, `jlWaitVBL`,
|
||||||
`jlInputPoll` and `jlStagePresent`, a few frames ahead, so a
|
`jlInputPoll`, and `jlStagePresent`, a few frames ahead, so a
|
||||||
`jlAudioTickRegister` callback runs in program time too -- but between
|
`jlAudioTickRegister` callback runs in program time too -- but between
|
||||||
exactly the samples it falls between, so its notes start in the right
|
exactly the samples it falls between, so its notes start in the right
|
||||||
place in the sound. `jlAudioInit` is needed for every sound, the tone
|
place in the sound. Every sound needs audio started (the default), the
|
||||||
voices included.
|
tone voices included.
|
||||||
* *No serial port.*
|
* *No serial port.*
|
||||||
|
|
||||||
`make sdl3` builds every example as a native program, each in
|
`make sdl3` builds every example as a native program, each in
|
||||||
|
|
@ -571,9 +835,12 @@ are DOS's here too. Everything in <<_sdl3_programs>> holds, except:
|
||||||
`jlInputPoll` alone, as `jlWaitForAnyKey` is).
|
`jlInputPoll` alone, as `jlWaitForAnyKey` is).
|
||||||
* *Sound starts with the first click or key press*, as browsers require; the
|
* *Sound starts with the first click or key press*, as browsers require; the
|
||||||
page says so.
|
page says so.
|
||||||
* *Saves persist.* `SAVES/` is kept in the browser's IndexedDB for the page's
|
* *Saves persist.* `SAVES/` is kept in the browser's IndexedDB, read back
|
||||||
address, read back when the program starts and written back every two
|
when the program starts and written back as each save changes, so a save
|
||||||
seconds, so a save outlives the page.
|
outlives the page however it ends. The store is the site's, not the page's:
|
||||||
|
every JoeyLib program served from the same site shares one `SAVES/`, so give
|
||||||
|
each game's saves names of their own. `jlDiskFree` there is the browser's
|
||||||
|
own estimate of what the site may still store.
|
||||||
* *No full screen by default and no closing.* Alt+Enter asks the browser for
|
* *No full screen by default and no closing.* Alt+Enter asks the browser for
|
||||||
the whole screen; the program ends when the page is closed.
|
the whole screen; the program ends when the page is closed.
|
||||||
|
|
||||||
|
|
@ -591,22 +858,31 @@ is the same chunky one, so UBER's hashes are DOS's. Everything in
|
||||||
* *A folder for `PSP/GAME`.* A program is a folder, `build/psp/bin/NAME/`:
|
* *A folder for `PSP/GAME`.* A program is a folder, `build/psp/bin/NAME/`:
|
||||||
`EBOOT.PBP`, titled with the program's name (or its description's `TITLE`)
|
`EBOOT.PBP`, titled with the program's name (or its description's `TITLE`)
|
||||||
in the PSP's menu, and its `DATA/`. Copy the folder into `PSP/GAME/` on the
|
in the PSP's menu, and its `DATA/`. Copy the folder into `PSP/GAME/` on the
|
||||||
memory stick. Its folder is its current directory, so `DATA/`, `SAVES/`
|
memory stick. Its folder is its current directory, so `DATA/`, `SAVES/`,
|
||||||
and `joeylog.txt` are there. It runs on a PSP with custom firmware (it is
|
and `joeylog.txt` are there. It runs on a PSP with custom firmware (it is
|
||||||
not signed) and in PPSSPP. The ELF it is packed from stays in
|
not signed) and in PPSSPP. The ELF it is packed from stays in
|
||||||
`build/psp/elf/`, for `psp-addr2line`.
|
`build/psp/elf/`, for `psp-addr2line`.
|
||||||
* *The screen.* The PSP's 480x272 screen shows the stage at 1:1 in its
|
* *The screen.* The PSP's 480x272 screen shows the stage at 1:1 in its
|
||||||
middle, black around it. The scale and aspect settings do not apply (a PSP
|
middle, black around it. The scale and aspect settings do not apply (a PSP
|
||||||
has no environment), and there is no full screen to switch to.
|
has no environment), and there is no full screen to switch to.
|
||||||
* *Its own controls, no keyboard.* The PSP's controls are joystick 0,
|
* *Its own controls, and a keyboard on the screen.* The PSP's controls are
|
||||||
through SDL's gamepad mapping: the d-pad or the analog nub, cross as
|
joystick 0, through SDL's gamepad mapping: the d-pad or the analog nub,
|
||||||
`JOY_BUTTON_0` and circle as `JOY_BUTTON_1`; joystick 1 is never there.
|
cross as `JL_JOY_BUTTON_0` and circle as `JL_JOY_BUTTON_1`; joystick 1 is
|
||||||
Having no keyboard, the PSP also gives Start as the Return key and Select
|
never there. Start is also the Return key and Select the Escape key, held
|
||||||
as the Escape key, held (`KEY_RETURN`, `KEY_ESCAPE`) and typed
|
(`JL_KEY_RETURN`, `JL_KEY_ESCAPE`) and typed (`JL_CHAR_RETURN`,
|
||||||
(`JL_CHAR_RETURN`, `JL_CHAR_ESCAPE` from `jlInputGetChar`) as those keys
|
`JL_CHAR_ESCAPE` from `jlInputGetChar`) as those keys are, so
|
||||||
are, so `jlWaitForAnyKey`, Return to go on and Escape to quit work. No
|
`jlWaitForAnyKey`, Return to go on, and Escape to quit work at a press.
|
||||||
other key is ever down, nothing else is typed (the PSP's on-screen
|
Square opens a PC keyboard drawn over the bottom of the screen, US layout,
|
||||||
keyboard is never opened), and there is no pointer to move.
|
for every other key: the d-pad moves its cursor, cross presses the key
|
||||||
|
under it (held down as long as cross is), L and R move the keyboard to
|
||||||
|
the top of the screen and back, out of the way of what the program shows
|
||||||
|
there, and Square closes it. Shift, Ctrl, and Alt stay down until the
|
||||||
|
next other key is let go (or until pressed again), and Caps turns capital
|
||||||
|
letters on and off. Its keys go down and up and type as a desktop's do,
|
||||||
|
so `jlKeyDown`, `jlKeyPressed`, and `jlInputGetChar` see the same as they
|
||||||
|
would there, F1 to F10, the arrows, and Ctrl and Alt chords included.
|
||||||
|
While it is open, joystick 0 rests: the d-pad and cross are the
|
||||||
|
keyboard's. There is no pointer to move.
|
||||||
* *HOME quits.* The HOME menu's Exit ends the program as closing a window
|
* *HOME quits.* The HOME menu's Exit ends the program as closing a window
|
||||||
does.
|
does.
|
||||||
* *Free space* (`jlDiskFree`) is that of the memory stick (or a PSP Go's
|
* *Free space* (`jlDiskFree`) is that of the memory stick (or a PSP Go's
|
||||||
|
|
@ -630,26 +906,28 @@ window to play in here: to play, copy the folder to a PSP, or open its
|
||||||
|
|
||||||
The Win32 port is the hosted core with a backend of its own, written to the
|
The Win32 port is the hosted core with a backend of its own, written to the
|
||||||
Win32 API (`src/hosted/win32`), for the Windows SDL3 does not reach --
|
Win32 API (`src/hosted/win32`), for the Windows SDL3 does not reach --
|
||||||
Windows 95, 98, ME, NT 4 and 2000 -- and every Windows since. Its stage is
|
Windows 95, 98, ME, NT 4, and 2000 -- and every Windows since. Its stage is
|
||||||
the same chunky one, so UBER's hashes are DOS's. Everything in
|
the same chunky one, so UBER's hashes are DOS's. Everything in
|
||||||
<<_sdl3_programs>> holds, except:
|
<<_sdl3_programs>> holds, except:
|
||||||
|
|
||||||
* *Old Windows on purpose.* A program is a 32-bit `NAME.EXE` built by
|
* *Old Windows on purpose.* A program is a 32-bit `NAME.EXE` built by
|
||||||
llvm-mingw against `MSVCRT.DLL` (the `win32` installer component), marked
|
llvm-mingw against `MSVCRT.DLL` (the `win32` installer component), marked
|
||||||
for Windows 4.0 so 95 and NT 4 will load it, linked statically, and
|
for Windows 4.0 so 95 and NT 4 will load it, linked statically, and
|
||||||
calling only what USER, GDI, WINMM and KERNEL32 had in 1995. `MSVCRT.DLL`
|
calling only what USER, GDI, WINMM, and KERNEL32 had in 1995. `MSVCRT.DLL`
|
||||||
came with Windows 95 OSR2 and Internet Explorer; a first-release Windows
|
came with Windows 95 OSR2 and Internet Explorer; a first-release Windows
|
||||||
95 needs it installed.
|
95 needs it installed.
|
||||||
* *One thread.* The window is made on the program's thread, so its messages
|
* *One thread.* The window is made on the program's thread, so its messages
|
||||||
arrive there and the port dispatches them at `jlInputPoll`, `jlWaitVBL`
|
arrive there and the port dispatches them at `jlInputPoll`, `jlWaitVBL`,
|
||||||
and `jlStagePresent`; the sound goes out through waveOut buffers handed
|
and `jlStagePresent`; the sound goes out through waveOut buffers handed
|
||||||
over at the same calls. There are no callbacks and no locks.
|
over at the same calls. There are no callbacks and no locks.
|
||||||
* *The window.* The stage is drawn with `StretchDIBits` at the stage's shape,
|
* *The window.* The stage is stretched with `StretchBlt` to the stage's
|
||||||
4:3 or square (the same `JOEYLIB_SCALE`, `JOEYLIB_ASPECT` and
|
shape, 4:3 or square (the same `JOEYLIB_SCALE`, `JOEYLIB_ASPECT`, and
|
||||||
`JOEYLIB_FULLSCREEN` settings), black around it; full screen is a
|
`JOEYLIB_FULLSCREEN` settings), black around it; full screen is a
|
||||||
borderless window over the whole monitor, which leaves the display mode
|
borderless window over the whole monitor, which leaves the display mode
|
||||||
alone. On a 256-colour desktop Windows maps every colour to the nearest
|
alone. On a 256-colour desktop Windows maps every colour to the nearest
|
||||||
one it has.
|
one it has. Each frame draws only what changed since the last: a game
|
||||||
|
that moves a few sprites pays for their rectangles, not for the whole
|
||||||
|
window, which on a Windows 95 machine is most of a frame's time.
|
||||||
* *Keys* by the PC scan code every key message carries (the same table as
|
* *Keys* by the PC scan code every key message carries (the same table as
|
||||||
DOS), typed characters through the user's keyboard layout, and the first
|
DOS), typed characters through the user's keyboard layout, and the first
|
||||||
two joysticks through `joyGetPosEx`, calibrated in the Control Panel. Keys
|
two joysticks through `joyGetPosEx`, calibrated in the Control Panel. Keys
|
||||||
|
|
@ -659,8 +937,9 @@ the same chunky one, so UBER's hashes are DOS's. Everything in
|
||||||
|
|
||||||
`make win32` builds every example as a Windows program in `build/win32/bin/`,
|
`make win32` builds every example as a Windows program in `build/win32/bin/`,
|
||||||
and `./joey-run --port win32 NAME` runs one under Wine (the `wine` installer
|
and `./joey-run --port win32 NAME` runs one under Wine (the `wine` installer
|
||||||
component, a portable Wine kept in `toolchains/`); the gates run there too.
|
component, a portable Wine kept in `toolchains/`); the gates run there too,
|
||||||
None of it has run on a real Windows 9x or NT 4 here.
|
`scripts/verify-win32-display.sh` among them, though that checks Wine's GDI
|
||||||
|
rather than Windows'. None of it has run on a real Windows 9x or NT 4 here.
|
||||||
|
|
||||||
The Win16 port is the hosted core with a backend written to the Windows 3.1
|
The Win16 port is the hosted core with a backend written to the Windows 3.1
|
||||||
API (`src/hosted/win16`), for Windows 3.1 and Windows for Workgroups 3.11 on
|
API (`src/hosted/win16`), for Windows 3.1 and Windows for Workgroups 3.11 on
|
||||||
|
|
@ -672,16 +951,54 @@ scan code, DTR dropped at close -- except:
|
||||||
with Open Watcom's Win386 target (the `win16` installer component): flat
|
with Open Watcom's Win386 target (the `win16` installer component): flat
|
||||||
32-bit code bound to Watcom's supervisor, which turns each call into the
|
32-bit code bound to Watcom's supervisor, which turns each call into the
|
||||||
16-bit one Windows takes, so it needs Windows in 386 enhanced mode. It
|
16-bit one Windows takes, so it needs Windows in 386 enhanced mode. It
|
||||||
calls only what Windows 3.1 has: USER, GDI, KERNEL and MMSYSTEM.
|
calls only what Windows 3.1 has: USER, GDI, KERNEL, and MMSYSTEM.
|
||||||
* *Exact colours.* The frame is 8-bit, a DIB whose 256-entry colour table
|
* *Exact colours.* The frame is 8-bit, each pixel one entry of a 256-entry
|
||||||
holds all 16 palettes and is realized as a logical palette whenever a frame
|
colour table holding all 16 palettes, which on a 256-colour display is a
|
||||||
is shown, so on a 256-colour display every colour is exact, SCB palettes
|
logical palette too, so every colour is exact, SCB palettes included, as
|
||||||
included. `JOEYLIB_SCALE` defaults to 2, and the settings are read from the
|
far as the display has room: Windows keeps 20 of a 256-colour display's
|
||||||
DOS environment Windows started from (`joey-run` puts its own there).
|
entries for itself, so with more than 14 palettes in use and more than 236
|
||||||
Under DOSBox, Alt+Enter is DOSBox's own full-screen key; use Alt with the
|
different colours on screen, some show as the nearest it has.
|
||||||
|
`JOEYLIB_SCALE` defaults to 2, and the settings are read from the DOS
|
||||||
|
environment Windows started from (`joey-run` puts its own there). Under
|
||||||
|
DOSBox, Alt+Enter is DOSBox's own full-screen key; use Alt with the
|
||||||
keypad's Enter.
|
keypad's Enter.
|
||||||
* *Sound* is 8-bit mono at 22.05 kHz through waveOut, what a Sound Blaster
|
* *Speed on a 386.* Sprites are compiled to x86 code, as on DOS. The port
|
||||||
and a 386 handle well; stereo pan is ignored.
|
expands the stage into the window's pixels itself, a presented row at a
|
||||||
|
time and scaled as it goes, and hands Windows only the rectangles that
|
||||||
|
changed, those close enough together as one. With Microsoft's WinG 1.0 on
|
||||||
|
the machine (its runtime in Windows' `SYSTEM` folder) the rows go straight
|
||||||
|
into a WinG bitmap the size of the window, and drawing a rectangle is a
|
||||||
|
plain copy to the screen; without it, each rectangle goes through a device
|
||||||
|
bitmap on a 256-colour display, and only another kind of display pays for
|
||||||
|
Windows matching every pixel's colour. `JOEYLIB_WING=0` leaves WinG alone.
|
||||||
|
JoeyLib does not ship WinG: a game that wants it on its players' machines
|
||||||
|
ships the runtime under Microsoft's terms (see `LICENSES.md`). While the
|
||||||
|
SCBs use 14 palettes or fewer, the frame's colours are the screen's own (an
|
||||||
|
identity palette, as WinG recommends), so a palette change, colour cycling
|
||||||
|
included, reaches the screen at once with nothing redrawn; more palettes
|
||||||
|
than that, or a display without a palette, redraw the window when colours
|
||||||
|
change. On a 386SX-16 (DOSBox's model of one), a game loop presenting
|
||||||
|
every frame runs at, in frames a second, with an Apple IIgs (under MAME)
|
||||||
|
for scale:
|
||||||
|
+
|
||||||
|
[cols="3,1,1,1,1", options="header"]
|
||||||
|
|===
|
||||||
|
| |2x with WinG |2x without |1:1 |IIgs
|
||||||
|
|Nothing moving, or colour cycling |60 |60 |60 |60
|
||||||
|
|One 16x16 sprite moving |60 |60 |60 |60
|
||||||
|
|Eight sprites moving |26 |20 |30 |20
|
||||||
|
|Eight sprites in a column |30 |20 |30 |20
|
||||||
|
|The whole screen changing |6 |4 |10 |8.5
|
||||||
|
|===
|
||||||
|
+
|
||||||
|
The 2x window has nearly five times the IIgs's pixels to fill (640x480 for
|
||||||
|
its 320x200): a 386 keeps up with that for sprites, but not for a whole new
|
||||||
|
screen every frame, so a game that redraws everything every frame wants
|
||||||
|
`JOEYLIB_SCALE=1` there.
|
||||||
|
* *Sound* is 16-bit mono at 22.05 kHz through waveOut, what a Sound Blaster
|
||||||
|
and a 386 handle well, or 8-bit on a card whose driver has no 16-bit (a
|
||||||
|
Sound Blaster 2.0, whose SNDBLST2.DRV is 8-bit only, which makes the quiet
|
||||||
|
attenuation steps coarser); stereo pan is ignored.
|
||||||
* *Joysticks* are the first two MMSYSTEM sticks, through `joyGetPos`, with
|
* *Joysticks* are the first two MMSYSTEM sticks, through `joyGetPos`, with
|
||||||
their buttons 1 and 2. Windows 3.x reads sticks only through a joystick
|
their buttons 1 and 2. Windows 3.x reads sticks only through a joystick
|
||||||
driver, which Windows 3.1 itself does not have; without one no stick is
|
driver, which Windows 3.1 itself does not have; without one no stick is
|
||||||
|
|
@ -718,15 +1035,21 @@ which the installer puts beside the driver in `toolchains/opt/`.
|
||||||
`make win16` builds every example as a Windows 3.x program in
|
`make win16` builds every example as a Windows 3.x program in
|
||||||
`build/win16/bin/`, and `./joey-run --port win16 NAME` runs one on Windows
|
`build/win16/bin/`, and `./joey-run --port win16 NAME` runs one on Windows
|
||||||
for Workgroups 3.11 in DOSBox Staging (the `win311` installer component, set
|
for Workgroups 3.11 in DOSBox Staging (the `win311` installer component, set
|
||||||
up from your own install disc); the gates run there too. None of it has run
|
up from your own install disc); the gates run there too, among them
|
||||||
on a real 386 here.
|
`scripts/verify-win16-display.sh`, which checks every pixel the window shows
|
||||||
|
against the stage after every present, with WinG and without (the optional
|
||||||
|
`wing` installer component: `./install.sh import WING10.EXE`, Microsoft's
|
||||||
|
WinG 1.0 SDK, then `./install.sh install wing`; the gates then install its
|
||||||
|
runtime in their Windows). None of it has run on a real 386
|
||||||
|
here; the speeds above are DOSBox's 386SX-16 model's, and the IIgs's are
|
||||||
|
MAME's.
|
||||||
|
|
||||||
=== Haiku and BeOS Programs
|
=== Haiku and BeOS Programs
|
||||||
|
|
||||||
The Be API ports are the hosted core with a backend written to the Be API
|
The Be API ports are the hosted core with a backend written to the Be API
|
||||||
(`src/hosted/be`, C++), which Haiku reimplements, kept to the calls BeOS R5
|
(`src/hosted/be`, C++), which Haiku reimplements, kept to the calls BeOS R5
|
||||||
already had. One source builds three ports: `haiku` (Haiku x86_64, gcc 13),
|
already had. One source builds three ports: `haiku` (Haiku x86_64, gcc 13),
|
||||||
`haiku32` (Haiku x86, gcc 2.95, the BeOS C++ ABI) and `beos` (BeOS R5 x86,
|
`haiku32` (Haiku x86, gcc 2.95, the BeOS C++ ABI), and `beos` (BeOS R5 x86,
|
||||||
built with the same gcc 2.95 against R5's own headers and libraries, which
|
built with the same gcc 2.95 against R5's own headers and libraries, which
|
||||||
the `beosR5` installer component copies off your BeOS 5 Professional CD).
|
the `beosR5` installer component copies off your BeOS 5 Professional CD).
|
||||||
Their stage is the same chunky one, so UBER's hashes are DOS's. Everything in
|
Their stage is the same chunky one, so UBER's hashes are DOS's. Everything in
|
||||||
|
|
@ -735,25 +1058,32 @@ Their stage is the same chunky one, so UBER's hashes are DOS's. Everything in
|
||||||
* *Threads.* The program's `main` keeps its own thread; the application and
|
* *Threads.* The program's `main` keeps its own thread; the application and
|
||||||
the window run on threads of their own, as every Be program's do. Key and
|
the window run on threads of their own, as every Be program's do. Key and
|
||||||
mouse messages reach the program as events taken at `jlInputPoll`,
|
mouse messages reach the program as events taken at `jlInputPoll`,
|
||||||
`jlWaitVBL` and `jlStagePresent`, and the sound comes from a
|
`jlWaitVBL`, and `jlStagePresent`, and the sound comes from a
|
||||||
`BSoundPlayer`.
|
`BSoundPlayer`.
|
||||||
* *The window* shows the stage from a bitmap the app_server scales without
|
* *The window* shows the stage scaled without filtering, at the stage's
|
||||||
filtering, at the stage's shape, 4:3 or square, black around it (the same
|
shape, 4:3 or square, black around it (the same `JOEYLIB_SCALE`,
|
||||||
`JOEYLIB_SCALE`, `JOEYLIB_ASPECT` and `JOEYLIB_FULLSCREEN` settings;
|
`JOEYLIB_ASPECT`, and `JOEYLIB_FULLSCREEN` settings; `JOEYLIB_SCALE`
|
||||||
`JOEYLIB_SCALE` defaults to 3). Alt+Enter switches between the window and
|
defaults to 3). The port scales the frame itself and draws only what
|
||||||
|
changed since the last frame. Alt+Enter switches between the window and
|
||||||
the whole screen. The pointer is hidden over the window.
|
the whole screen. The pointer is hidden over the window.
|
||||||
* *Keys* by raw key code, the key's place on the keyboard whatever the
|
* *Keys* by raw key code, the key's place on the keyboard whatever the
|
||||||
keymap; typed characters are the keymap's, 7-bit ASCII only. The first
|
keymap; typed characters are the keymap's, 7-bit ASCII only. The first
|
||||||
two joysticks (`BJoystick`) are `JOYSTICK_0` and `JOYSTICK_1`, their first
|
two joysticks (`BJoystick`) are `JL_JOYSTICK_0` and `JL_JOYSTICK_1`, their first
|
||||||
two axes and buttons.
|
two axes and buttons.
|
||||||
* *Files.* The program's folder is the current directory from the moment
|
* *Files.* The program's folder is the current directory from the moment
|
||||||
the window opens.
|
the window opens.
|
||||||
|
* *Its icon* is in its resources (<<_icons>>), but Tracker shows a file by
|
||||||
|
the icons in its attributes, which a program built on another machine does
|
||||||
|
not have. When it opens its window without them, the program copies them
|
||||||
|
across from its resources (as `mimeset -F` does), so Tracker shows it by
|
||||||
|
its icon once it has run; that is the one time a program writes to its own
|
||||||
|
file.
|
||||||
* *No serial port.*
|
* *No serial port.*
|
||||||
* *BeOS R5's loader* refuses any program with a dynamic tag it does not
|
* *BeOS R5's loader* refuses any program with a dynamic tag it does not
|
||||||
know, so `make beos` links without combined relocations (`-z
|
know, so `make beos` links without combined relocations (`-z
|
||||||
nocombreloc`). Build for R5 with `make beos`, not `make haiku32`.
|
nocombreloc`). Build for R5 with `make beos`, not `make haiku32`.
|
||||||
|
|
||||||
`make haiku`, `make haiku32` and `make beos` build every example into
|
`make haiku`, `make haiku32`, and `make beos` build every example into
|
||||||
`build/<port>/bin/`, and `./joey-run --port <port> NAME` runs one in that
|
`build/<port>/bin/`, and `./joey-run --port <port> NAME` runs one in that
|
||||||
port's VM under QEMU: Haiku R1/beta6 (the `haikuVm` and `haikuVm32` installer
|
port's VM under QEMU: Haiku R1/beta6 (the `haikuVm` and `haikuVm32` installer
|
||||||
components) or BeOS R5 (`beosVm`, made from your CD), booted from a disk it
|
components) or BeOS R5 (`beosVm`, made from your CD), booted from a disk it
|
||||||
|
|
@ -761,5 +1091,6 @@ never writes to, with the program's folder on a second disk. R5 drives none
|
||||||
of QEMU's video cards, so its VM is taken through the boot loader's menu to
|
of QEMU's video cards, so its VM is taken through the boot loader's menu to
|
||||||
the fail-safe (VESA) mode, 1024x768 in 16-bit colour; it has no sound card.
|
the fail-safe (VESA) mode, 1024x768 in 16-bit colour; it has no sound card.
|
||||||
The gates run in the same VMs (`scripts/verify-be-input.sh`,
|
The gates run in the same VMs (`scripts/verify-be-input.sh`,
|
||||||
`scripts/bench-be.sh`). None of it has run on a real BeOS or Haiku machine
|
`scripts/verify-be-display.sh`, `scripts/verify-be-icon.sh`, and
|
||||||
here.
|
`scripts/bench-be.sh`). None of it has
|
||||||
|
run on a real BeOS or Haiku machine here.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ copy.
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 32UL * 1024; // sprite blitter cache
|
config.codegenBytes = 32UL * 1024; // sprite blitter cache
|
||||||
|
config.noAudio = false; // jlInit starts audio too
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
// jlLastError() explains why. There is no display yet, so on a
|
// jlLastError() explains why. There is no display yet, so on a
|
||||||
|
|
@ -24,22 +25,27 @@ if (!jlInit(&config)) {
|
||||||
native sprite blitter code into. Getting it wrong is the most common cause of
|
native sprite blitter code into. Getting it wrong is the most common cause of
|
||||||
sprites drawing slowly, so it is worth understanding.
|
sprites drawing slowly, so it is worth understanding.
|
||||||
|
|
||||||
* `0` selects a per-platform default: 8 KB on the IIgs, 32 KB on
|
* `0` selects a per-platform default: 8 KB on the IIgs, 64 KB on the
|
||||||
Amiga, ST, DOS and DVX. SDL3 interprets every sprite and never uses it.
|
Amiga and ST, and 32 KB on the X68000, DOS, DVX, and Win16. The other
|
||||||
|
hosted ports interpret every sprite and never use it, though they still
|
||||||
|
allocate it.
|
||||||
* The 68000 and x86 emitters produce code roughly four to eight times less
|
* The 68000 and x86 emitters produce code roughly four to eight times less
|
||||||
dense than the 65816 one, so the same sprite needs far more arena space on
|
dense than the 65816 one, so the same sprite needs far more arena space on
|
||||||
those ports. A 32x32 sprite can need 8 to 16 KB there.
|
those ports. A 32x32 sprite can need 8 to 16 KB there.
|
||||||
* Measure, do not guess: after compiling a sprite, `jlSpriteCompiledSize()`
|
* Measure, do not guess: after compiling a sprite, `jlSpriteCompiledSize()`
|
||||||
reports its exact cost on the current platform.
|
reports its exact cost on the current platform.
|
||||||
* On the IIgs the arena is one Memory Manager block that may not cross a 64 KB
|
* On the IIgs a compiled sprite cannot cross a 64 KB bank boundary, so the
|
||||||
bank boundary, so requests above 65536 are clamped to 65536 -- and even a
|
arena is up to four Memory Manager blocks of at most 64 KB, each inside one
|
||||||
clamped request can fail if no free bank exists.
|
bank: 256 KB at most. A bank that is refused is tried again at 60 KB, and
|
||||||
|
`jlInit` takes whatever part of the request it was granted. Elsewhere the
|
||||||
|
arena is one block, and `jlInit` fails if it cannot be had.
|
||||||
|
|
||||||
If the arena is too small, sprites still draw: they fall back to an
|
If the arena is too small, sprites still draw: they fall back to an
|
||||||
interpreter that is roughly *forty times slower*. Nothing reports this as an
|
interpreter that is *two to six times slower*, depending on the machine.
|
||||||
error, so a program that never checks can be silently crawling.
|
Nothing reports this as an error, so a program that never checks can be
|
||||||
|
silently crawling.
|
||||||
|
|
||||||
=== Part 2: The Stage, the Palette and Assets
|
=== Part 2: The Stage, the Palette, and Assets
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
|
|
@ -54,10 +60,10 @@ Set up colour before drawing:
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
// ... fill in 16 entries of $0RGB ...
|
// ... fill in 16 entries of $0RGB ...
|
||||||
jlPaletteSet(stage, 0, colors);
|
jlPaletteSet(stage, 0, colors);
|
||||||
jlScbSetRange(stage, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(stage, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
----
|
----
|
||||||
|
|
||||||
Each surface carries *16 palettes of 16 colours* plus a 200-entry table (one
|
Each surface carries *16 palettes of 16 colours* plus a 200-entry table (one
|
||||||
|
|
@ -71,22 +77,21 @@ Load assets after init, because sprite loading needs the codegen arena:
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
jlSpriteT *cels[16];
|
jlSpriteT *cels[16];
|
||||||
uint16_t palette[SURFACE_COLORS_PER_PALETTE];
|
uint16_t palette[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t count = jlSpriteBankLoad("player.spr", cels, 16, palette);
|
uint16_t count = jlSpriteBankLoad("player.spr", cels, 16, palette);
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
jlSpritePrewarm(cels[i]); // compile NOW, not mid-frame
|
(void)jlSpriteCompile(cels[i]); // compile NOW, not mid-frame
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
`jlSpriteDraw` does *not* compile a sprite on demand. An uncompiled sprite
|
`jlSpriteDraw` does *not* compile a sprite on demand. An uncompiled sprite
|
||||||
draws through the interpreter every single time. Call `jlSpriteCompile` or
|
draws through the interpreter every single time. Call `jlSpriteCompile` during
|
||||||
`jlSpritePrewarm` during loading -- otherwise the first draw of each sprite
|
loading -- otherwise the first draw of each sprite either stalls a frame (if
|
||||||
either stalls a frame (if you compile lazily yourself) or never gets fast at
|
you compile lazily yourself) or never gets fast at all.
|
||||||
all.
|
|
||||||
====
|
====
|
||||||
|
|
||||||
=== Part 3: The Frame Loop
|
=== Part 3: The Frame Loop
|
||||||
|
|
@ -100,7 +105,7 @@ while (running) {
|
||||||
jlInputPoll(); // refresh keyboard, mouse, joystick
|
jlInputPoll(); // refresh keyboard, mouse, joystick
|
||||||
jlAudioFrameTick(); // pump the audio engine
|
jlAudioFrameTick(); // pump the audio engine
|
||||||
|
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,73 +118,68 @@ while (running) {
|
||||||
|
|
||||||
Four library calls, in that order, and everything else is yours.
|
Four library calls, in that order, and everything else is yours.
|
||||||
|
|
||||||
`jlWaitVBL()` blocks until the next vertical blank. It is cheap on every port
|
`jlWaitVBL()` blocks until the next vertical blank. On the machine ports that
|
||||||
because the underlying mechanism is always a hardware wait, never a software
|
is the display's own; the hosted ports, and DVX in a window, have none, and
|
||||||
timer.
|
wait for the next tick of a virtual frame clock instead.
|
||||||
|
|
||||||
`jlInputPoll()` must be called exactly once per frame. The edge predicates
|
`jlInputPoll()` must be called exactly once per frame. The edge predicates
|
||||||
(`jlKeyPressed`, `jlKeyReleased`, and the mouse and joystick equivalents) are
|
(`jlKeyPressed`, `jlKeyReleased`, and the mouse and joystick equivalents) are
|
||||||
defined relative to the *previous poll*, so calling it twice per frame makes
|
defined relative to the *previous poll*, so calling it twice per frame makes
|
||||||
edges disappear, and not calling it means nothing ever changes.
|
edges disappear, and not calling it means nothing ever changes.
|
||||||
|
|
||||||
`jlAudioFrameTick()` is a no-op on ports whose audio runs from a hardware
|
`jlAudioFrameTick()` advances tracker music and keeps streaming sound effects
|
||||||
interrupt, but it is required on ports that mix in user context, and it is what
|
fed. Call it once per frame.
|
||||||
advances tracker music and keeps streaming sound effects fed. Call it
|
|
||||||
unconditionally, once per frame.
|
|
||||||
|
|
||||||
`jlStagePresent()` flips the dirty parts of the stage to the display. The
|
`jlStagePresent()` flips the dirty parts of the stage to the display. The
|
||||||
drawing primitives record which rows they touched, so presenting only copies
|
drawing primitives record which rows they touched, so presenting only copies
|
||||||
what actually changed. You do not need to tell it what you drew.
|
what actually changed. You do not need to tell it what you drew: every call
|
||||||
|
that changes the stage's pixels marks them, and the API has no other way to
|
||||||
[NOTE]
|
write them. Palette and SCB changes reach the display at the next present
|
||||||
====
|
too.
|
||||||
If you write pixels by some route other than the library's primitives, the
|
|
||||||
dirty tracking does not know about it, and `jlStagePresent` will skip those
|
|
||||||
rows. That is the one case where you have to mark the region yourself.
|
|
||||||
====
|
|
||||||
|
|
||||||
==== Frame-Rate Independence
|
==== Frame-Rate Independence
|
||||||
|
|
||||||
The targets do not share a refresh rate: 50 Hz (Amiga PAL), 55 Hz
|
The targets do not share a refresh rate: 50 Hz (the Amiga, and a PAL IIgs or
|
||||||
(X68000), 60 Hz (IIgs, ST NTSC, and SDL3's virtual rate) and 70 Hz (VGA
|
ST), 55 Hz (X68000), 60 Hz (the IIgs, an NTSC ST, and every hosted port's
|
||||||
mode 13h, and DVX's virtual rate). A loop
|
virtual rate), and 70 Hz (VGA mode 13h, and DVX's virtual rate). A loop
|
||||||
that moves a sprite "two pixels per frame" runs 40 percent faster on DOS than
|
that moves a sprite "two pixels per frame" runs 40 percent faster on DOS than
|
||||||
on a PAL Amiga.
|
on a PAL Amiga.
|
||||||
|
|
||||||
Two ways to handle it. Ask the frame rate and scale your per-frame step:
|
So give every speed per second, never per frame, and work out each frame how
|
||||||
|
far that is. The millisecond clock does it best. `jlMillisElapsed()` reports
|
||||||
[source,c]
|
true elapsed wall-clock time, so a frame that took a long time to render
|
||||||
----
|
moves things as far as the time it took, rather than counting as one tick:
|
||||||
uint16_t hz = jlFrameHz(); // 50, 60 or 70
|
|
||||||
----
|
|
||||||
|
|
||||||
Or drive from the millisecond clock, which is the more robust choice for
|
|
||||||
anything that must feel the same everywhere:
|
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
uint32_t now = jlMillisElapsed();
|
uint32_t now = jlMillisElapsed();
|
||||||
uint32_t step = now - previous;
|
uint32_t elapsed = now - gLastMillis; // milliseconds since the last frame
|
||||||
previous = now;
|
|
||||||
|
gLastMillis = now;
|
||||||
----
|
----
|
||||||
|
|
||||||
`jlMillisElapsed()` reports true elapsed wall-clock time, so a frame that took
|
A thing then moves its speed times `elapsed` / 1000. At 70 frames a second
|
||||||
a long time to render is accounted for correctly rather than counted as one
|
that is often a fraction of a pixel, which only adds up if positions keep the
|
||||||
tick.
|
fraction, so keep them in fixed point (the skeleton below uses 1/256 of a
|
||||||
|
pixel) and draw at the whole-pixel part.
|
||||||
|
|
||||||
|
`jlFrameHz()` (50 to 70) is the other way: a per-frame step of speed /
|
||||||
|
`jlFrameHz()` is right while every frame takes one refresh, and falls behind
|
||||||
|
whenever one takes longer.
|
||||||
|
|
||||||
=== Part 4: Shut Down
|
=== Part 4: Shut Down
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
jlAudioShutdown(); // if you called jlAudioInit
|
|
||||||
jlShutdown();
|
jlShutdown();
|
||||||
return 0;
|
return 0;
|
||||||
----
|
----
|
||||||
|
|
||||||
`jlShutdown` restores the display mode, frees the library's allocations, and
|
`jlShutdown` stops audio, restores the display mode, frees the library's allocations, and
|
||||||
un-hooks interrupt vectors. Sprites and surfaces you created are released with
|
un-hooks interrupt vectors. Sprites and surfaces you created are *not*
|
||||||
it, but the buffers *you* allocated (including tile data you handed to
|
released with it: destroy them with `jlSpriteDestroy` and `jlSurfaceDestroy`
|
||||||
`jlSpriteCreate`, which is never copied) remain yours to free.
|
before you shut down. The buffers *you* allocated (including tile data you
|
||||||
|
handed to `jlSpriteCreate`, which is never copied) remain yours to free.
|
||||||
|
|
||||||
=== The Complete Skeleton
|
=== The Complete Skeleton
|
||||||
|
|
||||||
|
|
@ -189,25 +189,33 @@ This compiles and runs on every target as-is.
|
||||||
----
|
----
|
||||||
// A minimal but complete JoeyLib program.
|
// A minimal but complete JoeyLib program.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#include <joey/joey.h>
|
#include <joey/joey.h>
|
||||||
|
|
||||||
#define PLAYER_SPEED 2
|
#define PLAYER_SIZE 16
|
||||||
|
#define PLAYER_SPEED 120 // pixels per second, the same on every port
|
||||||
|
#define SUBPIXEL_SHIFT 8 // positions are kept in 1/256 of a pixel
|
||||||
|
#define MAX_STEP_MILLIS 100 // after a stall, move no further than this
|
||||||
|
|
||||||
static jlSurfaceT *gStage;
|
static jlSurfaceT *gStage;
|
||||||
static bool gRunning = true;
|
static bool gRunning = true;
|
||||||
static int16_t gPlayerX = 160;
|
static int32_t gPlayerX = 160L << SUBPIXEL_SHIFT;
|
||||||
static int16_t gPlayerY = 100;
|
static int32_t gPlayerY = 100L << SUBPIXEL_SHIFT;
|
||||||
|
static uint32_t gLastMillis = 0;
|
||||||
|
|
||||||
static void buildPalette(void);
|
static void buildPalette(void);
|
||||||
static void handleInput(void);
|
static int32_t clampPosition(int32_t position, int16_t limit);
|
||||||
|
static void handleInput(int32_t step);
|
||||||
static void renderFrame(void);
|
static void renderFrame(void);
|
||||||
|
static int32_t stepForFrame(void);
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(void) {
|
static void buildPalette(void) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = 0x000;
|
colors[i] = 0x000;
|
||||||
}
|
}
|
||||||
colors[1] = 0xFFF; // white
|
colors[1] = 0xFFF; // white
|
||||||
|
|
@ -215,58 +223,77 @@ static void buildPalette(void) {
|
||||||
colors[3] = 0x00C; // blue
|
colors[3] = 0x00C; // blue
|
||||||
|
|
||||||
jlPaletteSet(gStage, 0, colors);
|
jlPaletteSet(gStage, 0, colors);
|
||||||
jlScbSetRange(gStage, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(gStage, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void handleInput(void) {
|
// A position (in 1/256 of a pixel) kept between 0 and limit pixels.
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
static int32_t clampPosition(int32_t position, int16_t limit) {
|
||||||
|
int32_t max = (int32_t)limit << SUBPIXEL_SHIFT;
|
||||||
|
|
||||||
|
if (position < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (position > max) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Moves the player by step (1/256 of a pixel) in each direction held.
|
||||||
|
static void handleInput(int32_t step) {
|
||||||
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
gRunning = false;
|
gRunning = false;
|
||||||
}
|
}
|
||||||
if (jlKeyDown(KEY_LEFT)) {
|
if (jlKeyDown(JL_KEY_LEFT)) {
|
||||||
gPlayerX -= PLAYER_SPEED;
|
gPlayerX -= step;
|
||||||
}
|
}
|
||||||
if (jlKeyDown(KEY_RIGHT)) {
|
if (jlKeyDown(JL_KEY_RIGHT)) {
|
||||||
gPlayerX += PLAYER_SPEED;
|
gPlayerX += step;
|
||||||
}
|
}
|
||||||
if (jlKeyDown(KEY_UP)) {
|
if (jlKeyDown(JL_KEY_UP)) {
|
||||||
gPlayerY -= PLAYER_SPEED;
|
gPlayerY -= step;
|
||||||
}
|
}
|
||||||
if (jlKeyDown(KEY_DOWN)) {
|
if (jlKeyDown(JL_KEY_DOWN)) {
|
||||||
gPlayerY += PLAYER_SPEED;
|
gPlayerY += step;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A joystick, if one is attached, drives the same movement.
|
// A joystick, if one is attached, drives the same movement.
|
||||||
if (jlJoystickConnected(JOYSTICK_0)) {
|
if (jlJoystickConnected(JL_JOYSTICK_0)) {
|
||||||
if (jlJoystickX(JOYSTICK_0) < -64) {
|
if (jlJoystickX(JL_JOYSTICK_0) < -64) {
|
||||||
gPlayerX -= PLAYER_SPEED;
|
gPlayerX -= step;
|
||||||
}
|
}
|
||||||
if (jlJoystickX(JOYSTICK_0) > 64) {
|
if (jlJoystickX(JL_JOYSTICK_0) > 64) {
|
||||||
gPlayerX += PLAYER_SPEED;
|
gPlayerX += step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the player on screen: the primitives would clip safely, but
|
// Keep the player on screen: the primitives would clip safely, but
|
||||||
// clamping keeps the object whole.
|
// clamping keeps the object whole.
|
||||||
if (gPlayerX < 0) {
|
gPlayerX = clampPosition(gPlayerX, JL_SURFACE_WIDTH - PLAYER_SIZE);
|
||||||
gPlayerX = 0;
|
gPlayerY = clampPosition(gPlayerY, JL_SURFACE_HEIGHT - PLAYER_SIZE);
|
||||||
}
|
|
||||||
if (gPlayerX > SURFACE_WIDTH - 16) {
|
|
||||||
gPlayerX = SURFACE_WIDTH - 16;
|
|
||||||
}
|
|
||||||
if (gPlayerY < 0) {
|
|
||||||
gPlayerY = 0;
|
|
||||||
}
|
|
||||||
if (gPlayerY > SURFACE_HEIGHT - 16) {
|
|
||||||
gPlayerY = SURFACE_HEIGHT - 16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void renderFrame(void) {
|
static void renderFrame(void) {
|
||||||
jlSurfaceClear(gStage, 0);
|
jlSurfaceClear(gStage, 0);
|
||||||
jlDrawRect(gStage, 0, 0, SURFACE_WIDTH, SURFACE_HEIGHT, 1);
|
jlDrawRect(gStage, 0, 0, JL_SURFACE_WIDTH, JL_SURFACE_HEIGHT, 1);
|
||||||
jlFillRect(gStage, gPlayerX, gPlayerY, 16, 16, 2);
|
jlFillRect(gStage, (int16_t)(gPlayerX >> SUBPIXEL_SHIFT), (int16_t)(gPlayerY >> SUBPIXEL_SHIFT), PLAYER_SIZE, PLAYER_SIZE, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// How far the player moves this frame, in 1/256 of a pixel: PLAYER_SPEED
|
||||||
|
// times the time since the last frame, whatever the port's frame rate.
|
||||||
|
static int32_t stepForFrame(void) {
|
||||||
|
uint32_t now = jlMillisElapsed();
|
||||||
|
uint32_t elapsed = now - gLastMillis;
|
||||||
|
|
||||||
|
gLastMillis = now;
|
||||||
|
if (elapsed > MAX_STEP_MILLIS) {
|
||||||
|
elapsed = MAX_STEP_MILLIS;
|
||||||
|
}
|
||||||
|
return (int32_t)((((uint32_t)PLAYER_SPEED * elapsed) << SUBPIXEL_SHIFT) / 1000u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -274,6 +301,7 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
|
config.noAudio = false;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -287,13 +315,14 @@ int main(void) {
|
||||||
|
|
||||||
buildPalette();
|
buildPalette();
|
||||||
jlRandomSeed(12345); // deterministic on every port
|
jlRandomSeed(12345); // deterministic on every port
|
||||||
|
gLastMillis = jlMillisElapsed();
|
||||||
|
|
||||||
while (gRunning) {
|
while (gRunning) {
|
||||||
jlWaitVBL();
|
jlWaitVBL();
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
jlAudioFrameTick();
|
jlAudioFrameTick();
|
||||||
|
|
||||||
handleInput();
|
handleInput(stepForFrame());
|
||||||
renderFrame();
|
renderFrame();
|
||||||
|
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
|
|
@ -315,7 +344,7 @@ Before drawing a sprite, capture what is underneath; next frame, put it back.
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
uint16_t backing[JOEY_SPRITE_BACKUP_BYTES(2, 2) / 2];
|
uint16_t backing[jlSpriteBackupLength(uint16_t, 2, 2)];
|
||||||
jlSpriteBackupT backup;
|
jlSpriteBackupT backup;
|
||||||
|
|
||||||
backup.bytes = (uint8_t *)backing;
|
backup.bytes = (uint8_t *)backing;
|
||||||
|
|
@ -326,10 +355,10 @@ jlSpriteRestoreUnder(stage, &backup); // erase last frame's sprite
|
||||||
jlSpriteSaveAndDraw(stage, sprite, x, y, &backup);
|
jlSpriteSaveAndDraw(stage, sprite, x, y, &backup);
|
||||||
----
|
----
|
||||||
|
|
||||||
Size the buffer with the `JOEY_SPRITE_BACKUP_BYTES` macro, and declare it as
|
Size the buffer with `jlSpriteBackupLength`, and declare it as `uint16_t` (or
|
||||||
`uint16_t` (or otherwise 2-byte aligned) -- an odd address pushes save and
|
otherwise 2-byte aligned): on the Amiga, ST, and X68000 an odd address sends
|
||||||
restore onto slow interpreted paths on the planar ports, because the 68000
|
save and restore down a much slower path, and on the Amiga and ST it crashes
|
||||||
cannot do word accesses at odd addresses.
|
the trusted variants.
|
||||||
|
|
||||||
==== The Clean-Buffer Pattern
|
==== The Clean-Buffer Pattern
|
||||||
|
|
||||||
|
|
@ -341,12 +370,12 @@ jlSurfaceT *clean = jlSurfaceCreate();
|
||||||
// ... draw the static scene into clean, once ...
|
// ... draw the static scene into clean, once ...
|
||||||
|
|
||||||
// each frame:
|
// each frame:
|
||||||
jlSurfaceCopyRect(stage, clean, x, y, w, h); // erase EVERY window first
|
jlSurfaceCopyRect(stage, clean, x, y, w, h); // erase EVERY region first
|
||||||
// ... then draw ALL sprites ...
|
// ... then draw ALL sprites ...
|
||||||
----
|
----
|
||||||
|
|
||||||
This scales better with many sprites, and it is the right choice when sprites
|
This scales better with many sprites, and it is the right choice when sprites
|
||||||
overlap. Erase every window first, then draw every sprite -- never interleave
|
overlap. Erase every region first, then draw every sprite -- never interleave
|
||||||
erase and draw.
|
erase and draw.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
|
|
@ -354,6 +383,6 @@ erase and draw.
|
||||||
`jlSurfaceCopyRect` widens the copied region outward to the 16-pixel group grid
|
`jlSurfaceCopyRect` widens the copied region outward to the 16-pixel group grid
|
||||||
on *every* port, so it can touch up to 15 pixels either side of the rectangle
|
on *every* port, so it can touch up to 15 pixels either side of the rectangle
|
||||||
you asked for. That means it can overwrite content just outside your rectangle,
|
you asked for. That means it can overwrite content just outside your rectangle,
|
||||||
and it silently invalidates any live `jlSpriteBackupT` whose recorded window it
|
and it silently invalidates any live `jlSpriteBackupT` whose recorded region it
|
||||||
touches. This is exactly why the two patterns must not be mixed in one region.
|
touches. This is exactly why the two patterns must not be mixed in one region.
|
||||||
====
|
====
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -22,10 +22,10 @@
|
||||||
// keys on a timetable -- N once, S every 1.5 seconds -- for the IIgs and
|
// keys on a timetable -- N once, S every 1.5 seconds -- for the IIgs and
|
||||||
// X68000 audio gates, which read the sound chips but cannot type.
|
// X68000 audio gates, which read the sound chips but cannot type.
|
||||||
//
|
//
|
||||||
// On platforms where the audio HAL is still a stub, jlAudioInit
|
// On a machine whose sound does not start (jlInit logs it), every audio
|
||||||
// returns false, every audio call is a quiet no-op, and the demo runs
|
// call is a quiet no-op and the demo runs as a silent input loop -- you
|
||||||
// as a silent input loop -- you can confirm the build links and the
|
// can confirm the build links and the input plumbing still works without
|
||||||
// input plumbing still works without hearing anything.
|
// hearing anything.
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
|
|
||||||
#define BAR_X 16
|
#define BAR_X 16
|
||||||
#define BAR_Y 88
|
#define BAR_Y 88
|
||||||
#define BAR_W (SURFACE_WIDTH - 32)
|
#define BAR_W (JL_SURFACE_WIDTH - 32)
|
||||||
#define BAR_H 16
|
#define BAR_H 16
|
||||||
|
|
||||||
// The streamed tone's state, which streamFill advances.
|
// The streamed tone's state, which streamFill advances.
|
||||||
|
|
@ -113,7 +113,7 @@ typedef struct {
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen);
|
static void buildPalette(jlSurfaceT *screen);
|
||||||
static void initialPaint(jlSurfaceT *screen, bool audioOk);
|
static void initialPaint(jlSurfaceT *screen);
|
||||||
static bool keyPressed(jlKeyE key, uint16_t frame);
|
static bool keyPressed(jlKeyE key, uint16_t frame);
|
||||||
static bool loadFile(const char *name, uint32_t maxLen, uint8_t **outBytes, uint32_t *outLen);
|
static bool loadFile(const char *name, uint32_t maxLen, uint8_t **outBytes, uint32_t *outLen);
|
||||||
static bool noiseStep(uint16_t sweepFrame);
|
static bool noiseStep(uint16_t sweepFrame);
|
||||||
|
|
@ -129,10 +129,10 @@ static uint8_t gStreamTake = 0;
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = 0x0000;
|
colors[i] = 0x0000;
|
||||||
}
|
}
|
||||||
colors[COLOR_BG] = 0x0000;
|
colors[COLOR_BG] = 0x0000;
|
||||||
|
|
@ -146,10 +146,9 @@ static void buildPalette(jlSurfaceT *screen) {
|
||||||
// Visual feedback for "audio is running, but you cannot tell on a
|
// Visual feedback for "audio is running, but you cannot tell on a
|
||||||
// stub HAL": pulse a horizontal bar between the hint color and the
|
// stub HAL": pulse a horizontal bar between the hint color and the
|
||||||
// active color whenever a sound has fired this frame.
|
// active color whenever a sound has fired this frame.
|
||||||
static void initialPaint(jlSurfaceT *screen, bool audioOk) {
|
static void initialPaint(jlSurfaceT *screen) {
|
||||||
jlSurfaceClear(screen, COLOR_BG);
|
jlSurfaceClear(screen, COLOR_BG);
|
||||||
jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
|
jlFillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
|
||||||
audioOk ? COLOR_HINT : COLOR_BG);
|
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,17 +225,17 @@ static bool noiseStep(uint16_t sweepFrame) {
|
||||||
// so a gate finds the noise sweep and a stream running.
|
// so a gate finds the noise sweep and a stream running.
|
||||||
static jlKeyE probeKey(uint16_t frame) {
|
static jlKeyE probeKey(uint16_t frame) {
|
||||||
if (frame == PROBE_NOISE_FRAME) {
|
if (frame == PROBE_NOISE_FRAME) {
|
||||||
return KEY_N;
|
return JL_KEY_N;
|
||||||
}
|
}
|
||||||
if (frame % PROBE_STREAM_FRAMES == PROBE_STREAM_FIRST) {
|
if (frame % PROBE_STREAM_FRAMES == PROBE_STREAM_FIRST) {
|
||||||
return KEY_S;
|
return JL_KEY_S;
|
||||||
}
|
}
|
||||||
return KEY_NONE;
|
return JL_KEY_NONE;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static jlKeyE probeKey(uint16_t frame) {
|
static jlKeyE probeKey(uint16_t frame) {
|
||||||
(void)frame;
|
(void)frame;
|
||||||
return KEY_NONE;
|
return JL_KEY_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -306,7 +305,6 @@ static void streamStart(void) {
|
||||||
int main(void) {
|
int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
bool audioOk;
|
|
||||||
bool musicPaused;
|
bool musicPaused;
|
||||||
bool noiseOn;
|
bool noiseOn;
|
||||||
bool noiseResumes;
|
bool noiseResumes;
|
||||||
|
|
@ -319,6 +317,7 @@ int main(void) {
|
||||||
uint32_t sfxLen;
|
uint32_t sfxLen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = false;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -337,19 +336,16 @@ int main(void) {
|
||||||
modLen = 0;
|
modLen = 0;
|
||||||
sfxLen = 0;
|
sfxLen = 0;
|
||||||
|
|
||||||
audioOk = jlAudioInit();
|
|
||||||
if (audioOk) {
|
|
||||||
// Keep the MOD bytes resident so M can (re)start it later;
|
// Keep the MOD bytes resident so M can (re)start it later;
|
||||||
// jlAudioPlayMod copies them at play time.
|
// jlAudioPlayMod copies them at play time.
|
||||||
(void)loadFile(TEST_MOD_NAME, DATA_FILE_MAX, &modBytes, &modLen);
|
(void)loadFile(TEST_MOD_NAME, DATA_FILE_MAX, &modBytes, &modLen);
|
||||||
(void)loadFile(TEST_SFX_NAME, DATA_FILE_MAX, &sfxBytes, &sfxLen);
|
(void)loadFile(TEST_SFX_NAME, DATA_FILE_MAX, &sfxBytes, &sfxLen);
|
||||||
gRawFormat = jlAudioSfxRawFormat();
|
gRawFormat = jlAudioSfxRawFormat();
|
||||||
}
|
|
||||||
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
|
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
|
||||||
|
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
initialPaint(screen, audioOk);
|
initialPaint(screen);
|
||||||
|
|
||||||
musicPaused = false;
|
musicPaused = false;
|
||||||
noiseOn = false;
|
noiseOn = false;
|
||||||
|
|
@ -360,18 +356,18 @@ int main(void) {
|
||||||
jlWaitVBL();
|
jlWaitVBL();
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
jlAudioFrameTick();
|
jlAudioFrameTick();
|
||||||
if (keyPressed(KEY_ESCAPE, frame)) {
|
if (keyPressed(JL_KEY_ESCAPE, frame)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (keyPressed(KEY_SPACE, frame) && sfxBytes != NULL) {
|
if (keyPressed(JL_KEY_SPACE, frame) && sfxBytes != NULL) {
|
||||||
jlAudioPlaySfx(SFX_SLOT, sfxBytes, sfxLen, SFX_RATE_HZ);
|
jlAudioPlaySfx(SFX_SLOT, sfxBytes, sfxLen, SFX_RATE_HZ);
|
||||||
flashFrames = FLASH_FRAMES;
|
flashFrames = FLASH_FRAMES;
|
||||||
}
|
}
|
||||||
if (keyPressed(KEY_S, frame) && audioOk) {
|
if (keyPressed(JL_KEY_S, frame)) {
|
||||||
streamStart();
|
streamStart();
|
||||||
flashFrames = FLASH_FRAMES;
|
flashFrames = FLASH_FRAMES;
|
||||||
}
|
}
|
||||||
if (keyPressed(KEY_N, frame) && !noiseOn) {
|
if (keyPressed(JL_KEY_N, frame) && !noiseOn) {
|
||||||
// The song's own drums would fight the sweep for the noise
|
// The song's own drums would fight the sweep for the noise
|
||||||
// generator, so it pauses and picks up again afterwards.
|
// generator, so it pauses and picks up again afterwards.
|
||||||
noiseResumes = jlMusicIsPlaying() && !musicPaused;
|
noiseResumes = jlMusicIsPlaying() && !musicPaused;
|
||||||
|
|
@ -379,15 +375,15 @@ int main(void) {
|
||||||
noiseOn = true;
|
noiseOn = true;
|
||||||
noiseFrame = 0;
|
noiseFrame = 0;
|
||||||
}
|
}
|
||||||
if (keyPressed(KEY_M, frame) && modBytes != NULL) {
|
if (keyPressed(JL_KEY_M, frame) && modBytes != NULL) {
|
||||||
jlMusicStop();
|
jlMusicStop();
|
||||||
jlAudioPlayMod(modBytes, modLen, true);
|
jlAudioPlayMod(modBytes, modLen, true);
|
||||||
}
|
}
|
||||||
if (keyPressed(KEY_T, frame)) {
|
if (keyPressed(JL_KEY_T, frame)) {
|
||||||
jlAudioStopMod();
|
jlAudioStopMod();
|
||||||
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
|
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
|
||||||
}
|
}
|
||||||
if (keyPressed(KEY_P, frame)) {
|
if (keyPressed(JL_KEY_P, frame)) {
|
||||||
if (musicPaused) {
|
if (musicPaused) {
|
||||||
jlMusicResume();
|
jlMusicResume();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -416,10 +412,7 @@ int main(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
jlMusicStop();
|
jlMusicStop();
|
||||||
if (audioOk) {
|
|
||||||
jlAudioStopMod();
|
jlAudioStopMod();
|
||||||
}
|
|
||||||
jlAudioShutdown();
|
|
||||||
if (modBytes != NULL) {
|
if (modBytes != NULL) {
|
||||||
free(modBytes);
|
free(modBytes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
401
examples/audpar/audpar.c
Normal file
401
examples/audpar/audpar.c
Normal file
|
|
@ -0,0 +1,401 @@
|
||||||
|
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
// See LICENSE.md for license information.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Audio conformance probe (AUDPAR, a gate's program, not an example): one
|
||||||
|
// fixed timeline of what every port's audio must do alike, each step logged
|
||||||
|
// as it starts, so scripts/verify-audio-conformance.sh can record what a
|
||||||
|
// port played and tools/gradeAudioConformance.py can measure it against the
|
||||||
|
// API's contract: tone and voice pitch, 2 dB attenuation steps, every voice,
|
||||||
|
// the noise generator's rate and steps, sound effects at their rates and
|
||||||
|
// pans, a module that does not loop ending (and jlAudioIsPlayingMod saying
|
||||||
|
// so), and tick callbacks at their rates.
|
||||||
|
//
|
||||||
|
// The log, one line per event, "AUDPAR <ms> <event> <values>", ms counted
|
||||||
|
// from the first sync tone's start:
|
||||||
|
// sync <hz> a sync tone: the timeline starts and ends
|
||||||
|
// with one, so a recording can be aligned
|
||||||
|
// tone <hz> jlAudioTone
|
||||||
|
// voice <v> <hz> <atten> jlAudioVoice
|
||||||
|
// noise <pitch> <atten> jlAudioNoise
|
||||||
|
// sfx <slot> <rate> <hz> <pan> a one-shot sine sounding <hz> at <rate>
|
||||||
|
// mod start / mod end jlAudioPlayMod with loop false, and the
|
||||||
|
// first jlAudioIsPlayingMod false after it
|
||||||
|
// tick <hz> <ok> <calls> <ms> jlAudioTickRegister's result, the calls
|
||||||
|
// that came, and the time they had
|
||||||
|
// silence everything stopped
|
||||||
|
// done
|
||||||
|
// Escape ends it early.
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <joey/joey.h>
|
||||||
|
#include <joey/file.h>
|
||||||
|
|
||||||
|
#if defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
|
# define TEST_MOD_NAME "TEST.NTP"
|
||||||
|
#else
|
||||||
|
# define TEST_MOD_NAME "test.mod"
|
||||||
|
#endif
|
||||||
|
#define DATA_FILE_MAX (64UL * 1024UL)
|
||||||
|
|
||||||
|
#define SYNC_HZ 1000u
|
||||||
|
#define SYNC_MS 300u
|
||||||
|
#define GAP_MS 100u
|
||||||
|
#define PAUSE_MS 200u
|
||||||
|
#define ATTEN_STEPS 15u // 0..14 audible, 15 silent
|
||||||
|
#define ATTEN_SILENT 15u
|
||||||
|
#define TONE_MS 400u
|
||||||
|
#define STEP_MS 300u
|
||||||
|
#define NOISE_MS 400u
|
||||||
|
#define NOISE_STEP_MS 200u
|
||||||
|
#define NOISE_STEP_PITCH 8u
|
||||||
|
#define LEVEL_HZ 440u
|
||||||
|
#define VOICE_HZ 660u
|
||||||
|
#define TICK_MS 2000u
|
||||||
|
#define MOD_WAIT_MS 12000u
|
||||||
|
#define SFX_SLOT 0u
|
||||||
|
#define SFX_SAMPLES 4000u
|
||||||
|
#define SFX_PERIOD 8u // samples a cycle: the tone is rate / 8
|
||||||
|
#define SFX_AMPLITUDE 100
|
||||||
|
#define MS_PER_SECOND 1000u
|
||||||
|
#define EVENTS_MAX 128u
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
STEP_SYNC,
|
||||||
|
STEP_TONE,
|
||||||
|
STEP_VOICE,
|
||||||
|
STEP_NOISE,
|
||||||
|
STEP_SFX,
|
||||||
|
STEP_MOD,
|
||||||
|
STEP_TICK,
|
||||||
|
STEP_SILENCE
|
||||||
|
} StepKindE;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
StepKindE kind;
|
||||||
|
uint8_t channel; // voice, or the sfx pan
|
||||||
|
uint16_t value; // Hz, noise pitch, or sfx rate
|
||||||
|
uint8_t level; // attenuation
|
||||||
|
uint16_t ms; // how long it sounds (sfx: from its length)
|
||||||
|
} StepT;
|
||||||
|
|
||||||
|
// What the log will say of one step, kept in memory until the timeline is
|
||||||
|
// over: a log line can take a disk write (a fifth of a second on the ST),
|
||||||
|
// and written while it plays it would stall the program's sound.
|
||||||
|
typedef struct {
|
||||||
|
uint32_t ms;
|
||||||
|
StepKindE kind;
|
||||||
|
uint16_t value;
|
||||||
|
uint8_t channel;
|
||||||
|
uint8_t level;
|
||||||
|
uint32_t count; // mod: its length, then playing; tick: calls
|
||||||
|
uint32_t span; // tick: the time the calls had
|
||||||
|
bool ok; // tick: registered; mod end: still playing
|
||||||
|
bool modEnd;
|
||||||
|
} EventT;
|
||||||
|
|
||||||
|
static EventT *eventAdd(uint32_t ms, const StepT *step);
|
||||||
|
static void eventsLog(void);
|
||||||
|
static void loadMod(uint8_t **bytes, uint32_t *length);
|
||||||
|
static bool pump(void);
|
||||||
|
static void runStep(const StepT *step, const uint8_t *mod, uint32_t modLength);
|
||||||
|
static uint32_t since(void);
|
||||||
|
static void silenceAll(void);
|
||||||
|
static void tickCount(void);
|
||||||
|
static bool waitMs(uint16_t ms);
|
||||||
|
|
||||||
|
static const StepT kTimeline[] = {
|
||||||
|
{ STEP_SYNC, 0u, SYNC_HZ, 0u, SYNC_MS },
|
||||||
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// jlAudioTone across the octaves every port can play.
|
||||||
|
{ STEP_TONE, 0u, 110u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_TONE, 0u, 220u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_TONE, 0u, 440u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_TONE, 0u, 880u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_TONE, 0u, 1760u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_TONE, 0u, 3136u, 0u, TONE_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// Voice 0's attenuation, one step after another: 2 dB apart.
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 0u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 1u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 2u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 3u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 4u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 5u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 6u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 7u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 8u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 9u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 10u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 11u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 12u, STEP_MS }, { STEP_VOICE, 0u, LEVEL_HZ, 13u, STEP_MS },
|
||||||
|
{ STEP_VOICE, 0u, LEVEL_HZ, 14u, STEP_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// The other two voices.
|
||||||
|
{ STEP_VOICE, 1u, VOICE_HZ, 0u, STEP_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_VOICE, 2u, VOICE_HZ, 0u, STEP_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// The noise generator's rate, bright to dark.
|
||||||
|
{ STEP_NOISE, 0u, 1u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_NOISE, 0u, 2u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_NOISE, 0u, 4u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_NOISE, 0u, 8u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_NOISE, 0u, 16u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_NOISE, 0u, 31u, 0u, NOISE_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// The noise's attenuation steps.
|
||||||
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 0u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 2u, NOISE_STEP_MS },
|
||||||
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 4u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 6u, NOISE_STEP_MS },
|
||||||
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 8u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 10u, NOISE_STEP_MS },
|
||||||
|
{ STEP_NOISE, 0u, NOISE_STEP_PITCH, 12u, NOISE_STEP_MS }, { STEP_NOISE, 0u, NOISE_STEP_PITCH, 14u, NOISE_STEP_MS },
|
||||||
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// Sound effects at three rates, then panned each way.
|
||||||
|
{ STEP_SFX, JL_AUDIO_PAN_CENTER, 8000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_SFX, JL_AUDIO_PAN_CENTER, 16000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_SFX, JL_AUDIO_PAN_CENTER, 22050u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_SFX, JL_AUDIO_PAN_LEFT, 8000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, GAP_MS },
|
||||||
|
{ STEP_SFX, JL_AUDIO_PAN_RIGHT, 8000u, 0u, 0u }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// A module that does not loop, to its end.
|
||||||
|
{ STEP_MOD, 0u, 0u, 0u, MOD_WAIT_MS }, { STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
// Tick callbacks, slow and at a frame's rate.
|
||||||
|
{ STEP_TICK, 0u, 10u, 0u, TICK_MS },
|
||||||
|
{ STEP_TICK, 0u, 60u, 0u, TICK_MS },
|
||||||
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS },
|
||||||
|
{ STEP_SYNC, 0u, SYNC_HZ, 0u, SYNC_MS },
|
||||||
|
{ STEP_SILENCE, 0u, 0u, 0u, PAUSE_MS }
|
||||||
|
};
|
||||||
|
|
||||||
|
// One cycle of a sine, SFX_PERIOD samples, so a sound of it is rate / 8 Hz.
|
||||||
|
static const int8_t kSineCycle[SFX_PERIOD] = { 0, 71, SFX_AMPLITUDE, 71, 0, -71, -SFX_AMPLITUDE, -71 };
|
||||||
|
|
||||||
|
static EventT gEvents[EVENTS_MAX];
|
||||||
|
static uint16_t gEventCount = 0u;
|
||||||
|
static int8_t gSine[SFX_SAMPLES];
|
||||||
|
static uint32_t gStartMs = 0u;
|
||||||
|
static volatile uint32_t gTickCalls = 0u;
|
||||||
|
|
||||||
|
|
||||||
|
// A new event for step at ms; NULL once EVENTS_MAX are taken.
|
||||||
|
static EventT *eventAdd(uint32_t ms, const StepT *step) {
|
||||||
|
EventT *event;
|
||||||
|
|
||||||
|
if (gEventCount >= EVENTS_MAX) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
event = &gEvents[gEventCount++];
|
||||||
|
event->ms = ms;
|
||||||
|
event->kind = step->kind;
|
||||||
|
event->value = step->value;
|
||||||
|
event->channel = step->channel;
|
||||||
|
event->level = step->level;
|
||||||
|
event->count = 0u;
|
||||||
|
event->span = 0u;
|
||||||
|
event->ok = false;
|
||||||
|
event->modEnd = false;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The log, once the timeline is over (the format in the header).
|
||||||
|
static void eventsLog(void) {
|
||||||
|
const EventT *event;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
for (i = 0u; i < gEventCount; i++) {
|
||||||
|
event = &gEvents[i];
|
||||||
|
switch (event->kind) {
|
||||||
|
case STEP_SYNC:
|
||||||
|
jlLogF("AUDPAR %lu sync %u", (unsigned long)event->ms, (unsigned)event->value);
|
||||||
|
break;
|
||||||
|
case STEP_TONE:
|
||||||
|
jlLogF("AUDPAR %lu tone %u", (unsigned long)event->ms, (unsigned)event->value);
|
||||||
|
break;
|
||||||
|
case STEP_VOICE:
|
||||||
|
jlLogF("AUDPAR %lu voice %u %u %u", (unsigned long)event->ms, (unsigned)event->channel, (unsigned)event->value, (unsigned)event->level);
|
||||||
|
break;
|
||||||
|
case STEP_NOISE:
|
||||||
|
jlLogF("AUDPAR %lu noise %u %u", (unsigned long)event->ms, (unsigned)event->value, (unsigned)event->level);
|
||||||
|
break;
|
||||||
|
case STEP_SFX:
|
||||||
|
jlLogF("AUDPAR %lu sfx %u %u %u %u", (unsigned long)event->ms, (unsigned)SFX_SLOT, (unsigned)event->value, (unsigned)(event->value / SFX_PERIOD), (unsigned)event->channel);
|
||||||
|
break;
|
||||||
|
case STEP_MOD:
|
||||||
|
if (event->modEnd) {
|
||||||
|
jlLogF("AUDPAR %lu mod end %u", (unsigned long)event->ms, (unsigned)event->ok);
|
||||||
|
} else {
|
||||||
|
jlLogF("AUDPAR %lu mod start %lu", (unsigned long)event->ms, (unsigned long)event->count);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STEP_TICK:
|
||||||
|
jlLogF("AUDPAR %lu tick %u %u %lu %lu", (unsigned long)event->ms, (unsigned)event->value, (unsigned)event->ok, (unsigned long)event->count, (unsigned long)event->span);
|
||||||
|
break;
|
||||||
|
case STEP_SILENCE:
|
||||||
|
jlLogF("AUDPAR %lu silence", (unsigned long)event->ms);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DATA's module, whole; none (NULL, 0) when it is missing or too big.
|
||||||
|
static void loadMod(uint8_t **bytes, uint32_t *length) {
|
||||||
|
FILE *fp;
|
||||||
|
long size;
|
||||||
|
|
||||||
|
*bytes = NULL;
|
||||||
|
*length = 0u;
|
||||||
|
fp = jlDataOpen(TEST_MOD_NAME, "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size = -1L;
|
||||||
|
if (fseek(fp, 0L, SEEK_END) == 0) {
|
||||||
|
size = ftell(fp);
|
||||||
|
}
|
||||||
|
if (size > 0L && (uint32_t)size <= DATA_FILE_MAX && fseek(fp, 0L, SEEK_SET) == 0) {
|
||||||
|
*bytes = (uint8_t *)malloc((size_t)size);
|
||||||
|
if (*bytes != NULL && fread(*bytes, 1, (size_t)size, fp) == (size_t)size) {
|
||||||
|
*length = (uint32_t)size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// One frame: the engine's pump, the display's pace, and input. False when
|
||||||
|
// Escape asks to stop.
|
||||||
|
static bool pump(void) {
|
||||||
|
jlAudioFrameTick();
|
||||||
|
jlWaitVBL();
|
||||||
|
jlInputPoll();
|
||||||
|
return !jlKeyPressed(JL_KEY_ESCAPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Each step's time is taken as it starts.
|
||||||
|
static void runStep(const StepT *step, const uint8_t *mod, uint32_t modLength) {
|
||||||
|
uint32_t start;
|
||||||
|
EventT *event;
|
||||||
|
|
||||||
|
start = since();
|
||||||
|
event = eventAdd(start, step);
|
||||||
|
switch (step->kind) {
|
||||||
|
case STEP_SYNC:
|
||||||
|
case STEP_TONE:
|
||||||
|
jlAudioTone(step->value);
|
||||||
|
(void)waitMs(step->ms);
|
||||||
|
break;
|
||||||
|
case STEP_VOICE:
|
||||||
|
jlAudioVoice(step->channel, step->value, step->level);
|
||||||
|
(void)waitMs(step->ms);
|
||||||
|
break;
|
||||||
|
case STEP_NOISE:
|
||||||
|
jlAudioNoise((uint8_t)step->value, step->level);
|
||||||
|
(void)waitMs(step->ms);
|
||||||
|
break;
|
||||||
|
case STEP_SFX:
|
||||||
|
jlAudioSetPan(SFX_SLOT, (jlAudioPanE)step->channel);
|
||||||
|
jlAudioPlaySfx(SFX_SLOT, (const uint8_t *)gSine, SFX_SAMPLES, step->value);
|
||||||
|
(void)waitMs((uint16_t)((uint32_t)SFX_SAMPLES * MS_PER_SECOND / step->value));
|
||||||
|
break;
|
||||||
|
case STEP_MOD:
|
||||||
|
if (mod != NULL) {
|
||||||
|
jlAudioPlayMod(mod, modLength, false);
|
||||||
|
}
|
||||||
|
if (event != NULL) {
|
||||||
|
event->count = modLength;
|
||||||
|
}
|
||||||
|
while (jlAudioIsPlayingMod() && since() - start < step->ms) {
|
||||||
|
if (!pump()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event = eventAdd(since(), step);
|
||||||
|
if (event != NULL) {
|
||||||
|
event->modEnd = true;
|
||||||
|
event->ok = jlAudioIsPlayingMod();
|
||||||
|
}
|
||||||
|
jlAudioStopMod();
|
||||||
|
break;
|
||||||
|
case STEP_TICK:
|
||||||
|
gTickCalls = 0u;
|
||||||
|
if (event != NULL) {
|
||||||
|
event->ok = jlAudioTickRegister(tickCount, step->value);
|
||||||
|
}
|
||||||
|
(void)waitMs(step->ms);
|
||||||
|
(void)jlAudioTickRegister(NULL, 0u);
|
||||||
|
if (event != NULL) {
|
||||||
|
event->count = gTickCalls;
|
||||||
|
event->span = since() - start;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STEP_SILENCE:
|
||||||
|
silenceAll();
|
||||||
|
(void)waitMs(step->ms);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t since(void) {
|
||||||
|
return jlMillisElapsed() - gStartMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void silenceAll(void) {
|
||||||
|
uint8_t voice;
|
||||||
|
|
||||||
|
for (voice = 0u; voice < JL_AUDIO_VOICES; voice++) {
|
||||||
|
jlAudioVoice(voice, 0u, ATTEN_SILENT);
|
||||||
|
}
|
||||||
|
jlAudioNoise(0u, ATTEN_SILENT);
|
||||||
|
jlAudioStopSfx(SFX_SLOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void tickCount(void) {
|
||||||
|
gTickCalls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool waitMs(uint16_t ms) {
|
||||||
|
uint32_t start;
|
||||||
|
|
||||||
|
start = since();
|
||||||
|
while (since() - start < ms) {
|
||||||
|
if (!pump()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
jlConfigT config;
|
||||||
|
uint8_t *mod;
|
||||||
|
uint32_t modLength;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = false;
|
||||||
|
if (!jlInit(&config)) {
|
||||||
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
for (i = 0u; i < SFX_SAMPLES; i++) {
|
||||||
|
gSine[i] = kSineCycle[i % SFX_PERIOD];
|
||||||
|
}
|
||||||
|
loadMod(&mod, &modLength);
|
||||||
|
jlSurfaceClear(jlStageGet(), 0u);
|
||||||
|
jlStagePresent();
|
||||||
|
|
||||||
|
gStartMs = jlMillisElapsed();
|
||||||
|
for (i = 0u; i < (uint16_t)(sizeof(kTimeline) / sizeof(kTimeline[0])); i++) {
|
||||||
|
runStep(&kTimeline[i], mod, modLength);
|
||||||
|
if (jlKeyDown(JL_KEY_ESCAPE)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
silenceAll();
|
||||||
|
eventsLog();
|
||||||
|
jlLogF("AUDPAR %lu done", (unsigned long)since());
|
||||||
|
jlLogFlush();
|
||||||
|
free(mod);
|
||||||
|
jlShutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ static void waitForKey(void);
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
|
|
||||||
// 16 distinct $0RGB entries. Index 0 is forced to black anyway.
|
// 16 distinct $0RGB entries. Index 0 is forced to black anyway.
|
||||||
colors[0] = 0x000;
|
colors[0] = 0x000;
|
||||||
|
|
@ -67,7 +67,7 @@ static void buildPalette(jlSurfaceT *screen) {
|
||||||
colors[14] = 0x088;
|
colors[14] = 0x088;
|
||||||
colors[15] = 0x808;
|
colors[15] = 0x808;
|
||||||
jlPaletteSet(screen, 0, colors);
|
jlPaletteSet(screen, 0, colors);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -112,8 +112,7 @@ static void drawPrimitivesPixelLine(jlSurfaceT *screen) {
|
||||||
// Pixel row: 14 single-pixel writes at consecutive x to exercise
|
// Pixel row: 14 single-pixel writes at consecutive x to exercise
|
||||||
// both odd and even nibble paths.
|
// both odd and even nibble paths.
|
||||||
for (i = 0; i < 14; i++) {
|
for (i = 0; i < 14; i++) {
|
||||||
jlDrawPixel(screen, (int16_t)(10 + i * 10), (int16_t)(CELL_H - 6),
|
jlDrawPixel(screen, (int16_t)(10 + i * 10), (int16_t)(CELL_H - 6), (uint8_t)((i + 1) & 0x0F));
|
||||||
(uint8_t)((i + 1) & 0x0F));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,8 +201,7 @@ static void drawPrimitivesTileFlood(jlSurfaceT *screen) {
|
||||||
jlTilePaste(screen, (uint8_t)(bx + 4), (uint8_t)by, &snapBuf);
|
jlTilePaste(screen, (uint8_t)(bx + 4), (uint8_t)by, &snapBuf);
|
||||||
|
|
||||||
// jlTileCopy from the green quadrant onto a fresh location below.
|
// jlTileCopy from the green quadrant onto a fresh location below.
|
||||||
jlTileCopy(screen, (uint8_t)(bx + 4), (uint8_t)(by + 1),
|
jlTileCopy(screen, (uint8_t)(bx + 4), (uint8_t)(by + 1), screen, (uint8_t)(bx + 1), (uint8_t)by);
|
||||||
screen, (uint8_t)(bx + 1), (uint8_t)by);
|
|
||||||
|
|
||||||
// jlTileCopyMasked test: build a "transparent" striped pattern at
|
// jlTileCopyMasked test: build a "transparent" striped pattern at
|
||||||
// (208, 132). The tile's source has color 0 in alternating
|
// (208, 132). The tile's source has color 0 in alternating
|
||||||
|
|
@ -213,23 +211,19 @@ static void drawPrimitivesTileFlood(jlSurfaceT *screen) {
|
||||||
// Build a vertical-stripe source at (240, 132): col-pixel = (px % 2 ? color : 0)
|
// Build a vertical-stripe source at (240, 132): col-pixel = (px % 2 ? color : 0)
|
||||||
for (i = 0; i < 8; i++) {
|
for (i = 0; i < 8; i++) {
|
||||||
for (px = 0; px < 16; px++) {
|
for (px = 0; px < 16; px++) {
|
||||||
jlDrawPixel(screen, (int16_t)(ox + 112 + px), (int16_t)(oy + 32 + i),
|
jlDrawPixel(screen, (int16_t)(ox + 112 + px), (int16_t)(oy + 32 + i), (uint8_t)((px & 1) ? C_MAGENTA : 0));
|
||||||
(uint8_t)((px & 1) ? C_MAGENTA : 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// jlTileCopyMasked: source at block (ox+112)/8 = 34..35, by 16
|
// jlTileCopyMasked: source at block (ox+112)/8 = 34..35, by 16
|
||||||
// -> dst at backdrop block (ox+80)/8 = 30..31, by 16
|
// -> dst at backdrop block (ox+80)/8 = 30..31, by 16
|
||||||
jlTileCopyMasked(screen, (uint8_t)((ox + 80) / 8), (uint8_t)((oy + 32) / 8),
|
jlTileCopyMasked(screen, (uint8_t)((ox + 80) / 8), (uint8_t)((oy + 32) / 8), screen, (uint8_t)((ox + 112) / 8), (uint8_t)((oy + 32) / 8), 0);
|
||||||
screen, (uint8_t)((ox + 112) / 8), (uint8_t)((oy + 32) / 8),
|
|
||||||
0);
|
|
||||||
|
|
||||||
// Flood-fill region: a small bordered rectangle in the cell's
|
// Flood-fill region: a small bordered rectangle in the cell's
|
||||||
// lower portion. Outline drawn in C_BORDER; jlFloodFillBounded
|
// lower portion. Outline drawn in C_BORDER; jlFloodFillBounded
|
||||||
// from a point inside should fill with C_CYAN, stopping at the
|
// from a point inside should fill with C_CYAN, stopping at the
|
||||||
// border.
|
// border.
|
||||||
jlDrawRect(screen, ox + 16, oy + 60, 64, 32, C_BORDER);
|
jlDrawRect(screen, ox + 16, oy + 60, 64, 32, C_BORDER);
|
||||||
jlFloodFillBounded(screen, (int16_t)(ox + 48), (int16_t)(oy + 76),
|
jlFloodFillBounded(screen, (int16_t)(ox + 48), (int16_t)(oy + 76), C_CYAN, C_BORDER);
|
||||||
C_CYAN, C_BORDER);
|
|
||||||
|
|
||||||
// Plain jlFloodFill: solid block then re-fill to a new color.
|
// Plain jlFloodFill: solid block then re-fill to a new color.
|
||||||
jlFillRect(screen, ox + 96, oy + 60, 48, 32, C_GREEN);
|
jlFillRect(screen, ox + 96, oy + 60, 48, 32, C_GREEN);
|
||||||
|
|
@ -247,6 +241,7 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ int main(void) {
|
||||||
jlConfigT config;
|
jlConfigT config;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int16_t dotX;
|
int16_t dotX;
|
||||||
int16_t dotY;
|
int16_t dotY;
|
||||||
bool btn[JOY_BUTTON_COUNT];
|
bool btn[JL_JOY_BUTTON_COUNT];
|
||||||
bool connected;
|
bool connected;
|
||||||
bool valid;
|
bool valid;
|
||||||
} StickViewT;
|
} StickViewT;
|
||||||
|
|
@ -65,14 +65,14 @@ static void drawFrame(jlSurfaceT *screen, int16_t left);
|
||||||
static void initialPaint(jlSurfaceT *screen);
|
static void initialPaint(jlSurfaceT *screen);
|
||||||
static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left);
|
static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left);
|
||||||
|
|
||||||
static StickViewT gView[JOYSTICK_COUNT];
|
static StickViewT gView[JL_JOYSTICK_COUNT];
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = 0x0000;
|
colors[i] = 0x0000;
|
||||||
}
|
}
|
||||||
colors[COLOR_BACKGROUND] = 0x0000;
|
colors[COLOR_BACKGROUND] = 0x0000;
|
||||||
|
|
@ -104,7 +104,7 @@ static int16_t dotXFor(int16_t left, int8_t ax) {
|
||||||
int32_t off;
|
int32_t off;
|
||||||
|
|
||||||
cx = left + FRAME_SIZE / 2 - DOT_SIZE / 2;
|
cx = left + FRAME_SIZE / 2 - DOT_SIZE / 2;
|
||||||
off = ((int32_t)ax * DOT_TRAVEL) / JOYSTICK_AXIS_MAX;
|
off = ((int32_t)ax * DOT_TRAVEL) / JL_JOYSTICK_AXIS_MAX;
|
||||||
return (int16_t)(cx + off);
|
return (int16_t)(cx + off);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ static int16_t dotYFor(int8_t ay) {
|
||||||
int32_t off;
|
int32_t off;
|
||||||
|
|
||||||
cy = STICK_TOP + FRAME_SIZE / 2 - DOT_SIZE / 2;
|
cy = STICK_TOP + FRAME_SIZE / 2 - DOT_SIZE / 2;
|
||||||
off = ((int32_t)ay * DOT_TRAVEL) / JOYSTICK_AXIS_MAX;
|
off = ((int32_t)ay * DOT_TRAVEL) / JL_JOYSTICK_AXIS_MAX;
|
||||||
return (int16_t)(cy + off);
|
return (int16_t)(cy + off);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +142,7 @@ static void initialPaint(jlSurfaceT *screen) {
|
||||||
int16_t left;
|
int16_t left;
|
||||||
|
|
||||||
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
||||||
for (i = 0; i < JOYSTICK_COUNT; i++) {
|
for (i = 0; i < JL_JOYSTICK_COUNT; i++) {
|
||||||
left = (i == 0) ? STICK0_LEFT : STICK1_LEFT;
|
left = (i == 0) ? STICK0_LEFT : STICK1_LEFT;
|
||||||
drawFrame(screen, left);
|
drawFrame(screen, left);
|
||||||
gView[i].valid = false;
|
gView[i].valid = false;
|
||||||
|
|
@ -160,7 +160,7 @@ static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left) {
|
||||||
int8_t ay;
|
int8_t ay;
|
||||||
int16_t newDotX;
|
int16_t newDotX;
|
||||||
int16_t newDotY;
|
int16_t newDotY;
|
||||||
bool newBtn[JOY_BUTTON_COUNT];
|
bool newBtn[JL_JOY_BUTTON_COUNT];
|
||||||
bool connected;
|
bool connected;
|
||||||
int16_t i;
|
int16_t i;
|
||||||
int16_t bx;
|
int16_t bx;
|
||||||
|
|
@ -172,7 +172,7 @@ static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left) {
|
||||||
ay = jlJoystickY(js);
|
ay = jlJoystickY(js);
|
||||||
newDotX = dotXFor(left, ax);
|
newDotX = dotXFor(left, ax);
|
||||||
newDotY = dotYFor(ay);
|
newDotY = dotYFor(ay);
|
||||||
for (i = 0; i < JOY_BUTTON_COUNT; i++) {
|
for (i = 0; i < JL_JOY_BUTTON_COUNT; i++) {
|
||||||
newBtn[i] = jlJoyDown(js, (jlJoyButtonE)i);
|
newBtn[i] = jlJoyDown(js, (jlJoyButtonE)i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@ static void updateStick(jlSurfaceT *screen, jlJoystickE js, int16_t left) {
|
||||||
|
|
||||||
// Button squares.
|
// Button squares.
|
||||||
by = BUTTON_TOP;
|
by = BUTTON_TOP;
|
||||||
for (i = 0; i < JOY_BUTTON_COUNT; i++) {
|
for (i = 0; i < JL_JOY_BUTTON_COUNT; i++) {
|
||||||
if (v->valid && v->btn[i] == newBtn[i]) {
|
if (v->valid && v->btn[i] == newBtn[i]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -213,6 +213,7 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = CODEGEN_BYTES;
|
config.codegenBytes = CODEGEN_BYTES;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -227,17 +228,17 @@ int main(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
initialPaint(screen);
|
initialPaint(screen);
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
updateStick(screen, JOYSTICK_0, STICK0_LEFT);
|
updateStick(screen, JL_JOYSTICK_0, STICK0_LEFT);
|
||||||
updateStick(screen, JOYSTICK_1, STICK1_LEFT);
|
updateStick(screen, JL_JOYSTICK_1, STICK1_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
jlShutdown();
|
jlShutdown();
|
||||||
|
|
|
||||||
|
|
@ -111,16 +111,16 @@ static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint16_t valu
|
||||||
static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow);
|
static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow);
|
||||||
static void updateMouseVerifyRow(jlSurfaceT *screen);
|
static void updateMouseVerifyRow(jlSurfaceT *screen);
|
||||||
|
|
||||||
// Keys laid out row-by-row. KEY_NONE cells stay blank. Shape roughly
|
// Keys laid out row-by-row. JL_KEY_NONE cells stay blank. Shape roughly
|
||||||
// resembles a real keyboard (top number row, then QWERTY rows, then a
|
// resembles a real keyboard (top number row, then QWERTY rows, then a
|
||||||
// cluster of modifiers / arrows / function keys).
|
// cluster of modifiers / arrows / function keys).
|
||||||
static const jlKeyE gKeyGrid[GRID_ROWS][GRID_COLS] = {
|
static const jlKeyE gKeyGrid[GRID_ROWS][GRID_COLS] = {
|
||||||
{ KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0 },
|
{ JL_KEY_1, JL_KEY_2, JL_KEY_3, JL_KEY_4, JL_KEY_5, JL_KEY_6, JL_KEY_7, JL_KEY_8, JL_KEY_9, JL_KEY_0 },
|
||||||
{ KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P },
|
{ JL_KEY_Q, JL_KEY_W, JL_KEY_E, JL_KEY_R, JL_KEY_T, JL_KEY_Y, JL_KEY_U, JL_KEY_I, JL_KEY_O, JL_KEY_P },
|
||||||
{ KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_BACKSPACE },
|
{ JL_KEY_A, JL_KEY_S, JL_KEY_D, JL_KEY_F, JL_KEY_G, JL_KEY_H, JL_KEY_J, JL_KEY_K, JL_KEY_L, JL_KEY_BACKSPACE },
|
||||||
{ KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_LSHIFT, KEY_RSHIFT, KEY_TAB },
|
{ JL_KEY_Z, JL_KEY_X, JL_KEY_C, JL_KEY_V, JL_KEY_B, JL_KEY_N, JL_KEY_M, JL_KEY_LSHIFT, JL_KEY_RSHIFT, JL_KEY_TAB },
|
||||||
{ KEY_SPACE, KEY_ESCAPE, KEY_RETURN, KEY_LCTRL, KEY_LALT, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_NONE },
|
{ JL_KEY_SPACE, JL_KEY_ESCAPE, JL_KEY_RETURN, JL_KEY_LCTRL, JL_KEY_LALT, JL_KEY_UP, JL_KEY_DOWN, JL_KEY_LEFT, JL_KEY_RIGHT, JL_KEY_NONE },
|
||||||
{ KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10 }
|
{ JL_KEY_F1, JL_KEY_F2, JL_KEY_F3, JL_KEY_F4, JL_KEY_F5, JL_KEY_F6, JL_KEY_F7, JL_KEY_F8, JL_KEY_F9, JL_KEY_F10 }
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool gCellLit[GRID_ROWS][GRID_COLS];
|
static bool gCellLit[GRID_ROWS][GRID_COLS];
|
||||||
|
|
@ -224,7 +224,7 @@ static const uint8_t kFontGlyph[][FONT_H] = {
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
// Every entry gets a DISTINCT color (gray ramp for the unused
|
// Every entry gets a DISTINCT color (gray ramp for the unused
|
||||||
|
|
@ -232,7 +232,7 @@ static void buildPalette(jlSurfaceT *screen) {
|
||||||
// screenshot-based harness can decode the verification row's
|
// screenshot-based harness can decode the verification row's
|
||||||
// nibble pixels back to values on ports without direct
|
// nibble pixels back to values on ports without direct
|
||||||
// framebuffer reads.
|
// framebuffer reads.
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = (uint16_t)((i << 8) | (i << 4) | i);
|
colors[i] = (uint16_t)((i << 8) | (i << 4) | i);
|
||||||
}
|
}
|
||||||
colors[COLOR_BACKGROUND] = 0x0000; // black
|
colors[COLOR_BACKGROUND] = 0x0000; // black
|
||||||
|
|
@ -271,13 +271,13 @@ static bool cellTargetLit(int16_t col, int16_t row, int16_t cursorCol, int16_t c
|
||||||
jlKeyE key;
|
jlKeyE key;
|
||||||
|
|
||||||
key = gKeyGrid[row][col];
|
key = gKeyGrid[row][col];
|
||||||
if (key == KEY_NONE) {
|
if (key == JL_KEY_NONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (jlKeyDown(key)) {
|
if (jlKeyDown(key)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (col == cursorCol && row == cursorRow && jlMouseDown(MOUSE_BUTTON_LEFT)) {
|
if (col == cursorCol && row == cursorRow && jlMouseDown(JL_MOUSE_BUTTON_LEFT)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -346,7 +346,7 @@ static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx = (int16_t)(cx + FONT_W + 1);
|
cx = (int16_t)(cx + FONT_W + 1);
|
||||||
if (cx > SURFACE_WIDTH - FONT_W) {
|
if (cx > JL_SURFACE_WIDTH - FONT_W) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,23 +372,57 @@ static void drawVerifyRow(jlSurfaceT *screen) {
|
||||||
|
|
||||||
|
|
||||||
static int glyphIdx(char c) {
|
static int glyphIdx(char c) {
|
||||||
if (c == ' ') { return GLYPH_SPACE; }
|
if (c == ' ') {
|
||||||
if (c >= '0' && c <= '9') { return GLYPH_DIGIT_BASE + (c - '0'); }
|
return GLYPH_SPACE;
|
||||||
if (c >= 'A' && c <= 'Z') { return GLYPH_ALPHA_BASE + (c - 'A'); }
|
}
|
||||||
if (c >= 'a' && c <= 'z') { return GLYPH_ALPHA_BASE + (c - 'a'); } // uppercase fallback
|
if (c >= '0' && c <= '9') {
|
||||||
if (c == '.') { return GLYPH_PERIOD; }
|
return GLYPH_DIGIT_BASE + (c - '0');
|
||||||
if (c == ',') { return GLYPH_COMMA; }
|
}
|
||||||
if (c == ':') { return GLYPH_COLON; }
|
if (c >= 'A' && c <= 'Z') {
|
||||||
if (c == '!') { return GLYPH_BANG; }
|
return GLYPH_ALPHA_BASE + (c - 'A');
|
||||||
if (c == '?') { return GLYPH_QUESTION; }
|
}
|
||||||
if (c == '-') { return GLYPH_MINUS; }
|
if (c >= 'a' && c <= 'z') {
|
||||||
if (c == '\'') { return GLYPH_APOSTROPHE; }
|
return GLYPH_ALPHA_BASE + (c - 'a'); // uppercase fallback
|
||||||
if (c == '"') { return GLYPH_QUOTE; }
|
}
|
||||||
if (c == '/') { return GLYPH_SLASH; }
|
if (c == '.') {
|
||||||
if (c == ';') { return GLYPH_SEMICOLON; }
|
return GLYPH_PERIOD;
|
||||||
if (c == '=') { return GLYPH_EQUALS; }
|
}
|
||||||
if (c == '+') { return GLYPH_PLUS; }
|
if (c == ',') {
|
||||||
if (c == '_') { return GLYPH_UNDERSCORE; }
|
return GLYPH_COMMA;
|
||||||
|
}
|
||||||
|
if (c == ':') {
|
||||||
|
return GLYPH_COLON;
|
||||||
|
}
|
||||||
|
if (c == '!') {
|
||||||
|
return GLYPH_BANG;
|
||||||
|
}
|
||||||
|
if (c == '?') {
|
||||||
|
return GLYPH_QUESTION;
|
||||||
|
}
|
||||||
|
if (c == '-') {
|
||||||
|
return GLYPH_MINUS;
|
||||||
|
}
|
||||||
|
if (c == '\'') {
|
||||||
|
return GLYPH_APOSTROPHE;
|
||||||
|
}
|
||||||
|
if (c == '"') {
|
||||||
|
return GLYPH_QUOTE;
|
||||||
|
}
|
||||||
|
if (c == '/') {
|
||||||
|
return GLYPH_SLASH;
|
||||||
|
}
|
||||||
|
if (c == ';') {
|
||||||
|
return GLYPH_SEMICOLON;
|
||||||
|
}
|
||||||
|
if (c == '=') {
|
||||||
|
return GLYPH_EQUALS;
|
||||||
|
}
|
||||||
|
if (c == '+') {
|
||||||
|
return GLYPH_PLUS;
|
||||||
|
}
|
||||||
|
if (c == '_') {
|
||||||
|
return GLYPH_UNDERSCORE;
|
||||||
|
}
|
||||||
return GLYPH_UNKNOWN;
|
return GLYPH_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -402,7 +436,7 @@ static void initialPaint(jlSurfaceT *screen) {
|
||||||
for (row = 0; row < GRID_ROWS; row++) {
|
for (row = 0; row < GRID_ROWS; row++) {
|
||||||
for (col = 0; col < GRID_COLS; col++) {
|
for (col = 0; col < GRID_COLS; col++) {
|
||||||
key = gKeyGrid[row][col];
|
key = gKeyGrid[row][col];
|
||||||
if (key == KEY_NONE) {
|
if (key == JL_KEY_NONE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
drawCell(screen, col, row, false);
|
drawCell(screen, col, row, false);
|
||||||
|
|
@ -439,10 +473,7 @@ static void logTypedHistory(void) {
|
||||||
// drawMouseVerifyRow stamps them. Sampled once per use so the drawn
|
// drawMouseVerifyRow stamps them. Sampled once per use so the drawn
|
||||||
// row and updateMouseVerifyRow's change check can never disagree.
|
// row and updateMouseVerifyRow's change check can never disagree.
|
||||||
static uint16_t mouseFlags(void) {
|
static uint16_t mouseFlags(void) {
|
||||||
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0) |
|
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0) | (jlMouseDown(JL_MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0));
|
||||||
(jlMouseDown(MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0) |
|
|
||||||
(jlMouseDown(MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0) |
|
|
||||||
(jlMouseDown(MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -455,7 +486,7 @@ static void presentChangedCells(jlSurfaceT *screen, int16_t cursorCol, int16_t c
|
||||||
for (row = 0; row < GRID_ROWS; row++) {
|
for (row = 0; row < GRID_ROWS; row++) {
|
||||||
for (col = 0; col < GRID_COLS; col++) {
|
for (col = 0; col < GRID_COLS; col++) {
|
||||||
key = gKeyGrid[row][col];
|
key = gKeyGrid[row][col];
|
||||||
if (key == KEY_NONE) {
|
if (key == JL_KEY_NONE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
lit = cellTargetLit(col, row, cursorCol, cursorRow);
|
lit = cellTargetLit(col, row, cursorCol, cursorRow);
|
||||||
|
|
@ -513,7 +544,7 @@ static void processTypedChars(jlSurfaceT *screen) {
|
||||||
// this region).
|
// this region).
|
||||||
static void redrawTextStrip(jlSurfaceT *screen) {
|
static void redrawTextStrip(jlSurfaceT *screen) {
|
||||||
gLine[gLineLen] = '\0';
|
gLine[gLineLen] = '\0';
|
||||||
jlFillRect(screen, 0, TEXT_STRIP_Y, SURFACE_WIDTH, SURFACE_HEIGHT - TEXT_STRIP_Y, COLOR_BACKGROUND);
|
jlFillRect(screen, 0, TEXT_STRIP_Y, JL_SURFACE_WIDTH, JL_SURFACE_HEIGHT - TEXT_STRIP_Y, COLOR_BACKGROUND);
|
||||||
drawTextLine(screen, TEXT_X, TEXT_Y, "TYPE HERE:", COLOR_UNLIT);
|
drawTextLine(screen, TEXT_X, TEXT_Y, "TYPE HERE:", COLOR_UNLIT);
|
||||||
drawTextLine(screen, TEXT_INPUT_X, TEXT_Y, gLine, COLOR_CURSOR);
|
drawTextLine(screen, TEXT_INPUT_X, TEXT_Y, gLine, COLOR_CURSOR);
|
||||||
drawVerifyRow(screen);
|
drawVerifyRow(screen);
|
||||||
|
|
@ -599,6 +630,7 @@ int main(void) {
|
||||||
int16_t cursorRow;
|
int16_t cursorRow;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -613,13 +645,13 @@ int main(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
initialPaint(screen);
|
initialPaint(screen);
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cellAtPoint(jlMouseX(), jlMouseY(), &cursorCol, &cursorRow);
|
cellAtPoint(jlMouseX(), jlMouseY(), &cursorCol, &cursorRow);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ static void timeChanges(const char *name, bool scb);
|
||||||
// Paint the bands, make the two changes, and hold the screen.
|
// Paint the bands, make the two changes, and hold the screen.
|
||||||
static void check(void) {
|
static void check(void) {
|
||||||
static const uint16_t firsts[BANDS] = { 0xF00, 0x0F0, 0x00F, 0xFFF };
|
static const uint16_t firsts[BANDS] = { 0xF00, 0x0F0, 0x00F, 0xFFF };
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
jlSurfaceT *stage;
|
jlSurfaceT *stage;
|
||||||
uint16_t band;
|
uint16_t band;
|
||||||
uint16_t frame;
|
uint16_t frame;
|
||||||
|
|
@ -52,7 +52,7 @@ static void check(void) {
|
||||||
colors[PAINT_COLOR] = firsts[band];
|
colors[PAINT_COLOR] = firsts[band];
|
||||||
jlPaletteSet(stage, (uint8_t)band, colors);
|
jlPaletteSet(stage, (uint8_t)band, colors);
|
||||||
jlScbSetRange(stage, (uint16_t)(band * BAND_LINES), (uint16_t)(band * BAND_LINES + BAND_LINES - 1u), (uint8_t)band);
|
jlScbSetRange(stage, (uint16_t)(band * BAND_LINES), (uint16_t)(band * BAND_LINES + BAND_LINES - 1u), (uint8_t)band);
|
||||||
jlFillRect(stage, 0, (int16_t)(band * BAND_LINES), SURFACE_WIDTH, BAND_LINES, PAINT_COLOR);
|
jlFillRect(stage, 0, (int16_t)(band * BAND_LINES), JL_SURFACE_WIDTH, BAND_LINES, PAINT_COLOR);
|
||||||
}
|
}
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
jlWaitVBL();
|
jlWaitVBL();
|
||||||
|
|
@ -71,7 +71,7 @@ static void check(void) {
|
||||||
|
|
||||||
// PROBE_FRAMES frames of palette changes (or SCB changes), each presented.
|
// PROBE_FRAMES frames of palette changes (or SCB changes), each presented.
|
||||||
static void timeChanges(const char *name, bool scb) {
|
static void timeChanges(const char *name, bool scb) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
jlSurfaceT *stage;
|
jlSurfaceT *stage;
|
||||||
uint16_t start;
|
uint16_t start;
|
||||||
uint32_t ms0;
|
uint32_t ms0;
|
||||||
|
|
@ -105,13 +105,14 @@ int main(void) {
|
||||||
uint16_t band;
|
uint16_t band;
|
||||||
|
|
||||||
cfg.codegenBytes = 0;
|
cfg.codegenBytes = 0;
|
||||||
|
cfg.noAudio = true;
|
||||||
if (!jlInit(&cfg)) {
|
if (!jlInit(&cfg)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
stage = jlStageGet();
|
stage = jlStageGet();
|
||||||
for (band = 0; band < BANDS; band++) {
|
for (band = 0; band < BANDS; band++) {
|
||||||
jlScbSetRange(stage, (uint16_t)(band * BAND_LINES), (uint16_t)(band * BAND_LINES + BAND_LINES - 1u), (uint8_t)band);
|
jlScbSetRange(stage, (uint16_t)(band * BAND_LINES), (uint16_t)(band * BAND_LINES + BAND_LINES - 1u), (uint8_t)band);
|
||||||
jlFillRect(stage, 0, (int16_t)(band * BAND_LINES), SURFACE_WIDTH, BAND_LINES, (uint8_t)(band + 1u));
|
jlFillRect(stage, 0, (int16_t)(band * BAND_LINES), JL_SURFACE_WIDTH, BAND_LINES, (uint8_t)(band + 1u));
|
||||||
}
|
}
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
timeChanges("palette+present", false);
|
timeChanges("palette+present", false);
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
#include <joey/joey.h>
|
#include <joey/joey.h>
|
||||||
|
|
||||||
#define BAND_COUNT 8
|
#define BAND_COUNT 8
|
||||||
#define BAND_HEIGHT (SURFACE_HEIGHT / BAND_COUNT)
|
#define BAND_HEIGHT (JL_SURFACE_HEIGHT / BAND_COUNT)
|
||||||
#define STRIPE_COUNT 16
|
#define STRIPE_COUNT 16
|
||||||
#define STRIPE_WIDTH (SURFACE_WIDTH / STRIPE_COUNT)
|
#define STRIPE_WIDTH (JL_SURFACE_WIDTH / STRIPE_COUNT)
|
||||||
|
|
||||||
static void buildPalettes(jlSurfaceT *screen);
|
static void buildPalettes(jlSurfaceT *screen);
|
||||||
static void buildScbs(jlSurfaceT *screen);
|
static void buildScbs(jlSurfaceT *screen);
|
||||||
|
|
@ -27,7 +27,7 @@ static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn);
|
||||||
|
|
||||||
|
|
||||||
static void buildPalettes(jlSurfaceT *screen) {
|
static void buildPalettes(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
|
|
||||||
// Palette 0: grayscale
|
// Palette 0: grayscale
|
||||||
makeGradient(colors, 1, 1, 1);
|
makeGradient(colors, 1, 1, 1);
|
||||||
|
|
@ -83,7 +83,7 @@ static void drawStripes(jlSurfaceT *screen) {
|
||||||
jlSurfaceClear(screen, 0);
|
jlSurfaceClear(screen, 0);
|
||||||
for (colorIndex = 0; colorIndex < STRIPE_COUNT; colorIndex++) {
|
for (colorIndex = 0; colorIndex < STRIPE_COUNT; colorIndex++) {
|
||||||
x = (int16_t)(colorIndex * STRIPE_WIDTH);
|
x = (int16_t)(colorIndex * STRIPE_WIDTH);
|
||||||
jlFillRect(screen, x, 0, STRIPE_WIDTH, SURFACE_HEIGHT, colorIndex);
|
jlFillRect(screen, x, 0, STRIPE_WIDTH, JL_SURFACE_HEIGHT, colorIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn) {
|
||||||
uint8_t g;
|
uint8_t g;
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
|
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
r = (uint8_t)(redOn ? i : 0);
|
r = (uint8_t)(redOn ? i : 0);
|
||||||
g = (uint8_t)(greenOn ? i : 0);
|
g = (uint8_t)(greenOn ? i : 0);
|
||||||
b = (uint8_t)(blueOn ? i : 0);
|
b = (uint8_t)(blueOn ? i : 0);
|
||||||
|
|
@ -110,6 +110,7 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,10 @@ static bool storeCount(uint16_t count);
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = 0x0000;
|
colors[i] = 0x0000;
|
||||||
}
|
}
|
||||||
colors[COLOR_BACKGROUND] = 0x0000; // black
|
colors[COLOR_BACKGROUND] = 0x0000; // black
|
||||||
|
|
@ -125,6 +125,7 @@ int main(void) {
|
||||||
bool saved;
|
bool saved;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -143,7 +144,7 @@ int main(void) {
|
||||||
saved = storeCount(count);
|
saved = storeCount(count);
|
||||||
|
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
||||||
drawBits(screen, count);
|
drawBits(screen, count);
|
||||||
drawLamp(screen, saved);
|
drawLamp(screen, saved);
|
||||||
|
|
@ -153,10 +154,10 @@ int main(void) {
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (jlKeyPressed(KEY_SPACE)) {
|
if (jlKeyPressed(JL_KEY_SPACE)) {
|
||||||
// Erase the save and reset the counter to zero.
|
// Erase the save and reset the counter to zero.
|
||||||
jlSaveDelete(SAVE_NAME);
|
jlSaveDelete(SAVE_NAME);
|
||||||
count = 0u;
|
count = 0u;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
// ----- Constants -----
|
// ----- Constants -----
|
||||||
|
|
||||||
#define STRIPE_WIDTH 16u // two bytes of a plane: whole-byte fills
|
#define STRIPE_WIDTH 16u // two bytes of a plane: whole-byte fills
|
||||||
#define STRIPE_COUNT (SURFACE_WIDTH / STRIPE_WIDTH)
|
#define STRIPE_COUNT (JL_SURFACE_WIDTH / STRIPE_WIDTH)
|
||||||
#define COLOR_CHANNEL_MASK 0x0Fu
|
#define COLOR_CHANNEL_MASK 0x0Fu
|
||||||
#define RED_SHIFT 8u
|
#define RED_SHIFT 8u
|
||||||
#define GREEN_SHIFT 4u
|
#define GREEN_SHIFT 4u
|
||||||
|
|
@ -75,13 +75,13 @@ static bool escapeTyped(void);
|
||||||
// ----- Helpers (alphabetical) -----
|
// ----- Helpers (alphabetical) -----
|
||||||
|
|
||||||
static void buildPalettes(jlSurfaceT *screen) {
|
static void buildPalettes(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t pal;
|
uint16_t pal;
|
||||||
uint16_t col;
|
uint16_t col;
|
||||||
uint16_t blue;
|
uint16_t blue;
|
||||||
|
|
||||||
for (pal = 0u; pal < SURFACE_PALETTE_COUNT; pal++) {
|
for (pal = 0u; pal < JL_SURFACE_PALETTE_COUNT; pal++) {
|
||||||
for (col = 0u; col < SURFACE_COLORS_PER_PALETTE; col++) {
|
for (col = 0u; col < JL_SURFACE_COLORS_PER_PALETTE; col++) {
|
||||||
blue = (uint16_t)((pal * BLUE_PALETTE_STEP + col) & COLOR_CHANNEL_MASK);
|
blue = (uint16_t)((pal * BLUE_PALETTE_STEP + col) & COLOR_CHANNEL_MASK);
|
||||||
colors[col] = (uint16_t)((pal << RED_SHIFT) | (col << GREEN_SHIFT) | blue);
|
colors[col] = (uint16_t)((pal << RED_SHIFT) | (col << GREEN_SHIFT) | blue);
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +97,7 @@ static void buildScb(jlSurfaceT *screen) {
|
||||||
uint8_t runPalette;
|
uint8_t runPalette;
|
||||||
|
|
||||||
for (line = 0u; line < EVERY_LINE_END; line++) {
|
for (line = 0u; line < EVERY_LINE_END; line++) {
|
||||||
jlScbSet(screen, line, (uint8_t)(line % SURFACE_PALETTE_COUNT));
|
jlScbSet(screen, line, (uint8_t)(line % JL_SURFACE_PALETTE_COUNT));
|
||||||
}
|
}
|
||||||
run = 0u;
|
run = 0u;
|
||||||
runLeft = 0u;
|
runLeft = 0u;
|
||||||
|
|
@ -105,13 +105,13 @@ static void buildScb(jlSurfaceT *screen) {
|
||||||
for (line = EVERY_LINE_END; line < IRREGULAR_END; line++) {
|
for (line = EVERY_LINE_END; line < IRREGULAR_END; line++) {
|
||||||
if (runLeft == 0u) {
|
if (runLeft == 0u) {
|
||||||
runLeft = (uint16_t)(1u + (run * RUN_LENGTH_STEP) % RUN_LENGTH_SPAN);
|
runLeft = (uint16_t)(1u + (run * RUN_LENGTH_STEP) % RUN_LENGTH_SPAN);
|
||||||
runPalette = (uint8_t)((run * RUN_PALETTE_STEP + RUN_PALETTE_BASE) % SURFACE_PALETTE_COUNT);
|
runPalette = (uint8_t)((run * RUN_PALETTE_STEP + RUN_PALETTE_BASE) % JL_SURFACE_PALETTE_COUNT);
|
||||||
run++;
|
run++;
|
||||||
}
|
}
|
||||||
jlScbSet(screen, line, runPalette);
|
jlScbSet(screen, line, runPalette);
|
||||||
runLeft--;
|
runLeft--;
|
||||||
}
|
}
|
||||||
for (line = IRREGULAR_END; line < SURFACE_HEIGHT; line++) {
|
for (line = IRREGULAR_END; line < JL_SURFACE_HEIGHT; line++) {
|
||||||
jlScbSet(screen, line, (uint8_t)(((line & 1u) != 0u) ? ODD_LINE_PALETTE : EVEN_LINE_PALETTE));
|
jlScbSet(screen, line, (uint8_t)(((line & 1u) != 0u) ? ODD_LINE_PALETTE : EVEN_LINE_PALETTE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +121,7 @@ static void drawStripes(jlSurfaceT *screen) {
|
||||||
uint16_t stripe;
|
uint16_t stripe;
|
||||||
|
|
||||||
for (stripe = 0u; stripe < STRIPE_COUNT; stripe++) {
|
for (stripe = 0u; stripe < STRIPE_COUNT; stripe++) {
|
||||||
jlFillRect(screen, (int16_t)(stripe * STRIPE_WIDTH), 0, STRIPE_WIDTH, SURFACE_HEIGHT, (uint8_t)(stripe % SURFACE_COLORS_PER_PALETTE));
|
jlFillRect(screen, (int16_t)(stripe * STRIPE_WIDTH), 0, STRIPE_WIDTH, JL_SURFACE_HEIGHT, (uint8_t)(stripe % JL_SURFACE_COLORS_PER_PALETTE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +146,7 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
|
|
||||||
config.codegenBytes = CODEGEN_DEFAULT;
|
config.codegenBytes = CODEGEN_DEFAULT;
|
||||||
|
config.noAudio = true;
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
|
||||||
|
|
@ -79,17 +79,17 @@ typedef struct KeySendT {
|
||||||
} KeySendT;
|
} KeySendT;
|
||||||
|
|
||||||
static const KeySendT gKeySend[] = {
|
static const KeySendT gKeySend[] = {
|
||||||
{ KEY_A, 'A' }, { KEY_B, 'B' }, { KEY_C, 'C' }, { KEY_D, 'D' },
|
{ JL_KEY_A, 'A' }, { JL_KEY_B, 'B' }, { JL_KEY_C, 'C' }, { JL_KEY_D, 'D' },
|
||||||
{ KEY_E, 'E' }, { KEY_F, 'F' }, { KEY_G, 'G' }, { KEY_H, 'H' },
|
{ JL_KEY_E, 'E' }, { JL_KEY_F, 'F' }, { JL_KEY_G, 'G' }, { JL_KEY_H, 'H' },
|
||||||
{ KEY_I, 'I' }, { KEY_J, 'J' }, { KEY_K, 'K' }, { KEY_L, 'L' },
|
{ JL_KEY_I, 'I' }, { JL_KEY_J, 'J' }, { JL_KEY_K, 'K' }, { JL_KEY_L, 'L' },
|
||||||
{ KEY_M, 'M' }, { KEY_N, 'N' }, { KEY_O, 'O' }, { KEY_P, 'P' },
|
{ JL_KEY_M, 'M' }, { JL_KEY_N, 'N' }, { JL_KEY_O, 'O' }, { JL_KEY_P, 'P' },
|
||||||
{ KEY_Q, 'Q' }, { KEY_R, 'R' }, { KEY_S, 'S' }, { KEY_T, 'T' },
|
{ JL_KEY_Q, 'Q' }, { JL_KEY_R, 'R' }, { JL_KEY_S, 'S' }, { JL_KEY_T, 'T' },
|
||||||
{ KEY_U, 'U' }, { KEY_V, 'V' }, { KEY_W, 'W' }, { KEY_X, 'X' },
|
{ JL_KEY_U, 'U' }, { JL_KEY_V, 'V' }, { JL_KEY_W, 'W' }, { JL_KEY_X, 'X' },
|
||||||
{ KEY_Y, 'Y' }, { KEY_Z, 'Z' },
|
{ JL_KEY_Y, 'Y' }, { JL_KEY_Z, 'Z' },
|
||||||
{ KEY_0, '0' }, { KEY_1, '1' }, { KEY_2, '2' }, { KEY_3, '3' },
|
{ JL_KEY_0, '0' }, { JL_KEY_1, '1' }, { JL_KEY_2, '2' }, { JL_KEY_3, '3' },
|
||||||
{ KEY_4, '4' }, { KEY_5, '5' }, { KEY_6, '6' }, { KEY_7, '7' },
|
{ JL_KEY_4, '4' }, { JL_KEY_5, '5' }, { JL_KEY_6, '6' }, { JL_KEY_7, '7' },
|
||||||
{ KEY_8, '8' }, { KEY_9, '9' },
|
{ JL_KEY_8, '8' }, { JL_KEY_9, '9' },
|
||||||
{ KEY_RETURN, '\r' }
|
{ JL_KEY_RETURN, '\r' }
|
||||||
};
|
};
|
||||||
|
|
||||||
#define KEY_SEND_COUNT ((int16_t)(sizeof(gKeySend) / sizeof(gKeySend[0])))
|
#define KEY_SEND_COUNT ((int16_t)(sizeof(gKeySend) / sizeof(gKeySend[0])))
|
||||||
|
|
@ -107,10 +107,10 @@ static void sendBytes(const char *bytes, uint16_t len);
|
||||||
|
|
||||||
|
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = 0x0000;
|
colors[i] = 0x0000;
|
||||||
}
|
}
|
||||||
colors[COLOR_BACKGROUND] = 0x0000; // black
|
colors[COLOR_BACKGROUND] = 0x0000; // black
|
||||||
|
|
@ -186,6 +186,7 @@ int main(void) {
|
||||||
bool linkOpen;
|
bool linkOpen;
|
||||||
|
|
||||||
config.codegenBytes = 8 * 1024;
|
config.codegenBytes = 8 * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -212,7 +213,7 @@ int main(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, 0);
|
||||||
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
||||||
drawBits(screen, gLastByte);
|
drawBits(screen, gLastByte);
|
||||||
drawLamp(screen, LAMP_TX_X, false, COLOR_TX_ON);
|
drawLamp(screen, LAMP_TX_X, false, COLOR_TX_ON);
|
||||||
|
|
@ -229,7 +230,7 @@ int main(void) {
|
||||||
bool dirty;
|
bool dirty;
|
||||||
|
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
jlSerialPoll();
|
jlSerialPoll();
|
||||||
|
|
@ -238,7 +239,7 @@ int main(void) {
|
||||||
dirty = false;
|
dirty = false;
|
||||||
|
|
||||||
// Transmit: a banner on SPACE, otherwise the pressed key's ASCII.
|
// Transmit: a banner on SPACE, otherwise the pressed key's ASCII.
|
||||||
if (jlKeyPressed(KEY_SPACE)) {
|
if (jlKeyPressed(JL_KEY_SPACE)) {
|
||||||
sendBytes(gBanner, (uint16_t)(sizeof(gBanner) - 1u));
|
sendBytes(gBanner, (uint16_t)(sizeof(gBanner) - 1u));
|
||||||
txFired = true;
|
txFired = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@
|
||||||
// spread is read jitter
|
// spread is read jitter
|
||||||
// SERLOSS_SFX also keep a sound effect streaming and retrigger a one-shot
|
// SERLOSS_SFX also keep a sound effect streaming and retrigger a one-shot
|
||||||
// effect once per frame, so NinjaTrackerPlus's entry calls and
|
// effect once per frame, so NinjaTrackerPlus's entry calls and
|
||||||
// its DOC refill interrupt run throughout the receive
|
// its DOC refill interrupt run throughout the receive; the
|
||||||
|
// stream's refills are counted, the proof audio is running
|
||||||
// SERLOSS_LOOSE drop the serial add-on's hold on the tight interrupt-latency
|
// SERLOSS_LOOSE drop the serial add-on's hold on the tight interrupt-latency
|
||||||
// bound once the port is open: the negative control, which
|
// bound once the port is open: the negative control, which
|
||||||
// must lose bytes wherever a masked stretch outlasts the FIFO
|
// must lose bytes wherever a masked stretch outlasts the FIFO
|
||||||
|
|
@ -57,6 +58,7 @@
|
||||||
#define MB_JOY_MIN_Y 30
|
#define MB_JOY_MIN_Y 30
|
||||||
#define MB_JOY_MAX_Y 31
|
#define MB_JOY_MAX_Y 31
|
||||||
#define MB_SFX_SHOTS 32
|
#define MB_SFX_SHOTS 32
|
||||||
|
#define MB_SFX_FILLS 34
|
||||||
#define MB_SIG_VALUE_0 0xA5u
|
#define MB_SIG_VALUE_0 0xA5u
|
||||||
#define MB_SIG_VALUE_1 0x5Au
|
#define MB_SIG_VALUE_1 0x5Au
|
||||||
#define MB_JOY_BIAS 128
|
#define MB_JOY_BIAS 128
|
||||||
|
|
@ -117,6 +119,7 @@ typedef struct {
|
||||||
#ifdef SERLOSS_SFX
|
#ifdef SERLOSS_SFX
|
||||||
static int8_t gSfxShot[SFX_SHOT_BYTES];
|
static int8_t gSfxShot[SFX_SHOT_BYTES];
|
||||||
static uint8_t gSfxWave[SFX_WAVE_BYTES];
|
static uint8_t gSfxWave[SFX_WAVE_BYTES];
|
||||||
|
static uint16_t gSfxFills = 0u;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static uint16_t benchLoop(jlSurfaceT *stage, bool present);
|
static uint16_t benchLoop(jlSurfaceT *stage, bool present);
|
||||||
|
|
@ -126,7 +129,7 @@ static void joyPoll(JoySpreadT *spread);
|
||||||
static void mailbox16(uint16_t slot, uint16_t value);
|
static void mailbox16(uint16_t slot, uint16_t value);
|
||||||
static void publish(uint16_t received, uint16_t inSequence, uint16_t breaks, uint16_t presents, uint16_t ticks, uint16_t sum, uint16_t now);
|
static void publish(uint16_t received, uint16_t inSequence, uint16_t breaks, uint16_t presents, uint16_t ticks, uint16_t sum, uint16_t now);
|
||||||
#ifdef SERLOSS_SFX
|
#ifdef SERLOSS_SFX
|
||||||
static bool sfxStart(void);
|
static void sfxStart(void);
|
||||||
static uint32_t sfxStreamFill(void *ctx, int8_t *dst, uint32_t count);
|
static uint32_t sfxStreamFill(void *ctx, int8_t *dst, uint32_t count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -156,8 +159,8 @@ static void joyPoll(JoySpreadT *spread) {
|
||||||
int8_t y;
|
int8_t y;
|
||||||
|
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
x = jlJoystickX(JOYSTICK_0);
|
x = jlJoystickX(JL_JOYSTICK_0);
|
||||||
y = jlJoystickY(JOYSTICK_0);
|
y = jlJoystickY(JL_JOYSTICK_0);
|
||||||
if (spread->polls == 0u || x < spread->minX) {
|
if (spread->polls == 0u || x < spread->minX) {
|
||||||
spread->minX = x;
|
spread->minX = x;
|
||||||
}
|
}
|
||||||
|
|
@ -200,14 +203,11 @@ __attribute__((noinline)) static void publish(uint16_t received, uint16_t inSequ
|
||||||
|
|
||||||
|
|
||||||
#ifdef SERLOSS_SFX
|
#ifdef SERLOSS_SFX
|
||||||
// Bring up audio, build the one-shot's square wave and the stream's sawtooth, and start the
|
// Build the one-shot's square wave and the stream's sawtooth, and start the endless stream
|
||||||
// endless stream.
|
// (jlInit started audio).
|
||||||
static bool sfxStart(void) {
|
static void sfxStart(void) {
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
if (!jlAudioInit()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (i = 0u; i < SFX_SHOT_BYTES; i++) {
|
for (i = 0u; i < SFX_SHOT_BYTES; i++) {
|
||||||
gSfxShot[i] = (int8_t)(((i / (SFX_SHOT_PERIOD / 2u)) & 1u) != 0u ? SFX_LEVEL : -SFX_LEVEL);
|
gSfxShot[i] = (int8_t)(((i / (SFX_SHOT_PERIOD / 2u)) & 1u) != 0u ? SFX_LEVEL : -SFX_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
@ -219,15 +219,17 @@ static bool sfxStart(void) {
|
||||||
// overflow the receive ring.
|
// overflow the receive ring.
|
||||||
jlAudioPlaySfx((uint8_t)SFX_SHOT_SLOT, (const uint8_t *)gSfxShot, SFX_SHOT_BYTES, SFX_SHOT_RATE);
|
jlAudioPlaySfx((uint8_t)SFX_SHOT_SLOT, (const uint8_t *)gSfxShot, SFX_SHOT_BYTES, SFX_SHOT_RATE);
|
||||||
jlAudioPlaySfxStreamRaw((uint8_t)SFX_STREAM_SLOT, sfxStreamFill, (void *)0, SFX_STREAM_RATE);
|
jlAudioPlaySfxStreamRaw((uint8_t)SFX_STREAM_SLOT, sfxStreamFill, (void *)0, SFX_STREAM_RATE);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Endless sawtooth, whole periods: never returns 0, so the stream runs until the program ends.
|
// Endless sawtooth, whole periods: never returns 0, so the stream runs until the program ends.
|
||||||
|
// Only a running engine asks for refills, so their count proves audio is up.
|
||||||
static uint32_t sfxStreamFill(void *ctx, int8_t *dst, uint32_t count) {
|
static uint32_t sfxStreamFill(void *ctx, int8_t *dst, uint32_t count) {
|
||||||
uint32_t done;
|
uint32_t done;
|
||||||
|
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
|
gSfxFills++;
|
||||||
|
mailbox16(MB_SFX_FILLS, gSfxFills);
|
||||||
done = 0u;
|
done = 0u;
|
||||||
while (count - done >= SFX_WAVE_BYTES) {
|
while (count - done >= SFX_WAVE_BYTES) {
|
||||||
jlMemCopy(dst + done, gSfxWave, SFX_WAVE_BYTES);
|
jlMemCopy(dst + done, gSfxWave, SFX_WAVE_BYTES);
|
||||||
|
|
@ -269,6 +271,11 @@ int main(void) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
config.codegenBytes = 0u;
|
config.codegenBytes = 0u;
|
||||||
|
#ifdef SERLOSS_SFX
|
||||||
|
config.noAudio = false;
|
||||||
|
#else
|
||||||
|
config.noAudio = true;
|
||||||
|
#endif
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
@ -280,15 +287,12 @@ int main(void) {
|
||||||
MB_BASE[MB_PHASE] = PHASE_INIT;
|
MB_BASE[MB_PHASE] = PHASE_INIT;
|
||||||
mailbox16(MB_JOY_POLLS, 0u);
|
mailbox16(MB_JOY_POLLS, 0u);
|
||||||
mailbox16(MB_SFX_SHOTS, 0u);
|
mailbox16(MB_SFX_SHOTS, 0u);
|
||||||
|
mailbox16(MB_SFX_FILLS, 0u);
|
||||||
stage = jlStageGet();
|
stage = jlStageGet();
|
||||||
mailbox16(MB_FILL_TICKS, benchLoop(stage, false));
|
mailbox16(MB_FILL_TICKS, benchLoop(stage, false));
|
||||||
mailbox16(MB_PRESENT_TICKS, benchLoop(stage, true));
|
mailbox16(MB_PRESENT_TICKS, benchLoop(stage, true));
|
||||||
#ifdef SERLOSS_SFX
|
#ifdef SERLOSS_SFX
|
||||||
if (!sfxStart()) {
|
sfxStart();
|
||||||
MB_BASE[MB_PHASE] = PHASE_FAILED;
|
|
||||||
for (;;) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shots = 0u;
|
shots = 0u;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -316,7 +320,7 @@ int main(void) {
|
||||||
#ifdef SERLOSS_JOY
|
#ifdef SERLOSS_JOY
|
||||||
// Calibrate on the first poll, in the latency mode the rest will run in, so a still stick
|
// Calibrate on the first poll, in the latency mode the rest will run in, so a still stick
|
||||||
// reads 0 from then on.
|
// reads 0 from then on.
|
||||||
jlJoystickReset(JOYSTICK_0, 0u);
|
jlJoystickReset(JL_JOYSTICK_0, 0u);
|
||||||
spread.polls = 0u;
|
spread.polls = 0u;
|
||||||
joyPoll(&spread);
|
joyPoll(&spread);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,10 @@ static void updateMouseRow(jlSurfaceT *screen, MouseRowT *last);
|
||||||
|
|
||||||
// A grey ramp, so every index the verification rows stamp is visible.
|
// A grey ramp, so every index the verification rows stamp is visible.
|
||||||
static void buildPalette(jlSurfaceT *screen) {
|
static void buildPalette(jlSurfaceT *screen) {
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
|
|
||||||
for (i = 0u; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0u; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = (uint16_t)((i << PALETTE_RED_SHIFT) | (i << PALETTE_GREEN_SHIFT) | i);
|
colors[i] = (uint16_t)((i << PALETTE_RED_SHIFT) | (i << PALETTE_GREEN_SHIFT) | i);
|
||||||
}
|
}
|
||||||
jlPaletteSet(screen, GREY_PALETTE, colors);
|
jlPaletteSet(screen, GREY_PALETTE, colors);
|
||||||
|
|
@ -155,15 +155,15 @@ static void buildPalette(jlSurfaceT *screen) {
|
||||||
// The grey palette everywhere, or DENSE_SCB_LINES lines alternating between
|
// The grey palette everywhere, or DENSE_SCB_LINES lines alternating between
|
||||||
// it and the same ramp in blue.
|
// it and the same ramp in blue.
|
||||||
static void buildScb(jlSurfaceT *screen) {
|
static void buildScb(jlSurfaceT *screen) {
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, GREY_PALETTE);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, GREY_PALETTE);
|
||||||
#if DENSE_SCB_LINES > 0
|
#if DENSE_SCB_LINES > 0
|
||||||
{
|
{
|
||||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
uint16_t colors[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
uint16_t line;
|
uint16_t line;
|
||||||
|
|
||||||
jlPaletteGet(screen, GREY_PALETTE, colors);
|
jlPaletteGet(screen, GREY_PALETTE, colors);
|
||||||
for (i = 1u; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 1u; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
colors[i] = (uint16_t)(colors[i] | BLUE_FLOOR);
|
colors[i] = (uint16_t)(colors[i] | BLUE_FLOOR);
|
||||||
}
|
}
|
||||||
jlPaletteSet(screen, BLUE_PALETTE, colors);
|
jlPaletteSet(screen, BLUE_PALETTE, colors);
|
||||||
|
|
@ -183,8 +183,8 @@ static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color)
|
||||||
|
|
||||||
w = POINTER_SIZE;
|
w = POINTER_SIZE;
|
||||||
h = POINTER_SIZE;
|
h = POINTER_SIZE;
|
||||||
if ((int16_t)(x + w) > SURFACE_WIDTH) {
|
if ((int16_t)(x + w) > JL_SURFACE_WIDTH) {
|
||||||
w = (int16_t)(SURFACE_WIDTH - x);
|
w = (int16_t)(JL_SURFACE_WIDTH - x);
|
||||||
}
|
}
|
||||||
if ((int16_t)(y + h) > SERIAL_ROW_Y) {
|
if ((int16_t)(y + h) > SERIAL_ROW_Y) {
|
||||||
h = (int16_t)(SERIAL_ROW_Y - y);
|
h = (int16_t)(SERIAL_ROW_Y - y);
|
||||||
|
|
@ -197,10 +197,7 @@ static void fillPointer(jlSurfaceT *screen, int16_t x, int16_t y, uint8_t color)
|
||||||
|
|
||||||
|
|
||||||
static uint16_t mouseFlags(void) {
|
static uint16_t mouseFlags(void) {
|
||||||
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0u) |
|
return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0u) | (jlMouseDown(JL_MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0u));
|
||||||
(jlMouseDown(MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0u) |
|
|
||||||
(jlMouseDown(MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0u) |
|
|
||||||
(jlMouseDown(MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0u));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -334,6 +331,7 @@ int main(void) {
|
||||||
bool paused;
|
bool paused;
|
||||||
|
|
||||||
config.codegenBytes = 0u;
|
config.codegenBytes = 0u;
|
||||||
|
config.noAudio = true;
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -371,10 +369,10 @@ int main(void) {
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (jlKeyPressed(KEY_SPACE)) {
|
if (jlKeyPressed(JL_KEY_SPACE)) {
|
||||||
paused = !paused;
|
paused = !paused;
|
||||||
}
|
}
|
||||||
receiveStream(&stats);
|
receiveStream(&stats);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
// per source line) and converted at startup into the 2x2 tile layout
|
// per source line) and converted at startup into the 2x2 tile layout
|
||||||
// jlSpriteT expects. jlSpriteDraw handles transparent-color-0; we use
|
// jlSpriteT expects. jlSpriteDraw handles transparent-color-0; we use
|
||||||
// jlSpriteSaveUnder/RestoreUnder to undraw the previous frame's ball
|
// jlSpriteSaveUnder/RestoreUnder to undraw the previous frame's ball
|
||||||
// without redrawing the whole screen.
|
// without redrawing the whole screen. The ball's speed is given per
|
||||||
|
// second and each frame moves it by the time since the last one
|
||||||
|
// (jlMillisElapsed), so it crosses the screen as fast on every port.
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
@ -18,17 +20,20 @@
|
||||||
#define BALL_TILES_X (BALL_W / 8)
|
#define BALL_TILES_X (BALL_W / 8)
|
||||||
#define BALL_TILES_Y (BALL_H / 8)
|
#define BALL_TILES_Y (BALL_H / 8)
|
||||||
|
|
||||||
#define BALL_TILE_BYTES (BALL_TILES_X * BALL_TILES_Y * TILE_BYTES)
|
#define BALL_TILE_BYTES (BALL_TILES_X * BALL_TILES_Y * JL_TILE_BYTES)
|
||||||
// SaveUnder rounds x down to the platform's storage alignment: 2 px
|
// The save-under buffer is sized by jlSpriteBackupLength (joey/sprite.h):
|
||||||
// for chunky 4bpp (1 extra byte/row worst case), 8 px for planar
|
// the worst case at any x, on every port.
|
||||||
// 4-plane (4 extra bytes/row worst case -- one per plane). The +4
|
|
||||||
// covers the planar case and is a no-op overhead on chunky.
|
|
||||||
// Sized by the library-wide worst case (16-px-group window on the
|
|
||||||
// planar ports); see JOEY_SPRITE_BACKUP_BYTES in joey/sprite.h.
|
|
||||||
#define BALL_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(BALL_W / 8, BALL_H / 8)
|
|
||||||
|
|
||||||
#define BALL_PALETTE_IDX 0
|
#define BALL_PALETTE_IDX 0
|
||||||
|
|
||||||
|
#define BALL_SPEED_X 120 // pixels per second, the same on every port
|
||||||
|
#define BALL_SPEED_Y 60
|
||||||
|
#define SUBPIXEL_SHIFT 8 // positions are kept in 1/256 of a pixel
|
||||||
|
#define MAX_STEP_MILLIS 100 // after a stall, move no further than this
|
||||||
|
#define MILLIS_PER_SECOND 1000
|
||||||
|
#define BALL_X_MAX ((int32_t)(JL_SURFACE_WIDTH - BALL_W) << SUBPIXEL_SHIFT)
|
||||||
|
#define BALL_Y_MAX ((int32_t)(JL_SURFACE_HEIGHT - BALL_H) << SUBPIXEL_SHIFT)
|
||||||
|
|
||||||
#define COLOR_BG 0
|
#define COLOR_BG 0
|
||||||
|
|
||||||
// Authored layout: 16 pixels wide x 16 rows = 8 bytes/row x 16 rows.
|
// Authored layout: 16 pixels wide x 16 rows = 8 bytes/row x 16 rows.
|
||||||
|
|
@ -56,7 +61,7 @@ static const uint8_t gBallAuthored[BALL_H * (BALL_W / 2)] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint8_t gBallTiles[BALL_TILE_BYTES];
|
static uint8_t gBallTiles[BALL_TILE_BYTES];
|
||||||
static uint8_t gBallBackup[BALL_BACKUP_BYTES] __attribute__((aligned(2)));
|
static uint16_t gBallBackup[jlSpriteBackupLength(uint16_t, BALL_W / 8, BALL_H / 8)];
|
||||||
|
|
||||||
|
|
||||||
// Repack from authored "wide row" layout to tile-major jlSpriteT
|
// Repack from authored "wide row" layout to tile-major jlSpriteT
|
||||||
|
|
@ -71,12 +76,10 @@ static void repackBallTiles(void) {
|
||||||
|
|
||||||
for (ty = 0; ty < BALL_TILES_Y; ty++) {
|
for (ty = 0; ty < BALL_TILES_Y; ty++) {
|
||||||
for (tx = 0; tx < BALL_TILES_X; tx++) {
|
for (tx = 0; tx < BALL_TILES_X; tx++) {
|
||||||
dst = &gBallTiles[(ty * BALL_TILES_X + tx) * TILE_BYTES];
|
dst = &gBallTiles[(ty * BALL_TILES_X + tx) * JL_TILE_BYTES];
|
||||||
for (row = 0; row < 8; row++) {
|
for (row = 0; row < 8; row++) {
|
||||||
for (b = 0; b < 4; b++) {
|
for (b = 0; b < 4; b++) {
|
||||||
dst[row * 4 + b] =
|
dst[row * 4 + b] = gBallAuthored[((ty * 8) + row) * (BALL_W / 2) + (tx * 4) + b];
|
||||||
gBallAuthored[((ty * 8) + row) * (BALL_W / 2) +
|
|
||||||
(tx * 4) + b];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,17 +105,20 @@ int main(void) {
|
||||||
jlSurfaceT *screen;
|
jlSurfaceT *screen;
|
||||||
jlSpriteT *ball;
|
jlSpriteT *ball;
|
||||||
jlSpriteBackupT backup;
|
jlSpriteBackupT backup;
|
||||||
int16_t x;
|
int32_t x; // 1/256 of a pixel
|
||||||
int16_t y;
|
int32_t y;
|
||||||
int16_t vx;
|
int32_t vx; // pixels per second
|
||||||
int16_t vy;
|
int32_t vy;
|
||||||
bool haveBackup;
|
uint32_t lastMillis;
|
||||||
|
uint32_t now;
|
||||||
|
uint32_t elapsed;
|
||||||
|
|
||||||
/* Amiga planar emits 8 pre-shifted DRAW variants per sprite (one
|
/* Amiga planar emits 8 pre-shifted DRAW variants per sprite (one
|
||||||
* per x % 8 alignment) so the codegen arena needs roughly 8x what
|
* per x % 8 alignment) so the codegen arena needs roughly 8x what
|
||||||
* the chunky two-shift case asks for. 32 KB fits a 16x16 ball
|
* the chunky two-shift case asks for. 32 KB fits a 16x16 ball
|
||||||
* with all variants. */
|
* with all variants. */
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
|
config.noAudio = true;
|
||||||
|
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
|
|
@ -140,25 +146,24 @@ int main(void) {
|
||||||
(void)jlSpriteCompile(ball);
|
(void)jlSpriteCompile(ball);
|
||||||
|
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
|
jlScbSetRange(screen, 0, JL_SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
|
||||||
jlSurfaceClear(screen, COLOR_BG);
|
jlSurfaceClear(screen, COLOR_BG);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
|
|
||||||
backup.bytes = gBallBackup;
|
backup.bytes = (uint8_t *)gBallBackup;
|
||||||
|
|
||||||
x = 40;
|
x = (int32_t)40 << SUBPIXEL_SHIFT;
|
||||||
y = 30;
|
y = (int32_t)30 << SUBPIXEL_SHIFT;
|
||||||
vx = 2;
|
vx = BALL_SPEED_X;
|
||||||
vy = 1;
|
vy = BALL_SPEED_Y;
|
||||||
haveBackup = false;
|
|
||||||
|
|
||||||
jlSpriteSaveAndDraw(screen, ball, x, y, &backup);
|
jlSpriteSaveAndDraw(screen, ball, (int16_t)(x >> SUBPIXEL_SHIFT), (int16_t)(y >> SUBPIXEL_SHIFT), &backup);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
haveBackup = true;
|
lastMillis = jlMillisElapsed();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
jlInputPoll();
|
jlInputPoll();
|
||||||
if (jlKeyPressed(KEY_ESCAPE)) {
|
if (jlKeyPressed(JL_KEY_ESCAPE)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,22 +175,41 @@ int main(void) {
|
||||||
// chunky targets like IIgs SHR; on planar c2p platforms it
|
// chunky targets like IIgs SHR; on planar c2p platforms it
|
||||||
// also avoids c2p racing the raster). VBL wait is omitted
|
// also avoids c2p racing the raster). VBL wait is omitted
|
||||||
// here so the demo runs at the sprite pipeline's native
|
// here so the demo runs at the sprite pipeline's native
|
||||||
// throughput -- expect tearing on the ball.
|
// throughput -- expect tearing on the ball. The ball's speed
|
||||||
if (haveBackup) {
|
// does not depend on it: it moves by the time each frame took.
|
||||||
jlSpriteRestoreUnder(screen, &backup);
|
jlSpriteRestoreUnder(screen, &backup);
|
||||||
|
|
||||||
|
now = jlMillisElapsed();
|
||||||
|
elapsed = now - lastMillis;
|
||||||
|
lastMillis = now;
|
||||||
|
if (elapsed > MAX_STEP_MILLIS) {
|
||||||
|
elapsed = MAX_STEP_MILLIS;
|
||||||
|
}
|
||||||
|
x += (vx * (int32_t)elapsed * ((int32_t)1 << SUBPIXEL_SHIFT)) / MILLIS_PER_SECOND;
|
||||||
|
y += (vy * (int32_t)elapsed * ((int32_t)1 << SUBPIXEL_SHIFT)) / MILLIS_PER_SECOND;
|
||||||
|
|
||||||
|
// A wall sets the direction rather than flipping it, so a frame
|
||||||
|
// that moved nothing cannot turn the ball back twice.
|
||||||
|
if (x <= 0) {
|
||||||
|
x = 0;
|
||||||
|
vx = BALL_SPEED_X;
|
||||||
|
}
|
||||||
|
if (x >= BALL_X_MAX) {
|
||||||
|
x = BALL_X_MAX;
|
||||||
|
vx = -BALL_SPEED_X;
|
||||||
|
}
|
||||||
|
if (y <= 0) {
|
||||||
|
y = 0;
|
||||||
|
vy = BALL_SPEED_Y;
|
||||||
|
}
|
||||||
|
if (y >= BALL_Y_MAX) {
|
||||||
|
y = BALL_Y_MAX;
|
||||||
|
vy = -BALL_SPEED_Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
x = (int16_t)(x + vx);
|
jlSpriteSaveAndDraw(screen, ball, (int16_t)(x >> SUBPIXEL_SHIFT), (int16_t)(y >> SUBPIXEL_SHIFT), &backup);
|
||||||
y = (int16_t)(y + vy);
|
|
||||||
if (x <= 0) { x = 0; vx = (int16_t)-vx; }
|
|
||||||
if (x >= SURFACE_WIDTH - BALL_W) { x = SURFACE_WIDTH - BALL_W; vx = (int16_t)-vx; }
|
|
||||||
if (y <= 0) { y = 0; vy = (int16_t)-vy; }
|
|
||||||
if (y >= SURFACE_HEIGHT - BALL_H) { y = SURFACE_HEIGHT - BALL_H; vy = (int16_t)-vy; }
|
|
||||||
|
|
||||||
jlSpriteSaveAndDraw(screen, ball, x, y, &backup);
|
|
||||||
|
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
haveBackup = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jlSpriteDestroy(ball);
|
jlSpriteDestroy(ball);
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@
|
||||||
// expect.bin layout (see the header comment).
|
// expect.bin layout (see the header comment).
|
||||||
#define LE16_BYTES 2
|
#define LE16_BYTES 2
|
||||||
#define BYTE_BITS 8
|
#define BYTE_BITS 8
|
||||||
#define PALETTE_COLORS SURFACE_COLORS_PER_PALETTE
|
#define PALETTE_COLORS JL_SURFACE_COLORS_PER_PALETTE
|
||||||
#define EXPECT_OFF_PALETTE LE16_BYTES
|
#define EXPECT_OFF_PALETTE LE16_BYTES
|
||||||
#define EXPECT_HEADER_BYTES (EXPECT_OFF_PALETTE + PALETTE_COLORS * LE16_BYTES)
|
#define EXPECT_HEADER_BYTES (EXPECT_OFF_PALETTE + PALETTE_COLORS * LE16_BYTES)
|
||||||
#define TILE_PIXELS (TILE_PIXELS_PER_SIDE * TILE_PIXELS_PER_SIDE)
|
#define TILE_PIXELS (JL_TILE_PIXELS_PER_SIDE * JL_TILE_PIXELS_PER_SIDE)
|
||||||
|
|
||||||
// The stage is cleared to this before every load. It MUST equal FILL_COLOR in
|
// The stage is cleared to this before every load. It MUST equal FILL_COLOR in
|
||||||
// tests/host/tileBankHost.c: the host's hash is of a surface cleared to it.
|
// tests/host/tileBankHost.c: the host's hash is of a surface cleared to it.
|
||||||
|
|
@ -74,8 +74,8 @@
|
||||||
// the lamp colours cannot collide with the bank's sixteen.
|
// the lamp colours cannot collide with the bank's sixteen.
|
||||||
#define LAMP_W 64
|
#define LAMP_W 64
|
||||||
#define LAMP_H 64
|
#define LAMP_H 64
|
||||||
#define LAMP_X ((SURFACE_WIDTH - LAMP_W) / 2)
|
#define LAMP_X ((JL_SURFACE_WIDTH - LAMP_W) / 2)
|
||||||
#define LAMP_Y ((SURFACE_HEIGHT - LAMP_H) / 2)
|
#define LAMP_Y ((JL_SURFACE_HEIGHT - LAMP_H) / 2)
|
||||||
#define LAMP_PALETTE 1u
|
#define LAMP_PALETTE 1u
|
||||||
#define LAMP_PASS_COLOR 1u
|
#define LAMP_PASS_COLOR 1u
|
||||||
#define LAMP_FAIL_COLOR 2u
|
#define LAMP_FAIL_COLOR 2u
|
||||||
|
|
@ -145,7 +145,7 @@ static void pasteTiles(uint16_t count) {
|
||||||
|
|
||||||
jlSurfaceClear(gScreen, FILL_COLOR);
|
jlSurfaceClear(gScreen, FILL_COLOR);
|
||||||
for (i = 0u; i < count; i++) {
|
for (i = 0u; i < count; i++) {
|
||||||
jlTilePaste(gScreen, (uint8_t)(i % TILE_BLOCKS_PER_ROW), (uint8_t)(i / TILE_BLOCKS_PER_ROW), &gTiles[i]);
|
jlTilePaste(gScreen, (uint8_t)(i % JL_TILE_BLOCKS_PER_ROW), (uint8_t)(i / JL_TILE_BLOCKS_PER_ROW), &gTiles[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,12 +275,12 @@ static bool stageMatches(uint16_t tilesDrawn) {
|
||||||
uint16_t block;
|
uint16_t block;
|
||||||
uint8_t want;
|
uint8_t want;
|
||||||
|
|
||||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
for (x = 0; x < SURFACE_WIDTH; x++) {
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
block = (uint16_t)((y / TILE_PIXELS_PER_SIDE) * TILE_BLOCKS_PER_ROW + x / TILE_PIXELS_PER_SIDE);
|
block = (uint16_t)((y / JL_TILE_PIXELS_PER_SIDE) * JL_TILE_BLOCKS_PER_ROW + x / JL_TILE_PIXELS_PER_SIDE);
|
||||||
want = FILL_COLOR;
|
want = FILL_COLOR;
|
||||||
if (block < tilesDrawn) {
|
if (block < tilesDrawn) {
|
||||||
want = gExpectPixels[(uint32_t)block * TILE_PIXELS + (y % TILE_PIXELS_PER_SIDE) * TILE_PIXELS_PER_SIDE + x % TILE_PIXELS_PER_SIDE];
|
want = gExpectPixels[(uint32_t)block * TILE_PIXELS + (y % JL_TILE_PIXELS_PER_SIDE) * JL_TILE_PIXELS_PER_SIDE + x % JL_TILE_PIXELS_PER_SIDE];
|
||||||
}
|
}
|
||||||
if (jlSamplePixel(gScreen, x, y) != want) {
|
if (jlSamplePixel(gScreen, x, y) != want) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -296,6 +296,7 @@ int main(void) {
|
||||||
bool pass;
|
bool pass;
|
||||||
|
|
||||||
config.codegenBytes = CODEGEN_DEFAULT;
|
config.codegenBytes = CODEGEN_DEFAULT;
|
||||||
|
config.noAudio = true;
|
||||||
if (!jlInit(&config)) {
|
if (!jlInit(&config)) {
|
||||||
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,9 @@
|
||||||
// +1 (9DC9) uint8 phase (UBER_PHASE_*)
|
// +1 (9DC9) uint8 phase (UBER_PHASE_*)
|
||||||
// +2 (9DCA) uint16 heartbeat (one increment per timing-loop pass)
|
// +2 (9DCA) uint16 heartbeat (one increment per timing-loop pass)
|
||||||
// +4 (9DCC) uint16 last jlFrameCount() value the timing loop saw
|
// +4 (9DCC) uint16 last jlFrameCount() value the timing loop saw
|
||||||
// No-ops on every other port.
|
// No-ops on every other port. Phase 2 was UBER's own audio start, which
|
||||||
|
// jlInit does now.
|
||||||
#define UBER_PHASE_SETUP_SPRITE 1u
|
#define UBER_PHASE_SETUP_SPRITE 1u
|
||||||
#define UBER_PHASE_AUDIO_INIT 2u
|
|
||||||
#define UBER_PHASE_SHOWCASE 3u
|
#define UBER_PHASE_SHOWCASE 3u
|
||||||
#define UBER_PHASE_TIMED_RUN 4u
|
#define UBER_PHASE_TIMED_RUN 4u
|
||||||
#define UBER_PHASE_CHECKS 5u
|
#define UBER_PHASE_CHECKS 5u
|
||||||
|
|
@ -140,7 +140,7 @@ typedef void (*OpFn)(void);
|
||||||
static jlSurfaceT *gStage = NULL;
|
static jlSurfaceT *gStage = NULL;
|
||||||
static jlSpriteT *gSprite = NULL;
|
static jlSpriteT *gSprite = NULL;
|
||||||
static jlSpriteBackupT gBackup;
|
static jlSpriteBackupT gBackup;
|
||||||
static unsigned char gBackupBytes[JOEY_SPRITE_BACKUP_BYTES(2, 2)] __attribute__((aligned(2)));
|
static unsigned char gBackupBytes[jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2)));
|
||||||
|
|
||||||
static jlTileT gTileScratch;
|
static jlTileT gTileScratch;
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ static jlTileT gTileMapB;
|
||||||
#define UBER_FRAME_SPRITES 3
|
#define UBER_FRAME_SPRITES 3
|
||||||
|
|
||||||
static jlSpriteBackupT gFrameBackups[UBER_FRAME_SPRITES];
|
static jlSpriteBackupT gFrameBackups[UBER_FRAME_SPRITES];
|
||||||
static unsigned char gFrameBackupBytes[UBER_FRAME_SPRITES][JOEY_SPRITE_BACKUP_BYTES(2, 2)] __attribute__((aligned(2)));
|
static unsigned char gFrameBackupBytes[UBER_FRAME_SPRITES][jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2)));
|
||||||
|
|
||||||
|
|
||||||
// Current timed-op number (1-based); mirrored into the mailbox. Lives
|
// Current timed-op number (1-based); mirrored into the mailbox. Lives
|
||||||
|
|
@ -239,11 +239,11 @@ static unsigned long runForFrames(OpFn op, unsigned int targetFrames, uint16_t *
|
||||||
endMillis = jlMillisElapsed();
|
endMillis = jlMillisElapsed();
|
||||||
*actualFramesOut = (uint16_t)(endFrame - startFrame);
|
*actualFramesOut = (uint16_t)(endFrame - startFrame);
|
||||||
if (*actualFramesOut == 0u) {
|
if (*actualFramesOut == 0u) {
|
||||||
*actualFramesOut = 1u; /* defensive: avoid div-by-zero */
|
*actualFramesOut = 1u; // defensive: avoid div-by-zero
|
||||||
}
|
}
|
||||||
*millisDeltaOut = endMillis - startMillis;
|
*millisDeltaOut = endMillis - startMillis;
|
||||||
if (*millisDeltaOut == 0u) {
|
if (*millisDeltaOut == 0u) {
|
||||||
*millisDeltaOut = 1u; /* defensive: avoid div-by-zero */
|
*millisDeltaOut = 1u; // defensive: avoid div-by-zero
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
@ -285,10 +285,8 @@ static void timeOp(const char *name, OpFn op) {
|
||||||
opsPerSec = (iters * 1000UL) / (unsigned long)millisDelta;
|
opsPerSec = (iters * 1000UL) / (unsigned long)millisDelta;
|
||||||
opsPerSecFr = (iters * (unsigned long)jlFrameHz()) / (unsigned long)actualFrames;
|
opsPerSecFr = (iters * (unsigned long)jlFrameHz()) / (unsigned long)actualFrames;
|
||||||
hash = jlSurfaceHash(gStage);
|
hash = jlSurfaceHash(gStage);
|
||||||
jlLogF("UBER: %s: %lu iters / %u frames = %lu ops/sec | hash=%08lX",
|
jlLogF("UBER: %s: %lu iters / %u frames = %lu ops/sec | hash=%08lX", name, iters, actualFrames, opsPerSec, (unsigned long)hash);
|
||||||
name, iters, actualFrames, opsPerSec, (unsigned long)hash);
|
jlLogF("UBER-CLK: %s: frame=%lu millis=%lu ops/sec (%lu ms)", name, opsPerSecFr, opsPerSec, (unsigned long)millisDelta);
|
||||||
jlLogF("UBER-CLK: %s: frame=%lu millis=%lu ops/sec (%lu ms)",
|
|
||||||
name, opsPerSecFr, opsPerSec, (unsigned long)millisDelta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -336,15 +334,15 @@ static void op_spriteSaveAndDraw (void) { jlSpriteSaveAndDraw (gStage, gSprite,
|
||||||
static void op_stagePresent (void) { jlStagePresent(); }
|
static void op_stagePresent (void) { jlStagePresent(); }
|
||||||
|
|
||||||
static void op_inputPoll (void) { jlInputPoll(); }
|
static void op_inputPoll (void) { jlInputPoll(); }
|
||||||
static void op_keyDown (void) { (void)jlKeyDown(KEY_A); }
|
static void op_keyDown (void) { (void)jlKeyDown(JL_KEY_A); }
|
||||||
static void op_keyPressed (void) { (void)jlKeyPressed(KEY_A); }
|
static void op_keyPressed (void) { (void)jlKeyPressed(JL_KEY_A); }
|
||||||
static void op_mouseX (void) { (void)jlMouseX(); }
|
static void op_mouseX (void) { (void)jlMouseX(); }
|
||||||
static void op_joyConnected (void) { (void)jlJoystickConnected(JOYSTICK_1); }
|
static void op_joyConnected (void) { (void)jlJoystickConnected(JL_JOYSTICK_1); }
|
||||||
|
|
||||||
static void op_audioFrameTick (void) { jlAudioFrameTick(); }
|
static void op_audioFrameTick (void) { jlAudioFrameTick(); }
|
||||||
static void op_audioIsPlaying (void) { (void)jlAudioIsPlayingMod(); }
|
static void op_audioIsPlaying (void) { (void)jlAudioIsPlayingMod(); }
|
||||||
|
|
||||||
static void op_surfaceMarkDirty(void) { /* jlDrawPixel already marks; use fill instead */
|
static void op_surfaceMarkDirty(void) { // jlDrawPixel already marks; use fill instead
|
||||||
jlFillRect(gStage, 0, 0, 32, 32, 0); }
|
jlFillRect(gStage, 0, 0, 32, 32, 0); }
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -600,7 +598,7 @@ static void PROBE_ATTR op_probePresentIdle(void) {
|
||||||
// rows in this same log. Stage pixels are never written (saves read;
|
// rows in this same log. Stage pixels are never written (saves read;
|
||||||
// the restoreVal call fails validation), so these rows are hash-inert.
|
// the restoreVal call fails validation), so these rows are hash-inert.
|
||||||
static jlSpriteBackupT gProbeSpriteBackup;
|
static jlSpriteBackupT gProbeSpriteBackup;
|
||||||
static unsigned char gProbeSpriteBackupBytes[JOEY_SPRITE_BACKUP_BYTES(2, 2)] __attribute__((aligned(2)));
|
static unsigned char gProbeSpriteBackupBytes[jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2)));
|
||||||
|
|
||||||
static void PROBE_ATTR op_probeSpriteSaveVal(void) {
|
static void PROBE_ATTR op_probeSpriteSaveVal(void) {
|
||||||
jlSpriteSaveUnder(gStage, gSprite, -100, -100, &gProbeSpriteBackup);
|
jlSpriteSaveUnder(gStage, gSprite, -100, -100, &gProbeSpriteBackup);
|
||||||
|
|
@ -631,7 +629,7 @@ static void PROBE_ATTR op_probeSpriteSaveG16(void) {
|
||||||
// and gBackup untouched, so the row is inert to the golden hashes.
|
// and gBackup untouched, so the row is inert to the golden hashes.
|
||||||
static jlSurfaceT *gProbeRestoreWork = NULL;
|
static jlSurfaceT *gProbeRestoreWork = NULL;
|
||||||
static jlSpriteBackupT gProbeRestoreBackup;
|
static jlSpriteBackupT gProbeRestoreBackup;
|
||||||
static unsigned char gProbeRestoreBackupBytes[JOEY_SPRITE_BACKUP_BYTES(2, 2)] __attribute__((aligned(2)));
|
static unsigned char gProbeRestoreBackupBytes[jlSpriteBackupLength(unsigned char, 2, 2)] __attribute__((aligned(2)));
|
||||||
|
|
||||||
static void PROBE_ATTR op_probeRestoreWork(void) {
|
static void PROBE_ATTR op_probeRestoreWork(void) {
|
||||||
jlSpriteRestoreUnder(gProbeRestoreWork, &gProbeRestoreBackup);
|
jlSpriteRestoreUnder(gProbeRestoreWork, &gProbeRestoreBackup);
|
||||||
|
|
@ -682,15 +680,15 @@ static void PROBE_ATTR op_probeDrawTrusted(void) {
|
||||||
// The band arrays are port-neutral, so the value must also be
|
// The band arrays are port-neutral, so the value must also be
|
||||||
// IDENTICAL ACROSS PORTS -- the C-mark ports are the reference any
|
// IDENTICAL ACROSS PORTS -- the C-mark ports are the reference any
|
||||||
// fused asm mark must reproduce.
|
// fused asm mark must reproduce.
|
||||||
extern uint8_t gStageMinWord[SURFACE_HEIGHT];
|
extern uint8_t gStageMinWord[JL_SURFACE_HEIGHT];
|
||||||
extern uint8_t gStageMaxWord[SURFACE_HEIGHT];
|
extern uint8_t gStageMaxWord[JL_SURFACE_HEIGHT];
|
||||||
|
|
||||||
static uint32_t PROBE_ATTR probeBandFold(void) {
|
static uint32_t PROBE_ATTR probeBandFold(void) {
|
||||||
uint16_t y;
|
uint16_t y;
|
||||||
uint32_t sum;
|
uint32_t sum;
|
||||||
|
|
||||||
sum = 0;
|
sum = 0;
|
||||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
sum = (uint32_t)(sum * 33u + gStageMinWord[y]);
|
sum = (uint32_t)(sum * 33u + gStageMinWord[y]);
|
||||||
sum = (uint32_t)(sum * 33u + gStageMaxWord[y]);
|
sum = (uint32_t)(sum * 33u + gStageMaxWord[y]);
|
||||||
}
|
}
|
||||||
|
|
@ -1042,80 +1040,123 @@ static void PROBE_ATTR op_probeVgaCheck(void) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef JOEYLIB_PLATFORM_DVX
|
#if defined(JOEYLIB_PLATFORM_DVX) || defined(JOEYLIB_HOSTED_WIN16) || defined(JOEYLIB_HOSTED_WIN32) || defined(JOEYLIB_HOSTED_BE)
|
||||||
|
#if defined(JOEYLIB_PLATFORM_DVX)
|
||||||
extern uint32_t dvxPortProbeDisplayMismatch(const jlSurfaceT *src, uint32_t *compared);
|
extern uint32_t dvxPortProbeDisplayMismatch(const jlSurfaceT *src, uint32_t *compared);
|
||||||
|
#define PROBE_WINDOW_MISMATCH(_s, _c) dvxPortProbeDisplayMismatch((_s), (_c))
|
||||||
|
#define PROBE_WINDOW_TAG "dvxDisplayMismatch"
|
||||||
|
#elif defined(JOEYLIB_HOSTED_WIN16)
|
||||||
|
extern uint32_t hostWin16ProbeDisplayMismatch(const jlSurfaceT *src, uint32_t *compared);
|
||||||
|
#define PROBE_WINDOW_MISMATCH(_s, _c) hostWin16ProbeDisplayMismatch((_s), (_c))
|
||||||
|
#define PROBE_WINDOW_TAG "win16DisplayMismatch"
|
||||||
|
#elif defined(JOEYLIB_HOSTED_WIN32)
|
||||||
|
extern uint32_t hostWin32ProbeDisplayMismatch(const jlSurfaceT *src, uint32_t *compared);
|
||||||
|
#define PROBE_WINDOW_MISMATCH(_s, _c) hostWin32ProbeDisplayMismatch((_s), (_c))
|
||||||
|
#define PROBE_WINDOW_TAG "win32DisplayMismatch"
|
||||||
|
#else
|
||||||
|
extern uint32_t hostBeProbeDisplayMismatch(const jlSurfaceT *src, uint32_t *compared);
|
||||||
|
#define PROBE_WINDOW_MISMATCH(_s, _c) hostBeProbeDisplayMismatch((_s), (_c))
|
||||||
|
#define PROBE_WINDOW_TAG "beDisplayMismatch"
|
||||||
|
#endif
|
||||||
|
|
||||||
// P13: DVX display-side parity, P12's battery on the DVX present, which
|
// P13: display-side parity in a window, P12's battery on the DVX, Win16,
|
||||||
|
// Win32, and Be presents. The golden hashes gate the stage, never what is shown. DVX
|
||||||
// expands dirty rows through per-palette tables into the window's content
|
// expands dirty rows through per-palette tables into the window's content
|
||||||
// buffer (the desktop's pixel format, at the window's scale) or the
|
// buffer (the desktop's pixel format, at the window's scale) or the
|
||||||
// full-screen backbuffer. The golden hashes gate the stage, never those.
|
// full-screen backbuffer; dvxPortProbeDisplayMismatch derives every pixel
|
||||||
// dvxPortProbeDisplayMismatch derives every pixel from the stage's own
|
// from the stage's own palette entries (not the present's tables) and
|
||||||
// palette entries (not the present's tables) and counts the ones that
|
// counts the ones that differ. The hosted backends draw only the
|
||||||
// differ. A strip of one-pixel stripes in all 16 colours stays on screen,
|
// rectangles that changed at each show; their probes count the frame's
|
||||||
|
// pixels that differ from the stage and the window's that differ from the
|
||||||
|
// whole frame drawn afresh. A strip of one-pixel stripes in all 16 colours stays on screen,
|
||||||
// so every table entry, and both pixels of a byte, are checked; the small
|
// so every table entry, and both pixels of a byte, are checked; the small
|
||||||
// rect is on odd pixels. After P12's steps (tall band, small inner rect,
|
// rect is on odd pixels. After P12's steps (tall band, small inner rect,
|
||||||
// settle, idle) two more work the tables: the strip's rows moved to another
|
// settle, idle) two more work the tables: the strip's rows moved to another
|
||||||
// palette (an SCB change: every row again), then that palette changed
|
// palette (an SCB change: every row again), then that palette changed
|
||||||
// (its rows again; on the whole screen at 8 bits only the screen's
|
// (its rows again; on the whole screen at 8 bits only the screen's
|
||||||
// palette); then the SCBs and the palette go back. Every count must be 0.
|
// palette); then every row a palette of its own, all 16 in use (more than
|
||||||
|
// Win16's identity layout holds, so its fallback and the way back); then
|
||||||
|
// the SCBs and the palette go back; then two rects far
|
||||||
|
// apart in one present (two changed rectangles in one show). Steps B and
|
||||||
|
// Two are the ones the hosted backends draw piecemeal. Every count must be
|
||||||
|
// 0.
|
||||||
#define PROBE_DVX_PALETTE 1u
|
#define PROBE_DVX_PALETTE 1u
|
||||||
#define PROBE_DVX_STRIP_H 10
|
#define PROBE_DVX_STRIP_H 10
|
||||||
#define PROBE_DVX_SCB_LAST 99u
|
#define PROBE_DVX_SCB_LAST 99u
|
||||||
|
|
||||||
static void PROBE_ATTR op_probeDvxDisplayCheck(void) {
|
static void PROBE_ATTR op_probeWindowDisplayCheck(void) {
|
||||||
static const uint16_t probePalette[SURFACE_COLORS_PER_PALETTE] = {
|
static const uint16_t probePalette[JL_SURFACE_COLORS_PER_PALETTE] = {
|
||||||
0x000, 0xF00, 0x0F0, 0x00F, 0xFF0, 0xF0F, 0x0FF, 0xFFF,
|
0x000, 0xF00, 0x0F0, 0x00F, 0xFF0, 0xF0F, 0x0FF, 0xFFF,
|
||||||
0x800, 0x080, 0x008, 0x880, 0x808, 0x088, 0x888, 0x444
|
0x800, 0x080, 0x008, 0x880, 0x808, 0x088, 0x888, 0x444
|
||||||
};
|
};
|
||||||
static const uint16_t changedPalette[SURFACE_COLORS_PER_PALETTE] = {
|
static const uint16_t changedPalette[JL_SURFACE_COLORS_PER_PALETTE] = {
|
||||||
0x000, 0x123, 0x246, 0x369, 0x48C, 0x5AF, 0x6C2, 0x7E5,
|
0x000, 0x123, 0x246, 0x369, 0x48C, 0x5AF, 0x6C2, 0x7E5,
|
||||||
0x9A1, 0xAB4, 0xBC7, 0xCDA, 0xDEC, 0xE1F, 0xF52, 0x3D8
|
0x9A1, 0xAB4, 0xBC7, 0xCDA, 0xDEC, 0xE1F, 0xF52, 0x3D8
|
||||||
};
|
};
|
||||||
uint8_t savedScb[SURFACE_HEIGHT];
|
uint8_t savedScb[JL_SURFACE_HEIGHT];
|
||||||
uint16_t savedPalette[SURFACE_COLORS_PER_PALETTE];
|
uint16_t savedPalette[JL_SURFACE_COLORS_PER_PALETTE];
|
||||||
uint32_t afterB;
|
uint32_t afterB;
|
||||||
uint32_t afterSettle;
|
uint32_t afterSettle;
|
||||||
uint32_t afterIdle;
|
uint32_t afterIdle;
|
||||||
uint32_t afterScb;
|
uint32_t afterScb;
|
||||||
uint32_t afterPalette;
|
uint32_t afterPalette;
|
||||||
|
uint32_t afterAll;
|
||||||
uint32_t afterRestore;
|
uint32_t afterRestore;
|
||||||
|
uint32_t afterTwo;
|
||||||
uint32_t compared;
|
uint32_t compared;
|
||||||
int16_t x;
|
int16_t x;
|
||||||
uint16_t y;
|
uint16_t y;
|
||||||
|
|
||||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
savedScb[y] = jlScbGet(gStage, y);
|
savedScb[y] = jlScbGet(gStage, y);
|
||||||
}
|
}
|
||||||
jlPaletteGet(gStage, (uint8_t)PROBE_DVX_PALETTE, savedPalette);
|
jlPaletteGet(gStage, (uint8_t)PROBE_DVX_PALETTE, savedPalette);
|
||||||
jlPaletteSet(gStage, (uint8_t)PROBE_DVX_PALETTE, probePalette);
|
jlPaletteSet(gStage, (uint8_t)PROBE_DVX_PALETTE, probePalette);
|
||||||
|
// Shown before the steps, so the palette's own full redraw (Win16
|
||||||
|
// repaints its window when colours change) is behind them; and a frame
|
||||||
|
// between B's presents, so each is a show of its own (Win16 shows at
|
||||||
|
// most once a frame, everything changed since the last).
|
||||||
|
jlStagePresent();
|
||||||
|
jlWaitVBL();
|
||||||
|
|
||||||
jlSurfaceClear(gStage, 3);
|
jlSurfaceClear(gStage, 3);
|
||||||
for (x = 0; x < SURFACE_WIDTH; x++) {
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
jlFillRect(gStage, x, 0, 1, PROBE_DVX_STRIP_H, (uint8_t)(x % SURFACE_COLORS_PER_PALETTE));
|
jlFillRect(gStage, x, 0, 1, PROBE_DVX_STRIP_H, (uint8_t)(x % JL_SURFACE_COLORS_PER_PALETTE));
|
||||||
}
|
}
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
|
jlWaitVBL();
|
||||||
jlFillRect(gStage, 0, 50, 320, 100, 3);
|
jlFillRect(gStage, 0, 50, 320, 100, 3);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
|
jlWaitVBL();
|
||||||
jlFillRect(gStage, 101, 61, 37, 9, 9);
|
jlFillRect(gStage, 101, 61, 37, 9, 9);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterB = dvxPortProbeDisplayMismatch(gStage, &compared);
|
afterB = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterSettle = dvxPortProbeDisplayMismatch(gStage, &compared);
|
afterSettle = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterIdle = dvxPortProbeDisplayMismatch(gStage, &compared);
|
afterIdle = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
jlScbSetRange(gStage, 0, PROBE_DVX_SCB_LAST, (uint8_t)PROBE_DVX_PALETTE);
|
jlScbSetRange(gStage, 0, PROBE_DVX_SCB_LAST, (uint8_t)PROBE_DVX_PALETTE);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterScb = dvxPortProbeDisplayMismatch(gStage, &compared);
|
afterScb = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
jlPaletteSet(gStage, (uint8_t)PROBE_DVX_PALETTE, changedPalette);
|
jlPaletteSet(gStage, (uint8_t)PROBE_DVX_PALETTE, changedPalette);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterPalette = dvxPortProbeDisplayMismatch(gStage, &compared);
|
afterPalette = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
|
jlScbSet(gStage, y, (uint8_t)(y % JL_SURFACE_PALETTE_COUNT));
|
||||||
|
}
|
||||||
|
jlStagePresent();
|
||||||
|
afterAll = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
jlScbSet(gStage, y, savedScb[y]);
|
jlScbSet(gStage, y, savedScb[y]);
|
||||||
}
|
}
|
||||||
jlPaletteSet(gStage, (uint8_t)PROBE_DVX_PALETTE, savedPalette);
|
jlPaletteSet(gStage, (uint8_t)PROBE_DVX_PALETTE, savedPalette);
|
||||||
jlStagePresent();
|
jlStagePresent();
|
||||||
afterRestore = dvxPortProbeDisplayMismatch(gStage, &compared);
|
afterRestore = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
jlLogF("UBER-PROBE: dvxDisplayMismatch afterB=%lu afterSettle=%lu afterIdle=%lu afterScb=%lu afterPalette=%lu afterRestore=%lu compared=%lu", (unsigned long)afterB, (unsigned long)afterSettle, (unsigned long)afterIdle, (unsigned long)afterScb, (unsigned long)afterPalette, (unsigned long)afterRestore, (unsigned long)compared);
|
jlFillRect(gStage, 7, 21, 13, 5, 12);
|
||||||
|
jlFillRect(gStage, 251, 171, 31, 17, 6);
|
||||||
|
jlStagePresent();
|
||||||
|
afterTwo = PROBE_WINDOW_MISMATCH(gStage, &compared);
|
||||||
|
jlLogF("UBER-PROBE: " PROBE_WINDOW_TAG " afterB=%lu afterSettle=%lu afterIdle=%lu afterScb=%lu afterPalette=%lu afterAll=%lu afterRestore=%lu afterTwo=%lu compared=%lu", (unsigned long)afterB, (unsigned long)afterSettle, (unsigned long)afterIdle, (unsigned long)afterScb, (unsigned long)afterPalette, (unsigned long)afterAll, (unsigned long)afterRestore, (unsigned long)afterTwo, (unsigned long)compared);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -1218,8 +1259,7 @@ static void buildBallSprite(void) {
|
||||||
dst = &gBallTiles[(ty * BALL_TILES_X + tx) * 32u];
|
dst = &gBallTiles[(ty * BALL_TILES_X + tx) * 32u];
|
||||||
for (row = 0; row < 8; row++) {
|
for (row = 0; row < 8; row++) {
|
||||||
for (b = 0; b < 4; b++) {
|
for (b = 0; b < 4; b++) {
|
||||||
dst[row * 4 + b] =
|
dst[row * 4 + b] = gBallAuthored[((ty * 8) + row) * 8 + (tx * 4) + b];
|
||||||
gBallAuthored[((ty * 8) + row) * 8 + (tx * 4) + b];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1465,7 +1505,7 @@ static void DRAWSHOWCASE_ATTR drawShowcase(void) {
|
||||||
// the headless perf-capture run still completes without a keypress.
|
// the headless perf-capture run still completes without a keypress.
|
||||||
held = jlFrameCount();
|
held = jlFrameCount();
|
||||||
while ((uint16_t)(jlFrameCount() - held) < SC_HOLD_FRAMES) {
|
while ((uint16_t)(jlFrameCount() - held) < SC_HOLD_FRAMES) {
|
||||||
/* hold */
|
// hold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1556,7 +1596,7 @@ static void __attribute__((noinline)) checkTileMonoFlood(void) {
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
uberMbOp(103);
|
uberMbOp(103);
|
||||||
for (i = 0; i < TILE_BYTES; i++) {
|
for (i = 0; i < JL_TILE_BYTES; i++) {
|
||||||
mono.pixels[i] = (uint8_t)((i & 1) ? 0x0F : 0xF0);
|
mono.pixels[i] = (uint8_t)((i & 1) ? 0x0F : 0xF0);
|
||||||
}
|
}
|
||||||
jlTilePasteMono(gStage, 10, 10, &mono, 5, 9);
|
jlTilePasteMono(gStage, 10, 10, &mono, 5, 9);
|
||||||
|
|
@ -1574,7 +1614,7 @@ static void __attribute__((noinline)) checkDrawText(void) {
|
||||||
|
|
||||||
uberMbOp(104);
|
uberMbOp(104);
|
||||||
for (i = 0; i < 256u; i++) {
|
for (i = 0; i < 256u; i++) {
|
||||||
asciiMap[i] = TILE_NO_GLYPH;
|
asciiMap[i] = JL_TILE_NO_GLYPH;
|
||||||
}
|
}
|
||||||
// 'A' -> tile (5,5), 'B' -> tile (6,6). Seed both glyph tiles here --
|
// 'A' -> tile (5,5), 'B' -> tile (6,6). Seed both glyph tiles here --
|
||||||
// the section-opening surface clear wiped whatever the timed tile ops
|
// the section-opening surface clear wiped whatever the timed tile ops
|
||||||
|
|
@ -1715,7 +1755,7 @@ static void __attribute__((noinline)) checkTileMonoFloodExtra(void) {
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
||||||
uberMbOp(121);
|
uberMbOp(121);
|
||||||
for (i = 0; i < TILE_BYTES; i++) {
|
for (i = 0; i < JL_TILE_BYTES; i++) {
|
||||||
monoAll.pixels[i] = comboBytes[i & 3u];
|
monoAll.pixels[i] = comboBytes[i & 3u];
|
||||||
}
|
}
|
||||||
jlTilePasteMono(gStage, 10, 12, &monoAll, 5, 9);
|
jlTilePasteMono(gStage, 10, 12, &monoAll, 5, 9);
|
||||||
|
|
@ -2016,8 +2056,8 @@ static void __attribute__((noinline)) runAllTests(void) {
|
||||||
#ifdef JOEYLIB_PLATFORM_DOS
|
#ifdef JOEYLIB_PLATFORM_DOS
|
||||||
op_probeVgaCheck();
|
op_probeVgaCheck();
|
||||||
#endif
|
#endif
|
||||||
#ifdef JOEYLIB_PLATFORM_DVX
|
#if defined(JOEYLIB_PLATFORM_DVX) || defined(JOEYLIB_HOSTED_WIN16) || defined(JOEYLIB_HOSTED_WIN32) || defined(JOEYLIB_HOSTED_BE)
|
||||||
op_probeDvxDisplayCheck();
|
op_probeWindowDisplayCheck();
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -2057,8 +2097,7 @@ static void __attribute__((noinline)) reportElapsed(uint16_t startFrame) {
|
||||||
endFrame = jlFrameCount();
|
endFrame = jlFrameCount();
|
||||||
elapsedFrames = (uint16_t)(endFrame - startFrame);
|
elapsedFrames = (uint16_t)(endFrame - startFrame);
|
||||||
elapsedMs = ((unsigned long)elapsedFrames * 1000UL) / (unsigned long)jlFrameHz();
|
elapsedMs = ((unsigned long)elapsedFrames * 1000UL) / (unsigned long)jlFrameHz();
|
||||||
jlLogF("UBER: total wall time: %lu ms (%u frames @ %u Hz)",
|
jlLogF("UBER: total wall time: %lu ms (%u frames @ %u Hz)", elapsedMs, elapsedFrames, (unsigned)jlFrameHz());
|
||||||
elapsedMs, elapsedFrames, (unsigned)jlFrameHz());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2080,7 +2119,7 @@ static bool __attribute__((noinline)) setupSprite(void) {
|
||||||
jlLog("UBER: jlSpriteCompile failed");
|
jlLog("UBER: jlSpriteCompile failed");
|
||||||
}
|
}
|
||||||
while (jlFrameCount() == before) {
|
while (jlFrameCount() == before) {
|
||||||
/* wait for next VBL edge */
|
// wait for next VBL edge
|
||||||
}
|
}
|
||||||
jlLogF("UBER: jlSpriteCompile: 1 call in <= 1 frame");
|
jlLogF("UBER: jlSpriteCompile: 1 call in <= 1 frame");
|
||||||
gBackup.bytes = gBackupBytes;
|
gBackup.bytes = gBackupBytes;
|
||||||
|
|
@ -2099,6 +2138,8 @@ static bool __attribute__((noinline)) initJoeyLib(void) {
|
||||||
* thunk+table DRAWs ~12 KB; plus ~1-2 KB of save/restore classes).
|
* thunk+table DRAWs ~12 KB; plus ~1-2 KB of save/restore classes).
|
||||||
* UL on the multiply because a 16-bit int overflows on 32 * 1024. */
|
* UL on the multiply because a 16-bit int overflows on 32 * 1024. */
|
||||||
config.codegenBytes = 32UL * 1024;
|
config.codegenBytes = 32UL * 1024;
|
||||||
|
// Audio on: the timed run measures every op with the engine running.
|
||||||
|
config.noAudio = false;
|
||||||
return jlInit(&config);
|
return jlInit(&config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2138,21 +2179,15 @@ int main(void) {
|
||||||
*UBER_MB_SPRITEPTR = (uint32_t)gSprite;
|
*UBER_MB_SPRITEPTR = (uint32_t)gSprite;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Audio: only init/shutdown is exercised. Triggering jlAudioPlaySfx
|
// Audio: jlInit started the engine; nothing is played. Triggering
|
||||||
// without first calling jlAudioPlayMod leaves NTP's engine in a
|
// jlAudioPlaySfx without first calling jlAudioPlayMod leaves NTP's
|
||||||
// half-initialized state -- NTPstreamsound is designed to OVERLAY on
|
// engine in a half-initialized state -- NTPstreamsound is designed to
|
||||||
// an already-running module. Without NTPprepare/NTPplay first, the
|
// OVERLAY on an already-running module. Without NTPprepare/NTPplay
|
||||||
// streamer oscillator is fired but no music tick ever advances or
|
// first, the streamer oscillator is fired but no music tick ever
|
||||||
// silences it, and you get a stuck high-pitched scream. UBER doesn't
|
// advances or silences it, and you get a stuck high-pitched scream.
|
||||||
// ship a MOD asset, so we skip the SFX exercise. The frame-tick and
|
// UBER doesn't ship a MOD asset, so we skip the SFX exercise. The
|
||||||
// isPlayingMod calls below still get timed (both are no-op fast
|
// frame-tick and isPlayingMod calls below still get timed (both are
|
||||||
// paths on IIgs).
|
// no-op fast paths on IIgs).
|
||||||
uberMbPhase(UBER_PHASE_AUDIO_INIT);
|
|
||||||
if (jlAudioInit()) {
|
|
||||||
jlLogF("UBER: audioInit OK");
|
|
||||||
} else {
|
|
||||||
jlLogF("UBER: audioInit failed (skipping audio)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visual showcase: render one of each primitive into its own grid
|
// Visual showcase: render one of each primitive into its own grid
|
||||||
// cell so the run shows something legible (the timed benchmark below
|
// cell so the run shows something legible (the timed benchmark below
|
||||||
|
|
|
||||||
229
examples/wblaunch/wblaunch.c
Normal file
229
examples/wblaunch/wblaunch.c
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
// See LICENSE.md for license information.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Workbench probe (WBLAUNCH, a gate's program, not an example, and the
|
||||||
|
// Amiga's alone): a program's icon read and drawn by the ROM's icon.library,
|
||||||
|
// and the program started from it the way Workbench starts a tool.
|
||||||
|
//
|
||||||
|
// Wblaunch FOLDER NAME
|
||||||
|
//
|
||||||
|
// GetDiskObject reads FOLDER/NAME.info; its type, stack and image are
|
||||||
|
// logged, and the image is drawn at ICON_X,ICON_Y on a hires screen of
|
||||||
|
// Workbench's four colours, with the mouse pointer hidden, held HOLD_TICKS
|
||||||
|
// for a screenshot. Then
|
||||||
|
// FOLDER/NAME is started as Workbench starts a tool -- LoadSeg, CreateProc
|
||||||
|
// with the icon's stack, and a WBStartup message whose one argument is the
|
||||||
|
// folder's lock and the program's name -- from a current directory that is
|
||||||
|
// not FOLDER (AWAY_FOLDER), and the message coming back says the program
|
||||||
|
// has ended. Each step is a "WBLAUNCH:" line of LOG_NAME, beside Wblaunch.
|
||||||
|
// scripts/verify-amiga-icon.sh is the gate.
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <dos/dos.h>
|
||||||
|
#include <dos/dosextens.h>
|
||||||
|
#include <exec/memory.h>
|
||||||
|
#include <exec/ports.h>
|
||||||
|
#include <graphics/modeid.h>
|
||||||
|
#include <intuition/intuition.h>
|
||||||
|
#include <intuition/screens.h>
|
||||||
|
#include <workbench/startup.h>
|
||||||
|
#include <workbench/workbench.h>
|
||||||
|
|
||||||
|
#include <proto/dos.h>
|
||||||
|
#include <proto/exec.h>
|
||||||
|
#include <proto/graphics.h>
|
||||||
|
#include <proto/icon.h>
|
||||||
|
#include <proto/intuition.h>
|
||||||
|
|
||||||
|
#define LOG_NAME "wblaunch.txt"
|
||||||
|
#define AWAY_FOLDER "RAM:"
|
||||||
|
#define PATH_BYTES 256u
|
||||||
|
#define SCREEN_W 640
|
||||||
|
#define SCREEN_H STDSCREENHEIGHT
|
||||||
|
#define SCREEN_DEPTH 2
|
||||||
|
#define ICON_X 64
|
||||||
|
#define ICON_Y 64
|
||||||
|
#define HOLD_TICKS (5 * TICKS_PER_SECOND)
|
||||||
|
#define GUN_MASK 0xFu
|
||||||
|
#define RED_SHIFT 8u
|
||||||
|
#define GREEN_SHIFT 4u
|
||||||
|
#define ARGS_WANTED 3
|
||||||
|
// A blank pointer, in chip memory: its two control words, one empty line of
|
||||||
|
// two words, and the two words that end it.
|
||||||
|
#define BLANK_POINTER_BYTES (6u * sizeof(UWORD))
|
||||||
|
#define BLANK_POINTER_H 1
|
||||||
|
#define BLANK_POINTER_W 16
|
||||||
|
|
||||||
|
static bool drawIcon(struct DiskObject *icon);
|
||||||
|
static bool launch(const char *folder, const char *name, ULONG stack);
|
||||||
|
static void logLine(const char *format, ...);
|
||||||
|
|
||||||
|
// Workbench's four colours (2.0 on), 4 bits a gun: grey, black, white and
|
||||||
|
// blue (tools/iconBake.py's AMIGA_COLOURS).
|
||||||
|
static const UWORD gWorkbenchColours[] = { 0xAAA, 0x000, 0xFFF, 0x68B };
|
||||||
|
|
||||||
|
static FILE *gLog = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
// The icon's image on a screen of Workbench's colours, held for the gate's
|
||||||
|
// screenshot. A window over the whole screen, active, hides the pointer.
|
||||||
|
static bool drawIcon(struct DiskObject *icon) {
|
||||||
|
struct Screen *screen;
|
||||||
|
struct Window *window;
|
||||||
|
UWORD *blank;
|
||||||
|
UWORD i;
|
||||||
|
bool drawn;
|
||||||
|
|
||||||
|
drawn = false;
|
||||||
|
blank = (UWORD *)AllocMem(BLANK_POINTER_BYTES, MEMF_CHIP | MEMF_CLEAR);
|
||||||
|
screen = OpenScreenTags(NULL, SA_Width, SCREEN_W, SA_Height, SCREEN_H, SA_Depth, SCREEN_DEPTH, SA_DisplayID, HIRES_KEY, SA_Quiet, TRUE, SA_ShowTitle, FALSE, TAG_DONE);
|
||||||
|
window = NULL;
|
||||||
|
if (screen != NULL) {
|
||||||
|
window = OpenWindowTags(NULL, WA_CustomScreen, (ULONG)screen, WA_Backdrop, TRUE, WA_Borderless, TRUE, WA_Activate, TRUE, WA_RMBTrap, TRUE, TAG_DONE);
|
||||||
|
}
|
||||||
|
if (blank == NULL || window == NULL) {
|
||||||
|
logLine("FAIL cannot open a screen and a window");
|
||||||
|
} else {
|
||||||
|
SetPointer(window, blank, BLANK_POINTER_H, BLANK_POINTER_W, 0, 0);
|
||||||
|
for (i = 0; i < sizeof(gWorkbenchColours) / sizeof(gWorkbenchColours[0]); i++) {
|
||||||
|
SetRGB4(&screen->ViewPort, i, (gWorkbenchColours[i] >> RED_SHIFT) & GUN_MASK, (gWorkbenchColours[i] >> GREEN_SHIFT) & GUN_MASK, gWorkbenchColours[i] & GUN_MASK);
|
||||||
|
}
|
||||||
|
SetRast(window->RPort, 0);
|
||||||
|
DrawImage(window->RPort, (struct Image *)icon->do_Gadget.GadgetRender, ICON_X, ICON_Y);
|
||||||
|
logLine("drawn at %d,%d", ICON_X, ICON_Y);
|
||||||
|
Delay(HOLD_TICKS);
|
||||||
|
ClearPointer(window);
|
||||||
|
drawn = true;
|
||||||
|
}
|
||||||
|
if (window != NULL) {
|
||||||
|
CloseWindow(window);
|
||||||
|
}
|
||||||
|
if (screen != NULL) {
|
||||||
|
CloseScreen(screen);
|
||||||
|
}
|
||||||
|
if (blank != NULL) {
|
||||||
|
FreeMem(blank, BLANK_POINTER_BYTES);
|
||||||
|
}
|
||||||
|
return drawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FOLDER/NAME started as Workbench starts a tool, from AWAY_FOLDER; true
|
||||||
|
// once its message has come back.
|
||||||
|
static bool launch(const char *folder, const char *name, ULONG stack) {
|
||||||
|
struct WBStartup startup;
|
||||||
|
struct WBArg arg;
|
||||||
|
struct MsgPort *reply;
|
||||||
|
struct MsgPort *process;
|
||||||
|
char path[PATH_BYTES];
|
||||||
|
BPTR lock;
|
||||||
|
BPTR away;
|
||||||
|
BPTR before;
|
||||||
|
BPTR segment;
|
||||||
|
|
||||||
|
strncpy(path, folder, sizeof(path) - 1u);
|
||||||
|
path[sizeof(path) - 1u] = '\0';
|
||||||
|
if (!AddPart((STRPTR)path, (STRPTR)name, sizeof(path))) {
|
||||||
|
logLine("FAIL the path is too long");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lock = Lock((STRPTR)folder, SHARED_LOCK);
|
||||||
|
if (lock == 0) {
|
||||||
|
logLine("FAIL cannot lock %s", folder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
segment = LoadSeg((STRPTR)path);
|
||||||
|
if (segment == 0) {
|
||||||
|
logLine("FAIL cannot load %s", path);
|
||||||
|
UnLock(lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reply = CreateMsgPort();
|
||||||
|
away = Lock((STRPTR)AWAY_FOLDER, SHARED_LOCK);
|
||||||
|
if (reply == NULL || away == 0) {
|
||||||
|
logLine("FAIL no reply port or no %s", AWAY_FOLDER);
|
||||||
|
UnLoadSeg(segment);
|
||||||
|
UnLock(lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
before = CurrentDir(away);
|
||||||
|
process = CreateProc((STRPTR)name, 0, segment, (LONG)stack);
|
||||||
|
if (process == NULL) {
|
||||||
|
logLine("FAIL cannot start %s", path);
|
||||||
|
} else {
|
||||||
|
memset(&startup, 0, sizeof(startup));
|
||||||
|
startup.sm_Message.mn_Node.ln_Type = NT_MESSAGE;
|
||||||
|
startup.sm_Message.mn_ReplyPort = reply;
|
||||||
|
startup.sm_Message.mn_Length = sizeof(startup);
|
||||||
|
startup.sm_Process = process;
|
||||||
|
startup.sm_Segment = segment;
|
||||||
|
startup.sm_NumArgs = 1;
|
||||||
|
startup.sm_ArgList = &arg;
|
||||||
|
arg.wa_Lock = lock;
|
||||||
|
arg.wa_Name = (STRPTR)name;
|
||||||
|
logLine("started %s with %lu bytes of stack from %s", path, (unsigned long)stack, AWAY_FOLDER);
|
||||||
|
PutMsg(process, &startup.sm_Message);
|
||||||
|
WaitPort(reply);
|
||||||
|
GetMsg(reply);
|
||||||
|
}
|
||||||
|
CurrentDir(before);
|
||||||
|
UnLock(away);
|
||||||
|
DeleteMsgPort(reply);
|
||||||
|
UnLoadSeg(segment);
|
||||||
|
UnLock(lock);
|
||||||
|
return process != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void logLine(const char *format, ...) {
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
fputs("WBLAUNCH: ", gLog);
|
||||||
|
va_start(args, format);
|
||||||
|
vfprintf(gLog, format, args);
|
||||||
|
va_end(args);
|
||||||
|
fputc('\n', gLog);
|
||||||
|
fflush(gLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
struct DiskObject *icon;
|
||||||
|
struct Image *image;
|
||||||
|
char path[PATH_BYTES];
|
||||||
|
int status;
|
||||||
|
|
||||||
|
gLog = fopen(LOG_NAME, "w");
|
||||||
|
if (gLog == NULL) {
|
||||||
|
return RETURN_FAIL;
|
||||||
|
}
|
||||||
|
status = RETURN_FAIL;
|
||||||
|
if (argc != ARGS_WANTED) {
|
||||||
|
logLine("FAIL usage: Wblaunch FOLDER NAME");
|
||||||
|
} else if (IconBase == NULL) {
|
||||||
|
logLine("FAIL no icon.library");
|
||||||
|
} else {
|
||||||
|
strncpy(path, argv[1], sizeof(path) - 1u);
|
||||||
|
path[sizeof(path) - 1u] = '\0';
|
||||||
|
AddPart((STRPTR)path, (STRPTR)argv[2], sizeof(path));
|
||||||
|
icon = GetDiskObject((STRPTR)path);
|
||||||
|
if (icon == NULL) {
|
||||||
|
logLine("FAIL icon.library cannot read %s.info", path);
|
||||||
|
} else {
|
||||||
|
image = (struct Image *)icon->do_Gadget.GadgetRender;
|
||||||
|
logLine("icon type=%u stack=%ld gadget=%dx%d image=%dx%dx%d", (unsigned)icon->do_Type, (long)icon->do_StackSize, icon->do_Gadget.Width, icon->do_Gadget.Height, image->Width, image->Height, image->Depth);
|
||||||
|
if (drawIcon(icon) && launch(argv[1], argv[2], (ULONG)icon->do_StackSize)) {
|
||||||
|
logLine("returned");
|
||||||
|
status = RETURN_OK;
|
||||||
|
}
|
||||||
|
FreeDiskObject(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(gLog);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
@ -11,13 +11,19 @@
|
||||||
// Apple IIgs -- NinjaTrackerPlus .NTP, played by Ninjaforce's
|
// Apple IIgs -- NinjaTrackerPlus .NTP, played by Ninjaforce's
|
||||||
// 65816 replayer linked into the binary.
|
// 65816 replayer linked into the binary.
|
||||||
// Amiga -- raw .MOD, played by Frank Wille's PTPlayer.
|
// Amiga -- raw .MOD, played by Frank Wille's PTPlayer.
|
||||||
// DOS -- raw .MOD, played by libxmp-lite over SB DMA.
|
// DOS -- raw .MOD, mixed by JoeyLib's module player (PTPlayer's
|
||||||
// Atari ST -- raw .MOD, played by libxmp-lite (parser only) plus
|
// replay in C) into SB DMA (22 kHz mono 8-bit).
|
||||||
// a hand-rolled 68k 4-channel mixer that outputs via
|
// Atari ST -- raw .MOD, mixed by the module player at 12288 Hz and
|
||||||
// YM2149 4-bit PWM at ~12.5 kHz.
|
// played through the YM2149's channel A volume (PWM); the
|
||||||
|
// STe build plays it through DMA sound at 12517 Hz.
|
||||||
|
// X68000 -- raw .MOD, mixed by the module player at 7812 Hz into the
|
||||||
|
// MSM6258 ADPCM channel.
|
||||||
|
// DVX -- raw .MOD through DVX's music library.
|
||||||
|
// hosted -- raw .MOD, mixed by the module player in software.
|
||||||
//
|
//
|
||||||
// Game code always calls the same five entry points; the per-port
|
// Game code always calls the same entry points; the per-port HAL hides
|
||||||
// HAL hides the engine choice. A failed audio init is non-fatal --
|
// the engine choice. jlInit starts audio (jlConfigT.noAudio leaves it
|
||||||
|
// off) and jlShutdown stops it. Sound that does not start is non-fatal:
|
||||||
// jlInit still succeeds and audio calls become no-ops.
|
// jlInit still succeeds and audio calls become no-ops.
|
||||||
|
|
||||||
#ifndef JOEYLIB_AUDIO_H
|
#ifndef JOEYLIB_AUDIO_H
|
||||||
|
|
@ -26,36 +32,31 @@
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// SFX slot convention: the IIgs maps tracker tone voices onto SFX
|
// SFX slot convention: the IIgs plays tone voices 0-2 on SFX slots 0-2
|
||||||
// slots 0-2 and the tracker noise voice onto slot 3, so applications
|
// and the noise voice on slot 3 (a voice and an effect on one slot cut
|
||||||
// that also play digital SFX while music runs should prefer slot 4.
|
// each other off, and a held voice takes its slot back at the next
|
||||||
// On the other ports the slots are independent of the voice layer.
|
// jlAudioFrameTick), so applications that also play digital SFX while
|
||||||
#define JOEY_AUDIO_SFX_SLOTS 5
|
// music runs should prefer slot 4. On the Amiga effects, the module, the
|
||||||
|
// voices, and the noise share Paula's four channels. On the other ports
|
||||||
|
// the slots are independent of the voice layer.
|
||||||
|
#define JL_AUDIO_SFX_SLOTS 5
|
||||||
|
|
||||||
// Number of PSG-style polyphonic tone voices (jlAudioVoice selects
|
// Number of PSG-style polyphonic tone voices (jlAudioVoice selects
|
||||||
// voice 0/1/2). The core gate range-checks against this so each
|
// voice 0/1/2). The core gate range-checks against this so each
|
||||||
// per-port HAL can assume voice < JOEY_AUDIO_VOICES.
|
// per-port HAL can assume voice < JL_AUDIO_VOICES.
|
||||||
#define JOEY_AUDIO_VOICES 3
|
#define JL_AUDIO_VOICES 3
|
||||||
|
|
||||||
// Initialize audio. Returns true if the platform has a working audio
|
|
||||||
// engine and was able to start it. Returns false silently otherwise;
|
|
||||||
// the rest of the API stays callable but produces no sound.
|
|
||||||
bool jlAudioInit(void);
|
|
||||||
|
|
||||||
// Tear down the engine. Safe to call when audio is not initialized.
|
|
||||||
void jlAudioShutdown(void);
|
|
||||||
|
|
||||||
// Begin module playback. data points at the platform-native module
|
// Begin module playback. data points at the platform-native module
|
||||||
// blob (.MOD on most platforms, .NTP on IIgs); the asset pipeline
|
// blob (.MOD on every platform but the IIgs, .NTP there); the asset pipeline
|
||||||
// produces the right form for each target. If a module is already
|
// produces the right form for each target. If a module is already
|
||||||
// playing, it is replaced.
|
// playing, it is replaced.
|
||||||
//
|
//
|
||||||
// loop=true plays forever. loop=false stops at song end, but Amiga
|
// loop=true plays forever. loop=false stops at song end on every port.
|
||||||
// requires the module to contain an E8FF effect at song end for the
|
// PTPlayer (Amiga) has no native song-end signal, so the Amiga HAL adds an
|
||||||
// stop to fire (PTPlayer has no native song-end signal). The
|
// E8F stop marker to the module as it copies it to chip RAM; a module
|
||||||
// `joeymod` tool's .amod output extension injects that marker
|
// with no room for one (all 128 song positions or patterns in use) loops
|
||||||
// automatically; ship .amod for Amiga and .mod for the other ports.
|
// there anyway. The `joeymod` tool's .amod output adds the same marker
|
||||||
// loop=false on a .mod (no E8FF) loops anyway on Amiga.
|
// ahead of time.
|
||||||
void jlAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
void jlAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
||||||
|
|
||||||
// Stop the current module (if any). The playhead is reset so the next
|
// Stop the current module (if any). The playhead is reset so the next
|
||||||
|
|
@ -63,15 +64,18 @@ void jlAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
||||||
void jlAudioStopMod(void);
|
void jlAudioStopMod(void);
|
||||||
|
|
||||||
// True if a module is currently producing output (false during silence
|
// True if a module is currently producing output (false during silence
|
||||||
// after StopMod or before the first PlayMod, and on platforms where
|
// after StopMod, once a loop=false song has ended, before the first
|
||||||
// audio init failed).
|
// PlayMod, and on platforms where audio init failed).
|
||||||
bool jlAudioIsPlayingMod(void);
|
bool jlAudioIsPlayingMod(void);
|
||||||
|
|
||||||
// Trigger a one-shot digital SFX on the given slot (0..JOEY_AUDIO_SFX_SLOTS-1).
|
// Trigger a one-shot digital SFX on the given slot (0..JL_AUDIO_SFX_SLOTS-1).
|
||||||
// sample points at raw signed 8-bit PCM. rateHz is the playback rate
|
// sample points at raw signed 8-bit PCM. rateHz is the playback rate
|
||||||
// the sample was recorded at; the engine pitches as needed for its
|
// the sample was recorded at; the engine pitches as needed for its
|
||||||
// own output rate. If the slot is currently playing, the new sample
|
// own output rate. If the slot is currently playing, the new sample
|
||||||
// replaces it.
|
// replaces it. Most ports read the sample in place while it plays, so it
|
||||||
|
// must stay valid and unchanged until it has finished or the slot is
|
||||||
|
// stopped; the IIgs keeps a copy per slot keyed by address, length, and
|
||||||
|
// rate, so changed bytes replayed with all three the same are not heard.
|
||||||
//
|
//
|
||||||
// The full -128..127 range is accepted everywhere, but the IIgs plays
|
// The full -128..127 range is accepted everywhere, but the IIgs plays
|
||||||
// -128 as -127: its DOC reads 0x00 (which is what -128 becomes in the
|
// -128 as -127: its DOC reads 0x00 (which is what -128 becomes in the
|
||||||
|
|
@ -83,9 +87,10 @@ void jlAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16
|
||||||
// streaming slot's prefetch buffer; should write up to `count` signed
|
// streaming slot's prefetch buffer; should write up to `count` signed
|
||||||
// 8-bit samples to `dst` and return the number written (see
|
// 8-bit samples to `dst` and return the number written (see
|
||||||
// jlAudioPlaySfx for the one clamp the IIgs applies). Returning 0
|
// jlAudioPlaySfx for the one clamp the IIgs applies). Returning 0
|
||||||
// signals end-of-stream and the slot auto-deactivates. Called from
|
// signals end-of-stream and the slot auto-deactivates. Called in
|
||||||
// the same context as the audio engine's refill (main thread on
|
// program time on every port, from the call that starts the stream and
|
||||||
// DOS/ST, audio ISR on Amiga/IIgs); must not block or allocate.
|
// then from jlAudioFrameTick (on DVX also while the desktop runs); must
|
||||||
|
// not block, and should be quick.
|
||||||
typedef uint32_t (*jlAudioStreamFillT)(void *ctx, int8_t *dst, uint32_t count);
|
typedef uint32_t (*jlAudioStreamFillT)(void *ctx, int8_t *dst, uint32_t count);
|
||||||
|
|
||||||
// Trigger streaming SFX playback on the given slot. The mixer pulls
|
// Trigger streaming SFX playback on the given slot. The mixer pulls
|
||||||
|
|
@ -94,22 +99,22 @@ typedef uint32_t (*jlAudioStreamFillT)(void *ctx, int8_t *dst, uint32_t count);
|
||||||
// allocation. `rateHz` is the sample rate `fill` produces; the
|
// allocation. `rateHz` is the sample rate `fill` produces; the
|
||||||
// engine resamples (with linear interpolation) to its own output
|
// engine resamples (with linear interpolation) to its own output
|
||||||
// rate. If the slot is currently playing, the new stream replaces
|
// rate. If the slot is currently playing, the new stream replaces
|
||||||
// it. Implemented on every port: DOS/ST/X68000 pull through the shared
|
// it. Implemented on every port: DOS/ST/X68000/hosted pull through the
|
||||||
// SFX overlay mixer, while the IIgs (NTPstreamsound) and Amiga
|
// shared SFX overlay mixer as jlAudioFrameTick refills their sound
|
||||||
// (PTPlayer mt_playfx) play one chunk at a time and fire the next from
|
// buffers, while the IIgs (NTPstreamsound, 7 KB chunks) and Amiga
|
||||||
// jlAudioFrameTick as the current one runs down -- so on those two the
|
// (PTPlayer mt_playfx, 4 KB chunks) play one chunk at a time and fire the
|
||||||
// host must be calling jlAudioFrameTick for a stream to continue past
|
// next from jlAudioFrameTick as the current one runs down -- so the host
|
||||||
// its first chunk, and a seam can carry up to one frame of jitter.
|
// must be calling jlAudioFrameTick for a stream to continue, and on those
|
||||||
|
// two a seam can carry up to one frame of jitter.
|
||||||
void jlAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
|
void jlAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
|
||||||
|
|
||||||
// Raw-format streaming: jlAudioPlaySfxStream, except `fill` delivers
|
// Raw-format streaming: jlAudioPlaySfxStream, except `fill` delivers
|
||||||
// bytes already in the port's raw stream format (jlAudioSfxRawFormat),
|
// bytes already in the port's raw stream format (jlAudioSfxRawFormat),
|
||||||
// so the port skips its per-byte conversion of every chunk. On the IIgs
|
// so the port skips its per-byte conversion of every chunk. On the IIgs
|
||||||
// that conversion is a 65816 byte loop over each 3.5 KB chunk -- it
|
// that conversion costs ~7 us a byte, ~50 ms per 7 KB chunk. Every other
|
||||||
// stalled Space Taxi's ride for a third of a second per chunk of speech.
|
// port's raw format is signed PCM, so there Raw is the plain call (DVX
|
||||||
// Every other port streams signed PCM natively, so there Raw is the
|
// still flips each byte for its sound library). A producer that cannot
|
||||||
// plain call. A producer that cannot make the port's format uses
|
// make the port's format uses jlAudioPlaySfxStream.
|
||||||
// jlAudioPlaySfxStream.
|
|
||||||
#define JL_SFX_RAW_SIGNED8 0u // int8_t PCM, the jlAudioPlaySfxStream contract
|
#define JL_SFX_RAW_SIGNED8 0u // int8_t PCM, the jlAudioPlaySfxStream contract
|
||||||
#define JL_SFX_RAW_UNSIGNED8_NOZERO 1u // uint8_t PCM biased 0x80; 0x00 is forbidden (DOC stop byte)
|
#define JL_SFX_RAW_UNSIGNED8_NOZERO 1u // uint8_t PCM biased 0x80; 0x00 is forbidden (DOC stop byte)
|
||||||
uint8_t jlAudioSfxRawFormat(void);
|
uint8_t jlAudioSfxRawFormat(void);
|
||||||
|
|
@ -117,14 +122,14 @@ void jlAudioPlaySfxStreamRaw(uint8_t slot, jlAudioStreamFillT fill, void *ctx
|
||||||
|
|
||||||
// Stereo placement for a SFX slot.
|
// Stereo placement for a SFX slot.
|
||||||
typedef enum {
|
typedef enum {
|
||||||
AUDIO_PAN_LEFT = 0,
|
JL_AUDIO_PAN_LEFT = 0,
|
||||||
AUDIO_PAN_CENTER,
|
JL_AUDIO_PAN_CENTER,
|
||||||
AUDIO_PAN_RIGHT,
|
JL_AUDIO_PAN_RIGHT,
|
||||||
|
|
||||||
AUDIO_PAN_COUNT
|
JL_AUDIO_PAN_COUNT
|
||||||
} jlAudioPanE;
|
} jlAudioPanE;
|
||||||
|
|
||||||
// Place a SFX slot in the stereo field. Slots start at AUDIO_PAN_CENTER, and
|
// Place a SFX slot in the stereo field. Slots start at JL_AUDIO_PAN_CENTER, and
|
||||||
// the setting sticks to the SLOT, so it survives retriggers -- set it once
|
// the setting sticks to the SLOT, so it survives retriggers -- set it once
|
||||||
// when a sound is meant to come from one side.
|
// when a sound is meant to come from one side.
|
||||||
//
|
//
|
||||||
|
|
@ -135,26 +140,26 @@ typedef enum {
|
||||||
// A stock IIgs sums to mono anyway; this only shows up on a stereo
|
// A stock IIgs sums to mono anyway; this only shows up on a stereo
|
||||||
// card or an emulator.
|
// card or an emulator.
|
||||||
// Amiga left and right, by picking the Paula channel side (0 and 3 are
|
// Amiga left and right, by picking the Paula channel side (0 and 3 are
|
||||||
// left, 1 and 2 right). Paula cannot place one voice in both ears,
|
// left, 1 and 2 right). A channel plays on one side only, so a
|
||||||
// so AUDIO_PAN_CENTER keeps PTPlayer's own channel choice.
|
// centred sound plays on a free channel each side when both sides
|
||||||
|
// have one, and otherwise on the one channel PTPlayer picks.
|
||||||
// X68000 ignored. Everything is mixed into ONE MSM6258 ADPCM stream, so
|
// X68000 ignored. Everything is mixed into ONE MSM6258 ADPCM stream, so
|
||||||
// its pan bits would move all audio at once, not one slot.
|
// its pan bits would move all audio at once, not one slot.
|
||||||
// DOS, ST ignored -- their output is mono.
|
// DOS, ST ignored -- their output is mono (the STe build's too).
|
||||||
|
// DVX all three, through DVX's stereo mixer.
|
||||||
|
// hosted all three (the mix is stereo), except Win16, which is mono.
|
||||||
// Passing an out-of-range pan is ignored.
|
// Passing an out-of-range pan is ignored.
|
||||||
void jlAudioSetPan(uint8_t slot, jlAudioPanE pan);
|
void jlAudioSetPan(uint8_t slot, jlAudioPanE pan);
|
||||||
|
|
||||||
// Stop a SFX slot early. No-op if the slot is already idle.
|
// Stop a SFX slot early. No-op if the slot is already idle.
|
||||||
void jlAudioStopSfx(uint8_t slot);
|
void jlAudioStopSfx(uint8_t slot);
|
||||||
|
|
||||||
// Direct hardware tone generator: continuously play a square wave at
|
// A single tone at `freqHz` until the next call: jlAudioVoice(0, freqHz,
|
||||||
// `freqHz` until the next jlAudioTone call. freqHz == 0 silences the
|
// 0), tone voice 0 at full volume, on every port. freqHz == 0 silences
|
||||||
// generator. Designed for AGI-era PSG-style music where the engine
|
// it. Designed for AGI-era PSG-style music where the engine just
|
||||||
// just programs a divisor and lets the chip play; CPU cost during
|
// programs a divisor and lets the chip play; CPU cost during playback is
|
||||||
// playback is zero. Single voice on platforms with multiple
|
// zero. It shares voice 0 with jlAudioVoice and jlMusicPlay. On DOS
|
||||||
// hardware oscillators (uses voice 0). On DOS this programs PIT
|
// without an OPL2, voice 0 (and so this tone) is the PC speaker.
|
||||||
// counter 2 and the speaker gate (port 0x61). No-op on platforms
|
|
||||||
// without a reachable tone generator -- callers should fall back to
|
|
||||||
// jlAudioPlaySfxStream when they need PCM-synth sound on those.
|
|
||||||
void jlAudioTone(uint16_t freqHz);
|
void jlAudioTone(uint16_t freqHz);
|
||||||
|
|
||||||
// Periodic tick callback for sound schedulers that need better-than-
|
// Periodic tick callback for sound schedulers that need better-than-
|
||||||
|
|
@ -164,56 +169,68 @@ void jlAudioTone(uint16_t freqHz);
|
||||||
// into one render frame, so the chip only "hears" the last write
|
// into one render frame, so the chip only "hears" the last write
|
||||||
// per batch and short notes are dropped.
|
// per batch and short notes are dropped.
|
||||||
//
|
//
|
||||||
// fn fires from interrupt context at `hz` times per second. Keep
|
// fn fires `hz` times per second on average, on every port, and may
|
||||||
// fn short: only memory reads, direct hardware-register writes,
|
// fire from an interrupt (DOS, Amiga, ST, X68000): keep fn short, only
|
||||||
// no malloc / printf / blocking. Pass fn = NULL to uninstall.
|
// memory reads and direct hardware-register writes, no malloc / printf /
|
||||||
|
// blocking, and share data with it through jlAudioCritical*. Pass
|
||||||
|
// fn = NULL to uninstall (always returns true). Returns false when hz is
|
||||||
|
// 0, audio is off or did not start, or the port cannot take the rate.
|
||||||
//
|
//
|
||||||
// Per-port impl: DOS reprograms PIT counter 0 + hooks INT 8,
|
// Per-port impl: DOS reprograms PIT counter 0 + hooks INT 8,
|
||||||
// keeping the BIOS tick counter at $0040:$006C advancing at the
|
// keeping the BIOS tick counter at $0040:$006C advancing at the
|
||||||
// canonical 18.2 Hz via an accumulator (so uclock / time stay
|
// canonical 18.2 Hz via an accumulator (so uclock / time stay
|
||||||
// correct). Other ports return false until a port-appropriate
|
// correct). The Amiga uses a timer.device E-clock request and a
|
||||||
// timer source is wired.
|
// software interrupt; the ST and X68000 ride the system's Timer C
|
||||||
|
// interrupt (200 and 100 Hz), so calls due between its ticks come
|
||||||
|
// together. The IIgs and DVX call fn in program time from
|
||||||
|
// jlAudioFrameTick, and the hosted ports from the mixer, between the
|
||||||
|
// samples where each tick falls.
|
||||||
typedef void (*jlAudioTickFnT)(void);
|
typedef void (*jlAudioTickFnT)(void);
|
||||||
bool jlAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
|
bool jlAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
|
||||||
|
|
||||||
// Critical section against the tick ISR. Callers that mutate
|
// Critical section against the tick ISR. Callers that mutate
|
||||||
// state read by the registered tick callback must wrap the
|
// state read by the registered tick callback must wrap the
|
||||||
// mutation in Enter/Exit so the ISR can't observe a half-written
|
// mutation in Enter/Exit so the ISR can't observe a half-written
|
||||||
// state. On DOS this is `cli` / `sti`; on ports without an
|
// state. Sections nest: the outermost pair holds the interrupt off
|
||||||
// audio ISR these are no-ops.
|
// (on DOS, the Amiga, the ST, and the X68000, every interrupt, so keep
|
||||||
|
// it short), and its Exit puts back the interrupt state its Enter found.
|
||||||
|
// No-ops where the callback runs in program time (IIgs, DVX, hosted).
|
||||||
void jlAudioCriticalEnter(void);
|
void jlAudioCriticalEnter(void);
|
||||||
void jlAudioCriticalExit(void);
|
void jlAudioCriticalExit(void);
|
||||||
|
|
||||||
// 3-voice PSG-style polyphonic tone generator. `voice` selects 0/1/2;
|
// 3-voice PSG-style polyphonic tone generator. `voice` selects 0/1/2;
|
||||||
// `freqHz` is the new tone (0 = silence this voice); `atten` is the
|
// `freqHz` is the new tone (0 = silence this voice); `atten` is the
|
||||||
// AGI/SN76489-style 4-bit attenuation, 0 = loudest .. 15 = silent
|
// AGI/SN76489-style 4-bit attenuation, 0 = loudest .. 15 = silent
|
||||||
// (2 dB per step). The mapping to underlying hardware:
|
// (2 dB per step, as near as the chip comes: the ST's YM2149 levels are
|
||||||
|
// ~3.5 dB apart, so within 1.6 dB there). The mapping to underlying
|
||||||
|
// hardware:
|
||||||
//
|
//
|
||||||
// DOS -- AdLib OPL2 melodic channels 0/1/2 (ports 0x388/0x389).
|
// DOS -- AdLib OPL2 melodic channels 0/1/2 (ports 0x388/0x389);
|
||||||
// Atari ST -- YM2149 PSG channels A/B/C.
|
// with no OPL2, voice 0 is the PC speaker, 1 and 2 silent.
|
||||||
|
// Atari ST -- YM2149 PSG channels A/B/C (plain ST: voice 0 silent while
|
||||||
|
// digital sound plays, which drives channel A).
|
||||||
// Amiga -- Paula channels 0/1/2 looping a 2-byte square sample.
|
// Amiga -- Paula channels 0/1/2 looping a 2-byte square sample.
|
||||||
// IIgs -- Ensoniq DOC oscillators playing a resident square,
|
// IIgs -- Ensoniq DOC oscillators playing a resident square on SFX
|
||||||
// retriggered as an ~80 ms one-shot per call. Needs
|
// slots 0/1/2.
|
||||||
// jlAudioInit (the DOC path rides the NTP engine);
|
// X68000 -- YM2151 channels 0/1/2, one sine operator each.
|
||||||
// callers that hold a note longer than ~80 ms must
|
// DVX -- square tones of DVX's sound library.
|
||||||
// re-assert it about once a frame (jlMusicPlay's
|
// hosted -- square waves mixed in software.
|
||||||
// sequencer and AGI's per-frame driver both do).
|
|
||||||
//
|
//
|
||||||
// Voice state is sticky: a voice keeps playing its last frequency
|
// A note sounds until the next call for that voice. The IIgs plays a
|
||||||
// until the next jlAudioVoice call on it (IIgs excepted, above). CPU
|
// note as a short sample and restarts held notes from jlAudioFrameTick,
|
||||||
// cost during sustained playback is zero (the hardware oscillates on
|
// so call that every frame. On the Amiga the voices are the channels the
|
||||||
// its own). Safe to call without jlAudioInit on DOS/ST/Amiga -- the
|
// MOD engine drives, so while a MOD is playing, voice writes fight the
|
||||||
// voice path is direct hardware programming, independent of the
|
// player's and lose: use the voice/tracker layer or the MOD layer per
|
||||||
// MOD/SFX mixer. On ST and Amiga the voice channels are the same
|
// scene, not both at once.
|
||||||
// hardware the MOD engine drives: while a MOD is playing, voice
|
|
||||||
// writes fight the player's register writes and lose. Use the voice/
|
|
||||||
// tracker layer or the MOD layer per scene, not both at once.
|
|
||||||
void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
||||||
|
|
||||||
// Noise/percussion generator, the 4th logical voice of the tracker
|
// Noise/percussion generator, the 4th logical voice of the tracker
|
||||||
// layer. `pitch` is 0..31 in YM2149 noise-period style: 0 = the
|
// layer. `pitch` is 0..31 in YM2149 noise-period style: 0 = the
|
||||||
// brightest hiss (hats), 31 = the lowest rumble (toms); atten as in
|
// brightest hiss (hats), 31 = the lowest rumble (toms); the noise runs at
|
||||||
// jlAudioVoice, and atten >= 15 silences. The per-port mapping:
|
// 125000 / pitch Hz (pitch 0 as 1) as far as each chip goes (the Amiga
|
||||||
|
// tops out at 28.6 kHz and the IIgs at 26 kHz, so pitches 0-4 sound
|
||||||
|
// alike there). atten as in jlAudioVoice, and atten >= 15 silences. The
|
||||||
|
// per-port mapping:
|
||||||
//
|
//
|
||||||
// Atari ST -- the YM2149 noise generator (reg 6) mixed onto
|
// Atari ST -- the YM2149 noise generator (reg 6) mixed onto
|
||||||
// channel C. Noise shares channel C's volume register
|
// channel C. Noise shares channel C's volume register
|
||||||
|
|
@ -221,20 +238,21 @@ void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
||||||
// apart (songbake warns on overlap).
|
// apart (songbake warns on overlap).
|
||||||
// DOS -- OPL2 rhythm-mode snare drum (the chip's LFSR noise
|
// DOS -- OPL2 rhythm-mode snare drum (the chip's LFSR noise
|
||||||
// source) on channel 7; the three tone channels are
|
// source) on channel 7; the three tone channels are
|
||||||
// untouched.
|
// untouched. A tuned drum: its pitch spreads over the
|
||||||
|
// snare's range instead of following the rate above.
|
||||||
// Amiga -- Paula channel 3 looping a chip-RAM LFSR noise sample
|
// Amiga -- Paula channel 3 looping a chip-RAM LFSR noise sample
|
||||||
// (channels 0-2 are the tone voices).
|
// (channels 0-2 are the tone voices).
|
||||||
// IIgs -- a resident LFSR noise sample on SFX slot 3, same
|
// IIgs -- a resident LFSR noise sample on SFX slot 3.
|
||||||
// ~80 ms retriggered one-shot model as the tone voices
|
// X68000 -- the YM2151's noise generator, at the rate exactly.
|
||||||
// (jlMusicPlay's sequencer re-asserts it per frame).
|
// DVX -- a noise tone of DVX's sound library.
|
||||||
|
// hosted -- the YM2149's 17-bit generator, mixed in software.
|
||||||
//
|
//
|
||||||
// Same init contract as jlAudioVoice: direct hardware programming on
|
// The same contract as jlAudioVoice: it sounds until the next call.
|
||||||
// DOS/ST/Amiga, jlAudioInit required on the IIgs.
|
|
||||||
void jlAudioNoise(uint8_t pitch, uint8_t atten);
|
void jlAudioNoise(uint8_t pitch, uint8_t atten);
|
||||||
|
|
||||||
// Portable chip-tracker music: play a JYM1 event stream (baked from
|
// Portable chip-tracker music: play a JYM1 event stream (baked from
|
||||||
// tools/songbake text notation) through the jlAudioVoice tone layer.
|
// tools/songbake text notation) through the jlAudioVoice tone layer.
|
||||||
// One authored song plays on all four ports at register-write cost --
|
// One authored song plays on every port at register-write cost --
|
||||||
// there is no CPU mixing, so unlike jlAudioPlayMod this path is fully
|
// there is no CPU mixing, so unlike jlAudioPlayMod this path is fully
|
||||||
// viable on the ST and cheap on DOS. The stream is read IN PLACE:
|
// viable on the ST and cheap on DOS. The stream is read IN PLACE:
|
||||||
// data must stay valid until jlMusicStop or song end. Playback is
|
// data must stay valid until jlMusicStop or song end. Playback is
|
||||||
|
|
@ -242,8 +260,7 @@ void jlAudioNoise(uint8_t pitch, uint8_t atten);
|
||||||
// paced by jlMillisElapsed, so tempo holds across 50/60/70 Hz ports.
|
// paced by jlMillisElapsed, so tempo holds across 50/60/70 Hz ports.
|
||||||
// loop=true restarts at the song's authored loop point (or the top
|
// loop=true restarts at the song's authored loop point (or the top
|
||||||
// when it has none); loop=false silences the voices at song end.
|
// when it has none); loop=false silences the voices at song end.
|
||||||
// Returns false (and plays nothing) on a malformed stream. Call
|
// Returns false (and plays nothing) on a malformed stream.
|
||||||
// jlAudioInit first for IIgs audibility (see jlAudioVoice).
|
|
||||||
bool jlMusicPlay(const uint8_t *data, uint32_t length, bool loop);
|
bool jlMusicPlay(const uint8_t *data, uint32_t length, bool loop);
|
||||||
|
|
||||||
// Stop tracker playback and silence the voices (tones and noise).
|
// Stop tracker playback and silence the voices (tones and noise).
|
||||||
|
|
@ -270,10 +287,11 @@ void jlMusicResume(void);
|
||||||
// Step it over successive frames for fades.
|
// Step it over successive frames for fades.
|
||||||
void jlMusicSetAtten(uint8_t atten);
|
void jlMusicSetAtten(uint8_t atten);
|
||||||
|
|
||||||
// Hook the engine into the game loop. Most platforms drive their
|
// Hook the engine into the game loop; call it once per frame. Every
|
||||||
// engines off a hardware IRQ and ignore this call, but it's safe to
|
// port does sound work here: DOS, ST, X68000, DVX, and hosted mix or
|
||||||
// invoke once per frame regardless and required for any port that
|
// refill their sound buffers (modules and effects stop or repeat
|
||||||
// runs its mixer in user-thread context.
|
// without it), the IIgs and Amiga feed streams and notice a module's
|
||||||
|
// end (and the IIgs holds notes), and every port advances jlMusicPlay.
|
||||||
void jlAudioFrameTick(void);
|
void jlAudioFrameTick(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,35 @@
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Runtime compiled-sprite cache size. 0 selects a per-platform
|
// Runtime compiled-sprite cache size. 0 selects a per-platform
|
||||||
// default: 8 KB on IIgs, 32 KB on Amiga/ST/DOS (the 68k/x86
|
// default: 8 KB on the IIgs, 64 KB on the Amiga and ST, and 32 KB
|
||||||
// emitters are ~4-8x less code-dense than the 65816's, so a
|
// everywhere else (the 68k/x86 emitters are ~4-8x less code-dense
|
||||||
// 32x32 sprite can need 8-16 KB there). Size against
|
// than the 65816's, so a 32x32 sprite can need 8-16 KB there). The
|
||||||
// jlSpriteCompiledSize() when tuning. On the IIgs the cache is one
|
// ports that interpret every sprite still allocate it. Size against
|
||||||
// Memory Manager block allocated with attrNoCross (it may not span
|
// jlSpriteCompiledSize() when tuning. Elsewhere the cache is one
|
||||||
// a 64 KB bank boundary), so values above 65536 are clamped to
|
// block, and jlInit fails if it cannot be had. On the IIgs it is up
|
||||||
// 65536; even a clamped request can fail if no free bank exists.
|
// to four Memory Manager blocks of at most 64 KB, each inside one
|
||||||
|
// bank (a compiled sprite cannot cross a bank), so at most 256 KB;
|
||||||
|
// a bank that is refused is retried at 60 KB, and jlInit takes
|
||||||
|
// whatever part of the request it was granted.
|
||||||
uint32_t codegenBytes;
|
uint32_t codegenBytes;
|
||||||
|
// True: leave audio off. Every audio call is then silent, as on a
|
||||||
|
// machine without sound, and the program pays nothing for audio:
|
||||||
|
// on the IIgs no replayer read from disk, on DOS and the Amiga no
|
||||||
|
// interrupt hooked. jlInit starts audio otherwise, and jlShutdown
|
||||||
|
// stops it.
|
||||||
|
bool noAudio;
|
||||||
} jlConfigT;
|
} jlConfigT;
|
||||||
|
|
||||||
// Initialize the library. Returns true on success.
|
// Initialize the library, audio too (unless config->noAudio). Returns true
|
||||||
// On failure, jlLastError() returns a human-readable description.
|
// on success; sound that does not start is not a failure (modules and
|
||||||
|
// effects are then silent, and the log says so). On failure, jlLastError()
|
||||||
|
// returns a human-readable description; where the port's own start-up
|
||||||
|
// fails without saying why, that is just "jlpInit failed".
|
||||||
bool jlInit(const jlConfigT *config);
|
bool jlInit(const jlConfigT *config);
|
||||||
|
|
||||||
// Shut down the library, releasing all resources.
|
// Shut down the library, audio first, releasing the library's own
|
||||||
|
// resources. Surfaces and sprites the program created are not freed:
|
||||||
|
// destroy them first.
|
||||||
void jlShutdown(void);
|
void jlShutdown(void);
|
||||||
|
|
||||||
// Returns the most recent error message, or NULL if none.
|
// Returns the most recent error message, or NULL if none.
|
||||||
|
|
@ -41,13 +55,17 @@ const char *jlVersionString(void);
|
||||||
// Allocate a large or persistent buffer, backed by the platform's
|
// Allocate a large or persistent buffer, backed by the platform's
|
||||||
// native allocator rather than the small C heap. Use this (not malloc)
|
// native allocator rather than the small C heap. Use this (not malloc)
|
||||||
// for anything that exceeds the per-allocation or total ceiling of the
|
// for anything that exceeds the per-allocation or total ceiling of the
|
||||||
// stock C heap -- on the IIgs the C heap lives in bank 0 and is tiny,
|
// stock C heap -- on the IIgs the whole C heap is one Memory Manager
|
||||||
// and a single malloc is capped at ~32 KB, so multi-KB caches and frame
|
// block of 32 KB to 48 KB (or, if that block cannot be had, what the
|
||||||
// buffers must come from the IIgs Memory Manager instead. Returns NULL
|
// link left in the program's bank, which can be tiny), so multi-KB
|
||||||
// on failure. The returned pointer is NOT zero-filled.
|
// caches and frame buffers must come from the IIgs Memory Manager
|
||||||
|
// instead. Returns NULL on failure. The returned pointer is NOT
|
||||||
|
// zero-filled.
|
||||||
void *jlAlloc(uint32_t bytes);
|
void *jlAlloc(uint32_t bytes);
|
||||||
|
|
||||||
// Release a buffer returned by jlAlloc. NULL is accepted and ignored.
|
// Release a buffer returned by jlAlloc. NULL is accepted and ignored.
|
||||||
|
// Never mix jlFree and free: on the IIgs a jlAlloc block is a Memory
|
||||||
|
// Manager handle, and either one given the other's block corrupts memory.
|
||||||
void jlFree(void *p);
|
void jlFree(void *p);
|
||||||
|
|
||||||
// memcpy with a 32-bit count. Same contract as memcpy (no overlap); every
|
// memcpy with a 32-bit count. Same contract as memcpy (no overlap); every
|
||||||
|
|
@ -55,33 +73,36 @@ void jlFree(void *p);
|
||||||
// byte, split at bank boundaries).
|
// byte, split at bank boundaries).
|
||||||
void jlMemCopy(void *dst, const void *src, uint32_t bytes);
|
void jlMemCopy(void *dst, const void *src, uint32_t bytes);
|
||||||
|
|
||||||
// Block the calling thread until the next display vertical blank.
|
// Block the calling thread until the next display vertical blank, to
|
||||||
// Used to pace game loops to the display's native refresh rate
|
// pace a game loop to the display's refresh. On the machine ports it
|
||||||
// (~70 Hz on VGA mode 13h, ~50 or ~60 Hz on Amiga/ST PAL/NTSC, ~60 Hz
|
// waits for the display's own vertical blank. The hosted ports, and DVX
|
||||||
// on IIgs SHR). Cheap on every port since the underlying mechanism is
|
// in a window, have none to wait for: there it waits for the next tick
|
||||||
// always a hardware-level wait, not a software timer.
|
// of a virtual frame clock (60 Hz hosted, 70 Hz DVX), and nothing keeps
|
||||||
|
// a present from tearing.
|
||||||
void jlWaitVBL(void);
|
void jlWaitVBL(void);
|
||||||
|
|
||||||
// Monotonic 16-bit frame counter. Caller must poll faster than
|
// Monotonic 16-bit frame counter, kept by an interrupt or derived from a
|
||||||
// 2 * jlFrameHz() so no edge is missed. Used by benchmarks and
|
// clock, so it does not miss frames however seldom it is read (except on
|
||||||
// frame-rate-independent animation. (Per-port edge-detect / VBL-ISR
|
// an X68000 whose system refused JoeyLib's frame interrupt: there it
|
||||||
// mechanism is documented at jlpFrameCount in src/core/port.h.)
|
// counts only while jlWaitVBL, jlFrameCount, or jlStagePresent is
|
||||||
|
// called). It wraps about every 18 minutes at 60 Hz, so compare two
|
||||||
|
// readings by subtraction. Used by benchmarks and frame-rate-independent
|
||||||
|
// animation.
|
||||||
uint16_t jlFrameCount(void);
|
uint16_t jlFrameCount(void);
|
||||||
|
|
||||||
// Nominal display frame rate in Hz: 50 (Amiga PAL), 60 (IIgs / ST
|
// Display frame rate in Hz: 60 on the IIgs (50 on a PAL machine), 50 on
|
||||||
// NTSC default), 70 (VGA mode 13h). The actual VBL cadence may
|
// the Amiga (60 on an NTSC machine that cannot show PAL), 50 or 60 on
|
||||||
|
// the ST as the machine is set, 70 on DOS and DVX, 55 on the X68000 (56
|
||||||
|
// under MAME), and 60 on every hosted port. The actual VBL cadence may
|
||||||
// drift slightly; the value reported here is what benchmarks divide
|
// drift slightly; the value reported here is what benchmarks divide
|
||||||
// by to convert iters-per-N-frames to ops/sec.
|
// by to convert iters-per-N-frames to ops/sec.
|
||||||
uint16_t jlFrameHz(void);
|
uint16_t jlFrameHz(void);
|
||||||
|
|
||||||
// Monotonic wall-clock millisecond counter since jlInit. On DOS/ST it
|
// Monotonic wall-clock millisecond counter since jlInit. It may
|
||||||
// reads a real hardware timer; on IIgs/Amiga the generic implementation
|
// advance a frame at a time. Call it at least once every 15 minutes, or
|
||||||
// derives it from the frame counter (frame-granularity steps, and the
|
// the time between two calls can be lost. Two consecutive calls
|
||||||
// caller must poll at least once per 65535 frames -- ~18 min at 60Hz --
|
// separated by a long render report the true elapsed time, not the
|
||||||
// or wrapped frames are lost). Two consecutive calls separated by a long
|
// number of VBLs that fired.
|
||||||
// render report the true elapsed time, not the number of VBLs that
|
|
||||||
// fired. (Per-port millisecond timer sources are documented at
|
|
||||||
// jlpMillisElapsed in src/core/port.h.)
|
|
||||||
uint32_t jlMillisElapsed(void);
|
uint32_t jlMillisElapsed(void);
|
||||||
|
|
||||||
// Deterministic, seedable pseudo-random generator. Bit-identical
|
// Deterministic, seedable pseudo-random generator. Bit-identical
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,15 @@ void jlFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t col
|
||||||
|
|
||||||
// Flood fill a 4-connected region starting at (x, y). Replaces every
|
// Flood fill a 4-connected region starting at (x, y). Replaces every
|
||||||
// pixel of the original color reached via N/S/E/W steps. No-op if
|
// pixel of the original color reached via N/S/E/W steps. No-op if
|
||||||
// (x, y) is off-surface or already matches newColor.
|
// (x, y) is off-surface or already matches newColor. The span stack
|
||||||
|
// holds 512 entries; a region that needs more is left partly filled
|
||||||
|
// (identically on every port).
|
||||||
void jlFloodFill(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor);
|
void jlFloodFill(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor);
|
||||||
|
|
||||||
// Flood fill a 4-connected region starting at (x, y), stopping at
|
// Flood fill a 4-connected region starting at (x, y), stopping at
|
||||||
// boundary pixels of color boundaryColor. Replaces every reachable
|
// boundary pixels of color boundaryColor. Replaces every reachable
|
||||||
// pixel that is not boundaryColor with newColor. Used for vector-art
|
// pixel that is not boundaryColor with newColor; pixels already
|
||||||
|
// newColor stop the fill too. Same 512-entry span stack. Used for vector-art
|
||||||
// rendering (e.g. Sierra-style picture playback): outline a closed
|
// rendering (e.g. Sierra-style picture playback): outline a closed
|
||||||
// region with jlDrawLine in boundaryColor, then fill with this.
|
// region with jlDrawLine in boundaryColor, then fill with this.
|
||||||
void jlFloodFillBounded(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor, uint8_t boundaryColor);
|
void jlFloodFillBounded(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor, uint8_t boundaryColor);
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,14 @@
|
||||||
//
|
//
|
||||||
// Two directories sit next to the executable, staged there by the per-platform
|
// Two directories sit next to the executable, staged there by the per-platform
|
||||||
// disk / directory packagers:
|
// disk / directory packagers:
|
||||||
// DATA/ -- read-only game content (levels, sprites, audio). jlDataOpen.
|
// DATA/ -- game content (levels, sprites, audio). jlDataOpen.
|
||||||
// SAVES/ -- writable save / preference files. jlSave*.
|
// SAVES/ -- writable save / preference files. jlSave*.
|
||||||
// Both are addressed by a bare name relative to their directory; the prefix is
|
// Both are addressed by a bare name relative to their directory; the prefix is
|
||||||
// forced here so callers never hard-code "DATA/" or "SAVES/".
|
// forced here so callers never hard-code "DATA/" or "SAVES/". The hosted
|
||||||
|
// ports make the program's folder the current directory in jlInit; DOS, the
|
||||||
|
// ST, the Amiga, and the X68000 use the current directory as they find it;
|
||||||
|
// the IIgs finds DATA/ under GS/OS prefix 0 and SAVES/ in the application's
|
||||||
|
// folder (prefix 1); DVX roots both at run time (docs/ApiFile.adoc).
|
||||||
|
|
||||||
#ifndef JOEYLIB_FILE_H
|
#ifndef JOEYLIB_FILE_H
|
||||||
#define JOEYLIB_FILE_H
|
#define JOEYLIB_FILE_H
|
||||||
|
|
@ -23,18 +27,21 @@
|
||||||
|
|
||||||
// Open a data file by its name relative to the DATA/ directory. The DATA/
|
// Open a data file by its name relative to the DATA/ directory. The DATA/
|
||||||
// prefix is prepended automatically; pass "levels/title.dat", not
|
// prefix is prepended automatically; pass "levels/title.dat", not
|
||||||
// "DATA/levels/title.dat". mode is a standard fopen mode string. Returns NULL
|
// "DATA/levels/title.dat". mode is a standard fopen mode string, passed on
|
||||||
// if the resulting path would overflow the internal buffer or the file cannot
|
// unchecked (DATA/ is meant to be read, but a writing mode is not refused).
|
||||||
// be opened.
|
// Returns NULL if mode is NULL, the resulting path would overflow the
|
||||||
|
// internal buffer, or the file cannot be opened.
|
||||||
FILE *jlDataOpen(const char *name, const char *mode);
|
FILE *jlDataOpen(const char *name, const char *mode);
|
||||||
|
|
||||||
// ----- Writable save files (SAVES/) -----
|
// ----- Writable save files (SAVES/) -----
|
||||||
//
|
//
|
||||||
// Save / preference data lives under a SAVES/ directory beside the executable
|
// Save / preference data lives under a SAVES/ directory -- a writable
|
||||||
// -- a writable sibling of the read-only DATA/ tree. Names are relative to
|
// sibling of the DATA/ tree (where, per port: the top of this file). Names
|
||||||
// SAVES/: pass "hero.sav", not "SAVES/hero.sav". Keep names portable across
|
// are relative to SAVES/: pass "hero.sav", not "SAVES/hero.sav". A name is at
|
||||||
// the retro filesystems: at most 8 characters plus a short extension, letters
|
// most 8 letters and digits starting with a letter, then optionally a period
|
||||||
// and digits only, so they fit ProDOS (15-char) and DOS 8.3.
|
// and up to 3 more, so it fits ProDOS (15 characters, letter first) and DOS
|
||||||
|
// 8.3; every save call refuses any other name, on every port, as if the file
|
||||||
|
// could not be opened. Case must match on Linux, Haiku, BeOS, and the web.
|
||||||
//
|
//
|
||||||
// A failed open / write is non-fatal and simply reports failure (false / 0),
|
// A failed open / write is non-fatal and simply reports failure (false / 0),
|
||||||
// so save code can degrade gracefully on a platform or volume where SAVES/ is
|
// so save code can degrade gracefully on a platform or volume where SAVES/ is
|
||||||
|
|
@ -47,7 +54,9 @@ FILE *jlDataOpen(const char *name, const char *mode);
|
||||||
FILE *jlSaveOpen(const char *name, const char *mode);
|
FILE *jlSaveOpen(const char *name, const char *mode);
|
||||||
|
|
||||||
// Write `len` bytes from `buf` to save file `name`, replacing any existing
|
// Write `len` bytes from `buf` to save file `name`, replacing any existing
|
||||||
// contents. Returns true only if all `len` bytes were written.
|
// contents. Returns true only if all `len` bytes were written. Not atomic: a
|
||||||
|
// write that fails part way leaves the save short. buf NULL with len > 0
|
||||||
|
// returns false before the save is touched.
|
||||||
bool jlSaveWrite(const char *name, const void *buf, uint32_t len);
|
bool jlSaveWrite(const char *name, const void *buf, uint32_t len);
|
||||||
|
|
||||||
// Read up to `max` bytes from save file `name` into `buf`. Returns the number
|
// Read up to `max` bytes from save file `name` into `buf`. Returns the number
|
||||||
|
|
@ -62,9 +71,11 @@ bool jlSaveDelete(const char *name);
|
||||||
|
|
||||||
// ----- Disk space -----
|
// ----- Disk space -----
|
||||||
|
|
||||||
// Free space, in bytes, on the volume that holds SAVES/ (the program's
|
// Free space, in bytes, on the volume that holds SAVES/. Returns 0 if it
|
||||||
// volume). Returns 0 if it cannot be determined (e.g. the port has no query
|
// cannot be determined. Saturates at UINT32_MAX (4 GB - 1) on larger
|
||||||
// for it). Saturates at UINT32_MAX (4 GB - 1) on larger volumes.
|
// volumes; DOS's own query (DOS, Win16) stops near 2 GB, and the web's
|
||||||
|
// figure is a fixed number from Emscripten's file system, not the
|
||||||
|
// browser's real space.
|
||||||
uint32_t jlDiskFree(void);
|
uint32_t jlDiskFree(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
// Call jlInputPoll() once per frame (typically right before
|
// Call jlInputPoll() once per frame (typically right before
|
||||||
// drawing) to refresh keyboard, mouse, and joystick state. After
|
// drawing) to refresh keyboard, mouse, and joystick state. After
|
||||||
// polling, the joeyKey* predicates return the current state of every
|
// polling, the jlKey* predicates return the current state of every
|
||||||
// key:
|
// key:
|
||||||
//
|
//
|
||||||
// jlKeyDown(k) -- is key k held down right now
|
// jlKeyDown(k) -- is key k held down right now
|
||||||
|
|
@ -26,8 +26,8 @@
|
||||||
// The mouse predicates return the pointer state:
|
// The mouse predicates return the pointer state:
|
||||||
//
|
//
|
||||||
// jlMouseX/Y() -- pointer position in surface
|
// jlMouseX/Y() -- pointer position in surface
|
||||||
// coords (0..SURFACE_WIDTH-1,
|
// coords (0..JL_SURFACE_WIDTH-1,
|
||||||
// 0..SURFACE_HEIGHT-1)
|
// 0..JL_SURFACE_HEIGHT-1)
|
||||||
// jlMouseDown(b) -- is button b held right now
|
// jlMouseDown(b) -- is button b held right now
|
||||||
// jlMousePressed(b) -- rising edge since last poll
|
// jlMousePressed(b) -- rising edge since last poll
|
||||||
// jlMouseReleased(b) -- falling edge since last poll
|
// jlMouseReleased(b) -- falling edge since last poll
|
||||||
|
|
@ -39,7 +39,8 @@
|
||||||
// jlJoystickX/Y(js) -- axis values, signed -127..+127.
|
// jlJoystickX/Y(js) -- axis values, signed -127..+127.
|
||||||
// Digital sticks snap to the
|
// Digital sticks snap to the
|
||||||
// extremes; analog sticks return
|
// extremes; analog sticks return
|
||||||
// the raw centered value.
|
// their offset from centre, scaled,
|
||||||
|
// dead zone applied.
|
||||||
// jlJoyDown(js, b) -- is button b held on stick js
|
// jlJoyDown(js, b) -- is button b held on stick js
|
||||||
// jlJoyPressed(js, b) -- rising edge since last poll
|
// jlJoyPressed(js, b) -- rising edge since last poll
|
||||||
// jlJoyReleased(js, b) -- falling edge since last poll
|
// jlJoyReleased(js, b) -- falling edge since last poll
|
||||||
|
|
@ -54,63 +55,63 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
KEY_NONE = 0,
|
JL_KEY_NONE = 0,
|
||||||
|
|
||||||
KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I,
|
JL_KEY_A, JL_KEY_B, JL_KEY_C, JL_KEY_D, JL_KEY_E, JL_KEY_F, JL_KEY_G, JL_KEY_H, JL_KEY_I,
|
||||||
KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R,
|
JL_KEY_J, JL_KEY_K, JL_KEY_L, JL_KEY_M, JL_KEY_N, JL_KEY_O, JL_KEY_P, JL_KEY_Q, JL_KEY_R,
|
||||||
KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z,
|
JL_KEY_S, JL_KEY_T, JL_KEY_U, JL_KEY_V, JL_KEY_W, JL_KEY_X, JL_KEY_Y, JL_KEY_Z,
|
||||||
|
|
||||||
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
|
JL_KEY_0, JL_KEY_1, JL_KEY_2, JL_KEY_3, JL_KEY_4,
|
||||||
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
|
JL_KEY_5, JL_KEY_6, JL_KEY_7, JL_KEY_8, JL_KEY_9,
|
||||||
|
|
||||||
KEY_SPACE, KEY_ESCAPE, KEY_RETURN, KEY_TAB, KEY_BACKSPACE,
|
JL_KEY_SPACE, JL_KEY_ESCAPE, JL_KEY_RETURN, JL_KEY_TAB, JL_KEY_BACKSPACE,
|
||||||
|
|
||||||
KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT,
|
JL_KEY_UP, JL_KEY_DOWN, JL_KEY_LEFT, JL_KEY_RIGHT,
|
||||||
|
|
||||||
KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_LALT,
|
JL_KEY_LSHIFT, JL_KEY_RSHIFT, JL_KEY_LCTRL, JL_KEY_LALT,
|
||||||
|
|
||||||
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
|
JL_KEY_F1, JL_KEY_F2, JL_KEY_F3, JL_KEY_F4, JL_KEY_F5,
|
||||||
KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10,
|
JL_KEY_F6, JL_KEY_F7, JL_KEY_F8, JL_KEY_F9, JL_KEY_F10,
|
||||||
|
|
||||||
KEY_COUNT
|
JL_KEY_COUNT
|
||||||
} jlKeyE;
|
} jlKeyE;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MOUSE_BUTTON_NONE = 0,
|
JL_MOUSE_BUTTON_NONE = 0,
|
||||||
MOUSE_BUTTON_LEFT,
|
JL_MOUSE_BUTTON_LEFT,
|
||||||
MOUSE_BUTTON_RIGHT,
|
JL_MOUSE_BUTTON_RIGHT,
|
||||||
MOUSE_BUTTON_MIDDLE,
|
JL_MOUSE_BUTTON_MIDDLE,
|
||||||
|
|
||||||
MOUSE_BUTTON_COUNT
|
JL_MOUSE_BUTTON_COUNT
|
||||||
} jlMouseButtonE;
|
} jlMouseButtonE;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JOYSTICK_0 = 0,
|
JL_JOYSTICK_0 = 0,
|
||||||
JOYSTICK_1,
|
JL_JOYSTICK_1,
|
||||||
|
|
||||||
JOYSTICK_COUNT
|
JL_JOYSTICK_COUNT
|
||||||
} jlJoystickE;
|
} jlJoystickE;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JOY_BUTTON_0 = 0,
|
JL_JOY_BUTTON_0 = 0,
|
||||||
JOY_BUTTON_1,
|
JL_JOY_BUTTON_1,
|
||||||
|
|
||||||
JOY_BUTTON_COUNT
|
JL_JOY_BUTTON_COUNT
|
||||||
} jlJoyButtonE;
|
} jlJoyButtonE;
|
||||||
|
|
||||||
#define JOYSTICK_AXIS_MAX 127
|
#define JL_JOYSTICK_AXIS_MAX 127
|
||||||
#define JOYSTICK_AXIS_MIN (-127)
|
#define JL_JOYSTICK_AXIS_MIN (-127)
|
||||||
|
|
||||||
// Control characters delivered through jlInputGetChar alongside
|
// Control characters delivered through jlInputGetChar alongside
|
||||||
// printable ASCII. Everything else below 0x20, plus 0x7F, is
|
// printable ASCII. Everything else below 0x20, and everything above
|
||||||
// filtered out of the queue.
|
// 0x7F, is filtered out of the queue.
|
||||||
#define JL_CHAR_BACKSPACE 0x08
|
#define JL_CHAR_BACKSPACE 0x08
|
||||||
#define JL_CHAR_TAB 0x09
|
#define JL_CHAR_TAB 0x09
|
||||||
#define JL_CHAR_RETURN 0x0D
|
#define JL_CHAR_RETURN 0x0D
|
||||||
#define JL_CHAR_ESCAPE 0x1B
|
#define JL_CHAR_ESCAPE 0x1B
|
||||||
// The FORWARD delete (the key labelled Del/Delete on a PC, ST or Amiga keyboard - not backspace). A text
|
// The FORWARD delete (the key labelled Del/Delete on a PC, ST or Amiga keyboard - not backspace). A text
|
||||||
// editor needs both: backspace removes the character BEFORE the caret, this one removes the character
|
// editor needs both: backspace removes the character BEFORE the caret, this one removes the character
|
||||||
// UNDER it. 0x7F is ASCII DEL, which is what every one of these keyboards already reports.
|
// UNDER it. 0x7F is ASCII DEL. The IIgs never delivers it: its Delete key is backspace.
|
||||||
#define JL_CHAR_DELETE 0x7F
|
#define JL_CHAR_DELETE 0x7F
|
||||||
|
|
||||||
// Typed-character queue capacity (ring buffer; one slot stays empty,
|
// Typed-character queue capacity (ring buffer; one slot stays empty,
|
||||||
|
|
@ -125,16 +126,19 @@ void jlInputPoll(void);
|
||||||
// JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE, JL_CHAR_DELETE. Shift, caps lock, and
|
// JL_CHAR_TAB, JL_CHAR_RETURN, JL_CHAR_ESCAPE, JL_CHAR_DELETE. Shift, caps lock, and
|
||||||
// the keyboard layout are already applied by the backend, so
|
// the keyboard layout are already applied by the backend, so
|
||||||
// punctuation and shifted symbols arrive correctly on every port.
|
// punctuation and shifted symbols arrive correctly on every port.
|
||||||
// (The layout is the machine's own on IIgs/Amiga/ST/X68000; the DOS
|
// (The layout is the machine's own on IIgs/Amiga/ST/X68000, DVX's on
|
||||||
// port translates with a fixed US layout -- see claudeDocs/input.md.)
|
// DVX, and the OS's on the hosted ports; DOS and the PSP's on-screen
|
||||||
|
// keyboard use a fixed US layout. On the ST a key held with Alt types
|
||||||
|
// nothing.)
|
||||||
//
|
//
|
||||||
// The queue is refilled by jlInputPoll() -- no extra bring-up is
|
// The queue is refilled by jlInputPoll() -- no extra bring-up is
|
||||||
// needed. It holds JL_CHAR_QUEUE_SIZE - 1 characters; when full,
|
// needed. It holds JL_CHAR_QUEUE_SIZE - 1 characters; when full,
|
||||||
// further characters are dropped (drop-newest) until the app pops.
|
// further characters are dropped (drop-newest) until the app pops.
|
||||||
// OS auto-repeat may deliver repeated characters on ports whose
|
// A held key repeats its character on every port (the caller manages
|
||||||
// keyboard services repeat (the caller manages repeat policy).
|
// repeat policy).
|
||||||
|
// A chord types nothing on any port: Ctrl+M is not Return.
|
||||||
// 7-bit ASCII only; no Unicode / IME. Arrow keys are not characters
|
// 7-bit ASCII only; no Unicode / IME. Arrow keys are not characters
|
||||||
// and stay on the predicate API (jlKeyDown(KEY_UP) etc.).
|
// and stay on the predicate API (jlKeyDown(JL_KEY_UP) etc.).
|
||||||
// jlWaitForAnyKey empties the queue when it returns -- its dismissing
|
// jlWaitForAnyKey empties the queue when it returns -- its dismissing
|
||||||
// keystroke is consumed by the wait, not delivered as text.
|
// keystroke is consumed by the wait, not delivered as text.
|
||||||
//
|
//
|
||||||
|
|
@ -142,18 +146,26 @@ void jlInputPoll(void);
|
||||||
// "byte or -1" is the interface contract consumers expect.
|
// "byte or -1" is the interface contract consumers expect.
|
||||||
int jlInputGetChar(void);
|
int jlInputGetChar(void);
|
||||||
|
|
||||||
// Block until the user presses any key. Internally polls via
|
// Block until the user presses a key: one with a jlKeyE, or one that types
|
||||||
// jlInputPoll, so per-port jlpInputPoll machinery (including
|
// a character (punctuation, which has no jlKeyE). Mouse and joystick
|
||||||
// audio-friendly IRQ-driven samplers) keeps working while the
|
// buttons do not end it, and a key held when the wait starts must be let go
|
||||||
// wait loop runs.
|
// and pressed again.
|
||||||
|
// Internally polls via jlInputPoll, so per-port jlpInputPoll machinery
|
||||||
|
// (including audio-friendly IRQ-driven samplers) keeps working while
|
||||||
|
// the wait loop runs.
|
||||||
void jlWaitForAnyKey(void);
|
void jlWaitForAnyKey(void);
|
||||||
|
|
||||||
|
// A key two physical keys give (Return and the keypad's Enter, an arrow and
|
||||||
|
// the keypad's 8, 2, 4, or 6, the left and right Ctrl or Alt) is down while
|
||||||
|
// either is held.
|
||||||
bool jlKeyDown(jlKeyE key);
|
bool jlKeyDown(jlKeyE key);
|
||||||
bool jlKeyPressed(jlKeyE key);
|
bool jlKeyPressed(jlKeyE key);
|
||||||
bool jlKeyReleased(jlKeyE key);
|
bool jlKeyReleased(jlKeyE key);
|
||||||
|
|
||||||
// True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset;
|
// True when the platform confirmed an attached mouse at init (DOS: the INT 33h driver reset;
|
||||||
// Amiga/ST/IIgs/X68000: the OS mouse is always wired).
|
// Amiga: JoeyLib's input window opened; Win32/Win16: Windows reports one; SDL3/web: SDL reports one;
|
||||||
|
// IIgs/ST/X68000/DVX/Haiku/BeOS: always; PSP: never). The IIgs mouse has one button, the ST's and
|
||||||
|
// X68000's two.
|
||||||
bool jlMousePresent(void);
|
bool jlMousePresent(void);
|
||||||
int16_t jlMouseX(void);
|
int16_t jlMouseX(void);
|
||||||
int16_t jlMouseY(void);
|
int16_t jlMouseY(void);
|
||||||
|
|
@ -161,6 +173,12 @@ bool jlMouseDown(jlMouseButtonE button);
|
||||||
bool jlMousePressed(jlMouseButtonE button);
|
bool jlMousePressed(jlMouseButtonE button);
|
||||||
bool jlMouseReleased(jlMouseButtonE button);
|
bool jlMouseReleased(jlMouseButtonE button);
|
||||||
|
|
||||||
|
// Every stick has two buttons. On the ST's own joystick ports, and on
|
||||||
|
// the Amiga until a stick's second button is first pressed, a stick is
|
||||||
|
// taken to have one: a quick press of it is JL_JOY_BUTTON_0, down for
|
||||||
|
// the one poll after it is let go, and a press held for half a second
|
||||||
|
// is JL_JOY_BUTTON_1, down from then until it is let go: act on button
|
||||||
|
// 0 with jlJoyPressed, not jlJoyDown.
|
||||||
bool jlJoystickConnected(jlJoystickE js);
|
bool jlJoystickConnected(jlJoystickE js);
|
||||||
int8_t jlJoystickX(jlJoystickE js);
|
int8_t jlJoystickX(jlJoystickE js);
|
||||||
int8_t jlJoystickY(jlJoystickE js);
|
int8_t jlJoystickY(jlJoystickE js);
|
||||||
|
|
@ -168,22 +186,16 @@ bool jlJoyDown(jlJoystickE js, jlJoyButtonE button);
|
||||||
bool jlJoyPressed(jlJoystickE js, jlJoyButtonE button);
|
bool jlJoyPressed(jlJoystickE js, jlJoyButtonE button);
|
||||||
bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button);
|
bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button);
|
||||||
|
|
||||||
// Re-enable joystick polling and recalibrate the resting (center)
|
// Set the stick's dead zone (8 at jlInit): axis values within `deadZone` of 0 (on the
|
||||||
// position. The IIgs port auto-disables polling after a short window
|
// -127..127 scale) read 0 on the ports with analog sticks (IIgs, DOS,
|
||||||
// of detecting no stick (saves ~3 ms/frame of busy-wait). It does NOT
|
// DVX, hosted); use 0 to disable it. On the IIgs and DOS the call also
|
||||||
// auto-re-probe -- the application must call this function to resume
|
// re-centres the stick: the next reading after it is taken as the
|
||||||
// polling after plugging a stick in.
|
// centre, so the user must hold the stick centred when calling. The
|
||||||
//
|
// IIgs port stops polling after about a second (60 polls) with no stick
|
||||||
// The next poll after this call captures the stick's CURRENT raw
|
// seen (an absent stick costs ~1 ms a frame of busy-wait) and does NOT
|
||||||
// position as the new center -- so the user must hold the stick
|
// re-probe by itself: this call resumes polling. DOS probes the game
|
||||||
// centered when calling. Subsequent polls report position relative
|
// port again; the Amiga hands the port back to lowlevel.library's own
|
||||||
// to that center; raw readings within `deadZone` units of the center
|
// detection. Elsewhere only the dead zone changes.
|
||||||
// clamp to 0 (use 0 to disable the dead zone).
|
|
||||||
//
|
|
||||||
// On platforms with truly digital sticks (Amiga / ST / DOS) the
|
|
||||||
// recalibration is a no-op -- those ports already report -1 / 0 / +1
|
|
||||||
// directly -- and `deadZone` is ignored. The function still clears
|
|
||||||
// any auto-disconnect state so polling resumes.
|
|
||||||
void jlJoystickReset(jlJoystickE js, uint8_t deadZone);
|
void jlJoystickReset(jlJoystickE js, uint8_t deadZone);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
// Colors are 12-bit $0RGB (bits 11..8 red, 7..4 green, 3..0 blue).
|
// Colors are 12-bit $0RGB (bits 11..8 red, 7..4 green, 3..0 blue).
|
||||||
// The library forces color 0 of every palette to black; jlPaletteSet
|
// The library forces color 0 of every palette to black; jlPaletteSet
|
||||||
// silently masks that entry to $000 regardless of what the caller
|
// silently masks that entry to $000 regardless of what the caller
|
||||||
// passed. See claudeDocs/DESIGN.md sections 5 and 10.
|
// passed, and jlSurfaceLoadFile does the same. Palette and SCB changes
|
||||||
|
// to the stage reach the display at the next jlStagePresent. See
|
||||||
|
// claudeDocs/DESIGN.md sections 5 and 10.
|
||||||
|
|
||||||
#ifndef JOEYLIB_PALETTE_H
|
#ifndef JOEYLIB_PALETTE_H
|
||||||
#define JOEYLIB_PALETTE_H
|
#define JOEYLIB_PALETTE_H
|
||||||
|
|
@ -20,7 +22,9 @@
|
||||||
// uint16_t values in $0RGB format.
|
// uint16_t values in $0RGB format.
|
||||||
void jlPaletteSet(jlSurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16);
|
void jlPaletteSet(jlSurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16);
|
||||||
|
|
||||||
// Read all 16 colors of a palette into out16.
|
// Read all 16 colors of a palette into out16: the values set (color 0
|
||||||
|
// forced to $000, bits 12-15 as passed), not what the display shows of
|
||||||
|
// them. A paletteIndex above 15 is ignored by both calls.
|
||||||
void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
|
void jlPaletteGet(const jlSurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
|
||||||
|
|
||||||
// Assign a single palette index (0..15) to a single scanline (0..199).
|
// Assign a single palette index (0..15) to a single scanline (0..199).
|
||||||
|
|
|
||||||
|
|
@ -71,88 +71,96 @@
|
||||||
// ----- Derived CPU markers -----
|
// ----- Derived CPU markers -----
|
||||||
|
|
||||||
#if defined(JOEYLIB_PLATFORM_IIGS)
|
#if defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
#define JOEYLIB_CPU_65816
|
#define JL_CPU_65816
|
||||||
#define JOEYLIB_ENDIAN_LITTLE
|
#define JL_ENDIAN_LITTLE
|
||||||
#define JOEYLIB_NATIVE_CHUNKY
|
#define JL_NATIVE_CHUNKY
|
||||||
#define JOEYLIB_PLATFORM_NAME "Apple IIgs"
|
#define JL_PLATFORM_NAME "Apple IIgs"
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
||||||
#define JOEYLIB_CPU_68000
|
#define JL_CPU_68000
|
||||||
#define JOEYLIB_ENDIAN_BIG
|
#define JL_ENDIAN_BIG
|
||||||
#define JOEYLIB_NATIVE_PLANAR
|
#define JL_NATIVE_PLANAR
|
||||||
#define JOEYLIB_HAS_BLITTER
|
#define JL_HAS_BLITTER
|
||||||
#define JOEYLIB_HAS_COPPER
|
#define JL_HAS_COPPER
|
||||||
#define JOEYLIB_PLATFORM_NAME "Commodore Amiga"
|
#define JL_PLATFORM_NAME "Commodore Amiga"
|
||||||
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||||
#define JOEYLIB_CPU_68000
|
#define JL_CPU_68000
|
||||||
#define JOEYLIB_ENDIAN_BIG
|
#define JL_ENDIAN_BIG
|
||||||
#define JOEYLIB_NATIVE_PLANAR
|
#define JL_NATIVE_PLANAR
|
||||||
// JOEYLIB_ATARI_STE is the STe build (make ste): the same port using the
|
// JOEYLIB_ATARI_STE is the STe build (make ste): the same port using the
|
||||||
// BLiTTER, DMA sound and the 4-bit palette, for STe and Mega STe only.
|
// BLiTTER, DMA sound, the 4-bit palette and (1040 STe) the enhanced
|
||||||
|
// joystick ports, for STe and Mega STe only.
|
||||||
#if defined(JOEYLIB_ATARI_STE)
|
#if defined(JOEYLIB_ATARI_STE)
|
||||||
#define JOEYLIB_PLATFORM_NAME "Atari STe"
|
#define JL_PLATFORM_NAME "Atari STe"
|
||||||
#else
|
#else
|
||||||
#define JOEYLIB_PLATFORM_NAME "Atari ST"
|
#define JL_PLATFORM_NAME "Atari ST"
|
||||||
#endif
|
#endif
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_PLATFORM_DOS)
|
||||||
#define JOEYLIB_CPU_X86
|
#define JL_CPU_X86
|
||||||
#define JOEYLIB_ENDIAN_LITTLE
|
#define JL_ENDIAN_LITTLE
|
||||||
#define JOEYLIB_NATIVE_CHUNKY
|
#define JL_NATIVE_CHUNKY
|
||||||
#define JOEYLIB_PLATFORM_NAME "MS-DOS"
|
#define JL_PLATFORM_NAME "MS-DOS"
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_X68000)
|
||||||
// The stage is four separate 1bpp planes in main RAM (src/x68000/halPlanar.c,
|
// The stage is four separate 1bpp planes in main RAM (src/x68000/halPlanar.c,
|
||||||
// the Amiga's layout), and a present copies its dirty rows into the text
|
// the Amiga's layout), and a present copies its dirty rows into the text
|
||||||
// plane at $E00000, which is four 1bpp planes too. The text plane has one
|
// plane at $E00000, which is four 1bpp planes too. The text plane has one
|
||||||
// 16-colour palette, so per-line SCB palettes are reloaded as the beam scans
|
// 16-colour palette, so per-line SCB palettes are reloaded as the beam scans
|
||||||
// by a CRTC raster interrupt (src/x68000/x68kRasterIsr.s).
|
// by a CRTC raster interrupt (src/x68000/x68kRasterIsr.s).
|
||||||
#define JOEYLIB_CPU_68000
|
#define JL_CPU_68000
|
||||||
#define JOEYLIB_ENDIAN_BIG
|
#define JL_ENDIAN_BIG
|
||||||
#define JOEYLIB_NATIVE_PLANAR
|
#define JL_NATIVE_PLANAR
|
||||||
#define JOEYLIB_PLATFORM_NAME "Sharp X68000"
|
#define JL_PLATFORM_NAME "Sharp X68000"
|
||||||
#elif defined(JOEYLIB_PLATFORM_DVX)
|
#elif defined(JOEYLIB_PLATFORM_DVX)
|
||||||
// The DOS stage (chunky 4bpp, x86 sprite JIT and tile walker from
|
// The DOS stage (chunky 4bpp, x86 sprite JIT and tile walker from
|
||||||
// src/x86/), shown in a DVX window, or on DVX's full screen, instead of
|
// src/x86/), shown in a DVX window, or on DVX's full screen, instead of
|
||||||
// on the VGA.
|
// on the VGA.
|
||||||
#define JOEYLIB_CPU_X86
|
#define JL_CPU_X86
|
||||||
#define JOEYLIB_ENDIAN_LITTLE
|
#define JL_ENDIAN_LITTLE
|
||||||
#define JOEYLIB_NATIVE_CHUNKY
|
#define JL_NATIVE_CHUNKY
|
||||||
#define JOEYLIB_PLATFORM_NAME "DVX"
|
#define JL_PLATFORM_NAME "DVX"
|
||||||
#elif defined(JOEYLIB_PLATFORM_HOSTED)
|
#elif defined(JOEYLIB_PLATFORM_HOSTED)
|
||||||
// The generic chunky stage, resolved through its SCB palettes into a
|
// The generic chunky stage, resolved through its SCB palettes into a
|
||||||
// window's RGB pixels by one backend (src/hosted/<backend>). Whatever CPU
|
// window's RGB pixels by one backend (src/hosted/<backend>). Whatever CPU
|
||||||
// the host has: no machine code is generated, sprites are interpreted.
|
// the host has, sprites are interpreted -- except on Windows 3.x, whose
|
||||||
|
// 32-bit code runs on a 386 like DOS's: sprites are compiled to x86 code
|
||||||
|
// as on DOS (Win386's flat code and data selectors share one base, so the
|
||||||
|
// code arena runs as it is).
|
||||||
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
#define JOEYLIB_ENDIAN_BIG
|
#define JL_ENDIAN_BIG
|
||||||
#else
|
#else
|
||||||
#define JOEYLIB_ENDIAN_LITTLE
|
#define JL_ENDIAN_LITTLE
|
||||||
|
#endif
|
||||||
|
#define JL_NATIVE_CHUNKY
|
||||||
|
#if defined(JOEYLIB_HOSTED_WIN16)
|
||||||
|
#define JL_CPU_X86
|
||||||
|
#else
|
||||||
|
#define JL_SPRITE_INTERPRETED
|
||||||
#endif
|
#endif
|
||||||
#define JOEYLIB_NATIVE_CHUNKY
|
|
||||||
#define JOEYLIB_SPRITE_INTERPRETED
|
|
||||||
#if defined(JOEYLIB_HOSTED_SDL3) && defined(__PSP__)
|
#if defined(JOEYLIB_HOSTED_SDL3) && defined(__PSP__)
|
||||||
#define JOEYLIB_PLATFORM_NAME "PSP"
|
#define JL_PLATFORM_NAME "PSP"
|
||||||
#elif defined(JOEYLIB_HOSTED_SDL3)
|
#elif defined(JOEYLIB_HOSTED_SDL3)
|
||||||
#define JOEYLIB_PLATFORM_NAME "SDL3"
|
#define JL_PLATFORM_NAME "SDL3"
|
||||||
#elif defined(JOEYLIB_HOSTED_WIN32)
|
#elif defined(JOEYLIB_HOSTED_WIN32)
|
||||||
#define JOEYLIB_PLATFORM_NAME "Windows"
|
#define JL_PLATFORM_NAME "Windows"
|
||||||
#elif defined(JOEYLIB_HOSTED_WIN16)
|
#elif defined(JOEYLIB_HOSTED_WIN16)
|
||||||
#define JOEYLIB_PLATFORM_NAME "Windows 3.x"
|
#define JL_PLATFORM_NAME "Windows 3.x"
|
||||||
#elif defined(JOEYLIB_HOSTED_BE) && defined(__HAIKU__)
|
#elif defined(JOEYLIB_HOSTED_BE) && defined(__HAIKU__)
|
||||||
#define JOEYLIB_PLATFORM_NAME "Haiku"
|
#define JL_PLATFORM_NAME "Haiku"
|
||||||
#elif defined(JOEYLIB_HOSTED_BE)
|
#elif defined(JOEYLIB_HOSTED_BE)
|
||||||
#define JOEYLIB_PLATFORM_NAME "BeOS"
|
#define JL_PLATFORM_NAME "BeOS"
|
||||||
#else
|
#else
|
||||||
#error "JoeyLib: JOEYLIB_PLATFORM_HOSTED needs a backend, e.g. -DJOEYLIB_HOSTED_SDL3"
|
#error "JoeyLib: JOEYLIB_PLATFORM_HOSTED needs a backend, e.g. -DJOEYLIB_HOSTED_SDL3"
|
||||||
#endif
|
#endif
|
||||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
// Chunky little-endian is the simplest baseline for a new port: the generic
|
// Chunky little-endian is the simplest baseline for a new port: the generic
|
||||||
// backend gives you fully-working software rendering out of the box. Adjust
|
// backend gives you fully-working software rendering out of the box. Adjust
|
||||||
// these (or switch to JOEYLIB_NATIVE_PLANAR) once you know your hardware.
|
// these (or switch to JL_NATIVE_PLANAR) once you know your hardware.
|
||||||
#define JOEYLIB_ENDIAN_LITTLE
|
#define JL_ENDIAN_LITTLE
|
||||||
#define JOEYLIB_NATIVE_CHUNKY
|
#define JL_NATIVE_CHUNKY
|
||||||
#define JOEYLIB_SPRITE_INTERPRETED
|
#define JL_SPRITE_INTERPRETED
|
||||||
#define JOEYLIB_PLATFORM_NAME "Blank Template"
|
#define JL_PLATFORM_NAME "Blank Template"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// JOEYLIB_SPRITE_INTERPRETED: the port has no sprite code emitter (it links
|
// JL_SPRITE_INTERPRETED: the port has no sprite code emitter (it links
|
||||||
// src/generic/spriteEmitStub.c), so every sprite is drawn by the
|
// src/generic/spriteEmitStub.c), so every sprite is drawn by the
|
||||||
// interpreter. A pre-compiled bank (.spc) still gives it the cels' pixels;
|
// interpreter. A pre-compiled bank (.spc) still gives it the cels' pixels;
|
||||||
// their machine code is skipped.
|
// their machine code is skipped.
|
||||||
|
|
@ -193,7 +201,7 @@
|
||||||
#define JL_HAS_FRAME_HZ // runtime 50/60 from battery RAM, function
|
#define JL_HAS_FRAME_HZ // runtime 50/60 from battery RAM, function
|
||||||
// (frameCount: port.h #defines jlpFrameCount straight to iigsGetTickWord,
|
// (frameCount: port.h #defines jlpFrameCount straight to iigsGetTickWord,
|
||||||
// the VBL heartbeat tick read -- no function override to register here.)
|
// the VBL heartbeat tick read -- no function override to register here.)
|
||||||
#define JL_HAS_WAIT_VBL // $C019 VBL poll, function
|
#define JL_HAS_WAIT_VBL // the VBL heartbeat count advancing, function
|
||||||
// audio: every real port implements the full engine
|
// audio: every real port implements the full engine
|
||||||
#define JL_HAS_AUDIO_INIT
|
#define JL_HAS_AUDIO_INIT
|
||||||
#define JL_HAS_AUDIO_SHUTDOWN
|
#define JL_HAS_AUDIO_SHUTDOWN
|
||||||
|
|
@ -205,7 +213,6 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM_RAW
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM_RAW
|
||||||
#define JL_HAS_AUDIO_SET_PAN
|
#define JL_HAS_AUDIO_SET_PAN
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
|
|
@ -233,7 +240,8 @@
|
||||||
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
||||||
#define JL_HAS_SERIAL_SET_RTS
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
// save files + disk space: delete (GS/OS Destroy), dir-ensure (GS/OS Create
|
// save files + disk space: delete (GS/OS Destroy), dir-ensure (GS/OS Create
|
||||||
// $2001, storageType 13), disk-free (GS/OS Volume $2008 via prefix 0).
|
// $2001, storageType 13), disk-free (GS/OS Volume $2008 on prefix 1's
|
||||||
|
// volume, where SAVES/ is).
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
#define JL_HAS_DISK_FREE
|
#define JL_HAS_DISK_FREE
|
||||||
|
|
@ -267,7 +275,7 @@
|
||||||
#define JL_HAS_SURFACE_ALLOC_PIXELS // amiga: NULL (planar storage), function
|
#define JL_HAS_SURFACE_ALLOC_PIXELS // amiga: NULL (planar storage), function
|
||||||
#define JL_HAS_SURFACE_ALLOC_PORT_DATA // amiga AmigaPlanarT + chip planes, function
|
#define JL_HAS_SURFACE_ALLOC_PORT_DATA // amiga AmigaPlanarT + chip planes, function
|
||||||
#define JL_HAS_SURFACE_FREE_PORT_DATA // amiga FreeMem planes, function
|
#define JL_HAS_SURFACE_FREE_PORT_DATA // amiga FreeMem planes, function
|
||||||
#define JL_HAS_FRAME_HZ // PAL 50, function
|
#define JL_HAS_FRAME_HZ // 50 PAL / 60 NTSC, measured at init, function
|
||||||
#define JL_HAS_FRAME_COUNT // VBL-server counter, function
|
#define JL_HAS_FRAME_COUNT // VBL-server counter, function
|
||||||
#define JL_HAS_WAIT_VBL // WaitTOF, function
|
#define JL_HAS_WAIT_VBL // WaitTOF, function
|
||||||
// audio: every real port implements the full engine
|
// audio: every real port implements the full engine
|
||||||
|
|
@ -280,7 +288,6 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||||
#define JL_HAS_AUDIO_SET_PAN
|
#define JL_HAS_AUDIO_SET_PAN
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
|
|
@ -305,6 +312,8 @@
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
#define JL_HAS_SERIAL_SET_DTR
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
|
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
||||||
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
// save files + disk space (CreateDir/DeleteFile/Info via dos.library)
|
// save files + disk space (CreateDir/DeleteFile/Info via dos.library)
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
|
|
@ -341,7 +350,7 @@
|
||||||
#define JL_HAS_SURFACE_ALLOC_PIXELS // st: NULL (planar storage), function
|
#define JL_HAS_SURFACE_ALLOC_PIXELS // st: NULL (planar storage), function
|
||||||
#define JL_HAS_SURFACE_ALLOC_PORT_DATA // st StPlanarT + interleaved buffer, function
|
#define JL_HAS_SURFACE_ALLOC_PORT_DATA // st StPlanarT + interleaved buffer, function
|
||||||
#define JL_HAS_SURFACE_FREE_PORT_DATA // st free buffer, function
|
#define JL_HAS_SURFACE_FREE_PORT_DATA // st free buffer, function
|
||||||
#define JL_HAS_FRAME_HZ // PAL 50, function
|
#define JL_HAS_FRAME_HZ // the machine's 50 / 60 (71 mono), function
|
||||||
#define JL_HAS_FRAME_COUNT // VBL-ISR counter, function
|
#define JL_HAS_FRAME_COUNT // VBL-ISR counter, function
|
||||||
#define JL_HAS_WAIT_VBL // frame-counter wait, function
|
#define JL_HAS_WAIT_VBL // frame-counter wait, function
|
||||||
#define JL_HAS_MILLIS_ELAPSED // Supexec 200 Hz tick, function
|
#define JL_HAS_MILLIS_ELAPSED // Supexec 200 Hz tick, function
|
||||||
|
|
@ -354,13 +363,13 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX
|
#define JL_HAS_AUDIO_PLAY_SFX
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
||||||
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
||||||
#define JL_HAS_AUDIO_FRAME_TICK
|
#define JL_HAS_AUDIO_FRAME_TICK
|
||||||
|
#define JL_HAS_MOD_MIX_RUN // the module player's 8-bit mix, src/m68k/modMix.s
|
||||||
// lifecycle / present / input (every real port implements)
|
// lifecycle / present / input (every real port implements)
|
||||||
#define JL_HAS_INIT
|
#define JL_HAS_INIT
|
||||||
#define JL_HAS_SHUTDOWN
|
#define JL_HAS_SHUTDOWN
|
||||||
|
|
@ -382,6 +391,8 @@
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
#define JL_HAS_SERIAL_SET_DTR
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
|
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
||||||
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
// save files + disk space (POSIX mkdir/remove + GEMDOS Dfree)
|
// save files + disk space (POSIX mkdir/remove + GEMDOS Dfree)
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
|
|
@ -395,7 +406,7 @@
|
||||||
// (The planar dual-write hooks are not linked to generics: every chunky port
|
// (The planar dual-write hooks are not linked to generics: every chunky port
|
||||||
// elides them to ((void)0) in port.h's shared chunky block, PERF-AUDIT #42.)
|
// elides them to ((void)0) in port.h's shared chunky block, PERF-AUDIT #42.)
|
||||||
#define JL_HAS_FRAME_HZ // VGA ~70, function
|
#define JL_HAS_FRAME_HZ // VGA ~70, function
|
||||||
#define JL_HAS_FRAME_COUNT // $3DA retrace edge counter, function
|
#define JL_HAS_FRAME_COUNT // from the millisecond clock (70 a second), function
|
||||||
#define JL_HAS_WAIT_VBL // $3DA retrace poll, function
|
#define JL_HAS_WAIT_VBL // $3DA retrace poll, function
|
||||||
#define JL_HAS_MILLIS_ELAPSED // uclock / audio-ISR tick, function
|
#define JL_HAS_MILLIS_ELAPSED // uclock / audio-ISR tick, function
|
||||||
// audio: every real port implements the full engine
|
// audio: every real port implements the full engine
|
||||||
|
|
@ -407,7 +418,6 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX
|
#define JL_HAS_AUDIO_PLAY_SFX
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
|
|
@ -439,11 +449,9 @@
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
#define JL_HAS_DISK_FREE
|
#define JL_HAS_DISK_FREE
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_X68000)
|
||||||
// Bring-up: platform SERVICES only (src/x68000/hal.c), plus the serial
|
// Platform services in src/x68000/hal.c, the serial add-on in
|
||||||
// add-on (src/x68000/serial.c). Everything graphical comes from the portable
|
// src/x68000/serial.c, and planar graphics adapted from the Amiga
|
||||||
// generics, so the port renders correctly before any native primitive
|
// (identical surface layout, src/x68000/halPlanar.c).
|
||||||
// exists. Native ops arrive with the storage-model decision.
|
|
||||||
// Planar graphics, adapted from the Amiga (identical surface layout).
|
|
||||||
#define JL_HAS_SURFACE_CLEAR
|
#define JL_HAS_SURFACE_CLEAR
|
||||||
#define JL_HAS_DRAW_PIXEL
|
#define JL_HAS_DRAW_PIXEL
|
||||||
#define JL_HAS_DRAW_LINE
|
#define JL_HAS_DRAW_LINE
|
||||||
|
|
@ -474,7 +482,7 @@
|
||||||
#define JL_HAS_SURFACE_FREE_PORT_DATA
|
#define JL_HAS_SURFACE_FREE_PORT_DATA
|
||||||
#define JL_HAS_INIT // _iocs_crtmod display bring-up
|
#define JL_HAS_INIT // _iocs_crtmod display bring-up
|
||||||
#define JL_HAS_SHUTDOWN // restore the previous CRT mode
|
#define JL_HAS_SHUTDOWN // restore the previous CRT mode
|
||||||
#define JL_HAS_PRESENT // expand the chunky stage into GVRAM
|
#define JL_HAS_PRESENT // copy the stage's dirty plane rows into the text plane
|
||||||
#define JL_HAS_INPUT_INIT
|
#define JL_HAS_INPUT_INIT
|
||||||
#define JL_HAS_INPUT_SHUTDOWN
|
#define JL_HAS_INPUT_SHUTDOWN
|
||||||
#define JL_HAS_INPUT_POLL // IOCS work area keys/mouse, PPI joystick ports
|
#define JL_HAS_INPUT_POLL // IOCS work area keys/mouse, PPI joystick ports
|
||||||
|
|
@ -489,7 +497,8 @@
|
||||||
#define JL_HAS_DISK_FREE
|
#define JL_HAS_DISK_FREE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
// Serial add-on: IOCS polled RS-232C, structurally the ST HAL.
|
// Serial add-on: RSDRV.SYS transmit and line setup, receive on the
|
||||||
|
// port's own SCC interrupt (src/x68000/x68kSccRxIsr.s).
|
||||||
#define JL_HAS_SERIAL_OPEN
|
#define JL_HAS_SERIAL_OPEN
|
||||||
#define JL_HAS_SERIAL_CLOSE
|
#define JL_HAS_SERIAL_CLOSE
|
||||||
#define JL_HAS_SERIAL_POLL
|
#define JL_HAS_SERIAL_POLL
|
||||||
|
|
@ -499,17 +508,21 @@
|
||||||
#define JL_HAS_SERIAL_FLUSH
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
// Application-driven DTR: how a program hangs up (jlSerialSetDtr).
|
||||||
#define JL_HAS_SERIAL_SET_DTR
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
|
// Application-driven RTS for a caller doing its own receive-side flow control (jlSerialSetRts).
|
||||||
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
// Audio. CHIP side on the YM2151 (OPM): tone voices + noise, which is what
|
// Audio. CHIP side on the YM2151 (OPM): tone voices + noise, which is what
|
||||||
// the portable JYM1 tracker rides, with no CPU mixing.
|
// the portable JYM1 tracker rides, with no CPU mixing.
|
||||||
#define JL_HAS_AUDIO_INIT
|
#define JL_HAS_AUDIO_INIT
|
||||||
#define JL_HAS_AUDIO_SHUTDOWN
|
#define JL_HAS_AUDIO_SHUTDOWN
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
||||||
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
||||||
#define JL_HAS_AUDIO_FRAME_TICK
|
#define JL_HAS_AUDIO_FRAME_TICK
|
||||||
// SAMPLE side (src/x68000/audioPcm.c): libxmp-lite + the shared 5-slot SFX
|
#define JL_HAS_MOD_MIX_RUN // the module player's 8-bit mix, src/m68k/modMix.s
|
||||||
|
#define JL_HAS_CODE_WRITTEN // the X68030's caches, after codegen
|
||||||
|
// SAMPLE side (src/x68000/audioPcm.c): the module player + the shared 5-slot SFX
|
||||||
// overlay, encoded to MSM6258 ADPCM and played via _iocs_adpcmlot. Separate
|
// overlay, encoded to MSM6258 ADPCM and played via _iocs_adpcmlot. Separate
|
||||||
// device from the OPM, so chip music and digital audio do not contend.
|
// device from the OPM, so chip music and digital audio do not contend.
|
||||||
#define JL_HAS_AUDIO_PLAY_MOD
|
#define JL_HAS_AUDIO_PLAY_MOD
|
||||||
|
|
@ -554,7 +567,6 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||||
#define JL_HAS_AUDIO_SET_PAN
|
#define JL_HAS_AUDIO_SET_PAN
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
|
|
@ -591,10 +603,10 @@
|
||||||
#define JL_HAS_SAVE_DIR_ENSURE
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
#define JL_HAS_SAVE_DELETE
|
#define JL_HAS_SAVE_DELETE
|
||||||
#define JL_HAS_DISK_FREE
|
#define JL_HAS_DISK_FREE
|
||||||
// audio: a software mixer (MOD through libxmp-lite, the SFX slots, three
|
// audio: a software mixer (MOD through the module player, the SFX slots, three
|
||||||
// square-wave voices, YM-style noise and the speaker tone) run on the
|
// square-wave voices and YM-style noise) run on the program's thread, so
|
||||||
// program's thread, so the tick callback and the stream fills run there
|
// the tick callback and the stream fills run there too and the critical
|
||||||
// too and the critical sections stay the generic no-ops.
|
// sections stay the generic no-ops.
|
||||||
#define JL_HAS_AUDIO_INIT
|
#define JL_HAS_AUDIO_INIT
|
||||||
#define JL_HAS_AUDIO_SHUTDOWN
|
#define JL_HAS_AUDIO_SHUTDOWN
|
||||||
#define JL_HAS_AUDIO_PLAY_MOD
|
#define JL_HAS_AUDIO_PLAY_MOD
|
||||||
|
|
@ -604,7 +616,6 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||||
#define JL_HAS_AUDIO_SET_PAN
|
#define JL_HAS_AUDIO_SET_PAN
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
#define JL_HAS_AUDIO_TONE
|
|
||||||
#define JL_HAS_AUDIO_VOICE
|
#define JL_HAS_AUDIO_VOICE
|
||||||
#define JL_HAS_AUDIO_NOISE
|
#define JL_HAS_AUDIO_NOISE
|
||||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
|
|
@ -646,9 +657,9 @@
|
||||||
|
|
||||||
// ----- Library version -----
|
// ----- Library version -----
|
||||||
|
|
||||||
#define JOEYLIB_VERSION_MAJOR 1
|
#define JL_VERSION_MAJOR 1
|
||||||
#define JOEYLIB_VERSION_MINOR 0
|
#define JL_VERSION_MINOR 0
|
||||||
#define JOEYLIB_VERSION_PATCH 0
|
#define JL_VERSION_PATCH 0
|
||||||
#define JOEYLIB_VERSION_STRING "1.0.0"
|
#define JL_VERSION_STRING "1.0.0"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@
|
||||||
// Stage present.
|
// Stage present.
|
||||||
//
|
//
|
||||||
// jlStagePresent flips the library-owned stage (back-buffer) to the
|
// jlStagePresent flips the library-owned stage (back-buffer) to the
|
||||||
// display. On chunky platforms (IIgs, DOS) this is a direct copy; on
|
// display. Where the stage already has the display's layout it is a
|
||||||
// planar platforms (Amiga, Atari ST) this is a chunky-to-planar
|
// copy (the IIgs's 4bpp pixels; the planes of the Amiga, ST, and
|
||||||
// conversion. Drawing primitives mark per-row dirty ranges on the
|
// X68000); DOS, DVX, and the hosted ports expand each 4-bit pixel to
|
||||||
// stage as a side effect, so jlStagePresent only touches rows that
|
// their display's 8-bit or RGB pixels. Drawing primitives mark per-row
|
||||||
// actually changed since the last present. See claudeDocs/DESIGN.md.
|
// dirty ranges on the stage as a side effect, so jlStagePresent only
|
||||||
|
// touches rows that actually changed since the last present. Palette and
|
||||||
|
// SCB changes reach the display at the present too.
|
||||||
|
|
||||||
#ifndef JOEYLIB_PRESENT_H
|
#ifndef JOEYLIB_PRESENT_H
|
||||||
#define JOEYLIB_PRESENT_H
|
#define JOEYLIB_PRESENT_H
|
||||||
|
|
@ -23,11 +25,9 @@
|
||||||
// callers only need to call jlStagePresent at the end of a frame --
|
// callers only need to call jlStagePresent at the end of a frame --
|
||||||
// everything they drew shows up. Rows that nothing touched since the
|
// everything they drew shows up. Rows that nothing touched since the
|
||||||
// last present are skipped by the per-port present using the per-row
|
// last present are skipped by the per-port present using the per-row
|
||||||
// dirty bands.
|
// dirty bands. Every public call that writes the stage's pixels marks
|
||||||
//
|
// them; the API has no other way to write them, and no call to mark
|
||||||
// To present a region you didn't draw with the standard primitives
|
// rows by hand.
|
||||||
// (e.g. direct framebuffer poking), call surfaceMarkDirtyRect on
|
|
||||||
// the same rect first, then jlStagePresent.
|
|
||||||
void jlStagePresent(void);
|
void jlStagePresent(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,17 @@
|
||||||
// Atari ST -- BIOS Aux (Modem port) via XBIOS Rsconf; TOS buffers RX.
|
// Atari ST -- BIOS Aux (Modem port) via XBIOS Rsconf; TOS buffers RX.
|
||||||
// X68000 -- SCC channel A: Human68k's RSDRV.SYS for transmit and line
|
// X68000 -- SCC channel A: Human68k's RSDRV.SYS for transmit and line
|
||||||
// setup (75-38400 bps, RTS/CTS), REQUIRED -- the open fails
|
// setup (75-38400 bps, RTS/CTS), REQUIRED -- the open fails
|
||||||
// without it; its own RX interrupt fills the ring.
|
// without it; its own RX interrupt fills the ring (when
|
||||||
|
// opened in supervisor mode, as jlInit leaves every program).
|
||||||
|
// DVX -- a COM port through DVX's serial library.
|
||||||
|
// Win32/16 -- a COM port through Windows' communications calls.
|
||||||
|
// SDL3, the web, the PSP, Haiku, and BeOS have no serial port.
|
||||||
//
|
//
|
||||||
// Model: non-blocking + buffered. Received bytes accumulate in a background
|
// Model: non-blocking + buffered. Received bytes accumulate in a background
|
||||||
// ring (a hardware IRQ on DOS, the X68000 and the IIgs, the OS on Amiga/ST);
|
// ring (a hardware IRQ on DOS, the X68000 and the IIgs, the OS on Amiga/ST,
|
||||||
// jlSerialRead / jlSerialAvailable never block. Call jlSerialPoll()
|
// DVX, and Windows); jlSerialRead / jlSerialAvailable never block. Call
|
||||||
// once per game frame -- it is a no-op where the IRQ/OS already fills the
|
// jlSerialPoll() once per game frame. Transmit never waits long: a bounded
|
||||||
// ring, and the hardware drain where it does not, so calling it every frame is
|
// poll on most ports, a queue on the Amiga and Win16.
|
||||||
// always correct. Transmit is a bounded poll.
|
|
||||||
//
|
//
|
||||||
// Received bytes CAN be lost. Each port's receive hardware holds 1 to 3 bytes
|
// Received bytes CAN be lost. Each port's receive hardware holds 1 to 3 bytes
|
||||||
// (16 on a 16550), ~1-3 ms at 9600 baud, and anything that keeps interrupts off
|
// (16 on a 16550), ~1-3 ms at 9600 baud, and anything that keeps interrupts off
|
||||||
|
|
@ -33,12 +36,12 @@
|
||||||
// open (on the IIgs it cuts its long interrupts-off stretches to ~1.7 ms while
|
// open (on the IIgs it cuts its long interrupts-off stretches to ~1.7 ms while
|
||||||
// one is), but these are not: IIgs disk access (GS/OS holds interrupts off
|
// one is), but these are not: IIgs disk access (GS/OS holds interrupts off
|
||||||
// 65-220 ms per 3.5" floppy access, so every file call, asset load and
|
// 65-220 ms per 3.5" floppy access, so every file call, asset load and
|
||||||
// jlAudioInit), IIgs jlAudioPlayMod (~12 ms per KB of samples), a Super Serial
|
// jlInit, which reads the audio replayer), IIgs jlAudioPlayMod (~12 ms per
|
||||||
// Card above 2400 baud, and the program's own interrupt masking. Disk access
|
// KB of samples), a Super Serial Card above 2400 baud, and the program's own
|
||||||
// on the other ports is unmeasured. Schedule them between messages, pause the
|
// interrupt masking. Disk access on the other ports is unmeasured. Schedule
|
||||||
// peer first (jlSerialSetRts(false) or XOFF), or use a protocol that
|
// them between messages, pause the peer first (jlSerialSetRts(false) or
|
||||||
// retransmits. docs/ApiSerial.adoc, "What Can Lose Received Bytes", has the
|
// XOFF), or use a protocol that retransmits. docs/ApiSerial.adoc, "What Can
|
||||||
// per-port table.
|
// Lose Received Bytes", has the per-port table.
|
||||||
//
|
//
|
||||||
// A failed jlSerialOpen is non-fatal: it returns false and every other call is
|
// A failed jlSerialOpen is non-fatal: it returns false and every other call is
|
||||||
// a safe no-op (Available 0, Read 0), exactly like a failed audio init.
|
// a safe no-op (Available 0, Read 0), exactly like a failed audio init.
|
||||||
|
|
@ -54,10 +57,13 @@
|
||||||
// JL_SERIAL_PRINTER -- IIgs printer port (SCC channel A). Only the IIgs has
|
// JL_SERIAL_PRINTER -- IIgs printer port (SCC channel A). Only the IIgs has
|
||||||
// a second built-in port; elsewhere == DEFAULT.
|
// a second built-in port; elsewhere == DEFAULT.
|
||||||
// JL_SERIAL_SLOT -- a card/port selected by jlSerialConfigT.unit: IIgs
|
// JL_SERIAL_SLOT -- a card/port selected by jlSerialConfigT.unit: IIgs
|
||||||
// Super Serial Card slot (1..7); DOS COM number (1..4);
|
// Super Serial Card slot (1..7); DOS or DVX COM number
|
||||||
// Amiga serial.device unit.
|
// (1..4); Win16 COM (1..9); Win32 COM (1..255); Amiga
|
||||||
// JL_SERIAL_DEFAULT -- the platform's natural RS-232 port (IIgs modem, DOS
|
// serial.device unit. The ST and X68000 have one port
|
||||||
// COM1, ST Modem port, Amiga unit 0).
|
// and open it for every device.
|
||||||
|
// JL_SERIAL_DEFAULT -- the platform's natural RS-232 port (IIgs modem, DOS,
|
||||||
|
// DVX, and Windows COM1, ST Modem port, Amiga unit 0,
|
||||||
|
// X68000 RS-232C).
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JL_SERIAL_DEFAULT = 0,
|
JL_SERIAL_DEFAULT = 0,
|
||||||
JL_SERIAL_MODEM,
|
JL_SERIAL_MODEM,
|
||||||
|
|
@ -78,24 +84,28 @@ typedef enum {
|
||||||
} jlSerialFlowE;
|
} jlSerialFlowE;
|
||||||
|
|
||||||
// Line configuration. `unit` disambiguates JL_SERIAL_SLOT (see jlSerialDeviceE)
|
// Line configuration. `unit` disambiguates JL_SERIAL_SLOT (see jlSerialDeviceE)
|
||||||
// and is ignored for the other devices.
|
// and is ignored for the other devices. A field left 0 takes a default (baud
|
||||||
|
// 9600, 8 data bits, 1 stop bit); data bits outside 5..8, stop bits other
|
||||||
|
// than 1 or 2, or a parity or flow the enums do not name fail the open.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t baud; // 300 .. 115200 (each port clamps to what it supports)
|
uint32_t baud; // 300 .. 115200 (each port runs at the nearest rate it has: docs/ApiSerial.adoc)
|
||||||
uint8_t dataBits; // 5 .. 8
|
uint8_t dataBits; // 5 .. 8
|
||||||
uint8_t stopBits; // 1 or 2
|
uint8_t stopBits; // 1 or 2
|
||||||
jlSerialParityE parity;
|
jlSerialParityE parity;
|
||||||
jlSerialFlowE flow;
|
jlSerialFlowE flow;
|
||||||
uint8_t unit; // JL_SERIAL_SLOT: IIgs slot 1..7 / DOS COM 1..4 / Amiga unit
|
uint8_t unit; // JL_SERIAL_SLOT: IIgs slot 1..7 / DOS, DVX COM 1..4 / Win16 COM 1..9 / Win32 COM 1..255 / Amiga unit
|
||||||
} jlSerialConfigT;
|
} jlSerialConfigT;
|
||||||
|
|
||||||
// Open + configure the port. Only one port is open at a time; a second
|
// Open + configure the port. Only one port is open at a time; a second
|
||||||
// jlSerialOpen without a jlSerialClose fails. Returns false (non-fatal) if the
|
// jlSerialOpen without a jlSerialClose fails. Returns false (non-fatal) if the
|
||||||
// port cannot be opened or the platform has no serial support -- every other
|
// port cannot be opened or the platform has no serial support -- every other
|
||||||
// call then no-ops. On the X68000 it also fails when RSDRV.SYS is not loaded,
|
// call then no-ops. It also fails, with jlLastError() saying why, when no UART
|
||||||
// and jlLastError() says so.
|
// answers at the DOS COM port, when no Super Serial Card answers in the IIgs
|
||||||
|
// slot, and when RSDRV.SYS is not loaded on the X68000.
|
||||||
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
||||||
|
|
||||||
// Close the open port and release its resources. Safe to call when not open.
|
// Close the open port and release its resources. Safe to call when not open.
|
||||||
|
// Bytes jlSerialWrite accepted are still sent.
|
||||||
// A port still open when the program exits is closed then (the first
|
// A port still open when the program exits is closed then (the first
|
||||||
// successful jlSerialOpen registers an atexit handler): where the port's RX
|
// successful jlSerialOpen registers an atexit handler): where the port's RX
|
||||||
// interrupt is taken over (DOS, X68000, IIgs), the vector points into the
|
// interrupt is taken over (DOS, X68000, IIgs), the vector points into the
|
||||||
|
|
@ -109,10 +119,7 @@ bool jlSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config);
|
||||||
// (Hayes S25: 50 ms by default), then close: the line stays down.
|
// (Hayes S25: 50 ms by default), then close: the line stays down.
|
||||||
void jlSerialClose(void);
|
void jlSerialClose(void);
|
||||||
|
|
||||||
// Pump the RX/TX buffers. Call once per game frame. Next to a no-op where a
|
// Pump the RX/TX buffers. Call once per game frame.
|
||||||
// hardware IRQ or the OS already services the port; drains the hardware into
|
|
||||||
// the RX ring where nothing else does (an IIgs Super Serial Card whose
|
|
||||||
// interrupt switch is off). Calling it every frame is always correct.
|
|
||||||
void jlSerialPoll(void);
|
void jlSerialPoll(void);
|
||||||
|
|
||||||
// Bytes waiting in the RX buffer, readable now without blocking.
|
// Bytes waiting in the RX buffer, readable now without blocking.
|
||||||
|
|
@ -123,7 +130,8 @@ uint16_t jlSerialAvailable(void);
|
||||||
uint16_t jlSerialRead(uint8_t *buf, uint16_t max);
|
uint16_t jlSerialRead(uint8_t *buf, uint16_t max);
|
||||||
|
|
||||||
// Send `len` bytes; returns the count accepted (may be < len if a flow-
|
// Send `len` bytes; returns the count accepted (may be < len if a flow-
|
||||||
// controlled peer or a full transmit path stalls). Transmit is a bounded poll.
|
// controlled peer or a full transmit path stalls). Never waits long: a
|
||||||
|
// bounded poll on most ports; the Amiga and Win16 queue instead.
|
||||||
uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len);
|
uint16_t jlSerialWrite(const uint8_t *buf, uint16_t len);
|
||||||
|
|
||||||
// Single-byte convenience. jlSerialReadByte returns -1 when none is ready.
|
// Single-byte convenience. jlSerialReadByte returns -1 when none is ready.
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,15 @@
|
||||||
//
|
//
|
||||||
// Performance contract: jlSpriteDraw should be at least as fast as the
|
// Performance contract: jlSpriteDraw should be at least as fast as the
|
||||||
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
||||||
// code per CPU). Every port has a runtime emitter (DOS x86, Amiga/ST/
|
// code per CPU). The machine ports have runtime emitters (IIgs 65816,
|
||||||
// X68000 68k planar, IIgs 65816); jlSpriteCompile compiles the shift/op
|
// Amiga/X68000 68k planar, ST 68k interleaved, DOS/DVX x86), and so does
|
||||||
// variants into the codegen arena and the draw/save/restore calls
|
// Win16 (x86, as DOS);
|
||||||
// dispatch to the compiled routines, falling back to the interpreter
|
// jlSpriteCompile compiles the shift/op variants into the codegen arena
|
||||||
// for clipped draws or uncompiled variants.
|
// and the draw/save/restore calls dispatch to the compiled routines,
|
||||||
// jlSpritePrewarm compiles ahead of the first draw so the emit cost
|
// falling back to the interpreter for clipped draws or uncompiled
|
||||||
// does not land mid-frame.
|
// variants. Nothing compiles on demand: compile while loading, or every
|
||||||
|
// draw interprets. The other hosted ports (JL_SPRITE_INTERPRETED) have no
|
||||||
|
// emitter and always interpret.
|
||||||
|
|
||||||
#ifndef JOEYLIB_SPRITE_H
|
#ifndef JOEYLIB_SPRITE_H
|
||||||
#define JOEYLIB_SPRITE_H
|
#define JOEYLIB_SPRITE_H
|
||||||
|
|
@ -69,8 +71,7 @@ typedef struct {
|
||||||
// Returns the number of cels actually written into outCels[]. Returns
|
// Returns the number of cels actually written into outCels[]. Returns
|
||||||
// 0 if the file is missing, the magic / target byte is wrong, or no
|
// 0 if the file is missing, the magic / target byte is wrong, or no
|
||||||
// cel could be allocated.
|
// cel could be allocated.
|
||||||
uint16_t jlSpriteBankLoad(const char *path, jlSpriteT **outCels,
|
uint16_t jlSpriteBankLoad(const char *path, jlSpriteT **outCels, uint16_t maxCels, uint16_t *outPalette);
|
||||||
uint16_t maxCels, uint16_t *outPalette);
|
|
||||||
|
|
||||||
// Load up to maxCels cels from a baked .spc PRE-COMPILED sprite bank
|
// Load up to maxCels cels from a baked .spc PRE-COMPILED sprite bank
|
||||||
// (tools/spritebake output for THIS platform). Identical contract to
|
// (tools/spritebake output for THIS platform). Identical contract to
|
||||||
|
|
@ -79,27 +80,29 @@ uint16_t jlSpriteBankLoad(const char *path, jlSpriteT **outCels,
|
||||||
// full compiled speed with NO runtime jlSpriteCompile. A cel that does not fit
|
// full compiled speed with NO runtime jlSpriteCompile. A cel that does not fit
|
||||||
// the arena is loaded uncompiled (interpreter fallback), never an error -- so
|
// the arena is loaded uncompiled (interpreter fallback), never an error -- so
|
||||||
// this is a drop-in, faster-starting replacement for jlSpriteBankLoad + a
|
// this is a drop-in, faster-starting replacement for jlSpriteBankLoad + a
|
||||||
// jlSpriteCompile/Prewarm pass. Returns 0 if the file is missing or its magic
|
// jlSpriteCompile pass. Returns 0 if the file is missing, its magic /
|
||||||
// / target / shift-count does not match this build.
|
// target / shift-count does not match this build, its cel size is out of
|
||||||
uint16_t jlSpriteBankLoadPrecompiled(const char *path, jlSpriteT **outCels,
|
// range, or the 64 KB buffer a cel's code is read into cannot be allocated
|
||||||
uint16_t maxCels, uint16_t *outPalette);
|
// (the interpreting ports read it too, then drop the code).
|
||||||
|
uint16_t jlSpriteBankLoadPrecompiled(const char *path, jlSpriteT **outCels, uint16_t maxCels, uint16_t *outPalette);
|
||||||
|
|
||||||
// Write an already-compiled sprite bank to a .spc cache file (the runtime twin
|
// Write an already-compiled sprite bank to a .spc cache file (the runtime twin
|
||||||
// of the offline tools/spritebake). Call after jlSpriteBankLoad + a
|
// of the offline tools/spritebake). Call after jlSpriteBankLoad + a
|
||||||
// jlSpriteCompile pass so a later launch can jlSpriteBankLoadPrecompiled the
|
// jlSpriteCompile pass so a later launch can jlSpriteBankLoadPrecompiled the
|
||||||
// cache and skip the JIT -- i.e. SHIP the small cross-platform .spr, compile
|
// cache and skip the JIT -- i.e. SHIP the small cross-platform .spr, compile
|
||||||
// once, and cache the (large) per-platform compiled bank on first run. `cels`
|
// once, and cache the (large) per-platform compiled bank on first run. `cels`
|
||||||
// are `count` sprites (uncompiled ones are cached as-is and JIT again next
|
// are `count` sprites, all of one size (uncompiled ones are cached
|
||||||
// run); `palette` (or NULL) is embedded. Returns false if the destination
|
// uncompiled, and nothing compiles them at load: jlSpriteCompile any whose
|
||||||
// isn't writable (e.g. a write-protected disk), which is harmless -- the app
|
// jlSpriteCompiledSize is 0); `palette` (or NULL) is embedded. Returns false
|
||||||
// just keeps compiling at startup.
|
// if the cels differ in size or the destination isn't writable (e.g. a
|
||||||
bool jlSpriteBankSavePrecompiled(const char *path, jlSpriteT **cels,
|
// write-protected disk), which is harmless -- the app just keeps compiling
|
||||||
uint16_t count, const uint16_t *palette);
|
// at startup.
|
||||||
|
bool jlSpriteBankSavePrecompiled(const char *path, jlSpriteT **cels, uint16_t count, const uint16_t *palette);
|
||||||
|
|
||||||
// Wrap a tile-data blob in a jlSpriteT. The tile data must outlive the
|
// Wrap a tile-data blob in a jlSpriteT. The tile data must outlive the
|
||||||
// jlSpriteT; we do not copy it. Returns NULL if widthTiles or
|
// jlSpriteT; we do not copy it. Returns NULL if tileData is NULL,
|
||||||
// heightTiles is 0, or if the codegen arena cannot fit a placeholder
|
// widthTiles or heightTiles is 0, or memory runs out. The codegen arena
|
||||||
// entry for this sprite.
|
// is not touched until jlSpriteCompile.
|
||||||
jlSpriteT *jlSpriteCreate(const uint8_t *tileData, uint8_t widthTiles, uint8_t heightTiles);
|
jlSpriteT *jlSpriteCreate(const uint8_t *tileData, uint8_t widthTiles, uint8_t heightTiles);
|
||||||
|
|
||||||
// Release a jlSpriteT and any codegen entries cached for it. The tile
|
// Release a jlSpriteT and any codegen entries cached for it. The tile
|
||||||
|
|
@ -107,12 +110,17 @@ jlSpriteT *jlSpriteCreate(const uint8_t *tileData, uint8_t widthTiles, uint8_t h
|
||||||
// owns that buffer.
|
// owns that buffer.
|
||||||
void jlSpriteDestroy(jlSpriteT *sp);
|
void jlSpriteDestroy(jlSpriteT *sp);
|
||||||
|
|
||||||
// Compile the sprite's draw routines into the codegen arena. After
|
// Compile the sprite's draw, save, and restore routines into the codegen
|
||||||
// this returns true, jlSpriteDraw uses the compiled fast path on
|
// arena. jlSpriteDraw never compiles on demand, so call this while
|
||||||
// platforms where the emitter is wired (all four ports). Returns
|
// loading (or load a pre-compiled bank). After it returns true the draw,
|
||||||
// false if the arena is full (caller may run jlSpriteCompact and
|
// save, and restore calls use the compiled routines that fit: a short
|
||||||
// retry), the platform doesn't have a real emitter yet, or the
|
// arena keeps only the x-on-an-8-pixel-boundary draws on the Amiga, ST,
|
||||||
// sprite has no source tile data.
|
// and X68000, a routine over 16 KB is left out, and anything not compiled
|
||||||
|
// (and every clipped draw) interprets. Returns false if the arena is full
|
||||||
|
// even after compacting it (jlLastError says so), the sprite has no
|
||||||
|
// source tile data, the compiler's 64 KB working buffer cannot be
|
||||||
|
// allocated, no routine fits, or the port has no emitter
|
||||||
|
// (JL_SPRITE_INTERPRETED); the sprite still draws, interpreted.
|
||||||
//
|
//
|
||||||
// Idempotent: calling on a sprite that's already compiled is a
|
// Idempotent: calling on a sprite that's already compiled is a
|
||||||
// no-op and returns true.
|
// no-op and returns true.
|
||||||
|
|
@ -125,12 +133,6 @@ bool jlSpriteCompile(jlSpriteT *sp);
|
||||||
// the actual per-cel cost of the current platform's emitter.
|
// the actual per-cel cost of the current platform's emitter.
|
||||||
uint32_t jlSpriteCompiledSize(const jlSpriteT *sp);
|
uint32_t jlSpriteCompiledSize(const jlSpriteT *sp);
|
||||||
|
|
||||||
// Hint that this sprite will be drawn soon. Currently a wrapper
|
|
||||||
// around jlSpriteCompile that ignores the return value, kept for API
|
|
||||||
// symmetry with the rest of the library and for callers that don't
|
|
||||||
// care about compile success.
|
|
||||||
void jlSpritePrewarm(jlSpriteT *sp);
|
|
||||||
|
|
||||||
// Draw the sprite at pixel (x,y) on the destination surface. Pixels
|
// Draw the sprite at pixel (x,y) on the destination surface. Pixels
|
||||||
// equal to color 0 in the sprite source are skipped (transparent).
|
// equal to color 0 in the sprite source are skipped (transparent).
|
||||||
// Off-surface portions are clipped.
|
// Off-surface portions are clipped.
|
||||||
|
|
@ -144,27 +146,34 @@ void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
||||||
// sprites and on ports without a compiled fast path.
|
// sprites and on ports without a compiled fast path.
|
||||||
void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y);
|
||||||
|
|
||||||
// Worst-case backup capacity for a widthTiles x heightTiles sprite at
|
// How many _type elements a backup buffer needs for a widthTiles x
|
||||||
// ANY x, on every port. The planar 68k compiled save/restore operates
|
// heightTiles sprite at ANY x, on every port (rounded up), so that
|
||||||
// on the 16-pixel-group-aligned window covering the sprite (x rounded
|
//
|
||||||
// DOWN to a 16-px boundary, width UP), so the worst case is one extra
|
// static uint16_t gBacking[jlSpriteBackupLength(uint16_t, 4, 4)];
|
||||||
// 16-px group: (ceil(w/16) + 1) * 8 bytes per row. This also covers the
|
//
|
||||||
// chunky ports' (w/2 + 1) * h trivially. Give the backing array 2-byte
|
// is big enough, and jlSpriteBackupLength(uint8_t, w, h) is the size in
|
||||||
// alignment (e.g. uint16_t backing or __attribute__((aligned(2)))) --
|
// bytes (for jlAlloc, say). The worst case is the 16-pixel-group-aligned
|
||||||
// odd buffer pointers route save/restore to the slower interpreted
|
// region covering the sprite (x rounded DOWN to a 16-px boundary, width
|
||||||
// paths on planar ports (68000 word ops require even addresses).
|
// UP): one extra 16-px group, (ceil(w/16) + 1) * 8 bytes per row. Give
|
||||||
#define JOEY_SPRITE_BACKUP_BYTES(_wT, _hT) \
|
// the buffer 2-byte alignment (a uint16_t array has it): on the Amiga,
|
||||||
((uint16_t)((((((uint16_t)(_wT) * 8u) + 15u) >> 4) + 1u) * 8u * ((uint16_t)(_hT) * 8u)))
|
// ST, and X68000 an odd address sends save and restore down a much
|
||||||
|
// slower path, and on the Amiga and ST it crashes the trusted variants.
|
||||||
|
#define jlSpriteBackupLength(_type, _wT, _hT) \
|
||||||
|
((uint16_t)((((((((uint16_t)(_wT) * 8u) + 15u) >> 4) + 1u) * 8u * ((uint16_t)(_hT) * 8u)) + sizeof(_type) - 1u) / sizeof(_type)))
|
||||||
|
|
||||||
// Capture the destination region a subsequent jlSpriteDraw at the same
|
// Capture the destination region a subsequent jlSpriteDraw at the same
|
||||||
// (x,y) would write to. backup->bytes must have at least
|
// (x,y) would write to. backup->bytes must have room for
|
||||||
// JOEY_SPRITE_BACKUP_BYTES(widthTiles, heightTiles) bytes of capacity
|
// jlSpriteBackupLength(uint8_t, widthTiles, heightTiles) bytes for
|
||||||
// for fully in-bounds draws; for clipped draws only the visible bytes
|
// fully in-bounds draws; for clipped draws only the visible bytes are
|
||||||
// are stored. The captured region's exact geometry is reported in
|
// stored. The captured region's exact geometry is reported in
|
||||||
// backup->x/width/height/sizeBytes -- on planar ports (Amiga, ST) it is
|
// backup->x/y/width/height/sizeBytes: x is rounded down to even on every
|
||||||
// the 16-px-aligned window containing the sprite, so the region a
|
// port, a compiled save on the Amiga, ST, and X68000 takes the 16-px-
|
||||||
// caller must not invalidate before restoring is the RECORDED window,
|
// aligned region containing the sprite, a compiled save on the IIgs only
|
||||||
// not just the sprite rect.
|
// the rows holding an opaque pixel, and a clipped save the visible part.
|
||||||
|
// So the region a caller must not invalidate before restoring is the
|
||||||
|
// RECORDED region, not just the sprite rect. On the IIgs the buffer must
|
||||||
|
// not cross a 64 KB bank boundary. With backup->bytes NULL nothing is
|
||||||
|
// copied: the call only fills in the region a save would record.
|
||||||
void jlSpriteSaveUnder(const jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlSpriteBackupT *backup);
|
void jlSpriteSaveUnder(const jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlSpriteBackupT *backup);
|
||||||
|
|
||||||
// TRUSTED save-under (P7-1). Same trust contract as
|
// TRUSTED save-under (P7-1). Same trust contract as
|
||||||
|
|
@ -188,14 +197,18 @@ void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup);
|
||||||
// * s and backup (and backup->sprite, backup->bytes) are non-NULL,
|
// * s and backup (and backup->sprite, backup->bytes) are non-NULL,
|
||||||
// * backup was produced by a jlSpriteSaveUnder that took the COMPILED
|
// * backup was produced by a jlSpriteSaveUnder that took the COMPILED
|
||||||
// path (a "trusted" backup: 16-px-aligned x/width on planar ports,
|
// path (a "trusted" backup: 16-px-aligned x/width on planar ports,
|
||||||
// even x/width on chunky, backup->height == the sprite's pixel
|
// even x/width on chunky, backup->height == the rows the compiled
|
||||||
// height, buffer at least 2-byte aligned),
|
// save covers -- the sprite's pixel height, or on the IIgs the rows
|
||||||
// * the recorded window is fully on the destination surface.
|
// holding an opaque pixel),
|
||||||
|
// * on the Amiga and ST, backup->bytes is at least 2-byte aligned (the
|
||||||
|
// trusted save and save-and-draw need that too),
|
||||||
|
// * the recorded region is fully on the destination surface.
|
||||||
// It is the expert inner-loop entry for a game that pre-compiles its
|
// It is the expert inner-loop entry for a game that pre-compiles its
|
||||||
// sprites (jlSpritePrewarm) and already clamps positions on-screen --
|
// sprites (jlSpriteCompile) and already clamps positions on-screen --
|
||||||
// use jlSpriteRestoreUnder anywhere those invariants are not statically
|
// use jlSpriteRestoreUnder anywhere those invariants are not statically
|
||||||
// guaranteed. Anything the trusted fast path does not recognize (an
|
// guaranteed. Anything the trusted fast path does not recognize (an
|
||||||
// uncompiled shift, a non-stage / non-16-row window on the 68k ports)
|
// uncompiled shift, a non-stage / non-16-row region on the 68k and x86
|
||||||
|
// ports)
|
||||||
// falls back to the fully-validated jlSpriteRestoreUnder, so a
|
// falls back to the fully-validated jlSpriteRestoreUnder, so a
|
||||||
// conservative caller degrades safely rather than corrupting -- but the
|
// conservative caller degrades safely rather than corrupting -- but the
|
||||||
// NULL / on-surface guarantees above are still the caller's.
|
// NULL / on-surface guarantees above are still the caller's.
|
||||||
|
|
@ -229,9 +242,10 @@ void jlSpriteSaveAndDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t
|
||||||
// the source surface can be modified afterwards. Width and height
|
// the source surface can be modified afterwards. Width and height
|
||||||
// are in TILES (each tile = 8x8 pixels). x and y are in pixels and
|
// are in TILES (each tile = 8x8 pixels). x and y are in pixels and
|
||||||
// must be aligned to a tile boundary (multiple of 8) on the source
|
// must be aligned to a tile boundary (multiple of 8) on the source
|
||||||
// surface; misaligned coordinates return NULL.
|
// surface. Returns NULL if src is NULL, a dimension is 0, x or y is
|
||||||
jlSpriteT *jlSpriteCreateFromSurface(const jlSurfaceT *src, int16_t x, int16_t y,
|
// misaligned, the region is not wholly on the surface, or memory runs
|
||||||
uint8_t widthTiles, uint8_t heightTiles);
|
// out.
|
||||||
|
jlSpriteT *jlSpriteCreateFromSurface(const jlSurfaceT *src, int16_t x, int16_t y, uint8_t widthTiles, uint8_t heightTiles);
|
||||||
|
|
||||||
// Defragment the codegen arena. Walks live sprite slots and
|
// Defragment the codegen arena. Walks live sprite slots and
|
||||||
// memmoves them down to consolidate free space; any holes left by
|
// memmoves them down to consolidate free space; any holes left by
|
||||||
|
|
@ -245,7 +259,10 @@ void jlSpriteCompact(void);
|
||||||
// Arena introspection. Used to gauge whether jlSpriteCompact is
|
// Arena introspection. Used to gauge whether jlSpriteCompact is
|
||||||
// worth running, or whether the codegenBytes budget needs to grow.
|
// worth running, or whether the codegenBytes budget needs to grow.
|
||||||
// Free space is (Total - Used), but it may be fragmented across
|
// Free space is (Total - Used), but it may be fragmented across
|
||||||
// holes until jlSpriteCompact runs.
|
// holes until jlSpriteCompact runs. On the IIgs Total is what jlInit was
|
||||||
|
// granted, in blocks of at most 64 KB that compacting never merges (one
|
||||||
|
// sprite's code must fit in one). The interpreting ports still allocate
|
||||||
|
// the arena: Total reports it and Used stays 0.
|
||||||
uint32_t jlSpriteCodegenBytesUsed(void);
|
uint32_t jlSpriteCodegenBytesUsed(void);
|
||||||
uint32_t jlSpriteCodegenBytesTotal(void);
|
uint32_t jlSpriteCodegenBytesTotal(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
//
|
//
|
||||||
// All surfaces are 320x200 16-color images. In-memory storage is
|
// All surfaces are 320x200 16-color images. In-memory storage is
|
||||||
// target-native: chunky 4bpp packed (two pixels per byte, high nibble
|
// target-native: chunky 4bpp packed (two pixels per byte, high nibble
|
||||||
// is the left pixel) on IIgs and DOS; native planar (4 separate
|
// is the left pixel) on the IIgs, DOS, DVX, and the hosted ports;
|
||||||
// bitplanes on Amiga, word-interleaved planes on Atari ST) on the
|
// native planar (4 separate bitplanes on the Amiga and X68000,
|
||||||
// 68k ports. Public API entry points speak in color indices (0..15)
|
// word-interleaved planes on the Atari ST) on the 68k ports. Public API
|
||||||
// regardless, so game code does not see the storage format.
|
// entry points speak in color indices (0..15) regardless, so game code
|
||||||
|
// does not see the storage format.
|
||||||
//
|
//
|
||||||
// Each surface carries its own 200-entry SCB (scanline control byte)
|
// Each surface carries its own 200-entry SCB (scanline control byte)
|
||||||
// table and a 16-by-16 $0RGB palette set. See claudeDocs/DESIGN.md section 6.
|
// table and a 16-by-16 $0RGB palette set. See claudeDocs/DESIGN.md section 6.
|
||||||
|
|
@ -22,14 +23,14 @@
|
||||||
|
|
||||||
// ----- Fixed surface dimensions (library contract) -----
|
// ----- Fixed surface dimensions (library contract) -----
|
||||||
|
|
||||||
#define SURFACE_WIDTH 320
|
#define JL_SURFACE_WIDTH 320
|
||||||
#define SURFACE_HEIGHT 200
|
#define JL_SURFACE_HEIGHT 200
|
||||||
#define SURFACE_BYTES_PER_ROW 160
|
#define JL_SURFACE_BYTES_PER_ROW 160
|
||||||
#define SURFACE_PIXELS_SIZE (SURFACE_BYTES_PER_ROW * SURFACE_HEIGHT)
|
#define JL_SURFACE_PIXELS_SIZE (JL_SURFACE_BYTES_PER_ROW * JL_SURFACE_HEIGHT)
|
||||||
|
|
||||||
#define SURFACE_PALETTE_COUNT 16
|
#define JL_SURFACE_PALETTE_COUNT 16
|
||||||
#define SURFACE_COLORS_PER_PALETTE 16
|
#define JL_SURFACE_COLORS_PER_PALETTE 16
|
||||||
#define SURFACE_PALETTE_ENTRIES (SURFACE_PALETTE_COUNT * SURFACE_COLORS_PER_PALETTE)
|
#define JL_SURFACE_PALETTE_ENTRIES (JL_SURFACE_PALETTE_COUNT * JL_SURFACE_COLORS_PER_PALETTE)
|
||||||
|
|
||||||
// ----- Opaque public type -----
|
// ----- Opaque public type -----
|
||||||
|
|
||||||
|
|
@ -57,16 +58,16 @@ void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||||
// Copy a pixel rectangle from src into dst at the SAME position (no
|
// Copy a pixel rectangle from src into dst at the SAME position (no
|
||||||
// repositioning, so planar ports never shift bits). The rect is
|
// repositioning, so planar ports never shift bits). The rect is
|
||||||
// clipped to the surface, then widened OUTWARD to the 16-pixel group
|
// clipped to the surface, then widened OUTWARD to the 16-pixel group
|
||||||
// grid ON EVERY PORT -- the copied window is x rounded down and
|
// grid ON EVERY PORT -- the copied region is x rounded down and
|
||||||
// x + w rounded up to multiples of 16 (same window rule as
|
// x + w rounded up to multiples of 16 (same region rule as
|
||||||
// jlSpriteSaveUnder), so up to 15 extra pixels on each side are
|
// jlSpriteSaveUnder), so up to 15 extra pixels on each side are
|
||||||
// copied. Two consequences the caller owns: a snapped erase can
|
// copied. Two consequences the caller owns: a snapped erase can
|
||||||
// clobber on-screen content up to 15 px outside the requested rect,
|
// clobber on-screen content up to 15 px outside the requested rect,
|
||||||
// and it silently invalidates any live jlSpriteBackupT whose
|
// and it silently invalidates any live jlSpriteBackupT whose
|
||||||
// recorded window it touches. Copies pixels only: SCBs and palettes
|
// recorded region it touches. Copies pixels only: SCBs and palettes
|
||||||
// are untouched. The intended use is restoring background
|
// are untouched. The intended use is restoring background
|
||||||
// rectangles from a pristine copy of the scene (the clean-buffer
|
// rectangles from a pristine copy of the scene (the clean-buffer
|
||||||
// pattern) instead of per-sprite save/restore -- erase ALL windows,
|
// pattern) instead of per-sprite save/restore -- erase ALL regions,
|
||||||
// then redraw ALL sprites; do not mix with backup-restore in the
|
// then redraw ALL sprites; do not mix with backup-restore in the
|
||||||
// same region.
|
// same region.
|
||||||
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||||
|
|
@ -80,8 +81,12 @@ void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_
|
||||||
bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path);
|
bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path);
|
||||||
|
|
||||||
// Read a surface from disk, overwriting dst. Returns false if the
|
// Read a surface from disk, overwriting dst. Returns false if the
|
||||||
// file is missing, the wrong size, or the read fails. dst keeps its
|
// file is missing, the wrong size, has an SCB byte above 15, or the
|
||||||
// identity (no reallocation).
|
// read fails; a read that fails part way can leave dst's pixels partly
|
||||||
|
// overwritten, but its SCBs and palettes are then untouched. Colour 0
|
||||||
|
// of every palette loaded is set to black, as jlPaletteSet does. dst
|
||||||
|
// keeps its identity (no reallocation). Neither file call sets
|
||||||
|
// jlLastError.
|
||||||
bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path);
|
bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path);
|
||||||
|
|
||||||
// 32-bit content hash of the surface. NOT FNV-1a: a two-stream 16-bit
|
// 32-bit content hash of the surface. NOT FNV-1a: a two-stream 16-bit
|
||||||
|
|
@ -95,7 +100,7 @@ bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path);
|
||||||
// low byte.
|
// low byte.
|
||||||
// SURFACE_HASH_MIX_BYTE in src/core/surfaceInternal.h is the single
|
// SURFACE_HASH_MIX_BYTE in src/core/surfaceInternal.h is the single
|
||||||
// source of truth for the per-byte mix; a port override (the planar
|
// source of truth for the per-byte mix; a port override (the planar
|
||||||
// Amiga/ST readers) must reuse it byte-for-byte and feed the identical
|
// Amiga, ST, and X68000 readers) must reuse it byte-for-byte and feed the identical
|
||||||
// byte sequence, so the same logical pixels produce the same hash on
|
// byte sequence, so the same logical pixels produce the same hash on
|
||||||
// every port regardless of internal storage format. Used by the UBER
|
// every port regardless of internal storage format. Used by the UBER
|
||||||
// validation harness to pixel-compare ports against an IIgs golden
|
// validation harness to pixel-compare ports against an IIgs golden
|
||||||
|
|
|
||||||
|
|
@ -33,21 +33,23 @@
|
||||||
|
|
||||||
// ----- Constants -----
|
// ----- Constants -----
|
||||||
|
|
||||||
#define TILE_PIXELS_PER_SIDE 8
|
#define JL_TILE_PIXELS_PER_SIDE 8
|
||||||
#define TILE_BYTES_PER_ROW 4
|
#define JL_TILE_BYTES_PER_ROW 4
|
||||||
#define TILE_BYTES (TILE_BYTES_PER_ROW * TILE_PIXELS_PER_SIDE)
|
#define JL_TILE_BYTES (JL_TILE_BYTES_PER_ROW * JL_TILE_PIXELS_PER_SIDE)
|
||||||
#define TILE_BLOCKS_PER_ROW (SURFACE_WIDTH / TILE_PIXELS_PER_SIDE)
|
#define JL_TILE_BLOCKS_PER_ROW (JL_SURFACE_WIDTH / JL_TILE_PIXELS_PER_SIDE)
|
||||||
#define TILE_BLOCKS_PER_COL (SURFACE_HEIGHT / TILE_PIXELS_PER_SIDE)
|
#define JL_TILE_BLOCKS_PER_COL (JL_SURFACE_HEIGHT / JL_TILE_PIXELS_PER_SIDE)
|
||||||
|
|
||||||
// Sentinel for asciiMap entries that should not draw. jlDrawText
|
// Sentinel for asciiMap entries that should not draw. jlDrawText
|
||||||
// advances the cursor past TILE_NO_GLYPH chars without writing.
|
// advances the cursor past JL_TILE_NO_GLYPH chars without writing.
|
||||||
#define TILE_NO_GLYPH ((uint16_t)0xFFFFu)
|
#define JL_TILE_NO_GLYPH ((uint16_t)0xFFFFu)
|
||||||
|
|
||||||
// ----- Types -----
|
// ----- Types -----
|
||||||
|
|
||||||
// Stack-allocated 32-byte snapshot buffer for jlTileSnap / jlTilePaste.
|
// Stack-allocated 32-byte snapshot buffer for jlTileSnap / jlTilePaste.
|
||||||
|
// Target-native: plane-major on the Amiga and X68000, row by row with each
|
||||||
|
// row's four plane bytes together on the ST, chunky elsewhere.
|
||||||
typedef struct jlTileT {
|
typedef struct jlTileT {
|
||||||
uint8_t pixels[TILE_BYTES];
|
uint8_t pixels[JL_TILE_BYTES];
|
||||||
} jlTileT;
|
} jlTileT;
|
||||||
|
|
||||||
// ----- API -----
|
// ----- API -----
|
||||||
|
|
@ -55,17 +57,14 @@ typedef struct jlTileT {
|
||||||
// Copy the 8x8 block at (srcBx, srcBy) on src to (dstBx, dstBy) on
|
// Copy the 8x8 block at (srcBx, srcBy) on src to (dstBx, dstBy) on
|
||||||
// dst, opaque. Out-of-range block coordinates on either side are
|
// dst, opaque. Out-of-range block coordinates on either side are
|
||||||
// silent no-ops; src and dst can be the same surface.
|
// silent no-ops; src and dst can be the same surface.
|
||||||
void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
||||||
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
|
||||||
|
|
||||||
// Like jlTileCopy but pixels equal to transparentIndex are skipped --
|
// Like jlTileCopy but pixels equal to transparentIndex are skipped --
|
||||||
// the destination pixel keeps its original value. Use this for fonts
|
// the destination pixel keeps its original value. Use this for fonts
|
||||||
// (transparentIndex = 0 leaves the page background showing through
|
// (transparentIndex = 0 leaves the page background showing through
|
||||||
// glyph backgrounds) and for any tileset where some pixels are meant
|
// glyph backgrounds) and for any tileset where some pixels are meant
|
||||||
// to be see-through.
|
// to be see-through.
|
||||||
void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex);
|
||||||
const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy,
|
|
||||||
uint8_t transparentIndex);
|
|
||||||
|
|
||||||
// Fill the 8x8 block at (bx, by) with a solid color. Equivalent to
|
// Fill the 8x8 block at (bx, by) with a solid color. Equivalent to
|
||||||
// jlFillRect(s, bx*8, by*8, 8, 8, colorIndex) but skips the rect
|
// jlFillRect(s, bx*8, by*8, 8, 8, colorIndex) but skips the rect
|
||||||
|
|
@ -86,19 +85,19 @@ void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
|
||||||
//
|
//
|
||||||
// Returns the number of tiles actually written. Returns 0 if the
|
// Returns the number of tiles actually written. Returns 0 if the
|
||||||
// file is missing, the magic / target byte is wrong, or maxTiles is 0.
|
// file is missing, the magic / target byte is wrong, or maxTiles is 0.
|
||||||
uint16_t jlTileBankLoad(const char *path, jlTileT *outTiles, uint16_t maxTiles,
|
uint16_t jlTileBankLoad(const char *path, jlTileT *outTiles, uint16_t maxTiles, bool *outValid, uint16_t *outPalette);
|
||||||
bool *outValid, uint16_t *outPalette);
|
|
||||||
|
|
||||||
// Stream a baked .tbk straight onto a surface's 8x8 block grid: tile i
|
// Stream a baked .tbk straight onto a surface's 8x8 block grid: tile i
|
||||||
// lands at block (i % TILE_BLOCKS_PER_ROW, i / TILE_BLOCKS_PER_ROW),
|
// lands at block (i % JL_TILE_BLOCKS_PER_ROW, i / JL_TILE_BLOCKS_PER_ROW),
|
||||||
// the same row-major layout the bake tools author against. Use this
|
// the same row-major layout the bake tools author against. Use this
|
||||||
// instead of jlTileBankLoad + a jlTileT[] when the bank is too big for
|
// instead of jlTileBankLoad + a jlTileT[] when the bank is too big for
|
||||||
// a caller-side buffer -- a full 1000-tile font sheet needs 32000
|
// a caller-side buffer -- a full 1000-tile font sheet needs 32000
|
||||||
// bytes, which no IIgs example can heap-allocate (the clang libc heap
|
// bytes, which no IIgs example can afford from the C heap (32 KB to
|
||||||
// is a few hundred bytes) or hold in bank-0 BSS. One file pass with a
|
// 48 KB in all) or hold in bank-0 BSS. One file pass with a
|
||||||
// small internal scratch; per-target/magic validation and outPalette
|
// small internal scratch; per-target/magic validation and outPalette
|
||||||
// behave exactly like jlTileBankLoad. Blocks beyond the file's tile
|
// behave exactly like jlTileBankLoad. Blocks beyond the file's tile
|
||||||
// count keep their prior surface content. Returns tiles pasted.
|
// count keep their prior surface content; tiles past the surface's 1000
|
||||||
|
// blocks are ignored. Returns tiles pasted.
|
||||||
uint16_t jlTileBankLoadToSurface(const char *path, jlSurfaceT *dst, uint16_t *outPalette);
|
uint16_t jlTileBankLoadToSurface(const char *path, jlSurfaceT *dst, uint16_t *outPalette);
|
||||||
|
|
||||||
// Capture the 8x8 block at (bx, by) into the caller's jlTileT. Used
|
// Capture the 8x8 block at (bx, by) into the caller's jlTileT. Used
|
||||||
|
|
@ -118,8 +117,7 @@ void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in);
|
||||||
// to 4 bits. Opaque -- if you need transparent background, do
|
// to 4 bits. Opaque -- if you need transparent background, do
|
||||||
// jlTileFill(bg) first then mask separately, or follow up with a
|
// jlTileFill(bg) first then mask separately, or follow up with a
|
||||||
// jlTileCopyMasked call.
|
// jlTileCopyMasked call.
|
||||||
void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in, uint8_t fgColor, uint8_t bgColor);
|
||||||
const jlTileT *in, uint8_t fgColor, uint8_t bgColor);
|
|
||||||
|
|
||||||
// Paste an 8x8 ONE-BIT-PER-PIXEL glyph at block (bx, by): bits[row] is
|
// Paste an 8x8 ONE-BIT-PER-PIXEL glyph at block (bx, by): bits[row] is
|
||||||
// that row of the glyph, most significant bit leftmost; a set bit draws
|
// that row of the glyph, most significant bit leftmost; a set bit draws
|
||||||
|
|
@ -129,13 +127,13 @@ void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
||||||
// This is the shape a character generator already has -- a font ROM, a
|
// This is the shape a character generator already has -- a font ROM, a
|
||||||
// C64 charset, anything 1bpp -- so nothing has to expand it into a
|
// C64 charset, anything 1bpp -- so nothing has to expand it into a
|
||||||
// jlTileT first. Prefer it over jlTilePasteMono for glyphs: a jlTileT
|
// jlTileT first. Prefer it over jlTilePasteMono for glyphs: a jlTileT
|
||||||
// is target-native (plane-major on the planar ports), whereas the
|
// is target-native (planar on the Amiga, ST, and X68000), whereas the
|
||||||
// silhouette a mono paste wants is chunky, so feeding one a tile that
|
// silhouette a mono paste wants is chunky, so feeding one a tile that
|
||||||
// came from jlTileSnap is a format mismatch that draws the wrong shape.
|
// came from jlTileSnap or jlTileBankLoad is a format mismatch there that
|
||||||
|
// draws the wrong shape.
|
||||||
// Taking the bits directly makes the input unambiguous, and lets each
|
// Taking the bits directly makes the input unambiguous, and lets each
|
||||||
// port expand them once instead of twice.
|
// port expand them once instead of twice.
|
||||||
void jlTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
void jlTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *bits, uint8_t fgColor, uint8_t bgColor);
|
||||||
const uint8_t *bits, uint8_t fgColor, uint8_t bgColor);
|
|
||||||
|
|
||||||
|
|
||||||
// Draw a NUL-terminated ASCII string at block (bx, by) using glyphs
|
// Draw a NUL-terminated ASCII string at block (bx, by) using glyphs
|
||||||
|
|
@ -143,32 +141,33 @@ void jlTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
||||||
//
|
//
|
||||||
// asciiMap is a 256-entry table mapping ASCII code to glyph location
|
// asciiMap is a 256-entry table mapping ASCII code to glyph location
|
||||||
// on fontSurface, encoded as a packed uint16_t: low byte = source
|
// on fontSurface, encoded as a packed uint16_t: low byte = source
|
||||||
// blockX, high byte = source blockY. asciiMap[c] == TILE_NO_GLYPH
|
// blockX, high byte = source blockY. asciiMap[c] == JL_TILE_NO_GLYPH,
|
||||||
// causes that character to be skipped (cursor advances, nothing
|
// or an entry off the 40x25 grid, causes that character to be skipped
|
||||||
// drawn). Glyph color 0 is treated as transparent so the underlying
|
// (cursor advances, nothing drawn). Glyph color 0 is treated as
|
||||||
// surface shows through glyph backgrounds.
|
// transparent so the underlying surface shows through glyph backgrounds.
|
||||||
//
|
//
|
||||||
// The cursor wraps to the next row at the right edge and truncates
|
// The cursor wraps to the next row at the right edge and truncates
|
||||||
// at the bottom edge.
|
// at the bottom edge. A start off the grid uses up characters without
|
||||||
void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
// drawing them: a start column past 39 drops the first character and
|
||||||
const jlSurfaceT *fontSurface, const uint16_t *asciiMap,
|
// carries on at the start of the next row; a start row past 24 draws
|
||||||
const char *str);
|
// nothing.
|
||||||
|
void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlSurfaceT *fontSurface, const uint16_t *asciiMap, const char *str);
|
||||||
|
|
||||||
// Map entry value that leaves a cell untouched (nothing pasted, the
|
// Map entry value that leaves a cell untouched (nothing pasted, the
|
||||||
// destination shows through). Mirrors TILE_NO_GLYPH's role in
|
// destination shows through). Mirrors JL_TILE_NO_GLYPH's role in
|
||||||
// jlDrawText.
|
// jlDrawText.
|
||||||
#define TILE_MAP_SKIP ((uint8_t)0xFFu)
|
#define JL_TILE_MAP_SKIP ((uint8_t)0xFFu)
|
||||||
|
|
||||||
// Paste a wTiles x hTiles rectangle of tiles in ONE call: the
|
// Paste a wTiles x hTiles rectangle of tiles in ONE call: the
|
||||||
// validation, dispatch, and dirty union are paid once for the whole
|
// validation, dispatch, and dirty union are paid once for the whole
|
||||||
// map instead of per tile (the jlDrawText batching pattern
|
// map instead of per tile (the jlDrawText batching pattern
|
||||||
// generalized, NATIVE-PERF Phase 3). `tiles` is the tileset; `map`
|
// generalized, NATIVE-PERF Phase 3). `tiles` is the tileset; `map`
|
||||||
// holds wTiles * hTiles tileset indices in row-major order
|
// holds wTiles * hTiles tileset indices in row-major order
|
||||||
// (map[row * wTiles + col]). An index of TILE_MAP_SKIP leaves that
|
// (map[row * wTiles + col]). An index of JL_TILE_MAP_SKIP leaves that
|
||||||
// cell untouched; any other out-of-tileset index is the CALLER'S
|
// cell untouched; any other out-of-tileset index is the CALLER'S
|
||||||
// contract to avoid (the batch does not know the tileset length and
|
// contract to avoid (the batch does not know the tileset length and
|
||||||
// does not range-check it). The whole rectangle must fit on the
|
// does not range-check it). The whole rectangle must fit on the
|
||||||
// surface -- a map whose edge crosses TILE_BLOCKS_PER_ROW/COL is
|
// surface -- a map whose edge crosses JL_TILE_BLOCKS_PER_ROW/COL is
|
||||||
// rejected as a no-op, matching the per-tile ops' silent-reject
|
// rejected as a no-op, matching the per-tile ops' silent-reject
|
||||||
// convention. wTiles or hTiles of 0 is a no-op.
|
// convention. wTiles or hTiles of 0 is a no-op.
|
||||||
void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map);
|
void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
#
|
#
|
||||||
# EmuTOS 1.4 (GPL): a free TOS replacement for the Atari ST emulator, the
|
# EmuTOS 1.4 (GPL): a free TOS replacement for the Atari ST emulator, the
|
||||||
# 512 KB US-English image. The gates and joey-run boot it instead of
|
# 512 KB US-English image. The gates and joey-run boot it instead of
|
||||||
# Atari's copyrighted TOS.
|
# Atari's copyrighted TOS. The German image, from the same download, is the
|
||||||
|
# input gate's national keyboard (its Alt layer types @ [ ] { } \).
|
||||||
|
|
||||||
compDescription="Atari ST firmware: EmuTOS 1.4 (free TOS replacement)"
|
compDescription="Atari ST firmware: EmuTOS 1.4 (free TOS replacement)"
|
||||||
compGroups="emulator:atarist"
|
compGroups="emulator:atarist"
|
||||||
|
|
@ -14,27 +16,31 @@ compUrl="https://sourceforge.net/projects/emutos/files/emutos/1.4/emutos-512k-1.
|
||||||
compSha256="1ef7bd25f61bcfc66d19debc2f0ebb0d5e5ba811ccd7f0fddfcb105bb72d1663"
|
compSha256="1ef7bd25f61bcfc66d19debc2f0ebb0d5e5ba811ccd7f0fddfcb105bb72d1663"
|
||||||
compPin="$compSha256"
|
compPin="$compSha256"
|
||||||
imgPath="$JOEY_FIRMWARE/atarist/emutos-512k.img"
|
imgPath="$JOEY_FIRMWARE/atarist/emutos-512k.img"
|
||||||
|
imgPathDe="$JOEY_FIRMWARE/atarist/emutos-512k-de.img"
|
||||||
|
|
||||||
|
|
||||||
compInstall() {
|
compInstall() {
|
||||||
local tmp
|
local tmp
|
||||||
tmp="$(freshBuildDir "$compId")"
|
tmp="$(freshBuildDir "$compId")"
|
||||||
unzip -oq "$(fetchFile "$compUrl" "$compSha256" emutos-512k-1.4.zip)" 'emutos-512k-1.4/etos512us.img' -d "$tmp"
|
unzip -oq "$(fetchFile "$compUrl" "$compSha256" emutos-512k-1.4.zip)" 'emutos-512k-1.4/etos512us.img' 'emutos-512k-1.4/etos512de.img' -d "$tmp"
|
||||||
install -D -m 644 "$tmp/emutos-512k-1.4/etos512us.img" "$imgPath"
|
install -D -m 644 "$tmp/emutos-512k-1.4/etos512us.img" "$imgPath"
|
||||||
|
install -D -m 644 "$tmp/emutos-512k-1.4/etos512de.img" "$imgPathDe"
|
||||||
dropBuildDir "$compId"
|
dropBuildDir "$compId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
compInstalled() {
|
compInstalled() {
|
||||||
[ -f "$imgPath" ]
|
[ -f "$imgPath" ] && [ -f "$imgPathDe" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
compVerify() {
|
compVerify() {
|
||||||
[ "$(sha1sum "$imgPath" | cut -d' ' -f1)" = "7d28ee58f00854954073198983572e6921b87a21" ]
|
[ "$(sha1sum "$imgPath" | cut -d' ' -f1)" = "7d28ee58f00854954073198983572e6921b87a21" ] &&
|
||||||
|
[ "$(sha1sum "$imgPathDe" | cut -d' ' -f1)" = "0a65f49d98896ad3e769be5f038a8d3086c00316" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
compEnv() {
|
compEnv() {
|
||||||
envVar EMUTOS_IMG "$imgPath"
|
envVar EMUTOS_IMG "$imgPath"
|
||||||
|
envVar EMUTOS_DE_IMG "$imgPathDe"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
55
installer/components/furnace.sh
Normal file
55
installer/components/furnace.sh
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
# See LICENSE.md for license information.
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# Furnace, tildearrow's open-source multi-system chiptune tracker (GPLv2 or
|
||||||
|
# later), the recommended tool for JoeyLib's chip-tracker music
|
||||||
|
# (docs/Assets.adoc, "Content Creation Tools"): write the song for the TI
|
||||||
|
# SN76489 (or the AY-3-8910 / YM2149), export it as VGM, and
|
||||||
|
# tools/vgm2song.py turns that into a .song for tools/songbake. Its own Linux
|
||||||
|
# x86_64 release, unpacked into toolchains/opt/furnace. Nothing builds with
|
||||||
|
# it, so no group installs it: ./install.sh install furnace.
|
||||||
|
# scripts/check-vgm.sh exports the SN76489 and AY demo songs it comes with
|
||||||
|
# through it when it is installed.
|
||||||
|
#
|
||||||
|
# Furnace keeps its settings in ~/.config/furnace; the gates run it with HOME
|
||||||
|
# pointed at their work folder so they leave none.
|
||||||
|
|
||||||
|
compDescription="content: Furnace 0.6.8.3 chiptune tracker (optional; chip-tracker music, docs/Assets.adoc)"
|
||||||
|
compGroups="optional"
|
||||||
|
compRequires="host"
|
||||||
|
compHostDeps="gl-dev asound-dev x11-dev"
|
||||||
|
compUrl="https://github.com/tildearrow/furnace/releases/download/v0.6.8.3/furnace-0.6.8.3-linux-x86_64.tar.gz"
|
||||||
|
compSha256="298a4e30bbdfe5ad88ed7f3bd90bd657a3f0b2e6e7dfc2091663dda71424d555"
|
||||||
|
compPin="$compSha256"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local archive
|
||||||
|
archive="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
extractArchive "$archive" "$compDir" 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -x "$compDir/furnace" ] && [ -d "$compDir/demos" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# It starts and knows VGM export (its settings go to a scratch home).
|
||||||
|
compVerify() {
|
||||||
|
local tmp
|
||||||
|
local usage
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
usage="$(HOME="$tmp" "$compDir/furnace" -help 2>&1 || true)"
|
||||||
|
rm -rf "$tmp"
|
||||||
|
[[ "$usage" == *"-vgmout"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar FURNACE "$compDir/furnace"
|
||||||
|
envVar FURNACE_DEMOS "$compDir/demos"
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
#
|
#
|
||||||
# Haiku's own build-host tools, built from the R1/beta6 source: package
|
# Haiku's own build-host tools, built from the R1/beta6 source: package
|
||||||
# (unpacks .hpkg packages: the Haiku sysroots are made from them) and
|
# (unpacks .hpkg packages: the Haiku sysroots are made from them), bfs_shell
|
||||||
# bfs_shell (reads and writes Be File System volumes: the Haiku VM images
|
# (reads and writes Be File System volumes: the Haiku VM images get their
|
||||||
# get their job runner through it, and the gates hand programs to the VM
|
# job runner through it, and the gates hand programs to the VM and take
|
||||||
# and take results back on a BFS disk). jam, which builds them, comes from
|
# results back on a BFS disk), and xres (adds resources to a Haiku or BeOS
|
||||||
# Haiku's buildtools at the same release. Installed into
|
# executable: make/be.mk gives every program its icons with it). jam, which
|
||||||
# toolchains/opt/haikuTools; bin/ holds wrappers that find the tools'
|
# builds them, comes from Haiku's buildtools at the same release. Installed
|
||||||
|
# into toolchains/opt/haikuTools; bin/ holds wrappers that find the tools'
|
||||||
# libraries.
|
# libraries.
|
||||||
|
|
||||||
compDescription="haiku: Haiku R1/beta6 host tools (package, bfs_shell; also read BeOS volumes)"
|
compDescription="haiku: Haiku R1/beta6 host tools (package, bfs_shell, xres; also read BeOS volumes)"
|
||||||
compGroups="toolchain:haiku toolchain:haiku32 toolchain:beos emulator:haiku emulator:haiku32 emulator:beos"
|
compGroups="toolchain:haiku toolchain:haiku32 toolchain:beos emulator:haiku emulator:haiku32 emulator:beos"
|
||||||
compRequires="host"
|
compRequires="host"
|
||||||
compHostDeps="build-tools git python3 zlib-dev zstd-dev bison flex"
|
compHostDeps="build-tools git python3 zlib-dev zstd-dev bison flex"
|
||||||
|
|
@ -19,8 +21,8 @@ compUrl="https://github.com/haiku/haiku.git"
|
||||||
compCommit="f6d86031a107e16f4fff6814e9aea20a39450842"
|
compCommit="f6d86031a107e16f4fff6814e9aea20a39450842"
|
||||||
buildtoolsUrl="https://github.com/haiku/buildtools.git"
|
buildtoolsUrl="https://github.com/haiku/buildtools.git"
|
||||||
buildtoolsCommit="bfa561e3708c0e2d0155fb403b988ba515c9b16d"
|
buildtoolsCommit="bfa561e3708c0e2d0155fb403b988ba515c9b16d"
|
||||||
compPin="$compCommit:$buildtoolsCommit"
|
hostTools="bfs_shell package xres"
|
||||||
hostTools="bfs_shell package"
|
compPin="$compCommit:$buildtoolsCommit:$hostTools"
|
||||||
|
|
||||||
|
|
||||||
compInstall() {
|
compInstall() {
|
||||||
|
|
@ -43,7 +45,7 @@ compInstall() {
|
||||||
# The tools keep the attributes of the host's files (which have none
|
# The tools keep the attributes of the host's files (which have none
|
||||||
# here) in a folder named when they are built: theirs, not the build
|
# here) in a folder named when they are built: theirs, not the build
|
||||||
# tree's, which goes.
|
# tree's, which goes.
|
||||||
(cd "$src/haiku/generated" && PATH="$src/buildtools/jam/bin.linuxx86:$PATH" jam -q -j"$JOEY_JOBS" -sHAIKU_BUILD_ATTRIBUTES_DIR="$compDir/attributes" '<build>bfs_shell' '<build>package')
|
(cd "$src/haiku/generated" && PATH="$src/buildtools/jam/bin.linuxx86:$PATH" jam -q -j"$JOEY_JOBS" -sHAIKU_BUILD_ATTRIBUTES_DIR="$compDir/attributes" '<build>bfs_shell' '<build>package' '<build>xres')
|
||||||
objects="$src/haiku/generated/objects/linux"
|
objects="$src/haiku/generated/objects/linux"
|
||||||
rm -rf "$compDir"
|
rm -rf "$compDir"
|
||||||
mkdir -p "$compDir/bin" "$compDir/libexec" "$compDir/lib" "$compDir/attributes"
|
mkdir -p "$compDir/bin" "$compDir/libexec" "$compDir/lib" "$compDir/attributes"
|
||||||
|
|
@ -58,17 +60,23 @@ compInstall() {
|
||||||
|
|
||||||
|
|
||||||
compInstalled() {
|
compInstalled() {
|
||||||
[ -x "$compDir/bin/bfs_shell" ] && [ -x "$compDir/bin/package" ] && [ -x "$compDir/libexec/bfs_shell" ] && [ -x "$compDir/libexec/package" ] && [ -d "$compDir/attributes" ]
|
local tool
|
||||||
|
for tool in $hostTools; do
|
||||||
|
[ -x "$compDir/bin/$tool" ] && [ -x "$compDir/libexec/$tool" ] || return 1
|
||||||
|
done
|
||||||
|
[ -d "$compDir/attributes" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Make a small BFS volume, write a file onto it and read it back; package
|
# Make a small BFS volume, write a file onto it and read it back; package
|
||||||
# starts. (Outputs are captured, not piped into grep: under pipefail a tool's
|
# and xres start. (Outputs are captured, not piped into grep: under pipefail
|
||||||
# own exit status, or SIGPIPE from an early grep, would fail the check.)
|
# a tool's own exit status, or SIGPIPE from an early grep, would fail the
|
||||||
|
# check.)
|
||||||
compVerify() {
|
compVerify() {
|
||||||
local tmp
|
local tmp
|
||||||
local readBack
|
local readBack
|
||||||
local usage
|
local usage
|
||||||
|
local xresUsage
|
||||||
tmp="$(mktemp -d)"
|
tmp="$(mktemp -d)"
|
||||||
printf 'JoeyLib\n' > "$tmp/in.txt"
|
printf 'JoeyLib\n' > "$tmp/in.txt"
|
||||||
truncate -s 8M "$tmp/v.img"
|
truncate -s 8M "$tmp/v.img"
|
||||||
|
|
@ -77,8 +85,9 @@ compVerify() {
|
||||||
printf 'cp :%s /myfs/in.txt\nsync\nquit\n' "$tmp/in.txt" | "$compDir/bin/bfs_shell" "$tmp/v.img" > /dev/null 2>&1 || true
|
printf 'cp :%s /myfs/in.txt\nsync\nquit\n' "$tmp/in.txt" | "$compDir/bin/bfs_shell" "$tmp/v.img" > /dev/null 2>&1 || true
|
||||||
readBack="$(printf 'cat /myfs/in.txt\nquit\n' | "$compDir/bin/bfs_shell" "$tmp/v.img" 2>&1 || true)"
|
readBack="$(printf 'cat /myfs/in.txt\nquit\n' | "$compDir/bin/bfs_shell" "$tmp/v.img" 2>&1 || true)"
|
||||||
usage="$("$compDir/bin/package" 2>&1 || true)"
|
usage="$("$compDir/bin/package" 2>&1 || true)"
|
||||||
|
xresUsage="$("$compDir/bin/xres" -h 2>&1 || true)"
|
||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
[[ "$readBack" == *JoeyLib* && "$usage" == *"Usage: package"* ]]
|
[[ "$readBack" == *JoeyLib* && "$usage" == *"Usage: package"* && "$xresUsage" == *"Valid commands are"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
#
|
#
|
||||||
# libxmp-lite 4.7.0: the MOD decoder the DOS, Atari ST, X68000, SDL3, PSP,
|
# libxmp-lite 4.7.0: the MOD decoder the DOS, Atari ST, X68000, SDL3, PSP,
|
||||||
# web, Win16, Win32, Haiku and BeOS audio HALs compile in. Each port's
|
# web, Win16, Win32, Haiku and BeOS audio HALs compiled in until 2026-10-14,
|
||||||
# makefile builds it from this source tree.
|
# when JoeyLib's own module player (src/core/modPlayer.c) replaced it. No
|
||||||
|
# build uses it now; it stays a reference to compare that player against.
|
||||||
|
|
||||||
compDescription="audio: libxmp-lite 4.7.0 source (DOS, Atari ST, X68000, SDL3, PSP, web, Win16, Win32, Haiku, BeOS)"
|
compDescription="audio: libxmp-lite 4.7.0 source (no build links it; a reference for the module player)"
|
||||||
compGroups="toolchain:dos toolchain:atarist toolchain:x68000 toolchain:sdl3 toolchain:psp toolchain:web toolchain:win16 toolchain:win32 toolchain:haiku toolchain:haiku32 toolchain:beos"
|
compGroups="toolchain:dos toolchain:atarist toolchain:x68000 toolchain:sdl3 toolchain:psp toolchain:web toolchain:win16 toolchain:win32 toolchain:haiku toolchain:haiku32 toolchain:beos"
|
||||||
compRequires="host"
|
compRequires="host"
|
||||||
compHostDeps="tar"
|
compHostDeps="tar"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,34 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
#
|
#
|
||||||
# PTPlayer 6.4: Frank Wille's ProTracker replayer for the Amiga, used by
|
# PTPlayer 6.4: Frank Wille's ProTracker replayer for the Amiga, used by
|
||||||
# the Amiga audio HAL. Aminet's URL is not versioned, so the pin is the
|
# the Amiga audio HAL, with JoeyLib's patches (installer/patches/ptplayer),
|
||||||
# archive's checksum.
|
# which make it play as Protracker 2.3 and the other ports' module player do.
|
||||||
|
# Aminet's URL is not versioned, so the pin is the archive's checksum.
|
||||||
|
|
||||||
compDescription="Amiga audio: PTPlayer 6.4 (ProTracker replayer)"
|
compDescription="Amiga audio: PTPlayer 6.4 (ProTracker replayer) + JoeyLib patch"
|
||||||
compGroups="toolchain:amiga"
|
compGroups="toolchain:amiga"
|
||||||
compRequires="host"
|
compRequires="host"
|
||||||
compHostDeps="lhasa"
|
compHostDeps="lhasa patch"
|
||||||
compUrl="http://aminet.net/mus/play/ptplayer.lha"
|
compUrl="http://aminet.net/mus/play/ptplayer.lha"
|
||||||
compSha256="5ba1285e91413f6b6cb72768c0c31cff90029af6920870d9f589689aa2e2161a"
|
compSha256="5ba1285e91413f6b6cb72768c0c31cff90029af6920870d9f589689aa2e2161a"
|
||||||
compPin="$compSha256"
|
patchDir="$JOEY_PATCHES/ptplayer"
|
||||||
|
# Editing a patch reinstalls PTPlayer, so the patches are part of the pin.
|
||||||
|
compPin="$compSha256 $(cat "$patchDir"/*.patch | sha256sum | cut -d' ' -f1)"
|
||||||
|
|
||||||
|
|
||||||
compInstall() {
|
compInstall() {
|
||||||
local lha
|
local lha
|
||||||
|
local p
|
||||||
lha="$(fetchFile "$compUrl" "$compSha256")"
|
lha="$(fetchFile "$compUrl" "$compSha256")"
|
||||||
rm -rf "$compDir"
|
rm -rf "$compDir"
|
||||||
extractArchive "$lha" "$compDir"
|
extractArchive "$lha" "$compDir"
|
||||||
|
for p in "$patchDir"/*.patch; do
|
||||||
|
info "applying $(basename "$p")"
|
||||||
|
patch -d "$compDir" -p1 -s -F0 --no-backup-if-mismatch < "$p"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,8 +37,9 @@ compInstalled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# The patch is in: its volume slide that leaves the period alone.
|
||||||
compVerify() {
|
compVerify() {
|
||||||
grep -qi 'ptplayer' "$compDir/ptplayer.asm"
|
grep -q 'mt_volslide_noper' "$compDir/ptplayer.asm"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
# See LICENSE.md for license information.
|
# See LICENSE.md for license information.
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue