squashed rp2040-doom changes

This commit is contained in:
graham sanderson 2022-02-27 14:19:20 -06:00 committed by graham sanderson
parent 660cad3966
commit 368a6210f7
329 changed files with 53138 additions and 10401 deletions

View file

@ -1,101 +0,0 @@
Thanks for contributing to Chocolate Doom! Whatever your contribution,
whether it's code or just a bug report, it's greatly appreciated.
The project is governed by the
[Contributor Covenant](http://contributor-covenant.org/version/1/4/)
version 1.4. By contributing to the project you agree to abide by its
terms. To report violations, please send an email to fraggle@gmail.com.
### Reporting bugs
Before reporting a bug, it's worth checking if this really is a bug.
Chocolate Doom's mission is to reproduce the Vanilla (DOS) versions of
the Doom engine games, bugs and all. Check out the
[NOT-BUGS](../NOT-BUGS.md) file for a list of common issues which aren't
really bugs at all. You might also try searching [the GitHub issues
list](https://github.com/chocolate-doom/chocolate-doom/issues) to see
if your bug has already been reported.
If you're confident that you've found a real bug (or even if you're
not sure!) please go ahead and [file an issue on
GitHub](https://github.com/chocolate-doom/chocolate-doom/issues/new).
You'll need a GitHub account, but it's pretty easy to sign up.
Please try to give as much information as possible:
* What version of Chocolate Doom are you using? Check the title bar of
the window for the version number.
* Chocolate Doom runs on many different operating systems (not just
Windows!). Please say which operating system and what version of it
you're using.
* Please say which game you're playing (Doom 1, Doom 2, Heretic,
Hexen, Strife, etc.) and if you're using any fan-made WADs or mods,
please say which mods (and where they can be downloaded!). It helps
to give the full command line you're using to start the game.
* Please mention if you have any special configuration you think may be
relevant, too.
### Feature requests
Chocolate Doom is always open to new feature requests; however, please
be aware that the project is designed around a deliberately limited
[philosophy](../PHILOSOPHY.md), and many features common in other source
ports will not be accepted. Here are a few common requests which are
often rejected:
* "High resolution" rendering (greater than 320x200 display).
* An option to disable Vanilla limits, such as the visplane rendering
limit.
* Ability to play "No Rest For The Living", the expansion pack which
comes with the XBLA / BFG Edition of Doom.
If you're not sure whether your feature is in line with the project
philosophy, don't worry - just ask anyway!
To make a feature request, [file an issue on
GitHub](https://github.com/chocolate-doom/chocolate-doom/issues/new).
### Bug fixes / code submission
Thank you for contributing code to Chocolate Doom! Please check the
following guidelines before opening a pull request:
* All code must be licensed under [the GNU General Public License,
version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
Please don't reuse code that isn't GPL, or that is GPLv3 licensed.
Be aware that by submitting your code to the project, you're agreeing
to license it under the GPL.
* Please follow the coding style guidelines described in the
[HACKING](../HACKING.md) file.
* Please don't make unnecessary changes which just change formatting
without any actual change to program logic. While being consistent
is nice, such changes destroy the ability to use the `git blame`
command to see when code was last changed.
* The guidelines given above in the "feature requests" section also
apply here. New features which aren't in line with the project
philosophy are likely to be rejected. If you're not sure, open a
feature request first and ask before you start implementing your
feature.
* Follow the guidelines for [how to write a Git commit
message](http://chris.beams.io/posts/git-commit/). In short: the
first line should be a short summary; keep to an 80 column limit;
use the imperative mood ("fix bug X", rather than "fixed bug X" or
"fixing bug X"). If your change fixes a particular subsystem,
prefix the summary with that subsystem: eg. "doom: Fix bug X" or
"textscreen: Change size of X".
* If you're making a change related to a bug, reference the GitHub
issue number in the commit message, eg. "This is a partial fix
for #646". This will link your commit into the issue comments. If
your change is a fix for the bug, put the word "fixes" before the
issue number to automatically close the issue once your change
is merged.

View file

@ -1,28 +0,0 @@
<!--
Thank you for reporting a bug in Chocolate Doom. Please complete
the following template so that we can better diagnose the source
of your problem.
To save yourself some time, you may want to check the FAQ and the
NOT-BUGS list for solutions to some common problems:
https://www.chocolate-doom.org/wiki/index.php/FAQ
https://www.chocolate-doom.org/not-bugs
-->
### Background
Version of Chocolate Doom:
Operating System and version:
Game: (Doom/Heretic/Hexen/Strife/other)
Any loaded WADs and mods (please include full command line):
### Bug description
Observed behavior:
Expected behavior:

7
.gitignore vendored
View file

@ -7,7 +7,7 @@ aclocal.m4
autom4te.cache
autotools
bin
config.h
/config.h
config.hin
config.log
config.status
@ -52,3 +52,8 @@ GPATH
GRTAGS
GTAGS
/HTML/
*.midx
*.midx.z
*.wad
*.whd
/cmake-*/

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "3rdparty/tinyusb"]
path = 3rdparty/tinyusb
url = git@github.com:liamfraser/tinyusb.git

1
3rdparty/tinyusb vendored Submodule

@ -0,0 +1 @@
Subproject commit 900f9372c35be9bdb5374af66b0fe3d9d32aae2b

View file

@ -1,7 +1,30 @@
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
cmake_minimum_required(VERSION 3.7.2)
project("Chocolate Doom" VERSION 3.0.0 LANGUAGES C)
cmake_minimum_required(VERSION 3.13)
# We use PICO_SDK_PATH being set as an indicator that we're doing a Pico BUILD
if (PICO_SDK_PATH)
include(pico_sdk_import.cmake)
include(pico_extras_import.cmake)
endif()
project("Chocolate Doom" VERSION 3.0.0 LANGUAGES C CXX)
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14)
if (PICO_SDK_PATH)
# we are using git@github.com:liamfraser/tinyusb.git as it has some RP2040 fixes that aren't upstreamed yet
set(PICO_TINYUSB_PATH ${CMAKE_CURRENT_LIST_DIR}/3rdparty/tinyusb)
# this only affects device builds device, but we want to use zone for malloc in this case so we don't have two separate elastic spaces and can fit more in
set(SKIP_PICO_MALLOC 1)
pico_sdk_init()
if (PICO_ON_DEVICE AND NOT CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
message(WARNING "You should do a MinSizeRel build when targeting the RP2040
(with -DCMAKE_BUILD_TYPE=MinSizeRel)")
endif()
endif()
set(CMAKE_C_STANDARD 11)
# Autotools variables
set(top_srcdir ${CMAKE_CURRENT_SOURCE_DIR})
@ -24,26 +47,30 @@ if(MSVC)
add_definitions("/D_CRT_SECURE_NO_WARNINGS" "/D_CRT_SECURE_NO_DEPRECATE"
"/D_CRT_NONSTDC_NO_DEPRECATE")
else()
add_compile_options("-Wall" "-Wdeclaration-after-statement"
"-Wredundant-decls")
#add_compile_options("-Wall" "-Wdeclaration-after-statement" "-Wredundant-decls")
add_compile_options("-Wall" "-Wredundant-decls")
endif()
find_package(SDL2 2.0.1)
find_package(SDL2_mixer 2.0.0)
find_package(SDL2_net 2.0.0)
# Note PICO_SDK path is set by the SDK initialization if it occurs above
if (NOT PICO_SDK)
find_package(SDL2 2.0.1 REQUIRED)
find_package(SDL2_mixer 2.0.0 REQUIRED)
find_package(SDL2_net 2.0.0 REQUIRED)
# Check for libsamplerate.
find_package(samplerate)
if(SAMPLERATE_FOUND)
set(HAVE_LIBSAMPLERATE TRUE)
endif()
# Check for libpng.
find_package(PNG)
if(PNG_FOUND)
set(HAVE_LIBPNG TRUE)
# Check for libsamplerate.
find_package(samplerate)
if(SAMPLERATE_FOUND)
set(HAVE_LIBSAMPLERATE TRUE)
endif()
# Check for libpng.
find_package(PNG)
if(PNG_FOUND)
set(HAVE_LIBPNG TRUE)
endif()
endif()
set(HAVE_MMAP 1)
find_package(m)
include(CheckSymbolExists)

104
README-chocolate.md Normal file
View file

@ -0,0 +1,104 @@
# Chocolate Doom
Chocolate Doom aims to accurately reproduce the original DOS version of
Doom and other games based on the Doom engine in a form that can be
run on modern computers.
Originally, Chocolate Doom was only a Doom source port. The project
now includes ports of Heretic and Hexen, and Strife.
Chocolate Dooms aims are:
* To always be 100% Free and Open Source software.
* Portability to as many different operating systems as possible.
* Accurate reproduction of the original DOS versions of the games,
including bugs.
* Compatibility with the DOS demo, configuration and savegame files.
* To provide an accurate retro “feel” (display and input should
behave the same).
More information about the philosophy and design behind Chocolate Doom
can be found in the PHILOSOPHY file distributed with the source code.
## Setting up gameplay
For instructions on how to set up Chocolate Doom for play, see the
INSTALL file.
## Configuration File
Chocolate Doom is compatible with the DOS Doom configuration file
(normally named `default.cfg`). Existing configuration files for DOS
Doom should therefore simply work out of the box. However, Chocolate
Doom also provides some extra settings. These are stored in a
separate file named `chocolate-doom.cfg`.
The configuration can be edited using the chocolate-setup tool.
## Command line options
Chocolate Doom supports a number of command line parameters, including
some extras that were not originally suported by the DOS versions. For
binary distributions, see the CMDLINE file included with your
download; more information is also available on the Chocolate Doom
website.
## Playing TCs
With Vanilla Doom there is no way to include sprites in PWAD files.
Chocolate Dooms -file command line option behaves exactly the same
as Vanilla Doom, and trying to play TCs by adding the WAD files using
-file will not work.
Many Total Conversions (TCs) are distributed as a PWAD file which must
be merged into the main IWAD. Typically a copy of DEUSF.EXE is
included which performs this merge. Chocolate Doom includes a new
option, -merge, which will simulate this merge. Essentially, the
WAD directory is merged in memory, removing the need to modify the
IWAD on disk.
To play TCs using Chocolate Doom, run like this:
```
chocolate-doom -merge thetc.wad
```
Here are some examples:
```
chocolate-doom -merge batman.wad -deh batman.deh vbatman.deh (Batman Doom)
chocolate-doom -merge aoddoom1.wad -deh aoddoom1.deh (Army of Darkness Doom)
```
## Other information
* Chocolate Doom includes a number of different options for music
playback. See the README.Music file for more details.
* More information, including information about how to play various
classic TCs, is available on the Chocolate Doom website:
https://www.chocolate-doom.org/
You are encouraged to sign up and contribute any useful information
you may have regarding the port!
* Chocolate Doom is not perfect. Although it aims to accurately
emulate and reproduce the DOS executables, some behavior can be very
difficult to reproduce. Because of the nature of the project, you
may also encounter Vanilla Doom bugs; these are intentionally
present; see the NOT-BUGS file for more information.
New bug reports can be submitted to the issue tracker on Github:
https://github.com/chocolate-doom/chocolate-doom/issues
* Source code patches are welcome, but please follow the style
guidelines - see the file named HACKING included with the source
distribution.
* Chocolate Doom is distributed under the GNU GPL. See the COPYING
file for more information.
* Please send any feedback, questions or suggestions to
chocolate-doom-dev-list@chocolate-doom.org. Thanks!

259
README.md
View file

@ -1,104 +1,219 @@
# Chocolate Doom
# RP2040 Doom
Chocolate Doom aims to accurately reproduce the original DOS version of
Doom and other games based on the Doom engine in a form that can be
run on modern computers.
This is a port of Doom for RP2040 devices, derived from [Chocolate Doom](https://github.com/chocolate-doom/chocolate-doom).
Originally, Chocolate Doom was only a Doom source port. The project
now includes ports of Heretic and Hexen, and Strife.
Significant changes have been made to support running on the RP2040 device, but particularly to support running the
entire shareware `DOOM1.WAD` which is 4M big on a Raspberry Pi Pico with only 2M flash!
Chocolate Dooms aims are:
You can read many details on this port in the blog post [here](https://kilograham.github.io/rp2040-doom/).
* To always be 100% Free and Open Source software.
* Portability to as many different operating systems as possible.
* Accurate reproduction of the original DOS versions of the games,
including bugs.
* Compatibility with the DOS demo, configuration and savegame files.
* To provide an accurate retro “feel” (display and input should
behave the same).
Note that a hopefully-fully-functional `chocolate-doom` executable is buildable from this RP2040 code base as a
means of
verification that everything still works, but whilst they can still be built, Hexen, Strife and Heretic are almost
certainly broken, so are not built by default.
More information about the philosophy and design behind Chocolate Doom
can be found in the PHILOSOPHY file distributed with the source code.
This chocolate-doom commit that the code is branched off can be found in the `upstream` branch.
## Setting up gameplay
The original Chocolate Doom README is [here](README-chocolate.md).
For instructions on how to set up Chocolate Doom for play, see the
INSTALL file.
## Code State
## Configuration File
Thus far, the focus has been entirely on getting RP2040 Doom running. Not a lot of time has been
spent
cleaning
the code up. There are a bunch of defunct `#ifdefs` and other code that was useful at some point,
but no longer are, and indeed changing them may result in non-functional code. This is particularly
true of
the
`whd_gen` tool
used to
convert/compress WADs
who's code is
likely completely incomprehensible!
Chocolate Doom is compatible with the DOS Doom configuration file
(normally named `default.cfg`). Existing configuration files for DOS
Doom should therefore simply work out of the box. However, Chocolate
Doom also provides some extra settings. These are stored in a
separate file named `chocolate-doom.cfg`.
## Artifacts
The configuration can be edited using the chocolate-setup tool.
You can find a RP2040 Doom UF2s based on the standard VGA/I2S pins in the
releases of this repository. There are also versions with the shareware DOOM1.WAD already embedded.
## Command line options
Note you can always use `picotool info -a <UF2 file>` to see the pins used by a particular build.
Chocolate Doom supports a number of command line parameters, including
some extras that were not originally suported by the DOS versions. For
binary distributions, see the CMDLINE file included with your
download; more information is also available on the Chocolate Doom
website.
## Goals
## Playing TCs
The main goals for this port were:
With Vanilla Doom there is no way to include sprites in PWAD files.
Chocolate Dooms -file command line option behaves exactly the same
as Vanilla Doom, and trying to play TCs by adding the WAD files using
-file will not work.
1. Everything should match the original game experience, i.e. all the graphics at classic 320x200 resolution, stereo
sound,
OPL2 music, save/load, demo playback, cheats, network multiplayer... basically it should feel like the original game.
2. `DOOM1.WAD` should run on a Raspberry Pi Pico. There was also to be no sneaky discarding of splash screens, altering of levels, down-sampling of
textures or whatever. RP2040 boards with 8M should be able to play at least the full *Ultimate Doom* and *DOOM II*
WADs.
3. The RP2040 should output directly to VGA (16 color pins for RGB565 along with HSync/VSync) along with stereo sound.
Many Total Conversions (TCs) are distributed as a PWAD file which must
be merged into the main IWAD. Typically a copy of DEUSF.EXE is
included which performs this merge. Chocolate Doom includes a new
option, -merge, which will simulate this merge. Essentially, the
WAD directory is merged in memory, removing the need to modify the
IWAD on disk.
## Results
To play TCs using Chocolate Doom, run like this:
[![RP2040 Doom on a Raspberry Pi Pico](https://img.youtube.com/vi/eDVazQVycP4/maxresdefault.jpg)](https://youtu.be/eDVazQVycP4)
```
chocolate-doom -merge thetc.wad
Features:
* Full `DOOM1.WAD` playable on Raspberry Pi Pico with 2M flash.
* *Ultimate Doom* and *Doom II* are playable on 8M devices.
* 320x200x60 VGA output (really 1280x1024x60).
* 9 Channel OPL2 Sound at 49716Hz.
* 9 Channel Stereo Sound Effects.
* I2C networking for up to 4 players.
* Save/Load of games.
* All cheats supported.
* Demos from original WADs run correctly.
* USB Keyboard Input support.
* All end scenes, intermissions, help screens etc. supported.
* Good frame rate; generally 30-35+ FPS.
* Uses 270Mhz overclock (requires flash chip that will run at 135Mhz)
# Building
RP2040 Doom should build fine on Linux and macOS. The RP2040 targeting builds should also work on Windows, though I
haven't tried.
The build uses `CMake`.
## Regular chocolate-doom/native builds
To build everything, assuming you have SDL2 dependencies installed, you can create a build directory:
```bash
mkdir build
cd build
cmake ..
```
Here are some examples:
And then run `make` or `make -j<num_cpus>` from that directory. To build a particular target e.g. `chocolate-doom`,
do `make chocolate-doom`
```
chocolate-doom -merge batman.wad -deh batman.deh vbatman.deh (Batman Doom)
chocolate-doom -merge aoddoom1.wad -deh aoddoom1.deh (Army of Darkness Doom)
Note this is the way you build the `whd_gen` tool too.
## RP2040 Doom builds
You must have [pico-sdk](https://github.com/raspberrypi/pico-sdk) and
**the latest version of** [pico-extras](https://github.com/raspberrypi/pico-extras) installed, along with the regular
pico-sdk requisites (e.g.
`arm-none-eabi-gcc`). If in doubt, see the Raspberry Pi
[documentation](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf). I have been building against
the `develop` branch of `pico-sdk`, so I recommend that..
For USB keyboard input support, RP2040 Doom currently uses a modified version of TinyUSB included as a submodule.
Make sure you have initialized this submodule via `git submodule update --init`
You can create a build directly like this:
```bash
mkdir rp2040-build
cd rp2040-build
cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DPICO_BOARD=vgaboard -DPICO_SDK_PATH=/path/to/pico-sdk -DPICO_EXTRAS_PATH=/path/to/pico-extras ..
```
## Other information
Note that the `PICO_BOARD` setting is for the standard VGA demo board which has RGB on pins 0->15, sync pins on 16,17
and
I2S on 26,27,28.
* Chocolate Doom includes a number of different options for music
playback. See the README.Music file for more details.
As before, use `make` or `make <target>` to build.
* More information, including information about how to play various
classic TCs, is available on the Chocolate Doom website:
The RP2040 version has four targets, each of which create a similarly named `UF2` file (e.g. `doom_tiny_nh.uf2`).
These UF2 files contain the executable code/data, but they do not contain the WAD data which is converted into a
RP2040 Domom
specific WHD/WHX format by `whd_gen` (for more see below). The WHD/WHX file must also be loaded onto the device at a
specific address which varies by binary.
https://www.chocolate-doom.org/
"super-tiny" refers to RP2040 Doom builds that use the more compressed WHX format, and
required for`DOOM1.
WAD` to
run
on a 2M Raspberry Pi Pico. "Non super-tiny" refers to RP2040 Doom builds that use the WHD format which is larger, but
also is
required for *Ultimate Doom* and *Doom II* WADs. These binaries are distinct as supporting both formats in the same
binary would just have made things bigger and slower.
You are encouraged to sign up and contribute any useful information
you may have regarding the port!
* Chocolate Doom is not perfect. Although it aims to accurately
emulate and reproduce the DOS executables, some behavior can be very
difficult to reproduce. Because of the nature of the project, you
may also encounter Vanilla Doom bugs; these are intentionally
present; see the NOT-BUGS file for more information.
* **doom_tiny_nh** This is a "super tiny" version with no USB keyboard support. You can use
[SDL Event Forwarder](https://github.com/kilograham/sdl_event_forwarder) to tunnel keyboard input from your host
computer over UART. The WHX file must be loaded at `0x10040000`.
* **doom_tiny_nh_cd** This is a "super tiny" version with additional USB keyboard support. Because of the extra USB
code, the WHX file must be loaded at `0x10042000`. As you can see USB support via TinyUSB causes the binary to
grow by 2K (hence the move of the WHX file address) leaving less space for saved games (which are also stored in
flash).
* **doom_tiny_nh_nost** This is a "non super tiny" version of `doom_tiny_nh` supporting larger WADs stored as WHD. The WHD
file must be loaded at `0x10048000`
* **doom_tiny_nh_nost_cd** This is a "non super tiny" version of `doom_tiny_nh_cd` supporting larger WADs stored as
WHD. The WHD
file must be loaded at `0x10048000`
New bug reports can be submitted to the issue tracker on Github:
You can load you WHD/WHX file using [picotool](https://github.com/raspberrypi/picotool). e.g.
https://github.com/chocolate-doom/chocolate-doom/issues
```bash
picotool load -v -t bin doom1.whx -o 0x10042000.
```
* Source code patches are welcome, but please follow the style
guidelines - see the file named HACKING included with the source
distribution.
See `whd_gen` further below for generating `WHX` or `WHD` files.
* Chocolate Doom is distributed under the GNU GPL. See the COPYING
file for more information.
#### USB keyboard support
Note that TinyUSB host mode support for keyboard may not work with all keyboards especially since the RP2040 Doom
has been built with small limits for number/sizes of hubs etc. I know that Raspberry Pi keyboards work fine, as
did my ancient
Dell keyboard. Your keyboard may just do nothing, or may cause a crash. If so, for now, you are stuck forwarding
keys from another PC via sdl_event_forwarder.
### RP2040 Doom builds not targeting an RP2040 device
You can also build the RP2040 Doom to run on your host computer (Linux or macOS) by using
[pico_host_sdl](https://github.com/raspberrypi/pico-host-sdl) which simulates RP2040 based video/audio output using SDL.
This version currently embeds the WHD/WHX in `src/tiny.whd.h` so you must generate this file.
You can do this via `./cup.sh <whd/whx_file>`
```bash
mkdir host-build
cd host-build
cmake -DPICO_PLATFORM=host -DPICO_SDK_PATH=/path/to/pico-sdk -DPICO_EXTRAS_PATH=/path/to/pico-extras -DPICO_SDK_PRE_LIST_DIRS=/path/to/pico_host_sdl ..
```
... and then `make` as usual.
## whd_gen
`doom1.whx` is includd in this repository, otherwise you need to build `whd_gen` using the regular native build
instructions above.
To generate a WHX file (you must use this to convert DOOM1.WAD to run on a 2M Raspberry Pi Pico)
```bash
whd_gen <wad_file> <whx_file>
```
The larger WADs (e.g. *Ultimate Doom* or *Doom II* have levels which are too complex to convert into a super tiny
WHX file. These larger WADs are not going to fit in a 2M flash anywy, so the less compressed WHD format can be used
given that the device now probably has 8M of flash.
```bash
whd_gen <wad_file> <whd_file> -no-super-tiny
```
Note that `whd_gen` has not been tested with a wide variety of WADs, so whilst it is possible that non Id WADs may
work, it is by no means guaranteed!
NOTE: You should use a release build of `whd_gen` for the best sound effect fidelity, as the debug build
deliberately lowers the encoding quality for the sake of speed.
# Future
*Evilution* and *Plutonia* are not yet supported. There is an issue tracking it
[here](https://github.com/kilograham/rp2040-doom/issues/1).
# RP2040 Doom Licenses
* Any code derived from chocolate-doom matinains its existing license (generally GPLv2).
* New RP2040 Doom specific code not implementing existing chocolate-doom interfaces is licensed BSD-3.
* ADPCM-XA is unmodified and is licensed BSD-3.
* Modified emu8950 derived code retains its MIT license.
* Please send any feedback, questions or suggestions to
chocolate-doom-dev-list@chocolate-doom.org. Thanks!

View file

@ -7,5 +7,6 @@
#cmakedefine HAVE_LIBSAMPLERATE
#cmakedefine HAVE_LIBPNG
#cmakedefine HAVE_DIRENT_H
#cmakedefine HAVE_MMAP
#cmakedefine01 HAVE_DECL_STRCASECMP
#cmakedefine01 HAVE_DECL_STRNCASECMP

1
cup.sh Executable file
View file

@ -0,0 +1 @@
xxd -i $1 | sed "s/unsigned/const unsigned/g" | sed "s/$1/tiny_whd/g" >src/tiny.whd.h

BIN
doom1.whx Normal file

Binary file not shown.

BIN
m_deathmch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
m_game.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
m_host.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
m_join.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
m_name.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
m_network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
m_two.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,15 +1,41 @@
add_library(opl STATIC
opl_internal.h
opl.c opl.h
opl_linux.c
opl_obsd.c
opl_queue.c opl_queue.h
opl_sdl.c
opl_timer.c opl_timer.h
opl_win32.c
ioperm_sys.c ioperm_sys.h
opl3.c opl3.h)
target_include_directories(opl
INTERFACE "."
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(opl SDL2::mixer)
if (PICO_SDK)
add_library(opl INTERFACE)
target_sources(opl INTERFACE
${CMAKE_CURRENT_LIST_DIR}/opl_api.c
${CMAKE_CURRENT_LIST_DIR}/emu8950.c
${CMAKE_CURRENT_LIST_DIR}/slot_render.cpp
${CMAKE_CURRENT_LIST_DIR}/opl_pico.c)
target_compile_options(opl INTERFACE -fms-extensions) # want OPL_SLOT_RENDER to be unnamed within OPL_SLOT
if (APPLE)
target_compile_options(opl INTERFACE -Wno-microsoft-anon-tag)
endif()
target_compile_definitions(opl INTERFACE
EMU8950_NO_RATECONV
)
target_include_directories(opl INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(opl INTERFACE pico_audio_i2s hardware_gpio)
if (PICO_ON_DEVICE)
target_sources(opl INTERFACE
${CMAKE_CURRENT_LIST_DIR}/slot_render_pico.S
)
target_link_libraries(opl INTERFACE hardware_interp)
endif()
else()
add_library(opl INTERFACE)
target_sources(opl INTERFACE
${CMAKE_CURRENT_LIST_DIR}/opl_api.c
${CMAKE_CURRENT_LIST_DIR}/opl.c
${CMAKE_CURRENT_LIST_DIR}/opl_linux.c
${CMAKE_CURRENT_LIST_DIR}/opl_obsd.c
${CMAKE_CURRENT_LIST_DIR}/opl_queue.c
${CMAKE_CURRENT_LIST_DIR}/opl_sdl.c
${CMAKE_CURRENT_LIST_DIR}/opl_timer.c
${CMAKE_CURRENT_LIST_DIR}/opl_win32.c
${CMAKE_CURRENT_LIST_DIR}/ioperm_sys.c
${CMAKE_CURRENT_LIST_DIR}/emu8950.c
${CMAKE_CURRENT_LIST_DIR}/opl3.c)
target_include_directories(opl
INTERFACE "."
"${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(opl INTERFACE SDL2::mixer)
endif()

2028
opl/emu8950.c Normal file

File diff suppressed because it is too large Load diff

229
opl/emu8950.h Normal file
View file

@ -0,0 +1,229 @@
#ifndef _EMU8950_H_
#define _EMU8950_H_
#include <stdint.h>
#include "slot_render.h"
#ifdef __cplusplus
extern "C" {
#endif
#define OPL_DEBUG 0
/* mask */
#define OPL_MASK_CH(x) (1 << (x))
#define OPL_MASK_HH (1 << 9)
#define OPL_MASK_CYM (1 << 10)
#define OPL_MASK_TOM (1 << 11)
#define OPL_MASK_SD (1 << 12)
#define OPL_MASK_BD (1 << 13)
#define OPL_MASK_ADPCM (1 << 14)
#define OPL_MASK_RHYTHM (OPL_MASK_HH | OPL_MASK_CYM | OPL_MASK_TOM | OPL_MASK_SD | OPL_MASK_BD)
#if !EMU8950_NO_RATECONV
/* rate conveter */
typedef struct __OPL_RateConv {
int ch;
double timer;
double f_ratio;
int16_t *sinc_table;
int16_t **buf;
} OPL_RateConv;
OPL_RateConv *OPL_RateConv_new(double f_inp, double f_out, int ch);
void OPL_RateConv_reset(OPL_RateConv *conv);
void OPL_RateConv_putData(OPL_RateConv *conv, int ch, int16_t data);
int16_t OPL_RateConv_getData(OPL_RateConv *conv, int ch);
void OPL_RateConv_delete(OPL_RateConv *conv);
#endif
/* slot */
typedef struct __OPL_SLOT {
struct SLOT_RENDER;
uint8_t number;
#if !EMU8950_NO_PERCUSSION_MODE // only use was to set based on percussion mode
/* type flags:
* 000000SM
* |+-- M: 0:modulator 1:carrier
* +--- S: 0:normal 1:single slot mode (sd, tom, hh or cym)
*/
uint8_t type;
#endif
OPL_PATCH __patch;
/* phase generator (pg) */
uint32_t pg_out; /* pg output, as index of wave table */
#if !EMU8950_NO_PERCUSSION_MODE
uint8_t pg_keep; /* if 1, pg_phase is preserved when key-on */
#endif
uint16_t blk_fnum; /* (block << 9) | f-number */
uint32_t update_requests; /* flags to debounce update */
#if OPL_DEBUG
uint8_t last_eg_state;
#endif
} OPL_SLOT;
typedef struct __OPL {
uint32_t clk;
uint32_t rate;
#if !EMU8950_NO_TIMER
uint8_t csm_mode;
uint8_t csm_key_count;
#endif
uint8_t notesel;
uint32_t inp_step;
uint32_t out_step;
uint32_t out_time;
#if EMU8950_LINEAR
#if EMU8950_SLOT_RENDER
uint8_t *lfo_am_buffer_lsl3;
#else
uint8_t *lfo_am_buffer;
#endif
int16_t *mod_buffer;
int32_t *buffer;
#endif
#if !EMU8950_NO_TEST_FLAG
uint8_t test_flag;
#endif
uint32_t slot_key_status;
#if !EMU8950_NO_PERCUSSION_MODE
uint8_t perc_mode;
#endif
uint32_t eg_counter;
uint32_t pm_phase;
uint32_t pm_dphase;
#if !EMU8950_NO_TEST_FLAG
int32_t am_phase;
#else
uint8_t am_phase_index;
#endif
uint8_t lfo_am;
#if !EMU8950_NO_PERCUSSION_MODE
uint32_t noise;
uint8_t short_noise;
#endif
uint8_t reg[0x100];
uint8_t ch_alg[9]; // alg for each channels
uint8_t pan[16];
uint32_t mask;
uint8_t am_mode;
uint8_t pm_mode;
/* channel output */
/* 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14:adpcm */
int16_t ch_out[15];
int16_t mix_out[2];
OPL_SLOT slot[18];
#if !EMU8950_NO_RATECONV
OPL_RateConv *conv;
#endif
#if !EMU8950_NO_TIMER
uint32_t timer1_counter; // 80us counter
uint32_t timer2_counter; // 320us counter
void *timer1_user_data;
void *timer2_user_data;
void (*timer1_func)(void *user);
void (*timer2_func)(void *user);
#endif
uint8_t status;
} OPL;
#if !EMU8950_NO_TEST_FLAG
#define opl_test_flag(opl) opl->test_flag
#else
// waveform enable only
#define opl_test_flag(opl) 0x20
#endif
OPL *OPL_new(uint32_t clk, uint32_t rate);
void OPL_delete(OPL *);
void OPL_reset(OPL *);
/**
* Set output wave sampling rate.
* @param rate sampling rate. If clock / 72 (typically 49716 or 49715 at 3.58MHz) is set, the internal rate converter is disabled.
*/
void OPL_setRate(OPL *opl, uint32_t rate);
/**
* Set internal calcuration quality. Currently no effects, just for compatibility.
* >= v1.0.0 always synthesizes internal output at clock/72 Hz.
*/
void OPL_setQuality(OPL *opl, uint8_t q);
/**
* Set fine-grained panning
* @param ch 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14,15:reserved
* @param pan output strength of left/right channel.
* pan[0]: left, pan[1]: right. pan[0]=pan[1]=1.0f for center.
*/
void OPL_setPanFine(OPL *opl, uint32_t ch, float pan[2]);
void OPL_writeIO(OPL *opl, uint32_t reg, uint8_t val);
void OPL_writeReg(OPL *opl, uint32_t reg, uint8_t val);
/**
* Calculate sample
*/
int16_t OPL_calc(OPL *opl);
void OPL_calc_buffer(OPL *opl, int16_t *buffer, uint32_t nsamples);
// LE left/right channels int16:int16
void OPL_calc_buffer_stereo(OPL *opl, int32_t *buffer, uint32_t nsamples);
/**
* Set channel mask
* @param mask mask flag: OPL_MASK_* can be used.
* - bit 0..8: mask for ch 1 to 9 (OPL_MASK_CH(i))
* - bit 9: mask for Hi-Hat (OPL_MASK_HH)
* - bit 10: mask for Top-Cym (OPL_MASK_CYM)
* - bit 11: mask for Tom (OPL_MASK_TOM)
* - bit 12: mask for Snare Drum (OPL_MASK_SD)
* - bit 13: mask for Bass Drum (OPL_MASK_BD)
*/
uint32_t OPL_setMask(OPL *, uint32_t mask);
/**
* Read OPL status register
* @returns
* 76543210
* ||||| +- D0: PCM/BSY
* ||||+---- D3: BUF/RDY
* |||+----- D4: EOS
* ||+------ D5: TIMER2
* |+------- D6: TIMER1
* +-------- D7: IRQ
*/
uint8_t OPL_status(OPL *opl);
/* for compatibility */
#define OPL_set_rate OPL_setRate
#define OPL_set_quality OPL_setQuality
#define OPL_set_pan OPL_setPan
#define OPL_set_pan_fine OPL_setPanFine
#ifdef __cplusplus
}
#endif
#endif

445
opl/opl.c
View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -20,435 +21,12 @@
#include <stdio.h>
#include <stdlib.h>
#include "SDL.h"
#include "opl.h"
#include "opl_internal.h"
//#define OPL_DEBUG_TRACE
#include "SDL.h"
#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
extern opl_driver_t opl_linux_driver;
#endif
#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
extern opl_driver_t opl_openbsd_driver;
#endif
#ifdef _WIN32
extern opl_driver_t opl_win32_driver;
#endif
extern opl_driver_t opl_sdl_driver;
static opl_driver_t *drivers[] =
{
#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
&opl_linux_driver,
#endif
#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
&opl_openbsd_driver,
#endif
#ifdef _WIN32
&opl_win32_driver,
#endif
&opl_sdl_driver,
NULL
};
static opl_driver_t *driver = NULL;
static int init_stage_reg_writes = 1;
unsigned int opl_sample_rate = 22050;
//
// Init/shutdown code.
//
// Initialize the specified driver and detect an OPL chip. Returns
// true if an OPL is detected.
static opl_init_result_t InitDriver(opl_driver_t *_driver,
unsigned int port_base)
{
opl_init_result_t result1, result2;
// Initialize the driver.
if (!_driver->init_func(port_base))
{
return OPL_INIT_NONE;
}
// The driver was initialized okay, so we now have somewhere
// to write to. It doesn't mean there's an OPL chip there,
// though. Perform the detection sequence to make sure.
// (it's done twice, like how Doom does it).
driver = _driver;
init_stage_reg_writes = 1;
result1 = OPL_Detect();
result2 = OPL_Detect();
if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE)
{
printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
_driver->shutdown_func();
driver = NULL;
return OPL_INIT_NONE;
}
init_stage_reg_writes = 0;
printf("OPL_Init: Using driver '%s'.\n", driver->name);
return result2;
}
// Find a driver automatically by trying each in the list.
static opl_init_result_t AutoSelectDriver(unsigned int port_base)
{
int i;
opl_init_result_t result;
for (i=0; drivers[i] != NULL; ++i)
{
result = InitDriver(drivers[i], port_base);
if (result != OPL_INIT_NONE)
{
return result;
}
}
printf("OPL_Init: Failed to find a working driver.\n");
return OPL_INIT_NONE;
}
// Initialize the OPL library. Return value indicates type of OPL chip
// detected, if any.
opl_init_result_t OPL_Init(unsigned int port_base)
{
char *driver_name;
int i;
int result;
driver_name = getenv("OPL_DRIVER");
if (driver_name != NULL)
{
// Search the list until we find the driver with this name.
for (i=0; drivers[i] != NULL; ++i)
{
if (!strcmp(driver_name, drivers[i]->name))
{
result = InitDriver(drivers[i], port_base);
if (result)
{
return result;
}
else
{
printf("OPL_Init: Failed to initialize "
"driver: '%s'.\n", driver_name);
return OPL_INIT_NONE;
}
}
}
printf("OPL_Init: unknown driver: '%s'.\n", driver_name);
return OPL_INIT_NONE;
}
else
{
return AutoSelectDriver(port_base);
}
}
// Shut down the OPL library.
void OPL_Shutdown(void)
{
if (driver != NULL)
{
driver->shutdown_func();
driver = NULL;
}
}
// Set the sample rate used for software OPL emulation.
void OPL_SetSampleRate(unsigned int rate)
{
opl_sample_rate = rate;
}
void OPL_WritePort(opl_port_t port, unsigned int value)
{
if (driver != NULL)
{
#ifdef OPL_DEBUG_TRACE
printf("OPL_write: %i, %x\n", port, value);
fflush(stdout);
#endif
driver->write_port_func(port, value);
}
}
unsigned int OPL_ReadPort(opl_port_t port)
{
if (driver != NULL)
{
unsigned int result;
#ifdef OPL_DEBUG_TRACE
printf("OPL_read: %i...\n", port);
fflush(stdout);
#endif
result = driver->read_port_func(port);
#ifdef OPL_DEBUG_TRACE
printf("OPL_read: %i -> %x\n", port, result);
fflush(stdout);
#endif
return result;
}
else
{
return 0;
}
}
//
// Higher-level functions, based on the lower-level functions above
// (register write, etc).
//
unsigned int OPL_ReadStatus(void)
{
return OPL_ReadPort(OPL_REGISTER_PORT);
}
// Write an OPL register value
void OPL_WriteRegister(int reg, int value)
{
int i;
if (reg & 0x100)
{
OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg);
}
else
{
OPL_WritePort(OPL_REGISTER_PORT, reg);
}
// For timing, read the register port six times after writing the
// register number to cause the appropriate delay
for (i=0; i<6; ++i)
{
// An oddity of the Doom OPL code: at startup initialization,
// the spacing here is performed by reading from the register
// port; after initialization, the data port is read, instead.
if (init_stage_reg_writes)
{
OPL_ReadPort(OPL_REGISTER_PORT);
}
else
{
OPL_ReadPort(OPL_DATA_PORT);
}
}
OPL_WritePort(OPL_DATA_PORT, value);
// Read the register port 24 times after writing the value to
// cause the appropriate delay
for (i=0; i<24; ++i)
{
OPL_ReadStatus();
}
}
// Detect the presence of an OPL chip
opl_init_result_t OPL_Detect(void)
{
int result1, result2;
int i;
// Reset both timers:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
// Enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
// Read status
result1 = OPL_ReadStatus();
// Set timer:
OPL_WriteRegister(OPL_REG_TIMER1, 0xff);
// Start timer 1:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21);
// Wait for 80 microseconds
// This is how Doom does it:
for (i=0; i<200; ++i)
{
OPL_ReadStatus();
}
OPL_Delay(1 * OPL_MS);
// Read status
result2 = OPL_ReadStatus();
// Reset both timers:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
// Enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0)
{
result1 = OPL_ReadPort(OPL_REGISTER_PORT);
result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3);
if (result1 == 0x00)
{
return OPL_INIT_OPL3;
}
else
{
return OPL_INIT_OPL2;
}
}
else
{
return OPL_INIT_NONE;
}
}
// Initialize registers on startup
void OPL_InitRegisters(int opl3)
{
int r;
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r, 0x3f);
}
// Initialize other registers
// These two loops write to registers that actually don't exist,
// but this is what Doom does ...
// Similarly, the <= is also intenational.
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r, 0x00);
}
// Re-initialize the low registers:
// Reset both timers and enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
// "Allow FM chips to control the waveform of each operator":
OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r | 0x100, 0x3f);
}
// Initialize other registers
// These two loops write to registers that actually don't exist,
// but this is what Doom does ...
// Similarly, the <= is also intenational.
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
}
}
// Keyboard split point on (?)
OPL_WriteRegister(OPL_REG_FM_MODE, 0x40);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
}
}
//
// Timer functions.
//
void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data)
{
if (driver != NULL)
{
driver->set_callback_func(us, callback, data);
}
}
void OPL_ClearCallbacks(void)
{
if (driver != NULL)
{
driver->clear_callbacks_func();
}
}
void OPL_Lock(void)
{
if (driver != NULL)
{
driver->lock_func();
}
}
void OPL_Unlock(void)
{
if (driver != NULL)
{
driver->unlock_func();
}
}
extern opl_driver_t *driver;
typedef struct
{
@ -504,20 +82,3 @@ void OPL_Delay(uint64_t us)
SDL_DestroyMutex(delay_data.mutex);
SDL_DestroyCond(delay_data.cond);
}
void OPL_SetPaused(int paused)
{
if (driver != NULL)
{
driver->set_paused_func(paused);
}
}
void OPL_AdjustCallbacks(float value)
{
if (driver != NULL)
{
driver->adjust_callbacks_func(value);
}
}

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -126,7 +127,7 @@ void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data);
// Adjust callback times by the specified factor. For example, a value of
// 0.5 will halve all remaining times.
void OPL_AdjustCallbacks(float factor);
void OPL_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo);
// Clear all OPL callbacks that have been set.

View file

@ -1,5 +1,6 @@
//
// Copyright (C) 2013-2018 Alexey Khokholov (Nuke.YKT)
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -49,6 +50,7 @@ typedef struct _opl3_slot opl3_slot;
typedef struct _opl3_channel opl3_channel;
typedef struct _opl3_chip opl3_chip;
struct _opl3_slot {
opl3_channel *channel;
opl3_chip *chip;

483
opl/opl_api.c Normal file
View file

@ -0,0 +1,483 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// OPL interface.
//
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "opl.h"
#include "opl_internal.h"
//#define OPL_DEBUG_TRACE
#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
extern opl_driver_t opl_linux_driver;
#endif
#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
extern opl_driver_t opl_openbsd_driver;
#endif
#ifdef _WIN32
extern opl_driver_t opl_win32_driver;
#endif
#if PICO_BUILD
extern opl_driver_t opl_pico_driver;
#else
extern opl_driver_t opl_sdl_driver;
#endif
static const opl_driver_t *drivers[] =
{
#if (defined(__i386__) || defined(__x86_64__)) && defined(HAVE_IOPERM)
&opl_linux_driver,
#endif
#if defined(HAVE_LIBI386) || defined(HAVE_LIBAMD64)
&opl_openbsd_driver,
#endif
#ifdef _WIN32
&opl_win32_driver,
#endif
#if PICO_BUILD
&opl_pico_driver,
#else
&opl_sdl_driver,
#endif
NULL
};
const opl_driver_t *driver = NULL;
static int init_stage_reg_writes = 1;
unsigned int opl_sample_rate = 22050;
//
// Init/shutdown code.
//
// Initialize the specified driver and detect an OPL chip. Returns
// true if an OPL is detected.
static opl_init_result_t InitDriver(const opl_driver_t *_driver,
unsigned int port_base)
{
opl_init_result_t result1, result2;
// Initialize the driver.
if (!_driver->init_func(port_base))
{
return OPL_INIT_NONE;
}
// The driver was initialized okay, so we now have somewhere
// to write to. It doesn't mean there's an OPL chip there,
// though. Perform the detection sequence to make sure.
// (it's done twice, like how Doom does it).
driver = _driver;
init_stage_reg_writes = 1;
result1 = OPL_Detect();
result2 = OPL_Detect();
if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE)
{
printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
_driver->shutdown_func();
driver = NULL;
return OPL_INIT_NONE;
}
init_stage_reg_writes = 0;
printf("OPL_Init: Using driver '%s'.\n", driver->name);
return result2;
}
// Find a driver automatically by trying each in the list.
static opl_init_result_t AutoSelectDriver(unsigned int port_base)
{
int i;
opl_init_result_t result;
for (i=0; drivers[i] != NULL; ++i)
{
result = InitDriver(drivers[i], port_base);
if (result != OPL_INIT_NONE)
{
return result;
}
}
printf("OPL_Init: Failed to find a working driver.\n");
return OPL_INIT_NONE;
}
// Initialize the OPL library. Return value indicates type of OPL chip
// detected, if any.
opl_init_result_t OPL_Init(unsigned int port_base)
{
#if PICO_BUILD
driver = drivers[0];
driver->init_func(0);
return OPL_INIT_OPL2;
#else
char *driver_name;
int i;
int result;
driver_name = getenv("OPL_DRIVER");
if (driver_name != NULL)
{
// Search the list until we find the driver with this name.
for (i=0; drivers[i] != NULL; ++i)
{
if (!strcmp(driver_name, drivers[i]->name))
{
result = InitDriver(drivers[i], port_base);
if (result)
{
return result;
}
else
{
printf("OPL_Init: Failed to initialize "
"driver: '%s'.\n", driver_name);
return OPL_INIT_NONE;
}
}
}
printf("OPL_Init: unknown driver: '%s'.\n", driver_name);
return OPL_INIT_NONE;
}
else
{
return AutoSelectDriver(port_base);
}
#endif
}
// Shut down the OPL library.
void OPL_Shutdown(void)
{
if (driver != NULL)
{
driver->shutdown_func();
driver = NULL;
}
}
// Set the sample rate used for software OPL emulation.
void OPL_SetSampleRate(unsigned int rate)
{
opl_sample_rate = rate;
}
void OPL_WritePort(opl_port_t port, unsigned int value)
{
if (driver != NULL)
{
#ifdef OPL_DEBUG_TRACE
printf("OPL_write: %i, %x\n", port, value);
fflush(stdout);
#endif
driver->write_port_func(port, value);
}
}
unsigned int OPL_ReadPort(opl_port_t port)
{
if (driver != NULL)
{
unsigned int result;
#ifdef OPL_DEBUG_TRACE
printf("OPL_read: %i...\n", port);
fflush(stdout);
#endif
result = driver->read_port_func(port);
#ifdef OPL_DEBUG_TRACE
printf("OPL_read: %i -> %x\n", port, result);
fflush(stdout);
#endif
return result;
}
else
{
return 0;
}
}
//
// Higher-level functions, based on the lower-level functions above
// (register write, etc).
//
unsigned int OPL_ReadStatus(void)
{
return OPL_ReadPort(OPL_REGISTER_PORT);
}
// Write an OPL register value
void OPL_WriteRegister(int reg, int value)
{
int i;
if (reg & 0x100)
{
OPL_WritePort(OPL_REGISTER_PORT_OPL3, reg);
}
else
{
OPL_WritePort(OPL_REGISTER_PORT, reg);
}
// For timing, read the register port six times after writing the
// register number to cause the appropriate delay
for (i=0; i<6; ++i)
{
// An oddity of the Doom OPL code: at startup initialization,
// the spacing here is performed by reading from the register
// port; after initialization, the data port is read, instead.
if (init_stage_reg_writes)
{
OPL_ReadPort(OPL_REGISTER_PORT);
}
else
{
OPL_ReadPort(OPL_DATA_PORT);
}
}
OPL_WritePort(OPL_DATA_PORT, value);
// Read the register port 24 times after writing the value to
// cause the appropriate delay
for (i=0; i<24; ++i)
{
OPL_ReadStatus();
}
}
// Detect the presence of an OPL chip
opl_init_result_t OPL_Detect(void)
{
int result1, result2;
int i;
// Reset both timers:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
// Enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
// Read status
result1 = OPL_ReadStatus();
// Set timer:
OPL_WriteRegister(OPL_REG_TIMER1, 0xff);
// Start timer 1:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21);
// Wait for 80 microseconds
// This is how Doom does it:
for (i=0; i<200; ++i)
{
OPL_ReadStatus();
}
OPL_Delay(1 * OPL_MS);
// Read status
result2 = OPL_ReadStatus();
// Reset both timers:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
// Enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0)
{
result1 = OPL_ReadPort(OPL_REGISTER_PORT);
result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3);
if (result1 == 0x00)
{
return OPL_INIT_OPL3;
}
else
{
return OPL_INIT_OPL2;
}
}
else
{
return OPL_INIT_NONE;
}
}
// Initialize registers on startup
void OPL_InitRegisters(int opl3)
{
int r;
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r, 0x3f);
}
// Initialize other registers
// These two loops write to registers that actually don't exist,
// but this is what Doom does ...
// Similarly, the <= is also intenational.
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r, 0x00);
}
// Re-initialize the low registers:
// Reset both timers and enable interrupts:
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
// "Allow FM chips to control the waveform of each operator":
OPL_WriteRegister(OPL_REG_WAVEFORM_ENABLE, 0x20);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
// Initialize level registers
for (r=OPL_REGS_LEVEL; r <= OPL_REGS_LEVEL + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r | 0x100, 0x3f);
}
// Initialize other registers
// These two loops write to registers that actually don't exist,
// but this is what Doom does ...
// Similarly, the <= is also intenational.
for (r=OPL_REGS_ATTACK; r <= OPL_REGS_WAVEFORM + OPL_NUM_OPERATORS; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
}
// More registers ...
for (r=1; r < OPL_REGS_LEVEL; ++r)
{
OPL_WriteRegister(r | 0x100, 0x00);
}
}
// Keyboard split point on (?)
OPL_WriteRegister(OPL_REG_FM_MODE, 0x40);
if (opl3)
{
OPL_WriteRegister(OPL_REG_NEW, 0x01);
}
}
//
// Timer functions.
//
void OPL_SetCallback(uint64_t us, opl_callback_t callback, void *data)
{
if (driver != NULL)
{
driver->set_callback_func(us, callback, data);
}
}
void OPL_ClearCallbacks(void)
{
if (driver != NULL)
{
driver->clear_callbacks_func();
}
}
void OPL_Lock(void)
{
if (driver != NULL)
{
driver->lock_func();
}
}
void OPL_Unlock(void)
{
if (driver != NULL)
{
driver->unlock_func();
}
}
void OPL_SetPaused(int paused)
{
if (driver != NULL)
{
driver->set_paused_func(paused);
}
}
void OPL_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo)
{
if (driver != NULL)
{
driver->adjust_callbacks_func(old_tempo, new_tempo);
}
}

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -32,7 +33,7 @@ typedef void (*opl_clear_callbacks_func)(void);
typedef void (*opl_lock_func)(void);
typedef void (*opl_unlock_func)(void);
typedef void (*opl_set_paused_func)(int paused);
typedef void (*opl_adjust_callbacks_func)(float value);
typedef void (*opl_adjust_callbacks_func)(unsigned int old_tempo, unsigned int new_tempo);
typedef struct
{

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -37,12 +38,12 @@ static int OPL_Linux_Init(unsigned int port_base)
if (ioperm(port_base, 2, 1) < 0)
{
fprintf(stderr, "Failed to get I/O port permissions for 0x%x: %s\n",
stderr_print( "Failed to get I/O port permissions for 0x%x: %s\n",
port_base, strerror(errno));
if (errno == EPERM)
{
fprintf(stderr,
stderr_print(
"\tYou may need to run the program as root in order\n"
"\tto acquire I/O port permissions for OPL MIDI playback.\n");
}

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -60,7 +61,7 @@ static int OPL_OpenBSD_Init(unsigned int port_base)
if (set_iopl(3) < 0)
{
fprintf(stderr, "Failed to get raise I/O privilege level: "
stderr_print( "Failed to get raise I/O privilege level: "
"check that you are running as root.\n");
return 0;
}

605
opl/opl_pico.c Normal file
View file

@ -0,0 +1,605 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// OPL SDL interface.
//
// todo replace opl_queue with pheap
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "pico/mutex.h"
#include "pico/audio_i2s.h"
#include "pico/util/pheap.h"
#include "hardware/gpio.h"
#include "i_picosound.h"
#if USE_WOODY_OPL
#include "woody_opl.h"
#elif USE_EMU8950_OPL
#include "emu8950.h"
#else
#include "opl3.h"
#endif
#include "opl.h"
#include "opl_internal.h"
#include "opl_queue.h"
#define MAX_SOUND_SLICE_TIME 100 /* ms */
typedef struct
{
unsigned int rate; // Number of times the timer is advanced per sec.
unsigned int enabled; // Non-zero if timer is enabled.
unsigned int value; // Last value that was set.
uint64_t expire_time; // Calculated time that timer will expire.
} opl_timer_t;
// When the callback mutex is locked using OPL_Lock, callback functions
// are not invoked.
static mutex_t callback_mutex;
// Queue of callbacks waiting to be invoked.
static opl_callback_queue_t *callback_queue;
// Mutex used to control access to the callback queue.
static mutex_t callback_queue_mutex;
// Current time, in us since startup:
static uint64_t current_time;
// If non-zero, playback is currently paused.
static int opl_pico_paused;
// Time offset (in us) due to the fact that callbacks
// were previously paused.
static uint64_t pause_offset;
// OPL software emulator structure.
#if USE_WOODY_OPL
// todo configure this based on woody build flag
#define opl_op3mode 0
#elif USE_EMU8950_OPL
#define opl_op3mode 0
static OPL *emu8950_opl;
#else
static opl3_chip opl_chip;
static int opl_opl3mode;
#endif
// Register number that was written.
static int register_num = 0;
#if !EMU8950_NO_TIMER
// Timers; DBOPL does not do timer stuff itself.
static opl_timer_t timer1 = { 12500, 0, 0, 0 };
static opl_timer_t timer2 = { 3125, 0, 0, 0 };
#endif
// SDL parameters.
static bool audio_was_initialized = 0;
static inline void Pico_LockMutex(mutex_t *mutex) {
}
static inline void Pico_UnlockMutex(mutex_t *mutex) {
}
// Advance time by the specified number of samples, invoking any
// callback functions as appropriate.
static void AdvanceTime(unsigned int nsamples)
{
opl_callback_t callback;
void *callback_data;
uint64_t us;
Pico_LockMutex(&callback_queue_mutex);
// Advance time.
us = ((uint64_t) nsamples * OPL_SECOND) / PICO_SOUND_SAMPLE_FREQ;
current_time += us;
if (opl_pico_paused)
{
pause_offset += us;
}
// Are there callbacks to invoke now? Keep invoking them
// until there are no more left.
while (!OPL_Queue_IsEmpty(callback_queue)
&& current_time >= OPL_Queue_Peek(callback_queue) + pause_offset)
{
// Pop the callback from the queue to invoke it.
if (!OPL_Queue_Pop(callback_queue, &callback, &callback_data))
{
break;
}
// The mutex stuff here is a bit complicated. We must
// hold callback_mutex when we invoke the callback (so that
// the control thread can use OPL_Lock() to prevent callbacks
// from being invoked), but we must not be holding
// callback_queue_mutex, as the callback must be able to
// call OPL_SetCallback to schedule new callbacks.
Pico_UnlockMutex(&callback_queue_mutex);
Pico_LockMutex(&callback_mutex);
callback(callback_data);
Pico_UnlockMutex(&callback_mutex);
Pico_LockMutex(&callback_queue_mutex);
}
Pico_UnlockMutex(&callback_queue_mutex);
}
// Call the OPL emulator code to fill the specified buffer.
// Callback function to fill a new sound buffer:
#define LIMITED_CALLBACK_TYPES 1
#if LIMITED_CALLBACK_TYPES
extern void RestartSong(void *unused);
extern void TrackTimerCallback(void *track);
#endif
#if DOOM_TINY
extern uint8_t restart_song_state;
#endif
void OPL_Pico_Mix_callback(audio_buffer_t *audio_buffer)
{
unsigned int filled, buffer_samples;
#if DOOM_TINY
if (restart_song_state == 2) {
RestartSong(0);
}
#endif
// Repeatedly call the OPL emulator update function until the buffer is
// full.
filled = 0;
buffer_samples = audio_buffer->max_sample_count;
//#if PICO_ON_DEVICE
// absolute_time_t t0 = get_absolute_time();
// gpio_set_mask(1);
//#endif
while (filled < buffer_samples) {
//#if PICO_ON_DEVICE
// gpio_set_mask(32);
//#endif
uint64_t next_callback_time;
uint64_t nsamples;
Pico_LockMutex(&callback_queue_mutex);
// Work out the time until the next callback waiting in
// the callback queue must be invoked. We can then fill the
// buffer with this many samples.
if (opl_pico_paused || OPL_Queue_IsEmpty(callback_queue)) {
nsamples = buffer_samples - filled;
} else {
next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset;
nsamples = (next_callback_time - current_time) * PICO_SOUND_SAMPLE_FREQ;
nsamples = (nsamples + OPL_SECOND - 1) / OPL_SECOND;
if (nsamples > buffer_samples - filled) {
nsamples = buffer_samples - filled;
}
}
Pico_UnlockMutex(&callback_queue_mutex);
// Add emulator output to buffer.
//OPL3_GenerateStream(&opl_chip, (Bit16s *) (audio_buffer->buffer->bytes + filled * 4), nsamples);
#if USE_WOODY_OPL
int16_t *sndptr = (int16_t *) (audio_buffer->buffer->bytes + filled * 4);
// todo store in stereo?
adlib_getsample(sndptr, nsamples);
for(int i=nsamples-1; i>=0; i--) {
sndptr[i*2] = sndptr[i*2 + 1] = sndptr[i];
}
#elif USE_EMU8950_OPL
if (nsamples) {
int32_t *sndptr32 = (int32_t *) (audio_buffer->buffer->bytes + filled * 4);
OPL_calc_buffer_stereo(emu8950_opl, sndptr32, nsamples);
}
#else
int16_t *sndptr = (int16_t *) (audio_buffer->buffer->bytes + filled * 4);
for(int i = 0; i < nsamples; i++)
{
OPL3_GenerateResampled(&opl_chip, sndptr);
sndptr += 2;
}
#endif
filled += nsamples;
// Invoke callbacks for this point in time.
//#if PICO_ON_DEVICE
// gpio_clr_mask(32);
//#endif
AdvanceTime(nsamples);
}
audio_buffer->sample_count = audio_buffer->max_sample_count;
#if !USE_WOODY_OPL
int16_t *samples = (int16_t *)audio_buffer->buffer->bytes;
for(uint i=0;i<audio_buffer->sample_count * 2; i++) {
samples[i] <<= 3;
}
#endif
//#if PICO_ON_DEVICE
// gpio_clr_mask(1);
// int32_t t = (int32_t)absolute_time_diff_us(t0, get_absolute_time());
// static int max_t;
// static int ii;
// static int total;
// total += t;
// if (t > max_t) {
// max_t = t;
// }
// ii++;
// if (!(ii &127)) {
// printf("AVG %d MAX %d\n", total / 128, max_t);
// max_t = 0;
// total = 0;
// }
//#endif
}
static void OPL_Pico_Shutdown(void)
{
if (audio_was_initialized)
{
I_PicoSoundSetMusicGenerator(NULL);
OPL_Queue_Destroy(callback_queue);
audio_was_initialized = 0;
}
}
static int OPL_Pico_Init(unsigned int port_base)
{
if (I_PicoSoundIsInitialized()) {
opl_pico_paused = 0;
pause_offset = 0;
// Queue structure of callbacks to invoke.
callback_queue = OPL_Queue_Create();
current_time = 0;
#if USE_WOODY_OPL
adlib_init(mixing_freq);
#elif USE_EMU8950_OPL
emu8950_opl = OPL_new(3579552, PICO_SOUND_SAMPLE_FREQ); // todo check rate
#else
OPL3_Reset(&opl_chip, PICO_SOUND_SAMPLE_FREQ);
opl_opl3mode = 0;
#endif
// // Set postmix that adds the OPL music. This is deliberately done
// // as a postmix and not using Mix_HookMusic() as the latter disables
// // normal Pico_mixer music mixing.
// Mix_SetPostMix(OPL_Mix_Callback, NULL);
I_PicoSoundSetMusicGenerator(OPL_Pico_Mix_callback);
audio_was_initialized = 1;
} else {
audio_was_initialized = 0;
}
return 1;
}
static unsigned int OPL_Pico_PortRead(opl_port_t port)
{
unsigned int result = 0;
if (port == OPL_REGISTER_PORT_OPL3)
{
return 0xff;
}
#if !EMU8950_NO_TIMER
if (timer1.enabled && current_time > timer1.expire_time)
{
result |= 0x80; // Either have expired
result |= 0x40; // Timer 1 has expired
}
if (timer2.enabled && current_time > timer2.expire_time)
{
result |= 0x80; // Either have expired
result |= 0x20; // Timer 2 has expired
}
#endif
return result;
}
static void OPLTimer_CalculateEndTime(opl_timer_t *timer)
{
int tics;
// If the timer is enabled, calculate the time when the timer
// will expire.
if (timer->enabled)
{
tics = 0x100 - timer->value;
timer->expire_time = current_time
+ ((uint64_t) tics * OPL_SECOND) / timer->rate;
}
}
static void WriteRegister(unsigned int reg_num, unsigned int value)
{
switch (reg_num)
{
#if !EMU8950_NO_TIMER
case OPL_REG_TIMER1:
timer1.value = value;
OPLTimer_CalculateEndTime(&timer1);
break;
case OPL_REG_TIMER2:
timer2.value = value;
OPLTimer_CalculateEndTime(&timer2);
break;
case OPL_REG_TIMER_CTRL:
if (value & 0x80)
{
timer1.enabled = 0;
timer2.enabled = 0;
}
else
{
if ((value & 0x40) == 0)
{
timer1.enabled = (value & 0x01) != 0;
OPLTimer_CalculateEndTime(&timer1);
}
if ((value & 0x20) == 0)
{
timer1.enabled = (value & 0x02) != 0;
OPLTimer_CalculateEndTime(&timer2);
}
}
break;
#endif
case OPL_REG_NEW:
#if !USE_WOODY_OPL && !USE_EMU8950_OPL
opl_opl3mode = value & 0x01;
#endif
default:
#if USE_WOODY_OPL
adlib_write(reg_num, value);
#elif USE_EMU8950_OPL
OPL_writeReg(emu8950_opl, reg_num, value);
#else
OPL3_WriteRegBuffered(&opl_chip, reg_num, value);
#endif
break;
}
}
static void OPL_Pico_PortWrite(opl_port_t port, unsigned int value)
{
if (port == OPL_REGISTER_PORT)
{
register_num = value;
}
else if (port == OPL_REGISTER_PORT_OPL3)
{
register_num = value | 0x100;
}
else if (port == OPL_DATA_PORT)
{
WriteRegister(register_num, value);
}
}
static void OPL_Pico_SetCallback(uint64_t us, opl_callback_t callback,
void *data)
{
Pico_LockMutex(&callback_queue_mutex);
OPL_Queue_Push(callback_queue, callback, data,
current_time - pause_offset + us);
Pico_UnlockMutex(&callback_queue_mutex);
}
static void OPL_Pico_ClearCallbacks(void)
{
Pico_LockMutex(&callback_queue_mutex);
OPL_Queue_Clear(callback_queue);
Pico_UnlockMutex(&callback_queue_mutex);
}
static void OPL_Pico_Lock(void)
{
Pico_LockMutex(&callback_mutex);
}
static void OPL_Pico_Unlock(void)
{
Pico_UnlockMutex(&callback_mutex);
}
static void OPL_Pico_SetPaused(int paused)
{
opl_pico_paused = paused;
}
static void OPL_Pico_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo)
{
Pico_LockMutex(&callback_queue_mutex);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, old_tempo, new_tempo);
Pico_UnlockMutex(&callback_queue_mutex);
}
const opl_driver_t opl_pico_driver =
{
"Pico",
OPL_Pico_Init,
OPL_Pico_Shutdown,
OPL_Pico_PortRead,
OPL_Pico_PortWrite,
OPL_Pico_SetCallback,
OPL_Pico_ClearCallbacks,
OPL_Pico_Lock,
OPL_Pico_Unlock,
OPL_Pico_SetPaused,
OPL_Pico_AdjustCallbacks,
};
void OPL_Delay(uint64_t us) {
sleep_us(us); // todo not sure we want to block
}
// todo really limited to 1 event per track i think, so could go smaller
#define MAX_OPL_QUEUE 10
PHEAP_DEFINE_STATIC(opl_heap, MAX_OPL_QUEUE + 1);
// todo note also the callback is likely only one of a couple of functions
typedef struct queue_entry {
uint64_t time; // todo graham can likely be 32 bit, too much work atm
#if !LIMITED_CALLBACK_TYPES
opl_callback_t callback;
#endif
void *data;
} queue_entry_t;
struct opl_callback_queue_s {
queue_entry_t entries[MAX_OPL_QUEUE];
};
static struct opl_callback_queue_s queue;
static inline queue_entry_t *get_entry(opl_callback_queue_t *queue, pheap_node_id_t id) {
assert(id && id <= opl_heap.max_nodes);
return queue->entries + id - 1;
}
bool opl_queue_comparator(void *user_data, pheap_node_id_t a, pheap_node_id_t b) {
opl_callback_queue_t *q = (opl_callback_queue_t *)user_data;
return get_entry(q, a)->time < get_entry(q, b)->time;
}
opl_callback_queue_t *OPL_Queue_Create(void) {
ph_post_alloc_init(&opl_heap, MAX_OPL_QUEUE, opl_queue_comparator, &queue);
return &queue;
}
int OPL_Queue_IsEmpty(opl_callback_queue_t *queue) {
return ph_peek_head(&opl_heap) == 0;
}
void OPL_Queue_Clear(opl_callback_queue_t *queue) {
ph_clear(&opl_heap);
}
void OPL_Queue_Destroy(opl_callback_queue_t *queue) {
}
void OPL_Queue_Push(opl_callback_queue_t *queue,
opl_callback_t callback, void *data,
uint64_t time) {
pheap_node_id_t id = ph_new_node(&opl_heap);
assert(id); // check not full
queue_entry_t *qe = get_entry(queue, id);
qe->time = time;
#if !LIMITED_CALLBACK_TYPES
qe->callback = callback
#else
assert(data || callback == RestartSong);
assert(!data || callback == TrackTimerCallback);
#endif
qe->data = data;
ph_insert_node(&opl_heap, id);
}
int OPL_Queue_Pop(opl_callback_queue_t *queue,
opl_callback_t *callback, void **data) {
if (!ph_peek_head(&opl_heap)) return 0;
pheap_node_id_t id = ph_remove_head(&opl_heap, true);
queue_entry_t *qe = get_entry(queue, id);
#if !LIMITED_CALLBACK_TYPES
*callback = qe->callback;
#else
*callback = qe->data ? TrackTimerCallback : RestartSong;
#endif
*data = qe->data;
return 1;
}
uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue) {
pheap_node_id_t head = ph_peek_head(&opl_heap);
if (head) {
return get_entry(queue, head)->time;
} else {
return 0;
}
}
static uint AdjustCallbacks(pheap_node_id_t id, uint64_t time, unsigned int old_tempo, unsigned int new_tempo) {
uint count = 0;
if (id) {
pheap_node_t *node = ph_get_node(&opl_heap, id);
queue_entry_t *entry = get_entry(&queue, id);
uint64_t offset = entry->time - time;
entry->time = time + (offset * new_tempo) / old_tempo;
AdjustCallbacks(node->child, time, old_tempo, new_tempo);
AdjustCallbacks(node->sibling, time, old_tempo, new_tempo);
}
return count;
}
void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *_queue,
uint64_t time, unsigned int old_tempo, unsigned int new_tempo)
{
assert(_queue == &queue);
AdjustCallbacks(ph_peek_head(&opl_heap), time, old_tempo, new_tempo);
}

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -20,6 +21,7 @@
#include <stdlib.h>
#include <string.h>
#include "doomtype.h"
#include "opl_queue.h"
#define MAX_OPL_QUEUE 64
@ -71,7 +73,7 @@ void OPL_Queue_Push(opl_callback_queue_t *queue,
if (queue->num_entries >= MAX_OPL_QUEUE)
{
fprintf(stderr, "OPL_Queue_Push: Exceeded maximum callbacks\n");
stderr_print( "OPL_Queue_Push: Exceeded maximum callbacks\n");
return;
}
@ -202,11 +204,12 @@ uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue)
}
void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
uint64_t time, float factor)
uint64_t time, unsigned int old_tempo, unsigned int new_tempo)
{
int64_t offset;
int i;
float factor = old_tempo / new_tempo;
for (i = 0; i < queue->num_entries; ++i)
{
offset = queue->entries[i].time - time;

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -32,8 +33,8 @@ void OPL_Queue_Push(opl_callback_queue_t *queue,
int OPL_Queue_Pop(opl_callback_queue_t *queue,
opl_callback_t *callback, void **data);
uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue);
void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
uint64_t time, float factor);
void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue, uint64_t time,
unsigned int old_tempo, unsigned int new_tempo);
#endif /* #ifndef OPL_QUEUE_H */

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -24,8 +25,15 @@
#include "SDL.h"
#include "SDL_mixer.h"
#include "doomtype.h"
#if USE_WOODY_OPL
#include "woody_opl.h"
#elif USE_EMU8950_OPL
#include "emu8950.h"
#else
#include "opl3.h"
#endif
#include "opl.h"
#include "opl_internal.h"
@ -70,8 +78,16 @@ static uint64_t pause_offset;
// OPL software emulator structure.
#if USE_WOODY_OPL
// todo configure this based on woody build flag
#define opl_op3mode 0
#elif USE_EMU8950_OPL
#define opl_op3mode 0
static OPL *emu8950_opl;
#else
static opl3_chip opl_chip;
static int opl_opl3mode;
#endif
// Temporary mixing buffer used by the mixing callback.
@ -164,7 +180,20 @@ static void FillBuffer(uint8_t *buffer, unsigned int nsamples)
// OPL output is generated into temporary buffer and then mixed
// (to avoid overflows etc.)
#if USE_WOODY_OPL
Bit16s *mb16 = (Bit16s *) mix_buffer;
adlib_getsample(mb16, nsamples);
for(int i=nsamples-1; i>=0; i--) {
mb16[i*2] = mb16[i*2 + 1] = mb16[i];
}
#elif USE_EMU8950_OPL
int16_t *buf = (int16_t *) mix_buffer;
for(int i=0;i<nsamples;i++) {
buf[i*2] = buf[i*2+1] = OPL_calc(emu8950_opl);
}
#else
OPL3_GenerateStream(&opl_chip, (Bit16s *) mix_buffer, nsamples);
#endif
SDL_MixAudioFormat(buffer, mix_buffer, AUDIO_S16SYS, nsamples * 4,
SDL_MIX_MAXVOLUME);
}
@ -288,13 +317,13 @@ static int OPL_SDL_Init(unsigned int port_base)
{
if (SDL_Init(SDL_INIT_AUDIO) < 0)
{
fprintf(stderr, "Unable to set up sound.\n");
stderr_print( "Unable to set up sound.\n");
return 0;
}
if (Mix_OpenAudio(opl_sample_rate, AUDIO_S16SYS, 2, GetSliceSize()) < 0)
{
fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError());
stderr_print( "Error initialising SDL_mixer: %s\n", Mix_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return 0;
@ -328,7 +357,7 @@ static int OPL_SDL_Init(unsigned int port_base)
if (mixing_format != AUDIO_S16SYS || mixing_channels != 2)
{
fprintf(stderr,
stderr_print(
"OPL_SDL only supports native signed 16-bit LSB, "
"stereo format!\n");
@ -341,8 +370,14 @@ static int OPL_SDL_Init(unsigned int port_base)
// Create the emulator structure:
#if USE_WOODY_OPL
adlib_init(mixing_freq);
#elif USE_EMU8950_OPL
emu8950_opl = OPL_new(3579552, mixing_freq); // todo check rate
#else
OPL3_Reset(&opl_chip, mixing_freq);
opl_opl3mode = 0;
#endif
callback_mutex = SDL_CreateMutex();
callback_queue_mutex = SDL_CreateMutex();
@ -432,10 +467,17 @@ static void WriteRegister(unsigned int reg_num, unsigned int value)
break;
case OPL_REG_NEW:
#if !USE_WOODY_OPL && !USE_EMU8950_OPL
opl_opl3mode = value & 0x01;
#endif
default:
#if USE_WOODY_OPL
adlib_write(reg_num, value);
#elif USE_EMU8950_OPL
OPL_writeReg(emu8950_opl, reg_num, value);
#else
OPL3_WriteRegBuffered(&opl_chip, reg_num, value);
#endif
break;
}
}
@ -487,10 +529,10 @@ static void OPL_SDL_SetPaused(int paused)
opl_sdl_paused = paused;
}
static void OPL_SDL_AdjustCallbacks(float factor)
static void OPL_SDL_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, old_tempo, new_tempo);
SDL_UnlockMutex(callback_queue_mutex);
}

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -224,10 +225,10 @@ void OPL_Timer_ClearCallbacks(void)
SDL_UnlockMutex(callback_queue_mutex);
}
void OPL_Timer_AdjustCallbacks(float factor)
void OPL_Timer_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, old_tempo, new_tempo);
SDL_UnlockMutex(callback_queue_mutex);
}

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -27,7 +28,7 @@ void OPL_Timer_ClearCallbacks(void);
void OPL_Timer_Lock(void);
void OPL_Timer_Unlock(void);
void OPL_Timer_SetPaused(int paused);
void OPL_Timer_AdjustCallbacks(float factor);
void OPL_Timer_AdjustCallbacks(unsigned int old_tempo, unsigned int new_tempo);
#endif /* #ifndef OPL_TIMER_H */

655
opl/slot_render.cpp Normal file
View file

@ -0,0 +1,655 @@
/**
* Copyright (C) 2001-2020 Mitsutaka Okazaki
* Copyright (C) 2021-2022 Graham Sanderson
*/
#include "slot_render.h"
#include <cstdio>
#include <cstring>
#if PICO_ON_DEVICE
#include "hardware/interp.h"
#define SLOT_RENDER_DATA __scratch_y("slot_render_cpp")
#else
#define SLOT_RENDER_DATA
#endif
static_assert(PM_DPHASE > 0, "");
#define unlikely(x) __builtin_expect((x),0)
#if EMU8950_SLOT_RENDER
#include <algorithm>
#define INLINE inline
// todo combine all these tablles into 1
static uint8_t eg_step_tables_fast[4][8] = {
{1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 2, 1, 1, 1, 2},
{1, 2, 1, 2, 1, 2, 1, 2},
{1, 2, 2, 2, 1, 2, 2, 2},
};
static uint8_t eg_step_tables_fast2[4][8] = {
{2, 2, 2, 2, 2, 2, 2, 2},
{2, 2, 2, 4, 2, 2, 2, 4},
{2, 4, 2, 4, 2, 4, 2, 4},
{2, 4, 4, 4, 2, 4, 4, 4},
};
static uint8_t eg_step_tables[4][8] = {
{0, 1, 0, 1, 0, 1, 0, 1},
{0, 1, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1},
};
static uint8_t eg_step_table4[8] = {
4,4,4,4,4,4,4,4
};
// too can find this elesewhere
static uint8_t eg_step_table0[8] = {
0,0,0,0,0,0,0,0
};
// todo combine all these tablles into 1 - seems slower for now probably not in asm tho
//static uint8_t eg_step[14][8] = {
//#define ST_NORMAL 0
// {0, 1, 0, 1, 0, 1, 0, 1},
// {0, 1, 0, 1, 1, 1, 0, 1},
// {0, 1, 1, 1, 0, 1, 1, 1},
// {0, 1, 1, 1, 1, 1, 1, 1},
//#define ST_FAST 4
// {1, 1, 1, 1, 1, 1, 1, 1},
// {1, 1, 1, 2, 1, 1, 1, 2},
// {1, 2, 1, 2, 1, 2, 1, 2},
// {1, 2, 2, 2, 1, 2, 2, 2},
//#define ST_FAST2 8
// {2, 2, 2, 2, 2, 2, 2, 2},
// {2, 2, 2, 4, 2, 2, 2, 4},
// {2, 4, 2, 4, 2, 4, 2, 4},
// {2, 4, 4, 4, 2, 4, 4, 4},
//#define ST_FOUR 12
// {4, 4, 4, 4, 4, 4, 4, 4},
//#define ST_ZERO 13
// {0, 0, 0, 0, 0, 0, 0, 0},
//};
/* offset to fnum, rough approximation of 14 cents depth. */
static int8_t pm_table[8][PM_PG_WIDTH] = {
{0, 0, 0, 0, 0, 0, 0, 0}, // fnum = 000xxxxx
{0, 0, 1, 0, 0, 0, -1, 0}, // fnum = 001xxxxx
{0, 1, 2, 1, 0, -1, -2, -1}, // fnum = 010xxxxx
{0, 1, 3, 1, 0, -1, -3, -1}, // fnum = 011xxxxx
{0, 2, 4, 2, 0, -2, -4, -2}, // fnum = 100xxxxx
{0, 2, 5, 2, 0, -2, -5, -2}, // fnum = 101xxxxx
{0, 3, 6, 3, 0, -3, -6, -3}, // fnum = 110xxxxx
{0, 3, 7, 3, 0, -3, -7, -3}, // fnum = 111xxxxx
};
// todo we are probably fine with pm_table_half[x] = pm_table[x/2] >> 1 as an approcimation, but keeping separate for diff for now
static int8_t pm_table_half[8][PM_PG_WIDTH] = {
{0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, 0 >> 1}, // fnum = 000xxxxx
{0 >> 1, 0 >> 1, 1 >> 1, 0 >> 1, 0 >> 1, 0 >> 1, -1 >> 1, 0 >> 1}, // fnum = 001xxxxx
{0 >> 1, 1 >> 1, 2 >> 1, 1 >> 1, 0 >> 1, -1 >> 1, -2 >> 1, -1 >> 1}, // fnum = 010xxxxx
{0 >> 1, 1 >> 1, 3 >> 1, 1 >> 1, 0 >> 1, -1 >> 1, -3 >> 1, -1 >> 1}, // fnum = 011xxxxx
{0 >> 1, 2 >> 1, 4 >> 1, 2 >> 1, 0 >> 1, -2 >> 1, -4 >> 1, -2 >> 1}, // fnum = 100xxxxx
{0 >> 1, 2 >> 1, 5 >> 1, 2 >> 1, 0 >> 1, -2 >> 1, -5 >> 1, -2 >> 1}, // fnum = 101xxxxx
{0 >> 1, 3 >> 1, 6 >> 1, 3 >> 1, 0 >> 1, -3 >> 1, -6 >> 1, -3 >> 1}, // fnum = 110xxxxx
{0 >> 1, 3 >> 1, 7 >> 1, 3 >> 1, 0 >> 1, -3 >> 1, -7 >> 1, -3 >> 1}, // fnum = 111xxxxx
};
/* clang-format off */
/* exp_table[255-x] = round((exp2((double)x / 256.0) - 1) * 1024) */
static uint16_t exp_table[256] = {
1024+1018, 1024+1013, 1024+1007, 1024+1002, 1024+996, 1024+991, 1024+986, 1024+980, 1024+975, 1024+969, 1024+964, 1024+959, 1024+953, 1024+948, 1024+942, 1024+937,
1024+932, 1024+927, 1024+921, 1024+916, 1024+911, 1024+906, 1024+900, 1024+895, 1024+890, 1024+885, 1024+880, 1024+874, 1024+869, 1024+864, 1024+859, 1024+854,
1024+849, 1024+844, 1024+839, 1024+834, 1024+829, 1024+824, 1024+819, 1024+814, 1024+809, 1024+804, 1024+799, 1024+794, 1024+789, 1024+784, 1024+779, 1024+774,
1024+770, 1024+765, 1024+760, 1024+755, 1024+750, 1024+745, 1024+741, 1024+736, 1024+731, 1024+726, 1024+722, 1024+717, 1024+712, 1024+708, 1024+703, 1024+698,
1024+693, 1024+689, 1024+684, 1024+680, 1024+675, 1024+670, 1024+666, 1024+661, 1024+657, 1024+652, 1024+648, 1024+643, 1024+639, 1024+634, 1024+630, 1024+625,
1024+621, 1024+616, 1024+612, 1024+607, 1024+603, 1024+599, 1024+594, 1024+590, 1024+585, 1024+581, 1024+577, 1024+572, 1024+568, 1024+564, 1024+560, 1024+555,
1024+551, 1024+547, 1024+542, 1024+538, 1024+534, 1024+530, 1024+526, 1024+521, 1024+517, 1024+513, 1024+509, 1024+505, 1024+501, 1024+496, 1024+492, 1024+488,
1024+484, 1024+480, 1024+476, 1024+472, 1024+468, 1024+464, 1024+460, 1024+456, 1024+452, 1024+448, 1024+444, 1024+440, 1024+436, 1024+432, 1024+428, 1024+424,
1024+420, 1024+416, 1024+412, 1024+409, 1024+405, 1024+401, 1024+397, 1024+393, 1024+389, 1024+385, 1024+382, 1024+378, 1024+374, 1024+370, 1024+367, 1024+363,
1024+359, 1024+355, 1024+352, 1024+348, 1024+344, 1024+340, 1024+337, 1024+333, 1024+329, 1024+326, 1024+322, 1024+318, 1024+315, 1024+311, 1024+308, 1024+304,
1024+300, 1024+297, 1024+293, 1024+290, 1024+286, 1024+283, 1024+279, 1024+276, 1024+272, 1024+268, 1024+265, 1024+262, 1024+258, 1024+255, 1024+251, 1024+248,
1024+244, 1024+241, 1024+237, 1024+234, 1024+231, 1024+227, 1024+224, 1024+220, 1024+217, 1024+214, 1024+210, 1024+207, 1024+204, 1024+200, 1024+197, 1024+194,
1024+190, 1024+187, 1024+184, 1024+181, 1024+177, 1024+174, 1024+171, 1024+168, 1024+164, 1024+161, 1024+158, 1024+155, 1024+152, 1024+148, 1024+145, 1024+142,
1024+139, 1024+136, 1024+133, 1024+130, 1024+126, 1024+123, 1024+120, 1024+117, 1024+114, 1024+111, 1024+108, 1024+105, 1024+102, 1024+99, 1024+96, 1024+93,
1024+90, 1024+87, 1024+84, 1024+81, 1024+78, 1024+75, 1024+72, 1024+69, 1024+66, 1024+63, 1024+60, 1024+57, 1024+54, 1024+51, 1024+48, 1024+45,
1024+42, 1024+40, 1024+37, 1024+34, 1024+31, 1024+28, 1024+25, 1024+22, 1024+20, 1024+17, 1024+14, 1024+11, 1024+8, 1024+6, 1024+3, 1024+0,
};
/* logsin_table[x] = round(-log2(sin((x + 0.5) * PI / (PG_WIDTH / 4) / 2)) * 256) */
static uint32_t ml_table[16] = {1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2,
8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2};
#if !EMU8950_NO_WAVE_TABLE_MAP
#define LOGSIN_TABLE_SIZE PG_WIDTH / 4
#else
#define LOGSIN_TABLE_SIZE PG_WIDTH / 2
#endif
static uint16_t SLOT_RENDER_DATA logsin_table[LOGSIN_TABLE_SIZE] = {
2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013, 979, 949, 920, 894, 869,
846, 825, 804, 785, 767, 749, 732, 717, 701, 687, 672, 659, 646, 633, 621, 609,
598, 587, 576, 566, 556, 546, 536, 527, 518, 509, 501, 492, 484, 476, 468, 461,
453, 446, 439, 432, 425, 418, 411, 405, 399, 392, 386, 380, 375, 369, 363, 358,
352, 347, 341, 336, 331, 326, 321, 316, 311, 307, 302, 297, 293, 289, 284, 280,
276, 271, 267, 263, 259, 255, 251, 248, 244, 240, 236, 233, 229, 226, 222, 219,
215, 212, 209, 205, 202, 199, 196, 193, 190, 187, 184, 181, 178, 175, 172, 169,
167, 164, 161, 159, 156, 153, 151, 148, 146, 143, 141, 138, 136, 134, 131, 129,
127, 125, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96,
94, 92, 91, 89, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 70, 69,
67, 66, 64, 63, 62, 60, 59, 57, 56, 55, 53, 52, 51, 49, 48, 47,
46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30,
29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 17, 17,
16, 15, 15, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7,
7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2,
2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
#if EMU8950_NO_WAVE_TABLE_MAP
// double the table size to include second 1/4 cycle
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2,
2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7,
7, 8, 8, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14, 15, 15, 16,
17, 17, 18, 19, 20, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 46,
47, 48, 49, 51, 52, 53, 55, 56, 57, 59, 60, 62, 63, 64, 66, 67,
69, 70, 72, 74, 75, 77, 78, 80, 82, 83, 85, 87, 89, 91, 92, 94,
96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 125, 127,
129, 131, 134, 136, 138, 141, 143, 146, 148, 151, 153, 156, 159, 161, 164, 167,
169, 172, 175, 178, 181, 184, 187, 190, 193, 196, 199, 202, 205, 209, 212, 215,
219, 222, 226, 229, 233, 236, 240, 244, 248, 251, 255, 259, 263, 267, 271, 276,
280, 284, 289, 293, 297, 302, 307, 311, 316, 321, 326, 331, 336, 341, 347, 352,
358, 363, 369, 375, 380, 386, 392, 399, 405, 411, 418, 425, 432, 439, 446, 453,
461, 468, 476, 484, 492, 501, 509, 518, 527, 536, 546, 556, 566, 576, 587, 598,
609, 621, 633, 646, 659, 672, 687, 701, 717, 732, 749, 767, 785, 804, 825, 846,
869, 894, 920, 949, 979, 1013, 1050, 1091, 1137, 1190, 1252, 1326, 1419, 1543, 1731, 2137,
#endif
};
#if EMU8950_NO_WAVE_TABLE_MAP
// we start with
// _ _
// / \/ \ which is abs(sine) wave
//
static uint16_t wav_or_table_lookup[4][4] = {
{0x0000, 0x0000, 0x8000, 0x8000}, // .. negate second half
{0x0000, 0x0000, 0x0fff, 0x0fff}, // .. attenuate second half
{0x0000, 0x0000, 0x0000, 0x0000}, // .. leave second half alone
{0x0000, 0x0fff, 0x0000, 0x0fff}, // .. attenuate 1 and 3
};
#endif
static INLINE int16_t calc_sample(const SLOT_RENDER *slot, uint32_t index, int16_t am) {
#if !EMU8950_NO_WAVE_TABLE_MAP
uint16_t h = slot->wave_table[index & (PG_WIDTH - 1)];
#else
#if PICO_ON_DEVICE
interp0->accum[0] = index << 1;
uint16_t h = *(uint16_t *)(interp0->peek[0]) | *(uint16_t *)(interp0->peek[1]);
#else
uint16_t h = slot->wav_or_table[(index >> (PG_BITS - 2)) & 3] | slot->logsin_table[(index & (PG_WIDTH / 2 - 1))];
#endif
#endif
uint16_t att = h + slot->eg_out_tll_lsl3 + am;
int16_t t = exp_table[att&0xff];
// note we're really just bit clearing the original top bit 15 ..
// todo presumably the & is ignored on ARM?
int16_t res = t >> ((att>>8)&127);
if (!res) return res; // maybe make things more compatible
#if EMU8950_LINEAR_NEG_NOT_NOT
return ((att & 0x8000) ? -res : res) << 1;
#else
return ((att & 0x8000) ? ~res : res) << 1;
#endif
}
#if 0
static uint8_t *get_attack_step_table(SLOT_RENDER *slot) {
int index = slot->eg_rate_l;
uint32_t hm1 = (slot->eg_rate_h - 1);
if (hm1 < 12) {
index += ST_NORMAL;
} else if (hm1 >= 14) {
// 0 and 15
index = ST_ZERO;
} else if (hm1 == 12) {
index += ST_FAST;
} else {
index += ST_FAST2;
}
return eg_step[index];
}
// note this is the same as attack except for 15 => ST_FOUR
static uint8_t *get_decay_step_table(SLOT_RENDER *slot) {
uint32_t hm1 = (slot->eg_rate_h - 1);
int index = slot->eg_rate_l;
if (hm1 < 12) {
index += ST_NORMAL;
} else if (hm1 >= 14) {
index = hm1 == 14 ? ST_FOUR : ST_ZERO;
} else if (hm1 == 12) {
index += ST_FAST;
} else {
index += ST_FAST2;
}
return eg_step[index];
}
#else
static uint8_t *get_attack_step_table(SLOT_RENDER *slot) {
switch (slot->eg_rate_h) {
case 13:
return eg_step_tables_fast[slot->eg_rate_l];
case 14:
return eg_step_tables_fast2[slot->eg_rate_l];
case 0:
case 15:
return eg_step_table0;
default:
return eg_step_tables[slot->eg_rate_l];
}
}
static uint8_t *get_decay_step_table(SLOT_RENDER *slot) {
switch (slot->eg_rate_h) {
case 0:
return eg_step_table0;
case 13:
return eg_step_tables_fast[slot->eg_rate_l];
case 14:
return eg_step_tables_fast2[slot->eg_rate_l];
case 15:
return eg_step_table4;
default:
return eg_step_tables[slot->eg_rate_l];
}
}
#endif
template <int EG_STATE> int get_parameter_rate(SLOT_RENDER *slot) {
switch (EG_STATE) {
case ATTACK:
return slot->patch->AR;
case DECAY:
return slot->patch->DR;
case SUSTAIN:
return slot->patch->EG ? 0 : slot->patch->RR;
case RELEASE:
return slot->patch->RR;
default:
return 0;
}
}
template <int EG_STATE> void commit_slot_update_eg_only(SLOT_RENDER *slot) {
int p_rate = get_parameter_rate<EG_STATE>(slot);
if (p_rate == 0) {
slot->eg_shift = 0;
slot->eg_rate_h = 0;
slot->eg_rate_l = 0;
} else {
slot->eg_rate_h = std::min(15, p_rate + (slot->rks >> 2));
slot->eg_rate_l = slot->rks & 3;
if (EG_STATE == ATTACK) {
slot->eg_shift = (0 < slot->eg_rate_h && slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0;
} else {
slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0;
}
}
}
template<bool PM> uint32_t advance_phase(SLOT_RENDER *slot, uint32_t &pm_phase) {
int8_t pm = 0;
if (PM) {
#if !PICO_ON_DEVICE
// todo if we do this with interpolator, then we can just skip the if
// todo PM_DPHASE == 512
pm_phase = (pm_phase + PM_DPHASE) & (PM_DP_WIDTH - 1);
pm = slot->efix_pm_table[pm_phase >> (PM_DP_BITS - PM_PG_BITS)];
#else
interp1->add_raw[1] = 1;
// printf("%08x %08x\n", interp1->accum[1], interp1->peek[1]);
pm = *(int8_t *)interp1->peek[1];
#endif
}
#if !PICO_ON_DEVICE
slot->pg_phase += slot->efix_pg_pm_x_fnum3ff + pm * slot->efix_pg_phase_multiplier;
#if EMU8950_NIT_PICKS
slot->pg_phase &= (DP_WIDTH - 1) * 2; // note the clear bottom bit is just for exact compatability for comparison with original code
#else
slot->pg_phase &= (DP_WIDTH * 2 - 1); // should be quicker on ARM
#endif
return slot->pg_phase >> (DP_BASE_BITS + 1);
#else
static_assert(((DP_WIDTH * 2 - 1) >> (DP_BASE_BITS + 1)) == (1 << PG_BITS) -1, "");
// pg_phase += pm * slot->efix_pg_phase_multiplier
if (pm) {
interp1->add_raw[0] = pm * slot->efix_pg_phase_multiplier;
}
interp1->add_raw[0] = interp1->base[0];
// tmp = slot->pg_phase >> (DP_BASE_BITS + 1) & ((1 << PG_BITS) - 1);
// slot->pg_phase += slot->efix_pg_pm_x_fnum3ff
// return tmp;
return interp1->add_raw[0];
#endif
}
template<bool PM> void mod_am1_fb1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
int16_t fm = (slot->output[1] + slot->output[0]) >> slot->nine_minus_FB;
slot->output[1] = slot->output[0];
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
slot->mod_buffer[s] = slot->output[0] = calc_sample(slot, pg_out + fm, slot->lfo_am_buffer_lsl3[s]);
}
template<bool PM> void mod_am1_fb0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
slot->mod_buffer[s] = calc_sample(slot, pg_out, slot->lfo_am_buffer_lsl3[s]);
}
template <bool PM> void mod_am0_fb1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
int16_t fm = (slot->output[1] + slot->output[0]) >> slot->nine_minus_FB;
slot->output[1] = slot->output[0];
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
slot->mod_buffer[s] = slot->output[0] = calc_sample(slot, pg_out + fm, 0);
}
template <bool PM> void mod_am0_fb0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
slot->mod_buffer[s] = calc_sample(slot, pg_out, 0);
}
template <bool PM> void alg0_am1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
// todo is this masking realy necessary; i doubt it. .. seems to be always even anyway
// int32_t fm = 2 * (opl->mod_buffer[s] >> 1);
int32_t fm = slot->mod_buffer[s];
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
int32_t val = calc_sample(slot, pg_out + fm, slot->lfo_am_buffer_lsl3[s]);
slot->buffer[s] += val;
}
template <bool PM> void alg0_am0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
// todo could reset these when we start a new note
// slot->output[1] = slot->output[0];
// todo is this masking realy necessary; i doubt it. .. seems to be always even anyway
// int32_t fm = 2 * (opl->mod_buffer[s] >> 1);
int32_t fm = slot->mod_buffer[s];
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
int32_t val = calc_sample(slot, pg_out + fm, 0);
slot->buffer[s] += val;
}
template <bool PM> void alg1_am1_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
int16_t val = calc_sample(slot, pg_out, slot->lfo_am_buffer_lsl3[s]);
slot->buffer[s] += val + slot->mod_buffer[s];
}
template <bool PM> void alg1_am0_fn(SLOT_RENDER *slot, uint32_t& pm_phase, uint32_t s) {
uint32_t pg_out = advance_phase<PM>(slot, pm_phase);
int16_t val = calc_sample(slot, pg_out, 0);
slot->buffer[s] += val + slot->mod_buffer[s];
}
#if PICO_ON_DEVICE
extern "C" uint32_t test_slot_asm(SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint fn);
#endif
template <int F_NUM, typename F> uint32_t slot_envelope_loop(F&& fn, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
// factored out as it is constant per call
slot->efix_pg_phase_multiplier = ml_table[slot->patch->ML] << slot->blk;
uint32_t efix_pg_pm_x_fnum3ff = (slot->fnum & 0x3ff) * slot->efix_pg_phase_multiplier;
// pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)];
// pm >>= (slot->pm_mode ? 0 : 1);
int8_t *efix_pm_table = slot->pm_mode ? pm_table[(slot->fnum >> 7) & 7] :
pm_table_half[(slot->fnum >> 7) & 7];
#if !PICO_ON_DEVICE
slot->wav_or_table = wav_or_table_lookup[slot->patch->WS & 3];
slot->logsin_table = logsin_table;
slot->efix_pg_pm_x_fnum3ff = efix_pg_pm_x_fnum3ff;
slot->efix_pm_table = efix_pm_table;
#else
// note that index is pre-doubled
// wave_table[(index >> (PG_BITS - 2)) & 3]
interp_config c = interp_default_config();
interp_config_set_shift(&c, PG_BITS - 2);
interp_config_set_mask(&c, 1, 2);
interp_set_config(interp0, 0, &c);
interp0->base[0] = (uintptr_t)wav_or_table_lookup[slot->patch->WS & 3];
c = interp_default_config();
interp_config_set_cross_input(&c, true);
interp_config_set_mask(&c, 1, PG_BITS - 1);
interp_set_config(interp0, 1, &c);
// logsin_table[(index & (PG_WIDTH / 2 - 1))];
interp0->base[1] = (uintptr_t)logsin_table;
// return slot->pg_phase >> (DP_BASE_BITS + 1) & ((1 << PG_BITS) - 1);
// post increment with
c = interp_default_config();
interp_config_set_add_raw(&c, true);
interp_config_set_shift(&c, DP_BASE_BITS + 1);
interp_config_set_mask(&c, 0, PG_BITS - 1);
interp_set_config(interp1, 0, &c);
slot->efix_pg_pm_x_fnum3ff = efix_pg_pm_x_fnum3ff;
interp1->base[0] = efix_pg_pm_x_fnum3ff;
interp1->accum[0] = slot->pg_phase;
c = interp_default_config();
static_assert(PM_DPHASE == 1, ""); // better for everyone!
// pm_phase = (pm_phase + PM_DPHASE) & (PM_DP_WIDTH - 1);
// pm = slot->efix_pm_table[pm_phase >> (PM_DP_BITS - PM_PG_BITS)];
interp_config_set_shift(&c, PM_DP_BITS - PM_PG_BITS);
interp_config_set_mask(&c, 0, PM_PG_BITS - 1);
interp_set_config(interp1, 1, &c);
interp1->base[1] = (uintptr_t)efix_pm_table;
interp1->accum[1] = pm_phase;
// we lookup in the lfo_am_buffer_lsl3 at inter0->base[2] + sample_ptr_in_mod_buffer / 2 for mod
// or at inter0->base[2] + sample_ptr_in_buffer / 4
if (F_NUM <8) {
interp0->base[2] = (uintptr_t)slot->lfo_am_buffer_lsl3 - ((uintptr_t)slot->mod_buffer) / 2;
} else {
interp0->base[2] = (uintptr_t)slot->lfo_am_buffer_lsl3 - ((uintptr_t)slot->buffer) / 4;
}
interp1->base[2] = slot->efix_pg_phase_multiplier;
#endif
uint32_t s = 0;
uint32_t nsamples_bak = nsamples;
slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later
#if PICO_ON_DEVICE && EMU8950_ASM
// we lookup in mod_buffer at buffer_mod_buffer_offset + sample_ptr_in_buffer / 2
slot->buffer_mod_buffer_offset = (uintptr_t)slot->mod_buffer - ((uintptr_t)slot->buffer) / 2;
s = test_slot_asm(slot, nsamples, eg_counter, F_NUM);
slot->pg_phase = interp1->accum[0];
return s;
#endif
// todo eg_rate_h == 0 ...
// todo prate == 0 ?
if (slot->eg_state == ATTACK) {
// note we roll eg_rate_h > 0 check which is (was) always paired with (eg_counter++ && eg_shift_mask) == 0
// and guarantee that eg_counter is never zero because bit 31-30 area always set to 10 on entry
uint32_t eg_shift_mask = slot->eg_rate_h > 0 ? (1 << slot->eg_shift) - 1 : 0xffffffff;
uint8_t *eg_step_table = get_attack_step_table(slot);
// this original code did something like this every loop
//
// if ((++eg_counter & eg_shift_mask) == 0 && 0 < slot->eg_out)) {
// // update eg_out etc.
// }
// if (!eg_out) {
// // enter decay next time
// }
//
// since eg_out can only change as a result of the if, we do
//
// a) before the loop
//
// if (!eg_out) {
// eg_counter++;
// // enter decay next time
// }
//
// b) inside the loop
//
// if ((++eg_counter & eg_shift_mask) == 0) {
// // update eg_out etc.
// if (!eg_out) {
// // enter decay next time
// }
// }
if (slot->eg_out == 0) {
// straight into decay
eg_counter++;
slot->eg_state = DECAY;
commit_slot_update_eg_only<DECAY>(slot);
fn(slot, pm_phase, s++);
} else {
for (; s < nsamples; s++) {
#if DUMPO
if (hack_ch == 17 && s == 12) breako();
#endif
if (unlikely((++eg_counter & eg_shift_mask) == 0)) {
uint8_t step = eg_step_table[(eg_counter >> slot->eg_shift) & 7];
slot->eg_out += (~slot->eg_out * step) >> 3;
slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later
if (unlikely(slot->eg_out == 0)) {
slot->eg_state = DECAY;
commit_slot_update_eg_only<DECAY>(slot);
nsamples = s;
}
}
fn(slot, pm_phase, s);
}
}
}
if (slot->eg_state == DECAY) {
nsamples = nsamples_bak;
// todo if note ends we can stop early
uint32_t eg_shift_mask = slot->eg_rate_h > 0 ? (1 << slot->eg_shift) - 1 : 0xffffffff;
uint8_t *eg_step_table = get_decay_step_table(slot);
// the original code checked for sustain every loop, however we have move it inside the once per 1<<eg_shift_mask check
// so we must check it here outside the sample loop unless the first iteration of the sample loop would check it because of the
// value of eg_counter
if (!(((eg_counter+1) & eg_shift_mask) == 0) &&
((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL)) {
if (s < nsamples) {
// because we have moved the second line of the test above into the per !(++eg_coiunter & eg_shift_maask))
// test below, we have to half unroll the first loop
slot->eg_state = SUSTAIN;
eg_counter++;
commit_slot_update_eg_only<SUSTAIN>(slot);
fn(slot, pm_phase, s++);
}
} else {
for (; s < nsamples; s++) {
if (unlikely((++eg_counter & eg_shift_mask) == 0)) {
slot->eg_out = static_cast<int16_t>(std::min(EG_MUTE, slot->eg_out + (int) eg_step_table[
(eg_counter >> slot->eg_shift) & 7]));
slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later
// todo check for decay to zero
if (unlikely((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL)) {
slot->eg_state = SUSTAIN;
commit_slot_update_eg_only<SUSTAIN>(slot);
nsamples = s;
#if EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION
} else if (slot->eg_out == EG_MUTE) {
// we have decayed to zero, so give up
nsamples = s;
#endif
}
}
fn(slot, pm_phase, s);
}
}
}
if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) {
nsamples = nsamples_bak;
// todo if note ends we can stop early
uint32_t eg_shift_mask = slot->eg_rate_h > 0 ? (1 << slot->eg_shift) - 1 : 0xffffffff;
uint8_t *eg_step_table = get_decay_step_table(slot);
for (; s < nsamples; s++) {
if (unlikely((++eg_counter & eg_shift_mask) == 0)) {
slot->eg_out = static_cast<int16_t>(std::min(EG_MUTE, slot->eg_out + (int)eg_step_table[(eg_counter >> slot->eg_shift)&7]));
#if EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION
if (slot->eg_out == EG_MUTE) {
// we have decayed to zero, so give up
nsamples = s;
}
#endif
slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later
}
fn(slot, pm_phase, s);
}
}
#if PICO_ON_DEVICE
slot->pg_phase = interp1->accum[0];
#endif
// not necessary for correctness
//#if EMU8950_NIT_PICKS
// slot->pg_phase &= (DP_WIDTH - 1) * 2;
//#endif
return s;
}
typedef void OPL;
template<bool PM> uint32_t slot_mod_linear(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
if (slot->patch->AM) {
if (slot->patch->FB) {
slot->nine_minus_FB = 9 - slot->patch->FB;
return slot_envelope_loop<6+PM>(mod_am1_fb1_fn<PM>, slot, nsamples, eg_counter, pm_phase);
} else {
return slot_envelope_loop<2+PM>(mod_am1_fb0_fn<PM>, slot, nsamples, eg_counter, pm_phase);
}
} else {
if (slot->patch->FB) {
slot->nine_minus_FB = 9 - slot->patch->FB;
return slot_envelope_loop<4+PM>(mod_am0_fb1_fn<PM>, slot, nsamples, eg_counter, pm_phase);
} else {
return slot_envelope_loop<0+PM>(mod_am0_fb0_fn<PM>, slot, nsamples, eg_counter, pm_phase);
}
}
}
extern "C" uint32_t slot_mod_linear(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
if (slot->patch->PM)
return slot_mod_linear<true>(opl, slot, nsamples, eg_counter, pm_phase);
else
return slot_mod_linear<false>(opl, slot, nsamples, eg_counter, pm_phase);
}
template<bool PM> uint32_t slot_car_linear_alg0(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
if (slot->patch->AM) {
return slot_envelope_loop<10+PM>(alg0_am1_fn<PM>, slot, nsamples, eg_counter, pm_phase);
} else {
return slot_envelope_loop<8+PM>(alg0_am0_fn<PM>, slot, nsamples, eg_counter, pm_phase);
}
}
extern "C" uint32_t slot_car_linear_alg0(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
if (slot->patch->PM)
return slot_car_linear_alg0<true>(opl, slot, nsamples, eg_counter, pm_phase);
else
return slot_car_linear_alg0<false>(opl, slot, nsamples, eg_counter, pm_phase);
}
template<bool PM> uint32_t slot_car_linear_alg1(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
if (slot->patch->AM) {
return slot_envelope_loop<14+PM>(alg1_am1_fn<PM>, slot, nsamples, eg_counter, pm_phase);
} else {
return slot_envelope_loop<12+PM>(alg1_am0_fn<PM>, slot, nsamples, eg_counter, pm_phase);
}
}
extern "C" uint32_t slot_car_linear_alg1(OPL *opl, SLOT_RENDER *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) {
if (slot->patch->PM)
return slot_car_linear_alg1<true>(opl, slot, nsamples, eg_counter, pm_phase);
else
return slot_car_linear_alg1<false>(opl, slot, nsamples, eg_counter, pm_phase);
}
#endif

141
opl/slot_render.h Normal file
View file

@ -0,0 +1,141 @@
/**
* Copyright (C) 2001-2020 Mitsutaka Okazaki
* Copyright (C) 2021-2022 Graham Sanderson
*/
#ifndef _SLOT_RENDER_H_
#define _SLOT_RENDER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define EG_BITS 9
#define EG_MUTE ((1 << EG_BITS) - 1)
#define EG_MAX (0x1f0) // 93dB
/* sine table */
#define PG_BITS 10 /* 2^10 = 1024 length sine table */
#define PG_WIDTH (1 << PG_BITS)
/* phase increment counter */
#define DP_BITS 20
#define DP_WIDTH (1 << DP_BITS)
#define DP_BASE_BITS (DP_BITS - PG_BITS)
/* pitch modulator */
#define PM_PG_BITS 3
#define PM_PG_WIDTH (1 << PM_PG_BITS)
// no benefit that I can see to this being 22 - it just makes PM_DPHASE unruly at 512
#if 0
#define PM_DP_BITS 22
#else
#define PM_DP_BITS 13
#endif
#define PM_DP_WIDTH (1 << PM_DP_BITS)
#define PM_DPHASE (PM_DP_WIDTH / (1024 * 8))
/* voice data */
typedef struct __OPL_PATCH {
#if !EMU8950_NO_TLL
/* 0 */ uint8_t TL;
/* 1 */ uint8_t KL;
#else
/* 0 */ uint8_t TL4;
/* 1 */ uint8_t KL_SHIFT;
#endif
/* 2 */ uint8_t FB;
/* 3 */ uint8_t EG;
/* 4 */ uint8_t ML;
/* 5 */ uint8_t AR;
/* 6 */ uint8_t DR;
/* 7 */ uint8_t SL;
/* 8 */ uint8_t RR;
/* 9 */ uint8_t KR;
/* a */ uint8_t AM;
/* b */ uint8_t PM;
uint8_t WS;
} OPL_PATCH;
enum __OPL_EG_STATE
{
ATTACK, DECAY, SUSTAIN, RELEASE, UNKNOWN
};
struct SLOT_RENDER {
// stutff the assembly cares about is duplicate at the top so we can be sure of its order witout #ifs
#if EMU8950_ASM
// note there is code that writes zero to all for at once
/* 0x00 */ uint8_t eg_state; /* current state */
/* 0x01 */ uint8_t eg_rate_h; /* eg speed rate high 4bits */
/* 0x02 */ uint8_t eg_rate_l; /* eg speed rate low 2bits */
/* 0x03 */ uint8_t eg_shift; /* shift for eg global counter, controls envelope speed */
/* 0x04 */ uint8_t nine_minus_FB;
/* 0x05 */ uint8_t rks; /* key scale offset (rks) for eg speed */
uint16_t pad2;
/* 0x08 */ int16_t eg_out; /* eg output */
/* 0x0a */ uint16_t tll; /* total level + key scale level*/
/* 0x0c */ int32_t eg_out_tll_lsl3; /* (eg_out + tll) << 3 */
// only used for mod slot
/* 0x10 */ int32_t output[2]; /* output value, latest and previous. */
// these are actually opl local not slot local
/* 0x18 */ int16_t *mod_buffer;
/* 0x1c */ int32_t *buffer;
/* 0x20 */ uintptr_t buffer_mod_buffer_offset;
/* 0x24 */ OPL_PATCH *patch;
#else
uint8_t eg_state; /* current state */
uint8_t eg_rate_h; /* eg speed rate high 4bits */
uint8_t eg_rate_l; /* eg speed rate low 2bits */
#if EMU8950_SLOT_RENDER
uint8_t nine_minus_FB;
int32_t eg_out_tll_lsl3; /* (eg_out + tll) << 3 */
#endif
uint8_t rks; /* key scale offset (rks) for eg speed */
int16_t eg_out; /* eg output */
uint16_t tll; /* total level + key scale level*/
// only used for mod slot
int32_t output[2]; /* output value, latest and previous. */
// these are actually opl local not slot local
int16_t *mod_buffer;
int32_t *buffer;
uint32_t eg_shift; /* shift for eg global counter, controls envelope speed */
OPL_PATCH *patch;
#endif
uint16_t fnum; /* f-number (9 bits) */
uint16_t efix_pg_phase_multiplier; /* ml_table[slot->patch->ML]) << slot->blk >> 1 */
uint8_t blk; /* block (3 bits) */
uint32_t pg_phase; /* pg phase */ // note this moves twice as fast in slot_render version
#if EMU8950_SLOT_RENDER
//#if !PICO_ON_DEVICE
uint32_t efix_pg_pm_x_fnum3ff; /* efix_pg_phase_multiplier * (fnum & 0x3ff) */
//#endif
#endif
#if EMU8950_SLOT_RENDER
uint8_t *lfo_am_buffer_lsl3;
#else
uint8_t *lfo_am_buffer;
#endif
uint8_t pm_mode;
#if !EMU8950_NO_WAVE_TABLE_MAP
uint16_t *wave_table; /* wave table */
#else
#if EMU8950_SLOT_RENDER
#if !PICO_ON_DEVICE
uint16_t *logsin_table;
int8_t *efix_pm_table;
#endif
#endif
#if !PICO_ON_DEVICE
uint16_t *wav_or_table;
#endif
#endif
};
#ifdef __cplusplus
}
#endif
#endif

789
opl/slot_render_pico.S Normal file
View file

@ -0,0 +1,789 @@
//
// Copyright (C) 2001-2020 Mitsutaka Okazaki
// Copyright (C) 2021-2022 Graham Sanderson
//
.syntax unified
#define ATTACK 0
#define DECAY 1
#define SUSTAIN 2
#define RELEASE 3
//.section .data.slot_render_asm
.section .scratch_y.slot_render_S, "a"
exp_table:
.hword 1024+1018, 1024+1013, 1024+1007, 1024+1002, 1024+996, 1024+991, 1024+986, 1024+980, 1024+975, 1024+969, 1024+964, 1024+959, 1024+953, 1024+948, 1024+942, 1024+937
.hword 1024+932, 1024+927, 1024+921, 1024+916, 1024+911, 1024+906, 1024+900, 1024+895, 1024+890, 1024+885, 1024+880, 1024+874, 1024+869, 1024+864, 1024+859, 1024+854
.hword 1024+849, 1024+844, 1024+839, 1024+834, 1024+829, 1024+824, 1024+819, 1024+814, 1024+809, 1024+804, 1024+799, 1024+794, 1024+789, 1024+784, 1024+779, 1024+774
.hword 1024+770, 1024+765, 1024+760, 1024+755, 1024+750, 1024+745, 1024+741, 1024+736, 1024+731, 1024+726, 1024+722, 1024+717, 1024+712, 1024+708, 1024+703, 1024+698
.hword 1024+693, 1024+689, 1024+684, 1024+680, 1024+675, 1024+670, 1024+666, 1024+661, 1024+657, 1024+652, 1024+648, 1024+643, 1024+639, 1024+634, 1024+630, 1024+625
.hword 1024+621, 1024+616, 1024+612, 1024+607, 1024+603, 1024+599, 1024+594, 1024+590, 1024+585, 1024+581, 1024+577, 1024+572, 1024+568, 1024+564, 1024+560, 1024+555
.hword 1024+551, 1024+547, 1024+542, 1024+538, 1024+534, 1024+530, 1024+526, 1024+521, 1024+517, 1024+513, 1024+509, 1024+505, 1024+501, 1024+496, 1024+492, 1024+488
.hword 1024+484, 1024+480, 1024+476, 1024+472, 1024+468, 1024+464, 1024+460, 1024+456, 1024+452, 1024+448, 1024+444, 1024+440, 1024+436, 1024+432, 1024+428, 1024+424
.hword 1024+420, 1024+416, 1024+412, 1024+409, 1024+405, 1024+401, 1024+397, 1024+393, 1024+389, 1024+385, 1024+382, 1024+378, 1024+374, 1024+370, 1024+367, 1024+363
.hword 1024+359, 1024+355, 1024+352, 1024+348, 1024+344, 1024+340, 1024+337, 1024+333, 1024+329, 1024+326, 1024+322, 1024+318, 1024+315, 1024+311, 1024+308, 1024+304
.hword 1024+300, 1024+297, 1024+293, 1024+290, 1024+286, 1024+283, 1024+279, 1024+276, 1024+272, 1024+268, 1024+265, 1024+262, 1024+258, 1024+255, 1024+251, 1024+248
.hword 1024+244, 1024+241, 1024+237, 1024+234, 1024+231, 1024+227, 1024+224, 1024+220, 1024+217, 1024+214, 1024+210, 1024+207, 1024+204, 1024+200, 1024+197, 1024+194
.hword 1024+190, 1024+187, 1024+184, 1024+181, 1024+177, 1024+174, 1024+171, 1024+168, 1024+164, 1024+161, 1024+158, 1024+155, 1024+152, 1024+148, 1024+145, 1024+142
.hword 1024+139, 1024+136, 1024+133, 1024+130, 1024+126, 1024+123, 1024+120, 1024+117, 1024+114, 1024+111, 1024+108, 1024+105, 1024+102, 1024+99, 1024+96, 1024+93
.hword 1024+90, 1024+87, 1024+84, 1024+81, 1024+78, 1024+75, 1024+72, 1024+69, 1024+66, 1024+63, 1024+60, 1024+57, 1024+54, 1024+51, 1024+48, 1024+45
.hword 1024+42, 1024+40, 1024+37, 1024+34, 1024+31, 1024+28, 1024+25, 1024+22, 1024+20, 1024+17, 1024+14, 1024+11, 1024+8, 1024+6, 1024+3, 1024+0
eg_step_tables: // note the defines have 0x80 to indicate that there are 4 x 8 entries based for each of eg_rate_l
#define ST_NORMAL ((0 * 8) | 0x80)
.byte 0, 1, 0, 1, 0, 1, 0, 1
.byte 0, 1, 0, 1, 1, 1, 0, 1
.byte 0, 1, 1, 1, 0, 1, 1, 1
.byte 0, 1, 1, 1, 1, 1, 1, 1
#define ST_FAST ((4 * 8) | 0x80)
.byte 1, 1, 1, 1, 1, 1, 1, 1
.byte 1, 1, 1, 2, 1, 1, 1, 2
.byte 1, 2, 1, 2, 1, 2, 1, 2
.byte 1, 2, 2, 2, 1, 2, 2, 2
#define ST_FAST2 ((8 * 8) | 0x80)
.byte 2, 2, 2, 2, 2, 2, 2, 2
.byte 2, 2, 2, 4, 2, 2, 2, 4
.byte 2, 4, 2, 4, 2, 4, 2, 4
.byte 2, 4, 4, 4, 2, 4, 4, 4
#define ST_FOUR (12 * 8)
.byte 4, 4, 4, 4, 4, 4, 4, 4
#define ST_ZERO (13 * 8)
.byte 0, 0, 0, 0, 0, 0, 0, 0
attack_rate_def_table:
.byte ST_ZERO, ST_NORMAL, ST_NORMAL, ST_NORMAL
.byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL
.byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL
.byte ST_NORMAL, ST_FAST, ST_FAST2, ST_ZERO
decay_rate_def_table:
.byte ST_ZERO, ST_NORMAL, ST_NORMAL, ST_NORMAL
.byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL
.byte ST_NORMAL, ST_NORMAL, ST_NORMAL, ST_NORMAL
.byte ST_NORMAL, ST_FAST, ST_FAST2, ST_FOUR
.section .text.slot_render_asm
#include "pico/asm_helper.S"
#include "hardware/regs/sio.h"
#define rl_slot r0
#define rl_sample_out r1
#define rl_eg_counter r2
#define rl_eg_shift_mask r3
#define rl_tmp0 r4
#define rl_tmp1 r5
#define rl_tmp2 r6
#define rl_interp r7
#define rh_begin_loop r8
#define rh_end_loop r9
#define rh_sample_out_end r10
#define rh_fn r11
#define rh_exp_table r12
// only used for alg0 and alg1; hmm. don't care
#define rh_buffer_mod_buffer_offset lr
#define INTERP0_ACCUM0 (SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP0_PEEK0 (SIO_INTERP0_PEEK_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP0_PEEK1 (SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP0_BASE2_AKA_LFO_AM_BUFFER_LSL3_OFFSET (SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_PEEK0 (SIO_INTERP1_PEEK_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_PEEK1 (SIO_INTERP1_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_POP0 (SIO_INTERP1_POP_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_ADD_RAW0 (SIO_INTERP1_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_ADD_RAW1 (SIO_INTERP1_ACCUM1_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_BASE0 (SIO_INTERP1_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1_BASE2_AKA_PG_PHASE_MULTIPLIER (SIO_INTERP1_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define SLOTB_EG_STATE 0x00
#define SLOTB_EG_RATE_H 0x01
#define SLOTB_EG_RATE_L 0x02
#define SLOTB_EG_SHIFT 0x03
#define SLOTB_NINE_MINUS_FB 0x04
#define SLOTB_RKS 0x05
#define SLOTH_EG_OUT 0x08
#define SLOTH_TLL 0x0a
#define SLOTW_EG_OUT_TLL_LSL3 0x0c
#define SLOTW_OUTPUT0 0x10
#define SLOTW_OUTPUT1 0x14
#define SLOTW_MOD_BUFFER 0x18
#define SLOTW_BUFFER 0x1c
#define SLOTW_BUFFER_MOD_BUFFER_OFFSET 0x20
#define SLOTW_PATCH 0x24
#define PATCH_EG 0x3
#define PATCH_DR 0x6
#define PATCH_SL 0x7
#define PATCH_RR 0x8
.macro advance_phase_pm0
ldr rl_tmp0, [rl_interp, #INTERP1_BASE0]
str rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0]
ldr rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0]
.endm
.macro advance_phase_pm1
movs rl_tmp1, #1
str rl_tmp1, [rl_interp, #INTERP1_ADD_RAW1]
ldr rl_tmp0, [rl_interp, #INTERP1_PEEK1]
ldrb rl_tmp0, [rl_tmp0] // pm
sxtb rl_tmp0, rl_tmp0
ldr rl_tmp1, [rl_interp, #INTERP1_BASE2_AKA_PG_PHASE_MULTIPLIER]
muls rl_tmp0, rl_tmp1
ldr rl_tmp1, [rl_interp, #INTERP1_BASE0]
adds rl_tmp0, rl_tmp1
str rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0]
ldr rl_tmp0, [rl_interp, #INTERP1_ADD_RAW0]
.endm
.macro calc_sample is_am
// interp0->accum[0] = index << 1;
lsls rl_tmp0, #1 // todo can we avoid this
str rl_tmp0, [rl_interp, #INTERP0_ACCUM0]
// uint16_t h = *(uint16_t *)(interp0->peek[0]) | *(uint16_t *)(interp0->peek[1]);
ldr rl_tmp1, [rl_interp, #INTERP0_PEEK0]
ldr rl_tmp0, [rl_interp, #INTERP0_PEEK1]
ldrh rl_tmp0, [rl_tmp0]
ldrh rl_tmp1, [rl_tmp1]
orrs rl_tmp1, rl_tmp0 // rl_tmp1 = att
// uint16_t att = h + slot->eg_out_tll_lsl3
ldr rl_tmp0, [rl_slot, #SLOTW_EG_OUT_TLL_LSL3]
adds rl_tmp1, rl_tmp0
// if (am) { h += am }
.if \is_am
adds rl_tmp1, rl_tmp2
.endif
// int16_t t = exp_table[att&0xff];
lsls rl_tmp0, rl_tmp1, #24
lsrs rl_tmp0, rl_tmp0, #23 // rl_tmp0 = (att&0xff) *2
add rl_tmp0, rh_exp_table // or load
ldrh rl_tmp0, [rl_tmp0]
// int16_t res = t >> ((att>>8)&127);
// todo this is currently res : ~res but we can inverted our OR table
// return ((att & 0x8000) ? ~res : res);
lsls rl_tmp1, #17
sbcs rl_tmp2, rl_tmp2
mvns rl_tmp2, rl_tmp2 // todo remove this too, but we need to fix up fm
lsrs rl_tmp1, #25
lsrs rl_tmp0, rl_tmp1
// todo: inaudible todo force zeros for now
beq 9f
# note this matches the original ~res << 1 code, however
# we don't have an early cutoff for full attenuation, so get -2 not 0 in some
# cases, however this was true of the original code too; the annoying thing
# is that it isn't trivial to fix up the - otherwise I think unimportant - differences for the sake of diff
eors rl_tmp0, rl_tmp2
lsls rl_tmp0, #1 // todo remove
9:
.endm
.macro calc_sample_am0
calc_sample 0
.endm
.macro calc_sample_am1_lsr1
// must preserve rl_tmp0... set am << 3 in rl_tmp2
ldr rl_tmp1, [rl_interp, #INTERP0_BASE2_AKA_LFO_AM_BUFFER_LSL3_OFFSET]
lsrs rl_tmp2, rl_sample_out, #1
ldrb rl_tmp2, [rl_tmp1, rl_tmp2]
calc_sample 1
.endm
.macro calc_sample_am1_lsr2
// must preserve rl_tmp0... set am << 3 in rl_tmp2
ldr rl_tmp1, [rl_interp, #INTERP0_BASE2_AKA_LFO_AM_BUFFER_LSL3_OFFSET]
lsrs rl_tmp2, rl_sample_out, #2
ldrb rl_tmp2, [rl_tmp1, rl_tmp2]
calc_sample 1
.endm
.macro mod_sample_done
strh rl_tmp0, [rl_sample_out]
adds rl_sample_out, #2
cmp rl_sample_out, rh_sample_out_end
bge 9f
bx rh_begin_loop
9: bx rh_end_loop
.endm
.macro add_fb_fm
ldr rl_tmp1, [rl_slot, #SLOTW_OUTPUT0]
ldr rl_tmp2, [rl_slot, #SLOTW_OUTPUT1]
str rl_tmp1, [rl_slot, #SLOTW_OUTPUT1]
adds rl_tmp1, rl_tmp2
ldrb rl_tmp2, [rl_slot, #SLOTB_NINE_MINUS_FB]
lsrs rl_tmp1, rl_tmp2
adds rl_tmp0, rl_tmp1
.endm
.thumb_func
mod_am0_fb0_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
calc_sample_am0 // tmp0 = pg_out -> tmp0 = sample
mod_sample_done
.thumb_func
mod_am0_fb0_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
calc_sample_am0 // tmp0 = pg_out -> tmp0 = sample
mod_sample_done
.thumb_func
mod_am1_fb0_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
calc_sample_am1_lsr1 // tmp0 = pg_out -> tmp0 = sample
mod_sample_done
.thumb_func
mod_am1_fb0_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
calc_sample_am1_lsr1 // tmp0 = pg_out -> tmp0 = sample
mod_sample_done
.thumb_func
mod_am0_fb1_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm()
calc_sample_am0 // tmp0 = pg_out + fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
mod_sample_done
.thumb_func
mod_am0_fb1_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm()
calc_sample_am0 // tmp0 = pg_out + fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
mod_sample_done
.thumb_func
mod_am1_fb1_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm()
calc_sample_am1_lsr1 // tmp0 = pg_out + fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
mod_sample_done
.thumb_func
mod_am1_fb1_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
add_fb_fm // tmp0 = pg_out -> tmp0 = pg_out + self_fm()
calc_sample_am1_lsr1 // tmp0 = pg_out + fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
mod_sample_done
.macro alg_sample_done
ldr rl_tmp1, [rl_sample_out]
add rl_tmp1, rl_tmp0
str rl_tmp1, [rl_sample_out]
adds rl_sample_out, #4
cmp rl_sample_out, rh_sample_out_end
bge 9f
bx rh_begin_loop
9: bx rh_end_loop
.endm
// ah these two are the same, just called in differnt places
.macro alg0_f_mod
lsrs rl_tmp1, rl_sample_out, #1
add rl_tmp1, rh_buffer_mod_buffer_offset
ldrh rl_tmp2, [rl_tmp1]
add rl_tmp0, rl_tmp2
.endm
.macro alg1_a_mod
lsrs rl_tmp1, rl_sample_out, #1
add rl_tmp1, rh_buffer_mod_buffer_offset
ldrh rl_tmp2, [rl_tmp1]
add rl_tmp0, rl_tmp2
.endm
// alg0 is FM by the mod slot
.thumb_func
alg0_am0_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s]
calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
alg_sample_done
.thumb_func
alg0_am0_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s]
calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
alg_sample_done
.thumb_func
alg0_am1_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s]
calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
alg_sample_done
.thumb_func
alg0_am1_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
alg0_f_mod // tmp0 = pg_out -> tmp0 = pg_out + mod_slot[s]
calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample
str rl_tmp0, [rl_slot, #SLOTW_OUTPUT0]
alg_sample_done
// alg1 is AM by the mod slot
.thumb_func
alg1_am0_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample
alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s]
alg_sample_done
.thumb_func
alg1_am0_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
calc_sample_am0 // tmp0 = pg_out + mod_fm -> tmp0 = sample
alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s]
alg_sample_done
.thumb_func
alg1_am1_pm0_fn:
advance_phase_pm0 // -> tmp0 = pg_out
calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample
alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s]
alg_sample_done
.thumb_func
alg1_am1_pm1_fn:
advance_phase_pm1 // -> tmp0 = pg_out
calc_sample_am1_lsr2 // tmp0 = pg_out + mod_fm -> tmp0 = sample
alg1_a_mod // tmp0 = sample-> tmp- = sample + mod_slot[s]
alg_sample_done
.global slot_render_fn_table
.align 4
slot_render_fn_table:
.word mod_am0_fb0_pm0_fn
.word mod_am0_fb0_pm1_fn
.word mod_am1_fb0_pm0_fn
.word mod_am1_fb0_pm1_fn
.word mod_am0_fb1_pm0_fn
.word mod_am0_fb1_pm1_fn
.word mod_am1_fb1_pm0_fn
.word mod_am1_fb1_pm1_fn
.word alg0_am0_pm0_fn
.word alg0_am0_pm1_fn
.word alg0_am1_pm0_fn
.word alg0_am1_pm1_fn
.word alg1_am0_pm0_fn
.word alg1_am0_pm1_fn
.word alg1_am1_pm0_fn
.word alg1_am1_pm1_fn
.macro set_eg_shift_mask
ldrb rl_eg_shift_mask, [rl_slot, #SLOTB_EG_RATE_H]
cmp rl_eg_shift_mask, #0
beq 9f
ldrb rl_tmp0, [rl_slot, #SLOTB_EG_SHIFT]
movs rl_eg_shift_mask, #1
lsls rl_eg_shift_mask, rl_tmp0
9:
subs rl_eg_shift_mask, #1
.endm
// rl_tmp0 = eg_out
.macro update_eg_out_tll_lsl3
//slot->eg_out_tll_lsl3 = std::min(EG_MAX/*EG_MUTE*/, slot->eg_out + slot->tll) << 3; // note EG_MAX not EG_MUTE to avoid overflow check later
ldrh rl_tmp1, [rl_slot, #SLOTH_TLL]
adds rl_tmp0, rl_tmp1
movs rl_tmp2, #0x1f
lsls rl_tmp2, #4
cmp rl_tmp0, rl_tmp2
ble 9f
movs rl_tmp0, rl_tmp2
9:
lsls rl_tmp0, #3
str rl_tmp0, [rl_slot, #SLOTW_EG_OUT_TLL_LSL3]
.endm
#define FRAMEW_EG_STEP_TABLE 0
#define FRAMEW_SAMPLE_OUT_END_BACK 4
#define FRAME_SIZE 8
.macro set_step_table table_def
// get_attack_step_table
ldr rl_tmp0, =eg_step_tables
ldr rl_tmp1, =\table_def
ldrb rl_tmp2, [rl_slot, SLOTB_EG_RATE_H]
ldrb rl_tmp2, [rl_tmp1, rl_tmp2]
lsls rl_tmp1, rl_tmp2, #25
bcc 1f
lsrs rl_tmp2, rl_tmp1, #25
ldrb rl_tmp1, [rl_slot, SLOTB_EG_RATE_L]
lsls rl_tmp1, #3
adds rl_tmp2, rl_tmp1
1:
adds rl_tmp0, rl_tmp2
str rl_tmp0, [sp, FRAMEW_EG_STEP_TABLE]
.endm
.macro update_eg_vars_for_non_zero_rate
ldrb rl_tmp1, [rl_slot, #SLOTB_RKS]
// slot->eg_rate_l = slot->rks & 3;
lsls rl_tmp2, rl_tmp1, #30
lsrs rl_tmp2, rl_tmp2, #30
strb rl_tmp2, [rl_slot, #SLOTB_EG_RATE_L]
// slot->eg_rate_h = std::min(15, p_rate + (slot->rks >> 2));
lsrs rl_tmp1, #2
adds rl_tmp0, rl_tmp1
cmp rl_tmp0, #15
ble 9f
movs rl_tmp0, #15
9:
strb rl_tmp0, [rl_slot, #SLOTB_EG_RATE_H]
// slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0;
// note eg_shift was already zeroed above
movs rl_tmp1, #12
subs rl_tmp1, rl_tmp0
bcc 1f
strb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT]
.endm
// (slot, nsamples, eg_counter, fn);
.global test_slot_asm
.thumb_func
test_slot_asm:
#if 0
#define rl_slot r0 i
#define rl_sample_out r1 x
#define rl_eg_counter r2 i
#define rl_eg_shift_mask r3 x
#define rl_tmp0 r4 -
#define rl_tmp1 r5 -
#define rl_tmp2 r6 -
#define rl_interp r7
#endif
// rh_begin_loop r8 x
// rh_end_loop r9 x
// rh_sample_out_end r10 x
// rh_fn r11 x
// rh_exp_table r12 x
// rh_mod_buffer_offset lr -
push {r4-r7, lr}
mov r4, r8
mov r5, r9
mov r6, r10
mov r7, r11
push {r4-r7}
mov r4, r12
push {r3, r4} // we want to save r3 as well
sub sp, #FRAME_SIZE
// note r1 == rl_sample_out
lsrs r5, r3, #3
bne 1f
// is a mod fn
lsls r4, r1, #1
ldr rl_sample_out, [rl_slot, #SLOTW_MOD_BUFFER]
add r4, rl_sample_out
b 2f
1:
// is an arg fn
lsls r4, r1, #2
ldr rl_sample_out, [rl_slot, #SLOTW_BUFFER]
add r4, rl_sample_out
2:
mov rh_sample_out_end, r4
str r4, [sp, #FRAMEW_SAMPLE_OUT_END_BACK]
ldr r4, =slot_render_fn_table
lsls r3, #2
ldr r4, [r4, r3]
mov rh_fn, r4
ldr rl_tmp0, =exp_table
mov rh_exp_table, rl_tmp0
ldr rl_tmp0, [rl_slot, #SLOTW_BUFFER_MOD_BUFFER_OFFSET]
mov rh_buffer_mod_buffer_offset, rl_tmp0
ldr rl_interp, =0xd0000080
ldrb rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
cmp rl_tmp0, #ATTACK
bne check_decay
attack:
ldr rl_tmp0, =check_decay
mov rh_end_loop, rl_tmp0
set_eg_shift_mask
set_step_table attack_rate_def_table
// if (slot->eg_out == 0) {
ldrh rl_tmp1, [rl_slot, #SLOTH_EG_OUT]
cmp rl_tmp1, #0
bne attack_loop_enter
// early enter decay state
adds rl_eg_counter, #1
b enter_decay_state
attack_loop_enter:
ldr rl_tmp0, =attack_loop
mov rh_begin_loop, rl_tmp0
.thumb_func
attack_loop:
adds rl_eg_counter, #1
tst rl_eg_counter, rl_eg_shift_mask
beq 1f
bx rh_fn
1:
ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
// cmp rl_tmp0, #0
// bhi 1f
// bx rh_fn
//1:
// uint8_t step = eg_step_table[(eg_counter >> slot->eg_shift) & 7];
ldrb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT]
movs rl_tmp2, rl_eg_counter
lsrs rl_tmp2, rl_tmp1
lsls rl_tmp2, #29
lsrs rl_tmp2, #29
ldr rl_tmp1, [sp, #FRAMEW_EG_STEP_TABLE]
ldrb rl_tmp1, [rl_tmp1, rl_tmp2]
// slot->eg_out += (~slot->eg_out * step) >> 3;
mvns rl_tmp2, rl_tmp0
muls rl_tmp2, rl_tmp1
asrs rl_tmp2, #3
adds rl_tmp0, rl_tmp2
strh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
update_eg_out_tll_lsl3
ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
cmp rl_tmp0, #0
beq enter_decay_state
bx rh_fn
enter_decay_state:
// int p_rate = slot->patch->DR;
ldr rl_tmp0, [rl_slot, #SLOTW_PATCH]
ldrb rl_tmp0, [rl_tmp0, #PATCH_DR]
cmp rl_tmp0, #0
// set all eg_state, shift, rate_h, rate_l all to zero (if rl_tmp0 is zero)
str rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
beq 1f
update_eg_vars_for_non_zero_rate
1:
movs rl_tmp0, #DECAY
strb rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
// do last sample
mov rh_sample_out_end, rl_sample_out
ldr rl_tmp0, =check_decay
mov rh_begin_loop, rl_tmp0
bx rh_fn
.thumb_func
check_decay:
ldr rl_tmp0, [sp, #FRAMEW_SAMPLE_OUT_END_BACK]
cmp rl_sample_out, rl_tmp0
beq check_sustain_release // done is too far away
mov rh_sample_out_end, rl_tmp0
ldrb rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
cmp rl_tmp0, #DECAY
bne check_sustain_release
decay:
ldr rl_tmp0, =check_sustain_release
mov rh_end_loop, rl_tmp0
set_eg_shift_mask
set_step_table decay_rate_def_table
adds rl_tmp0, rl_eg_counter, #1
tst rl_tmp0, rl_eg_shift_mask
beq decay_loop_enter
// check ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL))
ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
ldr rl_tmp1, [rl_slot, #SLOTW_PATCH]
ldrb rl_tmp1, [rl_tmp1, #PATCH_SL]
cmp rl_tmp1, #15
beq decay_loop_enter
lsrs rl_tmp2, rl_tmp0, #4
cmp rl_tmp2, rl_tmp1
bne decay_loop_enter
// early enter sustain state
mov rl_eg_counter, rl_tmp0
b enter_sustain_state
decay_loop_enter:
ldr rl_tmp0, =decay_loop
mov rh_begin_loop, rl_tmp0
.thumb_func
decay_loop:
adds rl_eg_counter, #1
tst rl_eg_counter, rl_eg_shift_mask
beq 1f
bx rh_fn
1:
//slot->eg_out = static_cast<int16_t>(std::min(EG_MUTE, slot->eg_out + (int) eg_step_table[(eg_counter >> slot->eg_shift) & 7]));
ldr rl_tmp0, [sp, #FRAMEW_EG_STEP_TABLE]
ldrb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT]
movs rl_tmp2, rl_eg_counter
lsrs rl_tmp2, rl_tmp1
lsls rl_tmp2, #29
lsrs rl_tmp2, #29
ldrb rl_tmp1, [rl_tmp0, rl_tmp2]
ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
adds rl_tmp0, rl_tmp1
// todo do we actually need this min check
lsrs rl_tmp1, rl_tmp0, #9
beq 1f
ldr rl_tmp0, =0x1ff
1:
strh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
// uses rl_tmp0
update_eg_out_tll_lsl3
ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
ldr rl_tmp1, [rl_slot, #SLOTW_PATCH]
ldrb rl_tmp1, [rl_tmp1, #PATCH_SL]
cmp rl_tmp1, #15
beq 1f
lsrs rl_tmp2, rl_tmp0, #4
cmp rl_tmp2, rl_tmp1
beq enter_sustain_state
1:
// check for EG_MUTE (0x1ff)
adds rl_tmp1, rl_tmp0, #1
lsrs rl_tmp1, #9
bne 1f
bx rh_fn
1:
// we decayed to zero, but still need to run loop contents once more
mov rh_sample_out_end, rl_sample_out
bx rh_fn
enter_sustain_state:
// int p_rate = slot->patch->EG ? 0 : slot->patch->RR
ldr rl_tmp2, [rl_slot, #SLOTW_PATCH]
ldrb rl_tmp1, [rl_tmp2, #PATCH_EG]
movs rl_tmp0, #0
cmp rl_tmp1, #0
bne 1f
ldrb rl_tmp0, [rl_tmp2, #PATCH_RR]
1:
// set all eg_state, shift, rate_h, rate_l all to zero (if rl_tmp0 is zero)
str rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
cmp rl_tmp0, #0
beq 1f
update_eg_vars_for_non_zero_rate
1:
movs rl_tmp0, #SUSTAIN
strb rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
// do last sample
mov rh_sample_out_end, rl_sample_out
ldr rl_tmp0, =check_sustain_release
mov rh_begin_loop, rl_tmp0
bx rh_fn
.thumb_func
check_sustain_release:
ldr rl_tmp0, [sp, #FRAMEW_SAMPLE_OUT_END_BACK]
cmp rl_sample_out, rl_tmp0
beq done
mov rh_sample_out_end, rl_tmp0
ldrb rl_tmp0, [rl_slot, #SLOTB_EG_STATE]
cmp rl_tmp0, #SUSTAIN
blt done
ldr rl_tmp0, =done
mov rh_end_loop, rl_tmp0
set_eg_shift_mask
set_step_table decay_rate_def_table
sustain_release_loop_enter:
ldr rl_tmp0, =sustain_release_loop
mov rh_begin_loop, rl_tmp0
.thumb_func
sustain_release_loop:
adds rl_eg_counter, #1
tst rl_eg_counter, rl_eg_shift_mask
beq 1f
bx rh_fn
1:
//slot->eg_out = static_cast<int16_t>(std::min(EG_MUTE, slot->eg_out + (int) eg_step_table[(eg_counter >> slot->eg_shift) & 7]));
ldr rl_tmp0, [sp, #FRAMEW_EG_STEP_TABLE]
ldrb rl_tmp1, [rl_slot, #SLOTB_EG_SHIFT]
movs rl_tmp2, rl_eg_counter
lsrs rl_tmp2, rl_tmp1
lsls rl_tmp2, #29
lsrs rl_tmp2, #29
ldrb rl_tmp1, [rl_tmp0, rl_tmp2]
ldrh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
adds rl_tmp0, rl_tmp1
strh rl_tmp0, [rl_slot, #SLOTH_EG_OUT]
adds rl_tmp2, rl_tmp0, #1
lsrs rl_tmp1, rl_tmp2, #9
bne 1f
update_eg_out_tll_lsl3
bx rh_fn
1:
// we decayed to zero, but still need to run loop contents
mov rh_sample_out_end, rl_sample_out
bx rh_fn
.thumb_func
done:
add sp, #FRAME_SIZE
pop {r2-r7}
mov r12, r3
mov r8, r4
mov r9, r5
mov r10, r6
mov r11, r7
// figure out how many samples we did
lsrs r2, #3
bne 1f
// is a mod fn
ldr r0, [rl_slot, #SLOTW_MOD_BUFFER]
subs r1, r0
lsrs r0, r1, #1
pop {r4-r7, pc}
1:
// is an arg fn
ldr r0, [rl_slot, #SLOTW_BUFFER]
subs r1, r1
lsrs r1, r0, #2
pop {r4-r7, pc}
#if 0
movs rl_tmp0, #1
lsls rl_tmp0, #12
subs rl_tmp0, #2
lsls rl_tmp1, rl_eg_counter, #1
cmp rl_tmp0, rl_tmp1
bne 1f
bkpt #0
1:
#endif

View file

@ -1,11 +1,15 @@
add_library(pcsound STATIC
pcsound.c pcsound.h
pcsound_bsd.c
pcsound_sdl.c
pcsound_linux.c
pcsound_win32.c
pcsound_internal.h)
target_include_directories(pcsound
INTERFACE "."
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(pcsound SDL2::mixer)
if (PICO_SDK)
add_library(pcsound INTERFACE)
else()
add_library(pcsound STATIC
pcsound.c pcsound.h
pcsound_bsd.c
pcsound_sdl.c
pcsound_linux.c
pcsound_win32.c
pcsound_internal.h)
target_include_directories(pcsound
INTERFACE "."
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(pcsound SDL2::mixer)
endif()

62
pico_extras_import.cmake Normal file
View file

@ -0,0 +1,62 @@
# This is a copy of <PICO_EXTRAS_PATH>/external/pico_extras_import.cmake
# This can be dropped into an external project to help locate pico-extras
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH))
set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH})
message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')")
endif ()
if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT))
set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT})
message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH))
set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH})
message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')")
endif ()
if (NOT PICO_EXTRAS_PATH)
if (PICO_EXTRAS_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_EXTRAS_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
PICO_EXTRAS
GIT_REPOSITORY https://github.com/raspberrypi/pico-extras
GIT_TAG master
)
if (NOT PICO_EXTRAS)
message("Downloading PICO EXTRAS")
FetchContent_Populate(PICO_EXTRAS)
set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras")
set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras)
message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}")
else()
message(FATAL_ERROR
"PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git."
)
endif()
endif ()
endif ()
set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS")
set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable")
set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS")
get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_EXTRAS_PATH})
message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found")
endif ()
set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE)
add_subdirectory(${PICO_EXTRAS_PATH} pico_extras)

64
pico_sdk_import.cmake Normal file
View file

@ -0,0 +1,64 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# todo document
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY git@asic-git.pitowers.org:projectmu/pico_sdk.git
GIT_TAG master
)
if (NOT pico_sdk)
message("Downloading PICO SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

1
src/.gitignore vendored
View file

@ -17,6 +17,7 @@ chocolate-setup
*.desktop
*.txt
!CMakeLists.txt
!license.txt
*.appdata.xml
tags
TAGS

View file

@ -1,39 +1,78 @@
foreach(SUBDIR doom heretic hexen strife setup)
if (PICO_SDK)
set(BINARIES doom)
else()
# comment out heretic/hexen/strife for now, as whilst they may build, how well they work is unclear
# set(BINARIES doom heretic hexen strife setup)
set(BINARIES doom setup)
endif()
add_subdirectory(adpcm-xq)
foreach(SUBDIR ${BINARIES})
add_subdirectory("${SUBDIR}")
endforeach()
# Common source files used by absolutely everything:
cmake_policy(SET CMP0076 NEW)
set(COMMON_SOURCE_FILES
if (NOT PICO_SDK)
set(I_PLATFORM sdl)
else()
set(I_PLATFORM pico)
endif()
add_library(common INTERFACE)
target_sources(common INTERFACE
i_main.c
i_system.c i_system.h
i_system.h
m_argv.c m_argv.h
m_misc.c m_misc.h)
# Dedicated server (chocolate-server):
target_compile_definitions(common INTERFACE
#HACK_FINALE_E1M1=1
#HACK_FINALE_SHAREWARE=1
# DEBUG_MOBJ=1
)
set(DEDSERV_FILES
d_dedicated.c
d_mode.c d_mode.h
i_timer.c i_timer.h
net_common.c net_common.h
net_dedicated.c net_dedicated.h
net_io.c net_io.h
net_packet.c net_packet.h
net_sdl.c net_sdl.h
net_query.c net_query.h
net_server.c net_server.h
net_structrw.c net_structrw.h
z_native.c z_zone.h)
if (NOT PICO_SDK)
add_library(common_sdl INTERFACE)
target_sources(common_sdl INTERFACE
i_system.c
)
else()
add_subdirectory(pico)
endif()
add_executable("${PROGRAM_PREFIX}server" WIN32 ${COMMON_SOURCE_FILES} ${DEDSERV_FILES})
target_include_directories("${PROGRAM_PREFIX}server"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}server" SDL2::SDL2main SDL2::net)
target_link_libraries(common INTERFACE common_${I_PLATFORM})
# Source files used by the game binaries (chocolate-doom, etc.)
# todo chocolate-server also doesn't currently build
if (NOT PICO_SDK)
# Dedicated server (chocolate-server):
set(GAME_SOURCE_FILES
set(DEDSERV_FILES
d_dedicated.c
d_mode.c d_mode.h
i_timer.c i_timer.h
net_common.c net_common.h
net_dedicated.c net_dedicated.h
net_io.c net_io.h
net_packet.c net_packet.h
net_sdl.c net_sdl.h
net_query.c net_query.h
net_server.c net_server.h
net_structrw.c net_structrw.h
z_native.c z_zone.h)
add_executable("${PROGRAM_PREFIX}server" WIN32 ${COMMON_SOURCE_FILES} ${DEDSERV_FILES})
target_include_directories("${PROGRAM_PREFIX}server"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}server" common SDL2::SDL2main SDL2::net)
endif()
# Source files used by the game BINARIES (chocolate-doom, etc.)
add_library(game INTERFACE)
target_sources(game INTERFACE
aes_prng.c aes_prng.h
d_event.c d_event.h
doomkeys.h
@ -44,22 +83,17 @@ set(GAME_SOURCE_FILES
d_ticcmd.h
deh_str.c deh_str.h
gusconf.c gusconf.h
i_cdmus.c i_cdmus.h
i_endoom.c i_endoom.h
i_glob.c i_glob.h
i_input.c i_input.h
i_joystick.c i_joystick.h
i_swap.h
i_midipipe.c i_midipipe.h
i_musicpack.c
i_oplmusic.c
i_pcsound.c
i_sdlmusic.c
i_sdlsound.c
i_sound.c i_sound.h
i_timer.c i_timer.h
i_video.c i_video.h
i_videohr.c i_videohr.h
i_cdmus.h
i_endoom.h
i_glob.h
i_input.h
i_joystick.h
i_swap.h
i_midipipe.h
i_sound.h
i_timer.h
i_video.h
i_videohr.h
midifile.c midifile.h
mus2mid.c mus2mid.h
m_bbox.c m_bbox.h
@ -67,18 +101,8 @@ set(GAME_SOURCE_FILES
m_config.c m_config.h
m_controls.c m_controls.h
m_fixed.c m_fixed.h
net_client.c net_client.h
net_common.c net_common.h
net_dedicated.c net_dedicated.h
net_defs.h
net_gui.c net_gui.h
net_io.c net_io.h
net_loop.c net_loop.h
net_packet.c net_packet.h
net_query.c net_query.h
net_sdl.c net_sdl.h
net_server.c net_server.h
net_structrw.c net_structrw.h
sha1.c sha1.h
memio.c memio.h
tables.c tables.h
@ -89,134 +113,553 @@ set(GAME_SOURCE_FILES
w_main.c w_main.h
w_wad.c w_wad.h
w_file.c w_file.h
w_file_stdc.c
w_file_posix.c
w_file_win32.c
w_merge.c w_merge.h
z_zone.c z_zone.h)
w_file_posix.c
w_file_memory.c
w_merge.c w_merge.h
z_zone.c z_zone.h
i_oplmusic.c
i_sound.c
)
if (NOT PICO_SDK)
add_library(game_sdl INTERFACE)
target_sources(game_sdl INTERFACE
# todo maybe a separate network library
net_common.c net_common.h
net_dedicated.c net_dedicated.h
net_defs.h
net_gui.c net_gui.h
net_io.c net_io.h
net_loop.c net_loop.h
net_packet.c net_packet.h
net_query.c net_query.h
net_sdl.c net_sdl.h
net_server.c net_server.h
net_structrw.c net_structrw.h
w_file_stdc.c
w_file_win32.c
i_cdmus.c
i_endoom.c
i_glob.c
i_input.c
i_joystick.c
i_midipipe.c
i_musicpack.c
i_pcsound.c
i_sdlmusic.c
i_sdlsound.c
i_timer.c
i_video.c
i_videohr.c
)
else()
add_library(game_pico INTERFACE)
endif()
target_link_libraries(game INTERFACE game_${I_PLATFORM})
if(MSVC)
list(APPEND GAME_SOURCE_FILES
target_sources(game INTERFACE
"../win32/win_opendir.c" "../win32/win_opendir.h")
endif()
set(DEHACKED_SOURCE_FILES
target_link_libraries(game INTERFACE common opl)
add_library(game_deh INTERFACE)
target_sources(game_deh INTERFACE
deh_defs.h
deh_io.c deh_io.h
deh_main.c deh_main.h
deh_mapping.c deh_mapping.h
deh_text.c)
# Some games support dehacked patches, some don't:
target_link_libraries(game_deh INTERFACE game)
set(SOURCE_FILES ${COMMON_SOURCE_FILES} ${GAME_SOURCE_FILES})
set(SOURCE_FILES_WITH_DEH ${SOURCE_FILES} ${DEHACKED_SOURCE_FILES})
if (NOT PICO_SDK)
set(EXTRA_LIBS SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net textscreen pcsound)
if(SAMPLERATE_FOUND)
list(APPEND EXTRA_LIBS samplerate::samplerate)
endif()
if(PNG_FOUND)
list(APPEND EXTRA_LIBS PNG::PNG)
endif()
set(EXTRA_LIBS SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net textscreen pcsound opl)
if(SAMPLERATE_FOUND)
list(APPEND EXTRA_LIBS samplerate::samplerate)
endif()
if(PNG_FOUND)
list(APPEND EXTRA_LIBS PNG::PNG)
if ("doom" IN_LIST BINARIES)
if(WIN32)
add_executable("${PROGRAM_PREFIX}doom" WIN32 "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}doom")
endif()
target_link_libraries("${PROGRAM_PREFIX}doom" PRIVATE game_deh)
target_include_directories("${PROGRAM_PREFIX}doom"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}doom" PRIVATE doom ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}doom" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
endif()
if ("heretic" IN_LIST BINARIES)
if(WIN32)
add_executable("${PROGRAM_PREFIX}heretic" WIN32 "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}heretic" )
endif()
target_link_libraries("${PROGRAM_PREFIX}heretic" PRIVATE game_deh)
target_include_directories("${PROGRAM_PREFIX}heretic"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}heretic" PRIVATE heretic ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}heretic" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
endif()
if ("hexen" IN_LIST BINARIES)
if(WIN32)
add_executable("${PROGRAM_PREFIX}hexen" WIN32 "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}hexen")
endif()
target_link_libraries("${PROGRAM_PREFIX}hexen" PRIVATE game)
target_include_directories("${PROGRAM_PREFIX}hexen"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}hexen" PRIVATE hexen ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}hexen" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
endif()
if ("strife" IN_LIST BINARIES)
if(WIN32)
add_executable("${PROGRAM_PREFIX}strife" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}strife" ${SOURCE_FILES_WITH_DEH})
endif()
target_link_libraries("${PROGRAM_PREFIX}strife" PRIVATE game_deh)
target_include_directories("${PROGRAM_PREFIX}strife"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}strife" PRIVATE strife ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}strife" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
endif()
# Source files needed for chocolate-setup:
if ("setup" IN_LIST BINARIES)
set(SETUP_FILES
deh_str.c deh_str.h
d_mode.c d_mode.h
d_iwad.c d_iwad.h
i_timer.c i_timer.h
m_config.c m_config.h
m_controls.c m_controls.h
net_io.c net_io.h
net_packet.c net_packet.h
net_sdl.c net_sdl.h
net_query.c net_query.h
net_structrw.c net_structrw.h
z_native.c z_zone.h)
if(WIN32)
add_executable("${PROGRAM_PREFIX}setup" WIN32 ${SETUP_FILES} "${CMAKE_CURRENT_BINARY_DIR}/setup-res.rc")
else()
add_executable("${PROGRAM_PREFIX}setup" ${SETUP_FILES} )
endif()
target_link_libraries("${PROGRAM_PREFIX}setup" PRIVATE setup common)
target_include_directories("${PROGRAM_PREFIX}setup"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
if (NOT PICO_SDK)
target_link_libraries("${PROGRAM_PREFIX}setup" PRIVATE SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net)
endif()
target_link_libraries("${PROGRAM_PREFIX}setup" PRIVATE textscreen)
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}setup" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
endif()
add_executable(midiread midifile.c z_native.c i_system.c m_argv.c m_misc.c)
target_compile_definitions(midiread PRIVATE "-DTEST")
target_include_directories(midiread PRIVATE "." "${CMAKE_CURRENT_BINARY_DIR}/../")
if (NOT PICO_SDK)
target_link_libraries(midiread SDL2::SDL2main SDL2::SDL2)
endif()
add_executable(mus2mid mus2mid.c memio.c z_native.c i_system.c m_argv.c m_misc.c)
target_compile_definitions(mus2mid PRIVATE "-DSTANDALONE")
target_include_directories(mus2mid PRIVATE "." "${CMAKE_CURRENT_BINARY_DIR}/../")
if (NOT PICO_SDK)
target_link_libraries(mus2mid SDL2::SDL2main SDL2::SDL2)
endif()
endif() # NOT PICO_SDK
add_library(small_doom_common INTERFACE)
target_link_libraries(small_doom_common INTERFACE adpcm-lib) # handy to have about
target_compile_definitions(small_doom_common INTERFACE
SHRINK_MOBJ=1 # may break saved games
DOOM_ONLY=1 # don't support hexxen etc.
DOOM_SMALL=1
DOOM_CONST=1 # thread const thru lots of places
SOUND_LOW_PASS=1
NUM_SOUND_CHANNELS=8 # sounds ok (actually that is how many are used by default)
# functionality
NO_USE_CHECKSUM=1
NO_USE_RELOAD=1
USE_SINGLE_IWAD=1
NO_USE_WIPE=1 #memory if nothing else - we can fix this (you can do it)
NO_USE_JOYSTICK=1
NO_USE_DEH=1
NO_USE_MUSIC_PACKS=1
USE_FLAT_MAX_256=1
USE_MEMMAP_ONLY=1
USE_LIGHTMAP_INDEXES=1 # saves about 9K
#FRACBITS=15 # ha ha doesn't seem to be passed thru everywhere!
#NO_USE_ENDDOOM=1
#NO_DRAW_VISPLANES=1
USE_ERASE_FRAME=1 #
NO_DRAW_MID=1
NO_DRAW_TOP=1
NO_DRAW_BOTTOM=1
NO_DRAW_MASKED=1
NO_DRAW_SKY=1
#NO_DRAW_SPANS=1
NO_DRAW_SPRITES=1
NO_DRAW_PSPRITES=1
# NO_VISPLANES=1
NO_VISPLANE_GUTS=1
NO_VISPLANE_CACHES=1 # todo these might be fine as they can be temporary
NO_DRAWSEGS=1
NO_VISSPRITES=1
NO_MASKED_FLOOR_CLIP=1 # not needed with PD_ rendering and with floor clip it we have occasional visual glitches with sprites split across visplanes
PD_DRAW_COLUMNS=1
PD_DRAW_MARKERS=1
PD_DRAW_PLANES=1
PD_SCALE_SORT=1
PD_CLIP_WALLS=1
PD_QUANTIZE=1
PD_SANITY=1
#MU_STATS=1
PD_COLUMNS=1
PICO_DOOM=1
NO_USE_DS_COLORMAP=1
NO_USE_DC_COLORMAP=1
#PRINT_COLORMAPS=1
#PRINT_PALETTE=1
USE_READONLY_MMAP=1
# -----------------------------------------------------------------
# MUSIC RELATED
# -----------------------------------------------------------------
NO_USE_TIMIDITY=1
NO_USE_GUS=1
NO_USE_LIBSAMPLERATE=1
# slightly slower but only uses 1K of sin tables vs 9K
EMU8950_NO_WAVE_TABLE_MAP=1
EMU8950_NO_TLL=1 # don't use lookup table for total level
EMU8950_NO_FLOAT=1 # double check there is no float
EMU8950_NO_TIMER=1 # disable timer which isn't used
EMU8950_NO_TEST_FLAG=1 # disable test flags (which aren't used)
EMU8950_SIMPLER_NOISE=1 # only generate noise bit when needed
EMU8950_SHORT_NOISE_UPDATE_CHECK=1 # only update short noise if it is used
# actually this doesn't make things faster
#EMU8950_LINEAR_NEG_NOT_NOT=1 # negative values rounded towrds -infinity not 0 without this; does it matter?
EMU8950_LINEAR_SKIP=1 # skip silent slots
EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION # early out envelope when DECAY/SUSTAIN/RELEASE envelope reaches mute
EMU8950_NO_PERCUSSION_MODE=1 # rhythm only mode (doom doesn't use percussion; whhhaaaah!?)
EMU8950_LINEAR=1 # reorganize to do linear runs of channels
# things we really don't care about but need for diff-ing with non linear version
# BEWARE - need to turn this off for comparison with on device version which doesn't do nitpicks (though could i guess)
EMU8950_ASM=1
# EMU8950_NIT_PICKS=1
# DUMPO=1
# -----------------------------------------------------------------
# FLASH SIZE
# -----------------------------------------------------------------
NO_USE_STATE_MISC #doesn't appear to be used anyway - perhaps in doom only
# -----------------------------------------------------------------
# RAM SIZE
# -----------------------------------------------------------------
USE_RAW_MAPNODE=1
USE_RAW_MAPVERTEX=1
USE_RAW_MAPSEG=1
USE_RAW_MAPLINEDEF=1
# USE_RAW_MAPSUBSECTOR=1 # obsolete... now WHD
# USE_RAW_MAPSIDEDEF=1 obsolete
USE_RAW_MAPTHING=1
USE_INDEX_LINEBUFFER=1
NO_USE_ZLIGHT=1
NO_Z_ZONE_ID=1 # seems unused
Z_MALOOC_EXTRA_DATA=1
USE_THINKER_POOL=1
NO_INTERCEPTS_OVERRUN=1
# INCLUDE_SOUND_C_IN_S_SOUND=1 # avoid issues with non static const array
# -----------------------------------------------------------------
# IMMUTABLE
# -----------------------------------------------------------------
TEMP_IMMUTABLE_DISABLED=1
USE_CONST_SFX=1
USE_CONST_MUSIC=1
NO_DEMO_RECORDING=1
PICO_NO_TIMING_DEMO=1
NO_USE_EXIT=1 # not sure whether we have an exit... if so what does it need to do?
PICO_DEBUG_PIN_BASE=18
PICO_DEBUG_PIN_COUNT=2
)
target_compile_options(small_doom_common INTERFACE
-Wall
-Wno-unused-function
-Wno-unused-but-set-variable
-Wno-unused-variable
)
if (TARGET chocolate-doom)
target_compile_definitions(chocolate-doom PRIVATE
USE_FLAT_MAX_256=1
)
endif()
if(WIN32)
add_executable("${PROGRAM_PREFIX}doom" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
function(add_doom_tiny SUFFIX RENDER_LIB)
add_executable(doom_tiny${SUFFIX})
target_link_libraries(doom_tiny${SUFFIX} PRIVATE game)
target_sources(doom_tiny${SUFFIX} PRIVATE
tiny_huff.c
musx_decoder.c
image_decoder.c
)
target_include_directories(doom_tiny${SUFFIX} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(doom_tiny${SUFFIX} PRIVATE doom ${EXTRA_LIBS})
target_link_libraries(doom_tiny${SUFFIX} PRIVATE small_doom_common)
if (PICO_SDK)
pico_set_float_implementation(doom_tiny${SUFFIX} none)
pico_set_double_implementation(doom_tiny${SUFFIX} none)
pico_add_extra_outputs(doom_tiny${SUFFIX})
endif()
target_compile_options(doom_tiny${SUFFIX} PRIVATE
-Wno-format-truncation)
target_compile_definitions(doom_tiny${SUFFIX} PRIVATE
DOOM_TINY=1
EMU8950_SLOT_RENDER=1
SHRINK_MOBJ=1 # may break saved games
NO_RDRAW=1
#NO_DRAW_MENU=1
# SDK minimilization
PICO_TIME_DEFAULT_ALARM_POOL_DISABLED=1
PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS=1
PICO_DISABLE_SHARED_IRQ_HANDLERS=1
PICO_USE_OPTIMISTIC_SBRK=1
# MUSIC
USE_EMU8950_OPL=1
USE_DIRECT_MIDI_LUMP=1
NO_USE_NET=1 # standard networking
USE_PICO_NET=1
# for vgaboard
PICO_DEFAULT_I2C=1
PICO_DEFAULT_I2C_SDA_PIN=18
PICO_DEFAULT_I2C_SCL_PIN=19
#NO_USE_LOAD=1
#NO_USE_SAVE=1
$<$<BOOL:${PICO_ON_DEVICE}>:NO_FILE_ACCESS=1>
SAVE_COMPRESSED=1
LOAD_COMPRESSED=1
NO_USE_ARGS=1
NO_USE_SAVE_CONFIG=1
NO_USE_FLOAT=1
USE_VANILLA_KEYBOARD_MAPPING_ONLY=1
#FORCE_NODRAW=1
NO_USE_LOADING_DISK=1
# -- these aren't used any more, we adapt based on DOOM_TINY
#NO_USE_ST=1 # for now skip all the status crap
#NO_USE_DEFAULT_ST_LIB=1
#NO_PAGE_DRAWER=1 # splash screens
#NO_HU_DRAWER=1
USE_WHD=1
NO_Z_MALLOC_USER_PTR=1 # onyl needed for freeing textures i think
FIXED_SCREENWIDTH=1
FLOOR_CEILING_CLIP_8BIT=1
# NO_USE_WI=1
USE_MUSX=1
MUSX_COMPRESSED=1
NO_SCREENSHOT=1
NO_USE_BOUND_CONFIG=1
USE_FPS=1
# PICO_DOOM_INFO=1
)
target_compile_definitions(doom_tiny${SUFFIX} PRIVATE
#FORCE_NODRAW=1
USE_MEMORY_WAD=1
PICO_DEBUG_MALLOC=1
EMU8950_NO_RATECONV=1
PICO_CORE1_STACK_SIZE=0x4f8
NO_IERROR=1
)
if (PICO_ON_DEVICE)
target_compile_options(doom_tiny${SUFFIX} PRIVATE -fno-common -fdata-sections -Wl,--sort-section=alignment)
target_compile_definitions(doom_tiny${SUFFIX} PRIVATE
NO_ZONE_DEBUG=1
)
#target_link_libraries(doom_tiny${SUFFIX} PRIVATE hardware_flash)
endif()
if (FORCE_DEBUG)
target_compile_options(doom_tiny${SUFFIX} PRIVATE -g)
endif()
target_link_libraries(doom_tiny${SUFFIX} PRIVATE ${RENDER_LIB})
set(PICO_HACK 0)
set(STAMP_HACK 0)
if (PICO_HACK)
target_compile_definitions(doom_tiny${SUFFIX} PRIVATE
PICO_AUDIO_I2S_DATA_PIN=20
PICO_AUDIO_I2S_CLOCK_PIN_BASE=21
NO_USE_UART=1
)
pico_enable_stdio_uart(doom_tiny${SUFFIX} 0)
elseif(STAMP_HACK)
target_compile_definitions(doom_tiny${SUFFIX} PRIVATE
PICO_AUDIO_I2S_DATA_PIN=22
PICO_AUDIO_I2S_CLOCK_PIN_BASE=23
NO_USE_UART=1
INCREASE_I2S_DRIVE_STRENGTH=1
)
pico_enable_stdio_uart(doom_tiny${SUFFIX} 0)
endif()
endfunction()
add_subdirectory(whd_gen)
add_library(render_newhope INTERFACE)
target_sources(render_newhope INTERFACE
pd_render.cpp
)
target_compile_definitions(render_newhope INTERFACE
PICODOOM_RENDER_NEWHOPE=1
MERGE_DISTSCALE0_INTO_VIEWCOSSINANGLE=1
NO_USE_ZLIGHT=1
PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT=4
PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS=1
PICO_SCANVIDEO_SCANLINE_RELEASE_FUNCTION=1
)
if (PICO_ON_DEVICE)
target_compile_definitions(render_newhope INTERFACE
PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS=164
)
else()
add_executable("${PROGRAM_PREFIX}doom" ${SOURCE_FILES_WITH_DEH})
# we cannot rewrite the buffers in host mode so we need them to be big enough for text mode
target_compile_definitions(render_newhope INTERFACE
PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS=324
)
endif()
target_include_directories("${PROGRAM_PREFIX}doom"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}doom" doom ${EXTRA_LIBS})
if (PICO_SDK)
add_doom_tiny("" render_newhope)
add_library(tiny_settings INTERFACE)
target_compile_definitions(tiny_settings INTERFACE
WHD_SUPER_TINY=1
DEMO1_ONLY=1
NO_USE_FINALE_CAST=1
NO_USE_FINALE_BUNNY=1
)
target_compile_definitions(doom_tiny PRIVATE
TINY_WAD_ADDR=0x10040000
)
target_link_libraries(doom_tiny PRIVATE tiny_settings)
add_doom_tiny(_nost render_newhope)
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}doom" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
target_compile_definitions(doom_tiny_nost PRIVATE
# NO_USE_FINALE_CAST=1 # note this is only used by doom II but doesnt take that much more space
TINY_WAD_ADDR=0x10048000
)
if (PICO_ON_DEVICE)
suppress_tinyusb_warnings()
add_doom_tiny(_usb render_newhope)
target_link_libraries(doom_tiny_usb PRIVATE tiny_settings pico_cd)
target_compile_definitions(doom_tiny_usb PRIVATE
USB_SUPPORT=1
TINY_WAD_ADDR=0x10042000 # ugh tinyusb is >8K currently!!
)
add_doom_tiny(_nost_usb render_newhope)
target_link_libraries(doom_tiny_nost_usb PRIVATE pico_cd)
target_compile_definitions(doom_tiny_nost_usb PRIVATE
USB_SUPPORT=1
TINY_WAD_ADDR=0x10048000
)
endif()
endif()
if(WIN32)
add_executable("${PROGRAM_PREFIX}heretic" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}heretic" ${SOURCE_FILES_WITH_DEH})
endif()
target_include_directories("${PROGRAM_PREFIX}heretic"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}heretic" heretic ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}heretic" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
if(WIN32)
add_executable("${PROGRAM_PREFIX}hexen" WIN32 ${SOURCE_FILES} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}hexen" ${SOURCE_FILES})
endif()
target_include_directories("${PROGRAM_PREFIX}hexen"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}hexen" hexen ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}hexen" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
if(WIN32)
add_executable("${PROGRAM_PREFIX}strife" WIN32 ${SOURCE_FILES_WITH_DEH} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
else()
add_executable("${PROGRAM_PREFIX}strife" ${SOURCE_FILES_WITH_DEH})
endif()
target_include_directories("${PROGRAM_PREFIX}strife"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}strife" strife ${EXTRA_LIBS})
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}strife" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
# Source files needed for chocolate-setup:
set(SETUP_FILES
deh_str.c deh_str.h
d_mode.c d_mode.h
d_iwad.c d_iwad.h
i_timer.c i_timer.h
m_config.c m_config.h
m_controls.c m_controls.h
net_io.c net_io.h
net_packet.c net_packet.h
net_sdl.c net_sdl.h
net_query.c net_query.h
net_structrw.c net_structrw.h
z_native.c z_zone.h)
if(WIN32)
add_executable("${PROGRAM_PREFIX}setup" WIN32 ${SETUP_FILES} ${COMMON_SOURCE_FILES} "${CMAKE_CURRENT_BINARY_DIR}/setup-res.rc")
else()
add_executable("${PROGRAM_PREFIX}setup" ${SETUP_FILES} ${COMMON_SOURCE_FILES})
endif()
target_include_directories("${PROGRAM_PREFIX}setup"
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries("${PROGRAM_PREFIX}setup" SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net setup textscreen)
if(MSVC)
set_target_properties("${PROGRAM_PREFIX}setup" PROPERTIES
LINK_FLAGS "/MANIFEST:NO")
endif()
add_executable(midiread midifile.c z_native.c i_system.c m_argv.c m_misc.c)
target_compile_definitions(midiread PRIVATE "-DTEST")
target_include_directories(midiread PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(midiread SDL2::SDL2main SDL2::SDL2)
add_executable(mus2mid mus2mid.c memio.c z_native.c i_system.c m_argv.c m_misc.c)
target_compile_definitions(mus2mid PRIVATE "-DSTANDALONE")
target_include_directories(mus2mid PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
target_link_libraries(mus2mid SDL2::SDL2main SDL2::SDL2)

View file

@ -0,0 +1,10 @@
add_library(adpcm-lib INTERFACE)
target_sources(adpcm-lib INTERFACE
${CMAKE_CURRENT_LIST_DIR}/adpcm-lib.c)
target_include_directories(adpcm-lib INTERFACE ${CMAKE_CURRENT_LIST_DIR})
#if (NOT PICO_ON_DEVICE)
# add_executable(adpcm-xq adpcm-xq.c)
# target_link_libraries(adpcm-xq adpcm-lib)
#endif()

400
src/adpcm-xq/adpcm-lib.c Normal file
View file

@ -0,0 +1,400 @@
////////////////////////////////////////////////////////////////////////////
// **** ADPCM-XQ **** //
// Xtreme Quality ADPCM Encoder/Decoder //
// Copyright (c) 2015 David Bryant. //
// All Rights Reserved. //
// Distributed under the BSD Software License (see license.txt) //
////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <string.h>
#include "adpcm-lib.h"
/* This module encodes and decodes 4-bit ADPCM (DVI/IMA varient). ADPCM data is divided
* into independently decodable blocks that can be relatively small. The most common
* configuration is to store 505 samples into a 256 byte block, although other sizes are
* permitted as long as the number of samples is one greater than a multiple of 8. When
* multiple channels are present, they are interleaved in the data with an 8-sample
* interval.
*/
/********************************* 4-bit ADPCM encoder ********************************/
#define CLIP(data, min, max) \
if ((data) > (max)) data = max; \
else if ((data) < (min)) data = min;
/* step table */
static const uint16_t step_table[89] = {
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31,
34, 37, 41, 45, 50, 55, 60, 66,
73, 80, 88, 97, 107, 118, 130, 143,
157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411,
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
32767
};
/* step index tables */
static const int index_table[] = {
/* adpcm data size is 4 */
-1, -1, -1, -1, 2, 4, 6, 8
};
struct adpcm_channel {
int32_t pcmdata; // current PCM value
int32_t error, weight, history [2]; // for noise shaping
int8_t index; // current index into step size table
};
struct adpcm_context {
struct adpcm_channel channels [2];
int num_channels, lookahead, noise_shaping;
};
/* Create ADPCM encoder context with given number of channels.
* The returned pointer is used for subsequent calls. Note that
* even though an ADPCM encoder could be set up to encode frames
* independently, we use a context so that we can use previous
* data to improve quality (this encoder might not be optimal
* for encoding independent frames).
*/
void *adpcm_create_context (int num_channels, int lookahead, int noise_shaping, int32_t initial_deltas [2])
{
struct adpcm_context *pcnxt = malloc (sizeof (struct adpcm_context));
int ch, i;
memset (pcnxt, 0, sizeof (struct adpcm_context));
pcnxt->noise_shaping = noise_shaping;
pcnxt->num_channels = num_channels;
pcnxt->lookahead = lookahead;
// given the supplied initial deltas, search for and store the closest index
for (ch = 0; ch < num_channels; ++ch)
for (i = 0; i <= 88; i++)
if (i == 88 || initial_deltas [ch] < ((int32_t) step_table [i] + step_table [i+1]) / 2) {
pcnxt->channels [ch].index = i;
break;
}
return pcnxt;
}
/* Free the ADPCM encoder context.
*/
void adpcm_free_context (void *p)
{
struct adpcm_context *pcnxt = (struct adpcm_context *) p;
free (pcnxt);
}
static void set_decode_parameters (struct adpcm_context *pcnxt, int32_t *init_pcmdata, int8_t *init_index)
{
int ch;
for (ch = 0; ch < pcnxt->num_channels; ch++) {
pcnxt->channels[ch].pcmdata = init_pcmdata[ch];
pcnxt->channels[ch].index = init_index[ch];
}
}
static void get_decode_parameters (struct adpcm_context *pcnxt, int32_t *init_pcmdata, int8_t *init_index)
{
int ch;
for (ch = 0; ch < pcnxt->num_channels; ch++) {
init_pcmdata[ch] = pcnxt->channels[ch].pcmdata;
init_index[ch] = pcnxt->channels[ch].index;
}
}
static double minimum_error (const struct adpcm_channel *pchan, int nch, int32_t csample, const int16_t *sample, int depth, int *best_nibble)
{
int32_t delta = csample - pchan->pcmdata;
struct adpcm_channel chan = *pchan;
int step = step_table[chan.index];
int trial_delta = (step >> 3);
int nibble, nibble2;
double min_error;
if (delta < 0) {
int mag = (-delta << 2) / step;
nibble = 0x8 | (mag > 7 ? 7 : mag);
}
else {
int mag = (delta << 2) / step;
nibble = mag > 7 ? 7 : mag;
}
if (nibble & 1) trial_delta += (step >> 2);
if (nibble & 2) trial_delta += (step >> 1);
if (nibble & 4) trial_delta += step;
if (nibble & 8) trial_delta = -trial_delta;
chan.pcmdata += trial_delta;
CLIP(chan.pcmdata, -32768, 32767);
if (best_nibble) *best_nibble = nibble;
min_error = (double) (chan.pcmdata - csample) * (chan.pcmdata - csample);
if (depth) {
chan.index += index_table[nibble & 0x07];
CLIP(chan.index, 0, 88);
min_error += minimum_error (&chan, nch, sample [nch], sample + nch, depth - 1, NULL);
}
else
return min_error;
for (nibble2 = 0; nibble2 <= 0xF; ++nibble2) {
double error;
if (nibble2 == nibble)
continue;
chan = *pchan;
trial_delta = (step >> 3);
if (nibble2 & 1) trial_delta += (step >> 2);
if (nibble2 & 2) trial_delta += (step >> 1);
if (nibble2 & 4) trial_delta += step;
if (nibble2 & 8) trial_delta = -trial_delta;
chan.pcmdata += trial_delta;
CLIP(chan.pcmdata, -32768, 32767);
error = (double) (chan.pcmdata - csample) * (chan.pcmdata - csample);
if (error < min_error) {
chan.index += index_table[nibble2 & 0x07];
CLIP(chan.index, 0, 88);
error += minimum_error (&chan, nch, sample [nch], sample + nch, depth - 1, NULL);
if (error < min_error) {
if (best_nibble) *best_nibble = nibble2;
min_error = error;
}
}
}
return min_error;
}
static uint8_t encode_sample (struct adpcm_context *pcnxt, int ch, const int16_t *sample, int num_samples)
{
struct adpcm_channel *pchan = pcnxt->channels + ch;
int32_t csample = *sample;
int depth = num_samples - 1, nibble;
int step = step_table[pchan->index];
int trial_delta = (step >> 3);
if (pcnxt->noise_shaping == NOISE_SHAPING_DYNAMIC) {
int32_t sam = (3 * pchan->history [0] - pchan->history [1]) >> 1;
int32_t temp = csample - (((pchan->weight * sam) + 512) >> 10);
int32_t shaping_weight;
if (sam && temp) pchan->weight -= (((sam ^ temp) >> 29) & 4) - 2;
pchan->history [1] = pchan->history [0];
pchan->history [0] = csample;
shaping_weight = (pchan->weight < 256) ? 1024 : 1536 - (pchan->weight * 2);
temp = -((shaping_weight * pchan->error + 512) >> 10);
if (shaping_weight < 0 && temp) {
if (temp == pchan->error)
temp = (temp < 0) ? temp + 1 : temp - 1;
pchan->error = -csample;
csample += temp;
}
else
pchan->error = -(csample += temp);
}
else if (pcnxt->noise_shaping == NOISE_SHAPING_STATIC)
pchan->error = -(csample -= pchan->error);
if (depth > pcnxt->lookahead)
depth = pcnxt->lookahead;
minimum_error (pchan, pcnxt->num_channels, csample, sample, depth, &nibble);
if (nibble & 1) trial_delta += (step >> 2);
if (nibble & 2) trial_delta += (step >> 1);
if (nibble & 4) trial_delta += step;
if (nibble & 8) trial_delta = -trial_delta;
pchan->pcmdata += trial_delta;
pchan->index += index_table[nibble & 0x07];
CLIP(pchan->index, 0, 88);
CLIP(pchan->pcmdata, -32768, 32767);
if (pcnxt->noise_shaping)
pchan->error += pchan->pcmdata;
return nibble;
}
static void encode_chunks (struct adpcm_context *pcnxt, uint8_t **outbuf, size_t *outbufsize, const int16_t **inbuf, int inbufcount)
{
const int16_t *pcmbuf;
int chunks, ch, i;
chunks = (inbufcount - 1) / 8;
*outbufsize += (chunks * 4) * pcnxt->num_channels;
while (chunks--)
{
for (ch = 0; ch < pcnxt->num_channels; ch++)
{
pcmbuf = *inbuf + ch;
for (i = 0; i < 4; i++) {
**outbuf = encode_sample (pcnxt, ch, pcmbuf, chunks * 8 + (3 - i) * 2 + 2);
pcmbuf += pcnxt->num_channels;
**outbuf |= encode_sample (pcnxt, ch, pcmbuf, chunks * 8 + (3 - i) * 2 + 1) << 4;
pcmbuf += pcnxt->num_channels;
(*outbuf)++;
}
}
*inbuf += 8 * pcnxt->num_channels;
}
}
/* Encode a block of 16-bit PCM data into 4-bit ADPCM.
*
* Parameters:
* p the context returned by adpcm_begin()
* outbuf destination buffer
* outbufsize pointer to variable where the number of bytes written
* will be stored
* inbuf source PCM samples
* inbufcount number of composite PCM samples provided (note: this is
* the total number of 16-bit samples divided by the number
* of channels)
*
* Returns 1 (for success as there is no error checking)
*/
int adpcm_encode_block (void *p, uint8_t *outbuf, size_t *outbufsize, const int16_t *inbuf, int inbufcount)
{
struct adpcm_context *pcnxt = (struct adpcm_context *) p;
int32_t init_pcmdata[2];
int8_t init_index[2];
int ch;
*outbufsize = 0;
if (!inbufcount)
return 1;
get_decode_parameters(pcnxt, init_pcmdata, init_index);
for (ch = 0; ch < pcnxt->num_channels; ch++) {
init_pcmdata[ch] = *inbuf++;
outbuf[0] = init_pcmdata[ch];
outbuf[1] = init_pcmdata[ch] >> 8;
outbuf[2] = init_index[ch];
outbuf[3] = 0;
outbuf += 4;
*outbufsize += 4;
}
set_decode_parameters(pcnxt, init_pcmdata, init_index);
encode_chunks (pcnxt, &outbuf, outbufsize, &inbuf, inbufcount);
return 1;
}
/********************************* 4-bit ADPCM decoder ********************************/
/* Decode the block of ADPCM data into PCM. This requires no context because ADPCM blocks
* are indeppendently decodable. This assumes that a single entire block is always decoded;
* it must be called multiple times for multiple blocks and cannot resume in the middle of a
* block.
*
* Parameters:
* outbuf destination for interleaved PCM samples
* inbuf source ADPCM block
* inbufsize size of source ADPCM block
* channels number of channels in block (must be determined from other context)
*
* Returns number of converted composite samples (total samples divided by number of channels)
*/
int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize, int channels)
{
int ch, samples = 1, chunks;
int32_t pcmdata[2];
int8_t index[2];
if (inbufsize < (uint32_t) channels * 4)
return 0;
for (ch = 0; ch < channels; ch++) {
*outbuf++ = pcmdata[ch] = (int16_t) (inbuf [0] | (inbuf [1] << 8));
index[ch] = inbuf [2];
if (index [ch] < 0 || index [ch] > 88 || inbuf [3]) // sanitize the input a little...
return 0;
inbufsize -= 4;
inbuf += 4;
}
chunks = inbufsize / (channels * 4);
samples += chunks * 8;
while (chunks--) {
int ch, i;
for (ch = 0; ch < channels; ++ch) {
for (i = 0; i < 4; ++i) {
int step = step_table [index [ch]], delta = step >> 3;
if (*inbuf & 1) delta += (step >> 2);
if (*inbuf & 2) delta += (step >> 1);
if (*inbuf & 4) delta += step;
if (*inbuf & 8) delta = -delta;
pcmdata[ch] += delta;
index[ch] += index_table [*inbuf & 0x7];
CLIP(index[ch], 0, 88);
CLIP(pcmdata[ch], -32768, 32767);
outbuf [i * 2 * channels] = pcmdata[ch];
step = step_table [index [ch]], delta = step >> 3;
if (*inbuf & 0x10) delta += (step >> 2);
if (*inbuf & 0x20) delta += (step >> 1);
if (*inbuf & 0x40) delta += step;
if (*inbuf & 0x80) delta = -delta;
pcmdata[ch] += delta;
index[ch] += index_table [(*inbuf >> 4) & 0x7];
CLIP(index[ch], 0, 88);
CLIP(pcmdata[ch], -32768, 32767);
outbuf [(i * 2 + 1) * channels] = pcmdata[ch];
inbuf++;
}
outbuf++;
}
outbuf += channels * 7;
}
return samples;
}

35
src/adpcm-xq/adpcm-lib.h Normal file
View file

@ -0,0 +1,35 @@
////////////////////////////////////////////////////////////////////////////
// **** ADPCM-XQ **** //
// Xtreme Quality ADPCM Encoder/Decoder //
// Copyright (c) 2015 David Bryant. //
// All Rights Reserved. //
// Distributed under the BSD Software License (see license.txt) //
////////////////////////////////////////////////////////////////////////////
#ifndef ADPCMLIB_H_
#define ADPCMLIB_H_
#if defined(_MSC_VER) && _MSC_VER < 1600
typedef unsigned __int64 uint64_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int8 uint8_t;
typedef __int64 int64_t;
typedef __int32 int32_t;
typedef __int16 int16_t;
typedef __int8 int8_t;
#else
#include <stdint.h>
#endif
void *adpcm_create_context (int num_channels, int lookahead, int noise_shaping, int32_t initial_deltas [2]);
int adpcm_encode_block (void *p, uint8_t *outbuf, size_t *outbufsize, const int16_t *inbuf, int inbufcount);
int adpcm_decode_block (int16_t *outbuf, const uint8_t *inbuf, size_t inbufsize, int channels);
void adpcm_free_context (void *p);
#define NOISE_SHAPING_OFF 0 // flat noise (no shaping)
#define NOISE_SHAPING_STATIC 1 // first-order highpass shaping
#define NOISE_SHAPING_DYNAMIC 2 // dynamically tilted noise based on signal
#endif /* ADPCMLIB_H_ */

789
src/adpcm-xq/adpcm-xq.c Normal file
View file

@ -0,0 +1,789 @@
////////////////////////////////////////////////////////////////////////////
// **** ADPCM-XQ **** //
// Xtreme Quality ADPCM Encoder/Decoder //
// Copyright (c) 2015 David Bryant. //
// All Rights Reserved. //
// Distributed under the BSD Software License (see license.txt) //
////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "adpcm-lib.h"
static const char *sign_on = "\n"
" ADPCM-XQ Xtreme Quality IMA-ADPCM WAV Encoder / Decoder Version 0.3\n"
" Copyright (c) 2018 David Bryant. All Rights Reserved.\n\n";
static const char *usage =
" Usage: ADPCM-XQ [-options] infile.wav outfile.wav\n\n"
" Operation: conversion is performed based on the type of the infile\n"
" (either encode 16-bit PCM to 4-bit IMA-ADPCM or decode back)\n\n"
" Options: -[0-8] = encode lookahead samples (default = 3)\n"
" -bn = override auto block size, 2^n bytes (n = 8-15)\n"
" -d = decode only (fail on WAV file already PCM)\n"
" -e = encode only (fail on WAV file already ADPCM)\n"
" -f = encode flat noise (no dynamic noise shaping)\n"
" -h = display this help message\n"
" -q = quiet mode (display errors only)\n"
" -r = raw output (no WAV header written)\n"
" -v = verbose (display lots of info)\n"
" -y = overwrite outfile if it exists\n\n"
" Web: Visit www.github.com/dbry/adpcm-xq for latest version and info\n\n";
#define ADPCM_FLAG_NOISE_SHAPING 0x1
#define ADPCM_FLAG_RAW_OUTPUT 0x2
static int adpcm_converter (char *infilename, char *outfilename, int flags, int blocksize_pow2, int lookahead);
static int verbosity = 0, decode_only = 0, encode_only = 0;
int main (argc, argv) int argc; char **argv;
{
int lookahead = 3, flags = ADPCM_FLAG_NOISE_SHAPING, blocksize_pow2 = 0, overwrite = 0, asked_help = 0;
char *infilename = NULL, *outfilename = NULL;
FILE *outfile;
// if the name of the executable ends in "encoder" or "decoder", just do that function
encode_only = argc && strstr (argv [0], "encoder") && strlen (strstr (argv [0], "encoder")) == strlen ("encoder");
decode_only = argc && strstr (argv [0], "decoder") && strlen (strstr (argv [0], "decoder")) == strlen ("decoder");
// loop through command-line arguments
while (--argc) {
#if defined (_WIN32)
if ((**++argv == '-' || **argv == '/') && (*argv)[1])
#else
if ((**++argv == '-') && (*argv)[1])
#endif
while (*++*argv)
switch (**argv) {
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
lookahead = **argv - '0';
break;
case 'B': case 'b':
blocksize_pow2 = strtol (++*argv, argv, 10);
if (blocksize_pow2 < 8 || blocksize_pow2 > 15) {
fprintf (stderr, "\nblock size power must be 8 to 15!\n");
return -1;
}
--*argv;
break;
case 'D': case 'd':
decode_only = 1;
break;
case 'E': case 'e':
encode_only = 1;
break;
case 'F': case 'f':
flags &= ~ADPCM_FLAG_NOISE_SHAPING;
break;
case 'H': case 'h':
asked_help = 0;
break;
case 'Q': case 'q':
verbosity = -1;
break;
case 'R': case 'r':
flags |= ADPCM_FLAG_RAW_OUTPUT;
break;
case 'V': case 'v':
verbosity = 1;
break;
case 'Y': case 'y':
overwrite = 1;
break;
default:
fprintf (stderr, "\nillegal option: %c !\n", **argv);
return 1;
}
else if (!infilename) {
infilename = malloc (strlen (*argv) + 10);
strcpy (infilename, *argv);
}
else if (!outfilename) {
outfilename = malloc (strlen (*argv) + 10);
strcpy (outfilename, *argv);
}
else {
fprintf (stderr, "\nextra unknown argument: %s !\n", *argv);
return 1;
}
}
if (verbosity >= 0)
fprintf (stderr, "%s", sign_on);
if (!outfilename || asked_help) {
printf ("%s", usage);
return 0;
}
if (!strcmp (infilename, outfilename)) {
fprintf (stderr, "can't overwrite input file (specify different/new output file name)\n");
return -1;
}
if (!overwrite && (outfile = fopen (outfilename, "r"))) {
fclose (outfile);
fprintf (stderr, "output file \"%s\" exists (use -y to overwrite)\n", outfilename);
return -1;
}
return adpcm_converter (infilename, outfilename, flags, blocksize_pow2, lookahead);
}
typedef struct {
char ckID [4];
uint32_t ckSize;
char formType [4];
} RiffChunkHeader;
typedef struct {
char ckID [4];
uint32_t ckSize;
} ChunkHeader;
#define ChunkHeaderFormat "4L"
typedef struct {
uint16_t FormatTag, NumChannels;
uint32_t SampleRate, BytesPerSecond;
uint16_t BlockAlign, BitsPerSample;
uint16_t cbSize;
union {
uint16_t ValidBitsPerSample;
uint16_t SamplesPerBlock;
uint16_t Reserved;
} Samples;
int32_t ChannelMask;
uint16_t SubFormat;
char GUID [14];
} WaveHeader;
#define WaveHeaderFormat "SSLLSSSSLS"
typedef struct {
char ckID [4];
uint32_t ckSize;
uint32_t TotalSamples;
} FactHeader;
#define FactHeaderFormat "4LL"
#define WAVE_FORMAT_PCM 0x1
#define WAVE_FORMAT_IMA_ADPCM 0x11
#define WAVE_FORMAT_EXTENSIBLE 0xfffe
static int write_pcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate);
static int write_adpcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate, int samples_per_block);
static int adpcm_decode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int block_size);
static int adpcm_encode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int samples_per_block, int lookahead, int noise_shaping);
static void little_endian_to_native (void *data, char *format);
static void native_to_little_endian (void *data, char *format);
static int adpcm_converter (char *infilename, char *outfilename, int flags, int blocksize_pow2, int lookahead)
{
int format = 0, res = 0, bits_per_sample, sample_rate, num_channels;
uint32_t fact_samples = 0;
size_t num_samples = 0;
FILE *infile, *outfile;
RiffChunkHeader riff_chunk_header;
ChunkHeader chunk_header;
WaveHeader WaveHeader;
if (!(infile = fopen (infilename, "rb"))) {
fprintf (stderr, "can't open file \"%s\" for reading!\n", infilename);
return -1;
}
// read initial RIFF form header
if (!fread (&riff_chunk_header, sizeof (RiffChunkHeader), 1, infile) ||
strncmp (riff_chunk_header.ckID, "RIFF", 4) ||
strncmp (riff_chunk_header.formType, "WAVE", 4)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
// loop through all elements of the RIFF wav header (until the data chuck)
while (1) {
if (!fread (&chunk_header, sizeof (ChunkHeader), 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
little_endian_to_native (&chunk_header, ChunkHeaderFormat);
// if it's the format chunk, we want to get some info out of there and
// make sure it's a .wav file we can handle
if (!strncmp (chunk_header.ckID, "fmt ", 4)) {
int supported = 1;
if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) ||
!fread (&WaveHeader, chunk_header.ckSize, 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
little_endian_to_native (&WaveHeader, WaveHeaderFormat);
format = (WaveHeader.FormatTag == WAVE_FORMAT_EXTENSIBLE && chunk_header.ckSize == 40) ?
WaveHeader.SubFormat : WaveHeader.FormatTag;
bits_per_sample = (chunk_header.ckSize == 40 && WaveHeader.Samples.ValidBitsPerSample) ?
WaveHeader.Samples.ValidBitsPerSample : WaveHeader.BitsPerSample;
if (WaveHeader.NumChannels < 1 || WaveHeader.NumChannels > 2)
supported = 0;
else if (format == WAVE_FORMAT_PCM) {
if (decode_only) {
fprintf (stderr, "\"%s\" is PCM .WAV file, invalid in decode-only mode!\n", infilename);
return -1;
}
if (bits_per_sample < 9 || bits_per_sample > 16)
supported = 0;
if (WaveHeader.BlockAlign != WaveHeader.NumChannels * 2)
supported = 0;
}
else if (format == WAVE_FORMAT_IMA_ADPCM) {
if (encode_only) {
fprintf (stderr, "\"%s\" is ADPCM .WAV file, invalid in encode-only mode!\n", infilename);
return -1;
}
if (bits_per_sample != 4)
supported = 0;
if (WaveHeader.Samples.SamplesPerBlock != (WaveHeader.BlockAlign - WaveHeader.NumChannels * 4) * (WaveHeader.NumChannels ^ 3) + 1) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
}
else
supported = 0;
if (!supported) {
fprintf (stderr, "\"%s\" is an unsupported .WAV format!\n", infilename);
return -1;
}
if (verbosity > 0) {
fprintf (stderr, "format tag size = %d\n", chunk_header.ckSize);
fprintf (stderr, "FormatTag = 0x%x, NumChannels = %d, BitsPerSample = %d\n",
WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample);
fprintf (stderr, "BlockAlign = %d, SampleRate = %d, BytesPerSecond = %d\n",
WaveHeader.BlockAlign, WaveHeader.SampleRate, WaveHeader.BytesPerSecond);
if (chunk_header.ckSize > 16) {
if (format == WAVE_FORMAT_PCM)
fprintf (stderr, "cbSize = %d, ValidBitsPerSample = %d\n", WaveHeader.cbSize,
WaveHeader.Samples.ValidBitsPerSample);
else if (format == WAVE_FORMAT_IMA_ADPCM)
fprintf (stderr, "cbSize = %d, SamplesPerBlock = %d\n", WaveHeader.cbSize,
WaveHeader.Samples.SamplesPerBlock);
}
if (chunk_header.ckSize > 20)
fprintf (stderr, "ChannelMask = %x, SubFormat = %d\n",
WaveHeader.ChannelMask, WaveHeader.SubFormat);
}
}
else if (!strncmp (chunk_header.ckID, "fact", 4)) {
if (chunk_header.ckSize < 4 || !fread (&fact_samples, sizeof (fact_samples), 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
if (chunk_header.ckSize > 4) {
int bytes_to_skip = chunk_header.ckSize - 4;
char dummy;
while (bytes_to_skip--)
if (!fread (&dummy, 1, 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
}
}
else if (!strncmp (chunk_header.ckID, "data", 4)) {
// on the data chunk, get size and exit parsing loop
if (!WaveHeader.NumChannels) { // make sure we saw a "fmt" chunk...
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
if (!chunk_header.ckSize) {
fprintf (stderr, "this .WAV file has no audio samples, probably is corrupt!\n");
return -1;
}
if (format == WAVE_FORMAT_PCM) {
if (chunk_header.ckSize % WaveHeader.BlockAlign) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
num_samples = chunk_header.ckSize / WaveHeader.BlockAlign;
}
else {
int complete_blocks = chunk_header.ckSize / WaveHeader.BlockAlign;
int leftover_bytes = chunk_header.ckSize % WaveHeader.BlockAlign;
int samples_last_block;
num_samples = complete_blocks * WaveHeader.Samples.SamplesPerBlock;
if (leftover_bytes) {
if (leftover_bytes % (WaveHeader.NumChannels * 4)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
if (verbosity > 0) fprintf (stderr, "data chunk has %d bytes left over for final ADPCM block\n", leftover_bytes);
samples_last_block = (leftover_bytes - (WaveHeader.NumChannels * 4)) * (WaveHeader.NumChannels ^ 3) + 1;
num_samples += samples_last_block;
}
else
samples_last_block = WaveHeader.Samples.SamplesPerBlock;
if (fact_samples) {
if (fact_samples < num_samples && fact_samples > num_samples - samples_last_block) {
if (verbosity > 0) fprintf (stderr, "total samples reduced %lu by FACT chunk\n", (unsigned long) (num_samples - fact_samples));
num_samples = fact_samples;
}
else if (WaveHeader.NumChannels == 2 && (fact_samples >>= 1) < num_samples && fact_samples > num_samples - samples_last_block) {
if (verbosity > 0) fprintf (stderr, "num samples reduced %lu by [incorrect] FACT chunk\n", (unsigned long) (num_samples - fact_samples));
num_samples = fact_samples;
}
}
}
if (!num_samples) {
fprintf (stderr, "this .WAV file has no audio samples, probably is corrupt!\n");
return -1;
}
if (verbosity > 0)
fprintf (stderr, "num samples = %lu\n", (unsigned long) num_samples);
num_channels = WaveHeader.NumChannels;
sample_rate = WaveHeader.SampleRate;
break;
}
else { // just ignore unknown chunks
int bytes_to_eat = (chunk_header.ckSize + 1) & ~1L;
char dummy;
if (verbosity > 0)
fprintf (stderr, "extra unknown chunk \"%c%c%c%c\" of %d bytes\n",
chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2],
chunk_header.ckID [3], chunk_header.ckSize);
while (bytes_to_eat--)
if (!fread (&dummy, 1, 1, infile)) {
fprintf (stderr, "\"%s\" is not a valid .WAV file!\n", infilename);
return -1;
}
}
}
if (!(outfile = fopen (outfilename, "wb"))) {
fprintf (stderr, "can't open file \"%s\" for writing!\n", outfilename);
return -1;
}
if (format == WAVE_FORMAT_PCM) {
int block_size, samples_per_block;
if (blocksize_pow2)
block_size = 1 << blocksize_pow2;
else
block_size = 256 * num_channels * (sample_rate < 11000 ? 1 : sample_rate / 11000);
samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1;
if (verbosity > 0)
fprintf (stderr, "each %d byte ADPCM block will contain %d samples * %d channels\n",
block_size, samples_per_block, num_channels);
if (!(flags & ADPCM_FLAG_RAW_OUTPUT) && !write_adpcm_wav_header (outfile, num_channels, num_samples, sample_rate, samples_per_block)) {
fprintf (stderr, "can't write header to file \"%s\" !\n", outfilename);
return -1;
}
if (verbosity >= 0) fprintf (stderr, "encoding PCM file \"%s\" to%sADPCM file \"%s\"...\n",
infilename, (flags & ADPCM_FLAG_RAW_OUTPUT) ? " raw " : " ", outfilename);
res = adpcm_encode_data (infile, outfile, num_channels, num_samples, samples_per_block, lookahead,
(flags & ADPCM_FLAG_NOISE_SHAPING) ? (sample_rate > 64000 ? NOISE_SHAPING_STATIC : NOISE_SHAPING_DYNAMIC) : NOISE_SHAPING_OFF);
}
else if (format == WAVE_FORMAT_IMA_ADPCM) {
if (!(flags & ADPCM_FLAG_RAW_OUTPUT) && !write_pcm_wav_header (outfile, num_channels, num_samples, sample_rate)) {
fprintf (stderr, "can't write header to file \"%s\" !\n", outfilename);
return -1;
}
if (verbosity >= 0) fprintf (stderr, "decoding ADPCM file \"%s\" to%sPCM file \"%s\"...\n",
infilename, (flags & ADPCM_FLAG_RAW_OUTPUT) ? " raw " : " ", outfilename);
res = adpcm_decode_data (infile, outfile, num_channels, num_samples, WaveHeader.BlockAlign);
}
fclose (outfile);
fclose (infile);
return res;
}
static int write_pcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate)
{
RiffChunkHeader riffhdr;
ChunkHeader datahdr, fmthdr;
WaveHeader wavhdr;
int wavhdrsize = 16;
int bytes_per_sample = 2;
size_t total_data_bytes = num_samples * bytes_per_sample * num_channels;
memset (&wavhdr, 0, sizeof (wavhdr));
wavhdr.FormatTag = WAVE_FORMAT_PCM;
wavhdr.NumChannels = num_channels;
wavhdr.SampleRate = sample_rate;
wavhdr.BytesPerSecond = sample_rate * num_channels * bytes_per_sample;
wavhdr.BlockAlign = bytes_per_sample * num_channels;
wavhdr.BitsPerSample = 16;
strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID));
strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType));
riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (datahdr) + total_data_bytes;
strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID));
fmthdr.ckSize = wavhdrsize;
strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID));
datahdr.ckSize = total_data_bytes;
// write the RIFF chunks up to just before the data starts
native_to_little_endian (&riffhdr, ChunkHeaderFormat);
native_to_little_endian (&fmthdr, ChunkHeaderFormat);
native_to_little_endian (&wavhdr, WaveHeaderFormat);
native_to_little_endian (&datahdr, ChunkHeaderFormat);
return fwrite (&riffhdr, sizeof (riffhdr), 1, outfile) &&
fwrite (&fmthdr, sizeof (fmthdr), 1, outfile) &&
fwrite (&wavhdr, wavhdrsize, 1, outfile) &&
fwrite (&datahdr, sizeof (datahdr), 1, outfile);
}
static int write_adpcm_wav_header (FILE *outfile, int num_channels, size_t num_samples, int sample_rate, int samples_per_block)
{
RiffChunkHeader riffhdr;
ChunkHeader datahdr, fmthdr;
WaveHeader wavhdr;
FactHeader facthdr;
int wavhdrsize = 20;
int block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4);
size_t num_blocks = num_samples / samples_per_block;
int leftover_samples = num_samples % samples_per_block;
size_t total_data_bytes = num_blocks * block_size;
if (leftover_samples) {
int last_block_samples = ((leftover_samples + 6) & ~7) + 1;
int last_block_size = (last_block_samples - 1) / (num_channels ^ 3) + (num_channels * 4);
total_data_bytes += last_block_size;
}
memset (&wavhdr, 0, sizeof (wavhdr));
wavhdr.FormatTag = WAVE_FORMAT_IMA_ADPCM;
wavhdr.NumChannels = num_channels;
wavhdr.SampleRate = sample_rate;
wavhdr.BytesPerSecond = sample_rate * block_size / samples_per_block;
wavhdr.BlockAlign = block_size;
wavhdr.BitsPerSample = 4;
wavhdr.cbSize = 2;
wavhdr.Samples.SamplesPerBlock = samples_per_block;
strncpy (riffhdr.ckID, "RIFF", sizeof (riffhdr.ckID));
strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType));
riffhdr.ckSize = sizeof (riffhdr) + wavhdrsize + sizeof (facthdr) + sizeof (datahdr) + total_data_bytes;
strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID));
fmthdr.ckSize = wavhdrsize;
strncpy (facthdr.ckID, "fact", sizeof (facthdr.ckID));
facthdr.TotalSamples = num_samples;
facthdr.ckSize = 4;
strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID));
datahdr.ckSize = total_data_bytes;
// write the RIFF chunks up to just before the data starts
native_to_little_endian (&riffhdr, ChunkHeaderFormat);
native_to_little_endian (&fmthdr, ChunkHeaderFormat);
native_to_little_endian (&wavhdr, WaveHeaderFormat);
native_to_little_endian (&facthdr, FactHeaderFormat);
native_to_little_endian (&datahdr, ChunkHeaderFormat);
return fwrite (&riffhdr, sizeof (riffhdr), 1, outfile) &&
fwrite (&fmthdr, sizeof (fmthdr), 1, outfile) &&
fwrite (&wavhdr, wavhdrsize, 1, outfile) &&
fwrite (&facthdr, sizeof (facthdr), 1, outfile) &&
fwrite (&datahdr, sizeof (datahdr), 1, outfile);
}
static int adpcm_decode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int block_size)
{
int samples_per_block = (block_size - num_channels * 4) * (num_channels ^ 3) + 1, percent;
void *pcm_block = malloc (samples_per_block * num_channels * 2);
void *adpcm_block = malloc (block_size);
size_t progress_divider = 0;
if (!pcm_block || !adpcm_block) {
fprintf (stderr, "could not allocate memory for buffers!\n");
return -1;
}
if (verbosity >= 0 && num_samples > 1000) {
progress_divider = (num_samples + 50) / 100;
fprintf (stderr, "\rprogress: %d%% ", percent = 0);
fflush (stderr);
}
while (num_samples) {
int this_block_adpcm_samples = samples_per_block;
int this_block_pcm_samples = samples_per_block;
if (this_block_adpcm_samples > num_samples) {
this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1;
block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4);
this_block_pcm_samples = num_samples;
}
if (!fread (adpcm_block, block_size, 1, infile)) {
fprintf (stderr, "could not read all audio data from input file!\n");
return -1;
}
if (adpcm_decode_block (pcm_block, adpcm_block, block_size, num_channels) != this_block_adpcm_samples) {
fprintf (stderr, "adpcm_decode_block() did not return expected value!\n");
return -1;
}
if (!fwrite (pcm_block, this_block_pcm_samples * num_channels * 2, 1, outfile)) {
fprintf (stderr, "could not write all audio data to output file!\n");
return -1;
}
num_samples -= this_block_pcm_samples;
if (progress_divider) {
int new_percent = 100 - num_samples / progress_divider;
if (new_percent != percent) {
fprintf (stderr, "\rprogress: %d%% ", percent = new_percent);
fflush (stderr);
}
}
}
if (verbosity >= 0)
fprintf (stderr, "\r...completed successfully\n");
free (adpcm_block);
free (pcm_block);
return 0;
}
static int adpcm_encode_data (FILE *infile, FILE *outfile, int num_channels, size_t num_samples, int samples_per_block, int lookahead, int noise_shaping)
{
int block_size = (samples_per_block - 1) / (num_channels ^ 3) + (num_channels * 4), percent;
int16_t *pcm_block = malloc (samples_per_block * num_channels * 2);
void *adpcm_block = malloc (block_size);
size_t progress_divider = 0;
void *adpcm_cnxt = NULL;
if (!pcm_block || !adpcm_block) {
fprintf (stderr, "could not allocate memory for buffers!\n");
return -1;
}
if (verbosity >= 0 && num_samples > 1000) {
progress_divider = (num_samples + 50) / 100;
fprintf (stderr, "\rprogress: %d%% ", percent = 0);
fflush (stderr);
}
while (num_samples) {
int this_block_adpcm_samples = samples_per_block;
int this_block_pcm_samples = samples_per_block;
size_t num_bytes;
if (this_block_pcm_samples > num_samples) {
this_block_adpcm_samples = ((num_samples + 6) & ~7) + 1;
block_size = (this_block_adpcm_samples - 1) / (num_channels ^ 3) + (num_channels * 4);
this_block_pcm_samples = num_samples;
}
if (!fread (pcm_block, this_block_pcm_samples * num_channels * 2, 1, infile)) {
fprintf (stderr, "\rcould not read all audio data from input file!\n");
return -1;
}
// if this is the last block and it's not full, duplicate the last sample(s) so we don't
// create problems for the lookahead
if (this_block_adpcm_samples > this_block_pcm_samples) {
int16_t *dst = pcm_block + this_block_pcm_samples * num_channels, *src = dst - num_channels;
int dups = (this_block_adpcm_samples - this_block_pcm_samples) * num_channels;
while (dups--)
*dst++ = *src++;
}
// if this is the first block, compute a decaying average (in reverse) so that we can let the
// encoder know what kind of initial deltas to expect (helps initializing index)
if (!adpcm_cnxt) {
int32_t average_deltas [2];
int i;
average_deltas [0] = average_deltas [1] = 0;
for (i = this_block_adpcm_samples * num_channels; i -= num_channels;) {
average_deltas [0] -= average_deltas [0] >> 3;
average_deltas [0] += abs ((int32_t) pcm_block [i] - pcm_block [i - num_channels]);
if (num_channels == 2) {
average_deltas [1] -= average_deltas [1] >> 3;
average_deltas [1] += abs ((int32_t) pcm_block [i-1] - pcm_block [i+1]);
}
}
average_deltas [0] >>= 3;
average_deltas [1] >>= 3;
adpcm_cnxt = adpcm_create_context (num_channels, lookahead, noise_shaping, average_deltas);
}
adpcm_encode_block (adpcm_cnxt, adpcm_block, &num_bytes, pcm_block, this_block_adpcm_samples);
if (num_bytes != block_size) {
fprintf (stderr, "\radpcm_encode_block() did not return expected value (expected %d, got %d)!\n", block_size, (int) num_bytes);
return -1;
}
if (!fwrite (adpcm_block, block_size, 1, outfile)) {
fprintf (stderr, "\rcould not write all audio data to output file!\n");
return -1;
}
num_samples -= this_block_pcm_samples;
if (progress_divider) {
int new_percent = 100 - num_samples / progress_divider;
if (new_percent != percent) {
fprintf (stderr, "\rprogress: %d%% ", percent = new_percent);
fflush (stderr);
}
}
}
if (verbosity >= 0)
fprintf (stderr, "\r...completed successfully\n");
if (adpcm_cnxt)
adpcm_free_context (adpcm_cnxt);
free (adpcm_block);
free (pcm_block);
return 0;
}
static void little_endian_to_native (void *data, char *format)
{
unsigned char *cp = (unsigned char *) data;
int32_t temp;
while (*format) {
switch (*format) {
case 'L':
temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24);
* (int32_t *) cp = temp;
cp += 4;
break;
case 'S':
temp = cp [0] + (cp [1] << 8);
* (short *) cp = (short) temp;
cp += 2;
break;
default:
if (isdigit ((unsigned char) *format))
cp += *format - '0';
break;
}
format++;
}
}
static void native_to_little_endian (void *data, char *format)
{
unsigned char *cp = (unsigned char *) data;
int32_t temp;
while (*format) {
switch (*format) {
case 'L':
temp = * (int32_t *) cp;
*cp++ = (unsigned char) temp;
*cp++ = (unsigned char) (temp >> 8);
*cp++ = (unsigned char) (temp >> 16);
*cp++ = (unsigned char) (temp >> 24);
break;
case 'S':
temp = * (short *) cp;
*cp++ = (unsigned char) temp;
*cp++ = (unsigned char) (temp >> 8);
break;
default:
if (isdigit ((unsigned char) *format))
cp += *format - '0';
break;
}
format++;
}
}

26
src/adpcm-xq/license.txt Normal file
View file

@ -0,0 +1,26 @@
Copyright (c) David Bryant
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Conifer Software nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -22,7 +23,11 @@
#include <stdlib.h>
#include "d_event.h"
#if !DOOM_SMALL
#define MAXEVENTS 64
#else
#define MAXEVENTS 8
#endif
static event_t events[MAXEVENTS];
static int eventhead;

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -38,16 +39,20 @@ static const iwad_t iwads[] =
{ "tnt.wad", pack_tnt, commercial, "Final Doom: TNT: Evilution" },
{ "doom.wad", doom, retail, "Doom" },
{ "doom1.wad", doom, shareware, "Doom Shareware" },
#if !DOOM_ONLY
{ "chex.wad", pack_chex, retail, "Chex Quest" },
{ "hacx.wad", pack_hacx, commercial, "Hacx" },
#endif
{ "freedm.wad", doom2, commercial, "FreeDM" },
{ "freedoom2.wad", doom2, commercial, "Freedoom: Phase 2" },
{ "freedoom1.wad", doom, retail, "Freedoom: Phase 1" },
#if !DOOM_ONLY
{ "heretic.wad", heretic, retail, "Heretic" },
{ "heretic1.wad", heretic, shareware, "Heretic Shareware" },
{ "hexen.wad", hexen, commercial, "Hexen" },
//{ "strife0.wad", strife, commercial, "Strife" }, // haleyjd: STRIFE-FIXME
{ "strife1.wad", strife, commercial, "Strife" },
#endif
};
// Array of locations to search for IWAD files
@ -442,6 +447,7 @@ static void CheckDOSDefaults(void)
#endif
#if !DOOM_TINY
// Returns true if the specified path is a path to a file
// of the specified name.
@ -529,7 +535,7 @@ static GameMission_t IdentifyIWADByName(const char *name, int mask)
GameMission_t mission;
name = M_BaseName(name);
mission = none;
mission = mission_none;
for (i=0; i<arrlen(iwads); ++i)
{
@ -696,9 +702,11 @@ static void BuildIWADDirList(void)
// Look in the current directory. Doom always does this.
AddIWADDir(".");
#if !NO_USE_ARGS
// Next check the directory where the executable is located. This might
// be different from the current directory.
AddIWADDir(M_DirName(myargv[0]));
#endif
// Add DOOMWADDIR if it is in the environment
env = getenv("DOOMWADDIR");
@ -835,7 +843,10 @@ char *D_FindIWAD(int mask, GameMission_t *mission)
//
// @arg <file>
//
#if USE_MEMORY_WAD
return "<memory>.wad";
#else
#if !NO_USE_ARGS
iwadparm = M_CheckParmWithArgs("-iwad", 1);
if (iwadparm)
@ -854,6 +865,7 @@ char *D_FindIWAD(int mask, GameMission_t *mission)
*mission = IdentifyIWADByName(result, mask);
}
else
#endif
{
// Search through the list and look for an IWAD
@ -866,6 +878,7 @@ char *D_FindIWAD(int mask, GameMission_t *mission)
result = SearchDirectoryForIWAD(iwad_dirs[i], mask, mission);
}
}
#endif
return result;
}
@ -950,6 +963,9 @@ const char *D_SuggestIWADName(GameMission_t mission, GameMode_t mode)
return "unknown.wad";
}
#endif
const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode)
{
int i;
@ -957,7 +973,7 @@ const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode)
for (i = 0; i < arrlen(iwads); ++i)
{
if (iwads[i].mission == mission
&& (mode == indetermined || iwads[i].mode == mode))
&& (mode == indetermined || iwads[i].mode == mode))
{
return iwads[i].description;
}
@ -966,3 +982,21 @@ const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode)
return "Unknown game?";
}
#if DOOM_TINY
GameMission_t IdentifyIWADByName(const char *name) {
size_t i;
GameMission_t mission;
mission = mission_none;
for (i=0; i<arrlen(iwads); ++i)
{
if (!strcasecmp(name, iwads[i].name))
{
mission = iwads[i].mission;
break;
}
}
return mission;
}
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -21,6 +22,7 @@
#include "d_mode.h"
#if !DOOM_ONLY
#define IWAD_MASK_DOOM ((1 << doom) \
| (1 << doom2) \
| (1 << pack_tnt) \
@ -30,6 +32,12 @@
#define IWAD_MASK_HERETIC (1 << heretic)
#define IWAD_MASK_HEXEN (1 << hexen)
#define IWAD_MASK_STRIFE (1 << strife)
#else
#define IWAD_MASK_DOOM ((1 << doom) \
| (1 << doom2) \
| (1 << pack_tnt) \
| (1 << pack_plut))
#endif
typedef struct
{
@ -47,6 +55,8 @@ const char *D_SaveGameIWADName(GameMission_t gamemission);
const char *D_SuggestIWADName(GameMission_t mission, GameMode_t mode);
const char *D_SuggestGameName(GameMission_t mission, GameMode_t mode);
void D_CheckCorrectIWAD(GameMission_t mission);
#if DOOM_TINY
GameMission_t IdentifyIWADByName(const char *name);
#endif
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -38,13 +39,24 @@
#include "net_sdl.h"
#include "net_loop.h"
// The complete set of data for a particular tic.
#if PICO_BUILD
#include "pico/time.h"
#include "hardware/gpio.h"
#if USE_PICO_NET
#include "piconet.h"
#include "doom/m_menu.h"
#endif
CU_REGISTER_DEBUG_PINS(tics)
//CU_SELECT_DEBUG_PINS(tics)
#endif
#if DOOM_TINY
static_assert(sizeof(ticcmd_t) == 8, "");
static_assert(sizeof(ticcmd_set_t) == 8 * NET_MAXPLAYERS, "");
#endif
typedef struct
{
ticcmd_t cmds[NET_MAXPLAYERS];
boolean ingame[NET_MAXPLAYERS];
} ticcmd_set_t;
// Maximum time that we wait in TryRunTics() for netgame data to be
// received before we bail out and render a frame anyway.
@ -61,7 +73,7 @@ typedef struct
// from all players.
//
static ticcmd_set_t ticdata[BACKUPTICS];
ticcmd_set_t ticdata[BACKUPTICS];
// The index of the next tic to be made (with a call to BuildTiccmd).
@ -82,24 +94,33 @@ boolean singletics = false;
// Index of the local player.
static int localplayer;
#if !USE_PICO_NET
static isb_int8_t localplayer;
#else
#include "doom/doomstat.h"
#define localplayer consoleplayer
#endif
// Used for original sync code.
static int skiptics = 0;
static int skiptics = 0;
// Reduce the bandwidth needed by sampling game input less and transmitting
// less. If ticdup is 2, sample half normal, 3 = one third normal, etc.
int ticdup;
isb_int8_t ticdup;
// Amount to offset the timer for game sync.
fixed_t offsetms;
fixed_t offsetms;
// Use new client syncronisation code
static boolean new_sync = true;
#if !USE_PICO_NET
static boolean new_sync = true;
#else
#define new_sync true
#endif
// Callback functions for loop code.
@ -120,74 +141,85 @@ static int player_class;
// 35 fps clock adjusted by offsetms milliseconds
static int GetAdjustedTime(void)
{
static int GetAdjustedTime(void) {
int time_ms;
time_ms = I_GetTimeMS();
if (new_sync)
{
// Use the adjustments from net_client.c only if we are
// using the new sync mode.
if (new_sync) {
// Use the adjustments from net_client.c only if we are
// using the new sync mode.
time_ms += (offsetms / FRACUNIT);
}
return (time_ms * TICRATE) / 1000;
#if DOOM_TINY && TICRATE == 35
// using 64 bit so we can run demo for > 17 hours
uint64_t v = 150323855ull * (uint32_t)time_ms;
return (int)(v >> 32);
#else
return time_ms * TICRATE / 1000;
#endif
}
static boolean BuildNewTic(void)
{
int gameticdiv;
static boolean BuildNewTic(void) {
int gameticdiv;
ticcmd_t cmd;
gameticdiv = gametic/ticdup;
gameticdiv = gametic / ticdup;
I_StartTic ();
I_StartTic();
loop_interface->ProcessEvents();
// Always run the menu
loop_interface->RunMenu();
if (drone)
{
if (drone) {
// In drone mode, do not generate any ticcmds.
return false;
}
if (new_sync)
{
// If playing single player, do not allow tics to buffer
// up very far
if (new_sync) {
// If playing single player, do not allow tics to buffer
// up very far
if (!net_client_connected && maketic - gameticdiv > 2)
return false;
if (!net_client_connected && maketic - gameticdiv > 2)
return false;
// Never go more than ~200ms ahead
// Never go more than ~200ms ahead
if (maketic - gameticdiv > 8)
return false;
}
else
{
if (maketic - gameticdiv >= 5)
return false;
if (maketic - gameticdiv >= MIN(8, BACKUPTICS))
return false;
} else {
if (maketic - gameticdiv >= 5)
return false;
}
//printf ("mk:%i ",maketic);
memset(&cmd, 0, sizeof(ticcmd_t));
loop_interface->BuildTiccmd(&cmd, maketic);
#if !NO_USE_NET
if (net_client_connected)
{
NET_CL_SendTiccmd(&cmd, maketic);
}
#endif
ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd;
#if !DOOM_TINY
ticdata[maketic % BACKUPTICS].ingame[localplayer] = true;
#else
ticdata[maketic % BACKUPTICS].cmds[localplayer].ingame = true;
#endif
#if USE_PICO_NET
if (net_client_connected) {
// piconet uses ticdata directly, so we need it to be initialized above
piconet_new_local_tic(maketic);
}
#endif
++maketic;
@ -199,13 +231,12 @@ static boolean BuildNewTic(void)
// Builds ticcmds for console player,
// sends out a packet
//
int lasttime;
int lasttime;
void NetUpdate (void)
{
void NetUpdate(void) {
int nowtime;
int newtics;
int i;
int i;
// If we are running with singletics (timing a demo), this
// is all done separately.
@ -215,8 +246,10 @@ void NetUpdate (void)
// Run network subsystems
#if !NO_USE_NET
NET_CL_Run();
NET_SV_Run();
#endif
// check time
nowtime = GetAdjustedTime() / ticdup;
@ -224,40 +257,35 @@ void NetUpdate (void)
lasttime = nowtime;
if (skiptics <= newtics)
{
if (skiptics <= newtics) {
newtics -= skiptics;
skiptics = 0;
}
else
{
} else {
skiptics -= newtics;
newtics = 0;
}
// build new ticcmds for console player
for (i=0 ; i<newtics ; i++)
{
if (!BuildNewTic())
{
for (i = 0; i < newtics; i++) {
if (!BuildNewTic()) {
break;
}
}
}
static void D_Disconnected(void)
{
static void D_Disconnected(void) {
#if !USE_PICO_NET
// In drone mode, the game cannot continue once disconnected.
if (drone)
{
if (drone) {
I_Error("Disconnected from server in drone mode.");
}
// disconnected from server
printf("Disconnected from server.\n");
#endif
}
//
@ -265,28 +293,30 @@ static void D_Disconnected(void)
// available.
//
void D_ReceiveTic(ticcmd_t *ticcmds, boolean *players_mask)
{
void D_ReceiveTic(ticcmd_t *ticcmds, boolean *players_mask) {
int i;
// Disconnected from server?
if (ticcmds == NULL && players_mask == NULL)
{
if (ticcmds == NULL && players_mask == NULL) {
D_Disconnected();
return;
}
for (i = 0; i < NET_MAXPLAYERS; ++i)
{
if (!drone && i == localplayer)
{
for (i = 0; i < NET_MAXPLAYERS; ++i) {
if (!drone && i == localplayer) {
// This is us. Don't overwrite it.
}
else
{
} else {
ticdata[recvtic % BACKUPTICS].cmds[i] = ticcmds[i];
#if !USE_PICO_NET
#if !DOOM_TINY
ticdata[recvtic % BACKUPTICS].ingame[i] = players_mask[i];
#else
ticdata[recvtic % BACKUPTICS].cmds[i].ingame = players_mask[i];
#endif
#else
(void)players_mask;
#endif
}
}
@ -299,14 +329,14 @@ void D_ReceiveTic(ticcmd_t *ticcmds, boolean *players_mask)
// Called after the screen is set but before the game starts running.
//
void D_StartGameLoop(void)
{
void D_StartGameLoop(void) {
lasttime = GetAdjustedTime() / ticdup;
}
//
// Block until the game start message is received from the server.
//
#if !NO_USE_NET
static void BlockUntilStart(net_gamesettings_t *settings,
netgame_startup_callback_t callback)
@ -331,94 +361,6 @@ static void BlockUntilStart(net_gamesettings_t *settings,
}
}
void D_StartNetGame(net_gamesettings_t *settings,
netgame_startup_callback_t callback)
{
int i;
offsetms = 0;
recvtic = 0;
settings->consoleplayer = 0;
settings->num_players = 1;
settings->player_classes[0] = player_class;
//!
// @category net
//
// Use original network client sync code rather than the improved
// sync code.
//
settings->new_sync = !M_ParmExists("-oldsync");
//!
// @category net
// @arg <n>
//
// Send n extra tics in every packet as insurance against dropped
// packets.
//
i = M_CheckParmWithArgs("-extratics", 1);
if (i > 0)
settings->extratics = atoi(myargv[i+1]);
else
settings->extratics = 1;
//!
// @category net
// @arg <n>
//
// Reduce the resolution of the game by a factor of n, reducing
// the amount of network bandwidth needed.
//
i = M_CheckParmWithArgs("-dup", 1);
if (i > 0)
settings->ticdup = atoi(myargv[i+1]);
else
settings->ticdup = 1;
if (net_client_connected)
{
// Send our game settings and block until game start is received
// from the server.
NET_CL_StartGame(settings);
BlockUntilStart(settings, callback);
// Read the game settings that were received.
NET_CL_GetSettings(settings);
}
if (drone)
{
settings->consoleplayer = 0;
}
// Set the local player and playeringame[] values.
localplayer = settings->consoleplayer;
for (i = 0; i < NET_MAXPLAYERS; ++i)
{
local_playeringame[i] = i < settings->num_players;
}
// Copy settings to global variables.
ticdup = settings->ticdup;
new_sync = settings->new_sync;
// TODO: Message disabled until we fix new_sync.
//if (!new_sync)
//{
// printf("Syncing netgames like Vanilla Doom.\n");
//}
}
boolean D_InitNetGame(net_connect_data_t *connect_data)
{
@ -528,17 +470,19 @@ void D_QuitNetGame (void)
NET_SV_Shutdown();
NET_CL_Disconnect();
}
#endif
static int GetLowTic(void)
{
static int GetLowTic(void) {
int lowtic;
lowtic = maketic;
if (net_client_connected)
{
if (drone || recvtic < lowtic)
{
if (net_client_connected) {
#if USE_PICO_NET
recvtic = piconet_maybe_recv_tic(recvtic);
if (!net_client_connected) piconet_stop();
#endif
if (drone || recvtic < lowtic) {
lowtic = recvtic;
}
}
@ -550,8 +494,7 @@ static int frameon;
static int frameskip[4];
static int oldnettics;
static void OldNetSync(void)
{
static void OldNetSync(void) {
unsigned int i;
int keyplayer = -1;
@ -560,30 +503,23 @@ static void OldNetSync(void)
// ideally maketic should be 1 - 3 tics above lowtic
// if we are consistantly slower, speed up time
for (i=0 ; i<NET_MAXPLAYERS ; i++)
{
if (local_playeringame[i])
{
for (i = 0; i < NET_MAXPLAYERS; i++) {
if (local_playeringame[i]) {
keyplayer = i;
break;
}
}
if (keyplayer < 0)
{
if (keyplayer < 0) {
// If there are no players, we can never advance anyway
return;
}
if (localplayer == keyplayer)
{
if (localplayer == keyplayer) {
// the key player does not adapt
}
else
{
if (maketic <= recvtic)
{
} else {
if (maketic <= recvtic) {
lasttime--;
// printf ("-");
}
@ -591,8 +527,7 @@ static void OldNetSync(void)
frameskip[frameon & 3] = oldnettics > recvtic;
oldnettics = maketic;
if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
{
if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) {
skiptics = 1;
// printf ("+");
}
@ -601,18 +536,15 @@ static void OldNetSync(void)
// Returns true if there are players in the game:
static boolean PlayersInGame(void)
{
static boolean PlayersInGame(void) {
boolean result = false;
unsigned int i;
// If we are connected to a server, check if there are any players
// in the game.
if (net_client_connected)
{
for (i = 0; i < NET_MAXPLAYERS; ++i)
{
if (net_client_connected) {
for (i = 0; i < NET_MAXPLAYERS; ++i) {
result = result || local_playeringame[i];
}
}
@ -620,8 +552,7 @@ static boolean PlayersInGame(void)
// Whether single or multi-player, unless we are running as a drone,
// we are in the game.
if (!drone)
{
if (!drone) {
result = true;
}
@ -631,13 +562,11 @@ static boolean PlayersInGame(void)
// When using ticdup, certain values must be cleared out when running
// the duplicate ticcmds.
static void TicdupSquash(ticcmd_set_t *set)
{
static void TicdupSquash(ticcmd_set_t *set) {
ticcmd_t *cmd;
unsigned int i;
for (i = 0; i < NET_MAXPLAYERS ; ++i)
{
for (i = 0; i < NET_MAXPLAYERS; ++i) {
cmd = &set->cmds[i];
cmd->chatchar = 0;
if (cmd->buttons & BT_SPECIAL)
@ -648,15 +577,16 @@ static void TicdupSquash(ticcmd_set_t *set)
// When running in single player mode, clear all the ingame[] array
// except the local player.
static void SinglePlayerClear(ticcmd_set_t *set)
{
static void SinglePlayerClear(ticcmd_set_t *set) {
unsigned int i;
for (i = 0; i < NET_MAXPLAYERS; ++i)
{
if (i != localplayer)
{
for (i = 0; i < NET_MAXPLAYERS; ++i) {
if (i != localplayer) {
#if !DOOM_TINY
set->ingame[i] = false;
#else
set->cmds[i].ingame = false;
#endif
}
}
}
@ -665,16 +595,18 @@ static void SinglePlayerClear(ticcmd_set_t *set)
// TryRunTics
//
void TryRunTics (void)
{
int i;
int lowtic;
int entertic;
void TryRunTics(void) {
int i;
int lowtic;
int entertic;
static int oldentertics;
int realtics;
int availabletics;
int counts;
int availabletics;
int counts;
#if PICO_BUILD
DEBUG_PINS_SET(tics, 2);
#endif
// get real tics
entertic = I_GetTime() / ticdup;
realtics = entertic - oldentertics;
@ -683,30 +615,24 @@ void TryRunTics (void)
// in singletics mode, run a single tic every time this function
// is called.
if (singletics)
{
if (singletics) {
BuildNewTic();
}
else
{
NetUpdate ();
} else {
NetUpdate();
}
lowtic = GetLowTic();
availabletics = lowtic - gametic/ticdup;
availabletics = lowtic - gametic / ticdup;
// decide how many tics to run
if (new_sync)
{
counts = availabletics;
}
else
{
if (new_sync) {
counts = availabletics;
} else {
// decide how many tics to run
if (realtics < availabletics-1)
counts = realtics+1;
if (realtics < availabletics - 1)
counts = realtics + 1;
else if (realtics < availabletics)
counts = realtics;
else
@ -715,33 +641,29 @@ void TryRunTics (void)
if (counts < 1)
counts = 1;
if (net_client_connected)
{
if (net_client_connected) {
OldNetSync();
}
}
if (counts < 1)
counts = 1;
counts = 1;
// wait for new tics if needed
while (!PlayersInGame() || lowtic < gametic/ticdup + counts)
{
NetUpdate ();
while (!PlayersInGame() || lowtic < gametic / ticdup + counts) {
NetUpdate();
lowtic = GetLowTic();
if (lowtic < gametic/ticdup)
I_Error ("TryRunTics: lowtic < gametic");
if (lowtic < gametic / ticdup)
I_Error("TryRunTics: lowtic < gametic");
// Still no tics to run? Sleep until some are available.
if (lowtic < gametic/ticdup + counts)
{
if (lowtic < gametic / ticdup + counts) {
// If we're in a netgame, we might spin forever waiting for
// new network data to be received. So don't stay in here
// forever - give the menu a chance to work.
if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS)
{
if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS) {
return;
}
@ -750,43 +672,86 @@ void TryRunTics (void)
}
// run the count * ticdup dics
while (counts--)
{
while (counts--) {
#if PICO_BUILD
DEBUG_PINS_SET(tics, 1);
#endif
ticcmd_set_t *set;
if (!PlayersInGame())
{
if (!PlayersInGame()) {
return;
}
set = &ticdata[(gametic / ticdup) % BACKUPTICS];
if (!net_client_connected)
{
#if DEBUG_CONSISTENCY
if (netgame) printf("apply tic %d\n", gametic);
#endif
if (!net_client_connected) {
SinglePlayerClear(set);
}
for (i=0 ; i<ticdup ; i++)
{
if (gametic/ticdup > lowtic)
I_Error ("gametic>lowtic");
for (i = 0; i < ticdup; i++) {
if (gametic / ticdup > lowtic)
I_Error("gametic>lowtic");
#if !DOOM_TINY
memcpy(local_playeringame, set->ingame, sizeof(local_playeringame));
#else
int lplayer_count = 0;
for(int j=0;j<NET_MAXPLAYERS;j++) {
local_playeringame[j] = set->cmds[j].ingame;
lplayer_count += local_playeringame[j];
}
if (net_client_connected && lplayer_count < 2) {
net_client_connected = false;
piconet_stop();
}
#endif
loop_interface->RunTic(set->cmds, set->ingame);
gametic++;
//#define DUMP_TICS PICO_BUILD
#if DUMP_TICS
#define MAX_TIMES 64
static uint16_t times[MAX_TIMES];
uint32_t us = time_us_32();
#endif
loop_interface->RunTic(set->cmds, local_playeringame);
#if DUMP_TICS
static int ticks;
static uint32_t t0;
times[ticks & (MAX_TIMES - 1)] = time_us_32() - us;
ticks++;
if (0 == (ticks & (MAX_TIMES - 1))) {
int min = 100000000;
int max = 0;
int total = 0;
for (int t = 0; t < MAX_TIMES; t++) {
total += times[t];
if (times[t] > max) max = times[t];
if (times[t] < min) min = times[t];
}
printf("TICKS %d %d/block min/max/avg %d/%d/%d\n", ticks, (int) (time_us_32() - t0), min, max,
total / MAX_TIMES);
t0 = time_us_32();
}
#endif
gametic++;
// modify command for duplicated tics
// modify command for duplicated tics
TicdupSquash(set);
}
}
NetUpdate (); // check for new console commands
NetUpdate(); // check for new console commands
#if PICO_BUILD
DEBUG_PINS_CLR(tics, 1);
#endif
}
#if PICO_BUILD
DEBUG_PINS_CLR(tics, 2);
#endif
}
void D_RegisterLoopCallbacks(loop_interface_t *i)
{
void D_RegisterLoopCallbacks(loop_interface_t *i) {
loop_interface = i;
}
@ -794,8 +759,7 @@ void D_RegisterLoopCallbacks(loop_interface_t *i)
#include "m_misc.h"
#include "w_wad.h"
static boolean StrictDemos(void)
{
static boolean StrictDemos(void) {
//!
// @category demo
//
@ -812,10 +776,8 @@ static boolean StrictDemos(void)
// this extension (no extensions are allowed if -strictdemos is given
// on the command line). A warning is shown on the console using the
// provided string describing the non-vanilla expansion.
boolean D_NonVanillaRecord(boolean conditional, const char *feature)
{
if (!conditional || StrictDemos())
{
boolean D_NonVanillaRecord(boolean conditional, const char *feature) {
if (!conditional || StrictDemos()) {
return false;
}
@ -828,8 +790,8 @@ boolean D_NonVanillaRecord(boolean conditional, const char *feature)
// Returns true if the given lump number corresponds to data from a .lmp
// file, as opposed to a WAD.
static boolean IsDemoFile(int lumpnum)
{
static boolean IsDemoFile(int lumpnum) {
#if !USE_MEMMAP_ONLY
char *lower;
boolean result;
@ -839,6 +801,9 @@ static boolean IsDemoFile(int lumpnum)
free(lower);
return result;
#else
return false;
#endif
}
// If the provided conditional value is true, we're trying to play back
@ -850,15 +815,12 @@ static boolean IsDemoFile(int lumpnum)
// demo that comes from a .lmp file, not a .wad file.
// - Before proceeding, a warning is shown to the user on the console.
boolean D_NonVanillaPlayback(boolean conditional, int lumpnum,
const char *feature)
{
if (!conditional || StrictDemos())
{
const char *feature) {
if (!conditional || StrictDemos()) {
return false;
}
if (!IsDemoFile(lumpnum))
{
if (!IsDemoFile(lumpnum)) {
printf("Warning: WAD contains demo with a non-vanilla extension "
"(%s)\n", feature);
return false;
@ -871,3 +833,105 @@ boolean D_NonVanillaPlayback(boolean conditional, int lumpnum,
return true;
}
void D_StartNetGame(net_gamesettings_t *settings,
netgame_startup_callback_t callback) {
int i;
offsetms = 0;
recvtic = 0;
settings->consoleplayer = 0;
settings->num_players = 1;
settings->player_classes[0] = player_class;
#if !NO_USE_NET
//!
// @category net
//
// Use original network client sync code rather than the improved
// sync code.
//
settings->new_sync = !M_ParmExists("-oldsync");
#endif
//!
// @category net
// @arg <n>
//
// Send n extra tics in every packet as insurance against dropped
// packets.
//
#if !NO_USE_ARGS
i = M_CheckParmWithArgs("-extratics", 1);
if (i > 0)
settings->extratics = atoi(myargv[i+1]);
else
#endif
settings->extratics = 1;
//!
// @category net
// @arg <n>
//
// Reduce the resolution of the game by a factor of n, reducing
// the amount of network bandwidth needed.
//
#if !NO_USE_ARGS
i = M_CheckParmWithArgs("-dup", 1);
if (i > 0)
settings->ticdup = atoi(myargv[i+1]);
else
#endif
settings->ticdup = 1;
#if !NO_USE_NET
if (net_client_connected)
{
// Send our game settings and block until game start is received
// from the server.
NET_CL_StartGame(settings);
BlockUntilStart(settings, callback);
// Read the game settings that were received.
NET_CL_GetSettings(settings);
}
#endif
if (drone) {
settings->consoleplayer = 0;
}
// Set the local player and playeringame[] values.
localplayer = settings->consoleplayer;
for (i = 0; i < NET_MAXPLAYERS; ++i) {
local_playeringame[i] = i < settings->num_players;
}
// Copy settings to global variables.
ticdup = settings->ticdup;
#if !NO_USE_NET
new_sync = settings->new_sync;
#endif
// TODO: Message disabled until we fix new_sync.
//if (!new_sync)
//{
// printf("Syncing netgames like Vanilla Doom.\n");
//}
}
#if USE_PICO_NET
void D_StartPicoNetGame() {
gametic = maketic = recvtic = 0;
net_client_connected = true;
}
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -75,7 +76,8 @@ void D_StartNetGame(net_gamesettings_t *settings,
netgame_startup_callback_t callback);
extern boolean singletics;
extern int gametic, ticdup;
extern int gametic;
extern isb_int8_t ticdup;
// Check if it is permitted to record a demo with a non-vanilla feature.
boolean D_NonVanillaRecord(boolean conditional, const char *feature);
@ -84,5 +86,18 @@ boolean D_NonVanillaRecord(boolean conditional, const char *feature);
boolean D_NonVanillaPlayback(boolean conditional, int lumpnum,
const char *feature);
// The complete set of data for a particular tic.
typedef struct {
ticcmd_t cmds[NET_MAXPLAYERS];
#if !DOOM_TINY
boolean ingame[NET_MAXPLAYERS];
#endif
} ticcmd_set_t;
#if USE_PICO_NET
// setup game loop for piconet play
void D_StartPicoNetGame();
extern ticcmd_set_t ticdata[BACKUPTICS]; // we share the ticdata with piconet
#endif
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -23,26 +24,30 @@
// Valid game mode/mission combinations, with the number of
// episodes/maps for each.
static struct
static const struct
{
GameMission_t mission;
GameMode_t mode;
int episode;
int map;
isb_int8_t episode;
isb_int8_t map;
} valid_modes[] = {
#if !DOOM_ONLY
{ pack_chex, retail, 1, 5 },
#endif
{ doom, shareware, 1, 9 },
{ doom, registered, 3, 9 },
{ doom, retail, 4, 9 },
{ doom2, commercial, 1, 32 },
{ pack_tnt, commercial, 1, 32 },
{ pack_plut, commercial, 1, 32 },
#if !DOOM_ONLY
{ pack_hacx, commercial, 1, 32 },
{ heretic, shareware, 1, 9 },
{ heretic, registered, 3, 9 },
{ heretic, retail, 5, 9 },
{ hexen, commercial, 1, 60 },
{ strife, commercial, 1, 34 },
#endif
};
// Check that a gamemode+gamemission received over the network is valid.
@ -69,7 +74,7 @@ boolean D_ValidEpisodeMap(GameMission_t mission, GameMode_t mode,
// Hacks for Heretic secret episodes
if (mission == heretic)
if (gamemission_is_heretic(mission))
{
if (mode == retail && episode == 6)
{
@ -120,20 +125,26 @@ static struct {
GameMission_t mission;
GameVersion_t version;
} valid_versions[] = {
#if !DOOM_TINY
{ doom, exe_doom_1_2 },
{ doom, exe_doom_1_666 },
{ doom, exe_doom_1_7 },
{ doom, exe_doom_1_8 },
#endif
{ doom, exe_doom_1_9 },
#if !DOOM_ONLY
{ doom, exe_hacx },
#endif
{ doom, exe_ultimate },
{ doom, exe_final },
{ doom, exe_final2 },
#if !DOOM_ONLY
{ doom, exe_chex },
{ heretic, exe_heretic_1_3 },
{ hexen, exe_hexen_1_1 },
{ strife, exe_strife_1_2 },
{ strife, exe_strife_1_31 },
#endif
};
boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version)
@ -143,7 +154,7 @@ boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version)
// All Doom variants can use the Doom versions.
if (mission == doom2 || mission == pack_plut || mission == pack_tnt
|| mission == pack_hacx || mission == pack_chex)
|| gamemission_is_hacx(mission) || gamemission_is_chex(mission))
{
mission = doom;
}
@ -167,17 +178,21 @@ boolean D_IsEpisodeMap(GameMission_t mission)
switch (mission)
{
case doom:
#if !DOOM_ONLY
case heretic:
case pack_chex:
#endif
return true;
case none:
case hexen:
case mission_none:
case doom2:
case pack_hacx:
case pack_tnt:
case pack_plut:
#if !DOOM_ONLY
case hexen:
case pack_hacx:
case strife:
#endif
default:
return false;
}
@ -187,7 +202,7 @@ const char *D_GameMissionString(GameMission_t mission)
{
switch (mission)
{
case none:
case mission_none:
default:
return "none";
case doom:
@ -198,6 +213,7 @@ const char *D_GameMissionString(GameMission_t mission)
return "tnt";
case pack_plut:
return "plutonia";
#if !DOOM_ONLY
case pack_hacx:
return "hacx";
case pack_chex:
@ -208,6 +224,7 @@ const char *D_GameMissionString(GameMission_t mission)
return "hexen";
case strife:
return "strife";
#endif
}
}

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -30,14 +31,25 @@ typedef enum
doom2, // Doom 2
pack_tnt, // Final Doom: TNT: Evilution
pack_plut, // Final Doom: The Plutonia Experiment
#if !DOOM_ONLY
pack_chex, // Chex Quest (modded doom)
pack_hacx, // Hacx (modded doom2)
heretic, // Heretic
hexen, // Hexen
strife, // Strife
#endif
none
mission_none
} GameMission_t;
#if !DOOM_ONLY
#define gamemission_is_chex(m) ((m) == pack_chex)
#define gamemission_is_hacx(m) ((m) == pack_hacx)
#define gamemission_is_heretic(m) ((m) == heretic)
#else
#define gamemission_is_chex(m) 0
#define gamemission_is_hacx(m) 0
#define gamemission_is_heretic(m) 0
#endif
// The "mode" allows more accurate specification of the game mode we are
// in: eg. shareware vs. registered. So doom1.wad and doom.wad are the
@ -61,10 +73,13 @@ typedef enum
exe_doom_1_7, // Doom 1.7/1.7a: "
exe_doom_1_8, // Doom 1.8: "
exe_doom_1_9, // Doom 1.9: "
#if !DOOM_ONLY
exe_hacx, // Hacx
#endif
exe_ultimate, // Ultimate Doom (retail)
exe_final, // Final Doom
exe_final2, // Final Doom (alternate exe)
#if !DOOM_ONLY
exe_chex, // Chex Quest executable (based on Final Doom)
exe_heretic_1_3, // Heretic 1.3
@ -72,7 +87,13 @@ typedef enum
exe_hexen_1_1, // Hexen 1.1
exe_strife_1_2, // Strife v1.2
exe_strife_1_31 // Strife v1.31
#endif
} GameVersion_t;
#if !DOOM_ONLY
#define gameversion_is_chex(v) ((v) == exe_chex)
#else
#define gameversion_is_chex(v) 0
#endif
// What IWAD variant are we using?

View file

@ -2,6 +2,7 @@
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -39,18 +40,24 @@ typedef struct
// villsa [STRIFE] according to the asm,
// consistancy is a short, not a byte
byte consistancy; // checks for net game
#if !DOOM_ONLY
// villsa - Strife specific:
byte buttons2;
int inventory;
// Heretic/Hexen specific:
byte lookfly; // look/fly up/down/centering
byte arti; // artitype_t to use
} ticcmd_t;
#endif
#if DOOM_TINY
boolean ingame; // saves space elsewhere by moving from separate array (and the ^ is 7 bytes)
#endif
} ticcmd_t;
#if DOOM_TINY
#include <assert.h>
static_assert(sizeof(ticcmd_t) == 8, "");
#endif
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -28,6 +29,7 @@
#include "deh_defs.h"
#include "deh_io.h"
#if !NO_USE_DEH
typedef enum
{
DEH_INPUT_FILE,
@ -66,12 +68,12 @@ static deh_context_t *DEH_NewContext(void)
{
deh_context_t *context;
context = Z_Malloc(sizeof(*context), PU_STATIC, NULL);
context = Z_Malloc(sizeof(*context), PU_STATIC, 0);
// Initial read buffer size of 128 bytes
context->readbuffer_size = 128;
context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, NULL);
context->readbuffer = Z_Malloc(context->readbuffer_size, PU_STATIC, 0);
context->linenum = 0;
context->last_was_newline = true;
@ -120,7 +122,11 @@ deh_context_t *DEH_OpenLump(int lumpnum)
context->input_buffer_pos = 0;
context->filename = malloc(9);
#if !USE_MEMMAP_ONLY
M_StringCopy(context->filename, lumpinfo[lumpnum]->name, 9);
#else
M_StringCopy(context->filename, "<unknown>", 9);
#endif
return context;
}
@ -213,7 +219,7 @@ static void IncreaseReadBuffer(deh_context_t *context)
int newbuffer_size;
newbuffer_size = context->readbuffer_size * 2;
newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, NULL);
newbuffer = Z_Malloc(newbuffer_size, PU_STATIC, 0);
memcpy(newbuffer, context->readbuffer, context->readbuffer_size);
@ -335,3 +341,4 @@ boolean DEH_HadError(deh_context_t *context)
return context->had_error;
}
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -31,6 +32,7 @@
#include "deh_io.h"
#include "deh_main.h"
#if !NO_USE_DEH
extern deh_section_t *deh_section_types[];
extern const char *deh_signatures[];
@ -487,6 +489,7 @@ int DEH_LoadLumpByName(const char *name, boolean allow_long, boolean allow_error
// Check the command line for -deh argument, and others.
void DEH_ParseCommandLine(void)
{
#if !NO_USE_ARGS
char *filename;
int p;
@ -511,5 +514,7 @@ void DEH_ParseCommandLine(void)
++p;
}
}
#endif
}
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -27,6 +28,7 @@
#include "deh_mapping.h"
#if !NO_USE_DEH
static deh_mapping_entry_t *GetMappingEntryByName(deh_context_t *context,
deh_mapping_t *mapping,
char *name)
@ -198,3 +200,4 @@ void DEH_StructSHA1Sum(sha1_context_t *context, deh_mapping_t *mapping,
}
}
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -26,6 +27,7 @@
#include "z_zone.h"
#if !NO_USE_DEH
typedef struct
{
char *from_text;
@ -103,7 +105,7 @@ static void InitHashTable(void)
hash_table_entries = 0;
hash_table_length = 16;
hash_table = Z_Malloc(sizeof(deh_substitution_t *) * hash_table_length,
PU_STATIC, NULL);
PU_STATIC, 0);
memset(hash_table, 0, sizeof(deh_substitution_t *) * hash_table_length);
}
@ -124,7 +126,7 @@ static void IncreaseHashtable(void)
hash_table_length *= 2;
hash_table = Z_Malloc(sizeof(deh_substitution_t *) * hash_table_length,
PU_STATIC, NULL);
PU_STATIC, 0);
memset(hash_table, 0, sizeof(deh_substitution_t *) * hash_table_length);
// go through the old table and insert all the old entries
@ -184,7 +186,7 @@ void DEH_AddStringReplacement(const char *from_text, const char *to_text)
Z_Free(sub->to_text);
len = strlen(to_text) + 1;
sub->to_text = Z_Malloc(len, PU_STATIC, NULL);
sub->to_text = Z_Malloc(len, PU_STATIC, 0);
memcpy(sub->to_text, to_text, len);
}
else
@ -194,11 +196,11 @@ void DEH_AddStringReplacement(const char *from_text, const char *to_text)
// We need to create our own duplicates of the provided strings.
len = strlen(from_text) + 1;
sub->from_text = Z_Malloc(len, PU_STATIC, NULL);
sub->from_text = Z_Malloc(len, PU_STATIC, 0);
memcpy(sub->from_text, from_text, len);
len = strlen(to_text) + 1;
sub->to_text = Z_Malloc(len, PU_STATIC, NULL);
sub->to_text = Z_Malloc(len, PU_STATIC, 0);
memcpy(sub->to_text, to_text, len);
DEH_AddToHashtable(sub);
@ -431,3 +433,4 @@ void DEH_snprintf(char *buffer, size_t len, const char *fmt, ...)
va_end(args);
}
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -24,19 +25,18 @@
// Used to do dehacked text substitutions throughout the program
#if !NO_USE_DEH
const char *DEH_String(const char *s) PRINTF_ARG_ATTR(1);
void DEH_printf(const char *fmt, ...) PRINTF_ATTR(1, 2);
void DEH_fprintf(FILE *fstream, const char *fmt, ...) PRINTF_ATTR(2, 3);
void DEH_snprintf(char *buffer, size_t len, const char *fmt, ...) PRINTF_ATTR(3, 4);
void DEH_AddStringReplacement(const char *from_text, const char *to_text);
#if 0
#else
// Static macro versions of the functions above
#define DEH_String(x) (x)
#define DEH_printf printf
#define DEH_fprintf fprintf
#define DEH_fprintf(x,...) stderr_print(__VA_ARGS__)
#define DEH_snprintf snprintf
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -27,6 +28,7 @@
#include "deh_io.h"
#include "deh_main.h"
#if !NO_USE_DEH
// Given a string length, find the maximum length of a
// string that can replace it.
@ -111,3 +113,4 @@ deh_section_t deh_section_text =
NULL,
};
#endif

View file

@ -1,4 +1,6 @@
add_library(doom STATIC
add_library(doom INTERFACE)
cmake_policy(SET CMP0076 NEW)
target_sources(doom INTERFACE
am_map.c am_map.h
deh_ammo.c
deh_bexstr.c
@ -21,8 +23,10 @@ add_library(doom STATIC
dstrings.c dstrings.h
d_textur.h
d_think.h
f_finale.c f_finale.h
f_wipe.c f_wipe.h
f_finale.h
f_finale.c
f_wipe.c
f_wipe.h
g_game.c g_game.h
hu_lib.c hu_lib.h
hu_stuff.c hu_stuff.h
@ -50,6 +54,7 @@ add_library(doom STATIC
p_tick.c p_tick.h
p_user.c
r_bsp.c r_bsp.h
r_data_whd.c
r_data.c r_data.h
r_defs.h
r_draw.c r_draw.h
@ -67,5 +72,9 @@ add_library(doom STATIC
st_stuff.c st_stuff.h
wi_stuff.c wi_stuff.h)
target_include_directories(doom PRIVATE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../")
target_link_libraries(doom SDL2::SDL2 SDL2::mixer SDL2::net)
target_include_directories(doom INTERFACE "../" "${CMAKE_CURRENT_BINARY_DIR}/../../")
if (NOT PICO_SDK)
target_link_libraries(doom INTERFACE SDL2::SDL2 SDL2::mixer SDL2::net)
else()
target_link_libraries(doom INTERFACE pico_stdlib pico_multicore)
endif()

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -143,7 +144,7 @@ typedef struct
// starting from the middle.
//
#define R ((8*PLAYERRADIUS)/7)
mline_t player_arrow[] = {
static const mline_t player_arrow[] = {
{ { -R+R/8, 0 }, { R, 0 } }, // -----
{ { R, 0 }, { R-R/2, R/4 } }, // ----->
{ { R, 0 }, { R-R/2, -R/4 } },
@ -155,7 +156,7 @@ mline_t player_arrow[] = {
#undef R
#define R ((8*PLAYERRADIUS)/7)
mline_t cheat_player_arrow[] = {
static const mline_t cheat_player_arrow[] = {
{ { -R+R/8, 0 }, { R, 0 } }, // -----
{ { R, 0 }, { R-R/2, R/6 } }, // ----->
{ { R, 0 }, { R-R/2, -R/6 } },
@ -175,16 +176,16 @@ mline_t cheat_player_arrow[] = {
};
#undef R
#define R (FRACUNIT)
mline_t triangle_guy[] = {
{ { (fixed_t)(-.867*R), (fixed_t)(-.5*R) }, { (fixed_t)(.867*R ), (fixed_t)(-.5*R) } },
{ { (fixed_t)(.867*R ), (fixed_t)(-.5*R) }, { (fixed_t)(0 ), (fixed_t)(R ) } },
{ { (fixed_t)(0 ), (fixed_t)(R ) }, { (fixed_t)(-.867*R), (fixed_t)(-.5*R) } }
};
#undef R
//#define R (FRACUNIT)
//static const mline_t triangle_guy[] = {
// { { (fixed_t)(-.867*R), (fixed_t)(-.5*R) }, { (fixed_t)(.867*R ), (fixed_t)(-.5*R) } },
// { { (fixed_t)(.867*R ), (fixed_t)(-.5*R) }, { (fixed_t)(0 ), (fixed_t)(R ) } },
// { { (fixed_t)(0 ), (fixed_t)(R ) }, { (fixed_t)(-.867*R), (fixed_t)(-.5*R) } }
//};
//#undef R
#define R (FRACUNIT)
mline_t thintriangle_guy[] = {
static const mline_t thintriangle_guy[] = {
{ { (fixed_t)(-.5*R), (fixed_t)(-.7*R) }, { (fixed_t)(R ), (fixed_t)(0 ) } },
{ { (fixed_t)(R ), (fixed_t)(0 ) }, { (fixed_t)(-.5*R), (fixed_t)(.7*R ) } },
{ { (fixed_t)(-.5*R), (fixed_t)(.7*R ) }, { (fixed_t)(-.5*R), (fixed_t)(-.7*R) } }
@ -194,14 +195,14 @@ mline_t thintriangle_guy[] = {
static int cheating = 0;
static int grid = 0;
static isb_int8_t cheating = 0;
static isb_int8_t grid = 0;
static int leveljuststarted = 1; // kluge until AM_LevelInit() is called
boolean automapactive = false;
static int finit_width = SCREENWIDTH;
static int finit_height = SCREENHEIGHT - ST_HEIGHT;
#define finit_width SCREENWIDTH
#define finit_height (SCREENHEIGHT - ST_HEIGHT)
// location of window on screen
static int f_x;
@ -211,7 +212,11 @@ static int f_y;
static int f_w;
static int f_h;
#if DOOM_TINY
#define lightlev 0 // unused anyway
#else
static int lightlev; // used for funky strobing effect
#endif
static pixel_t* fb; // pseudo-frame buffer
static int amclock;
@ -259,11 +264,13 @@ static fixed_t scale_ftom;
static player_t *plr; // the player represented by an arrow
static patch_t *marknums[10]; // numbers used for marking by the automap
#if !USE_WHD
static vpatch_handle_large_t marknums[10]; // numbers used for marking by the automap
#endif
static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are
static int markpointnum = 0; // next point to be assigned
static int followplayer = 1; // specifies whether to follow the player around
static isb_int8_t followplayer = 1; // specifies whether to follow the player around
cheatseq_t cheat_amap = CHEAT("iddt", 0);
@ -328,8 +335,8 @@ void AM_restoreScaleAndLoc(void)
m_x = old_m_x;
m_y = old_m_y;
} else {
m_x = plr->mo->x - m_w/2;
m_y = plr->mo->y - m_h/2;
m_x = plr->mo->xy.x - m_w/2;
m_y = plr->mo->xy.y - m_h/2;
}
m_x2 = m_x + m_w;
m_y2 = m_y + m_h;
@ -365,17 +372,22 @@ void AM_findMinMaxBoundaries(void)
for (i=0;i<numvertexes;i++)
{
if (vertexes[i].x < min_x)
min_x = vertexes[i].x;
else if (vertexes[i].x > max_x)
max_x = vertexes[i].x;
if (vertex_x_raw(&vertexes[i]) < min_x)
min_x = vertex_x_raw(&vertexes[i]);
else if (vertex_x_raw(&vertexes[i]) > max_x)
max_x = vertex_x_raw(&vertexes[i]);
if (vertexes[i].y < min_y)
min_y = vertexes[i].y;
else if (vertexes[i].y > max_y)
max_y = vertexes[i].y;
if (vertex_y_raw(&vertexes[i]) < min_y)
min_y = vertex_y_raw(&vertexes[i]);
else if (vertex_y_raw(&vertexes[i]) > max_y)
max_y = vertex_y_raw(&vertexes[i]);
}
min_x = vertex_raw_to_fixed(min_x);
min_y = vertex_raw_to_fixed(min_y);
max_x = vertex_raw_to_fixed(max_x);
max_y = vertex_raw_to_fixed(max_y);
max_w = max_x - min_x;
max_h = max_y - min_y;
@ -429,11 +441,12 @@ void AM_initVariables(void)
static event_t st_notify = { ev_keyup, AM_MSGENTERED, 0, 0 };
automapactive = true;
fb = I_VideoBuffer;
f_oldloc.x = INT_MAX;
amclock = 0;
#if !DOOM_TINY
lightlev = 0;
#endif
m_paninc.x = m_paninc.y = 0;
ftom_zoommul = FRACUNIT;
@ -461,8 +474,8 @@ void AM_initVariables(void)
}
}
m_x = plr->mo->x - m_w/2;
m_y = plr->mo->y - m_h/2;
m_x = plr->mo->xy.x - m_w/2;
m_y = plr->mo->xy.y - m_h/2;
AM_changeWindowLoc();
// for saving & restoring
@ -486,14 +499,17 @@ void AM_loadPics(void)
for (i=0;i<10;i++)
{
#if !USE_WHD
DEH_snprintf(namebuf, 9, "AMMNUM%d", i);
marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
#endif
}
}
void AM_unloadPics(void)
{
#if !DOOM_TINY
int i;
char namebuf[9];
@ -502,6 +518,7 @@ void AM_unloadPics(void)
DEH_snprintf(namebuf, 9, "AMMNUM%d", i);
W_ReleaseLumpName(namebuf);
}
#endif
}
void AM_clearMarks(void)
@ -555,7 +572,7 @@ void AM_Stop (void)
//
void AM_Start (void)
{
static int lastlevel = -1, lastepisode = -1;
static isb_int8_t lastlevel = -1, lastepisode = -1;
if (!stopped) AM_Stop();
stopped = false;
@ -605,6 +622,7 @@ AM_Responder
rc = false;
#if !NO_USE_JOYSTICK
if (ev->type == ev_joystick && joybautomap >= 0
&& (ev->data1 & (1 << joybautomap)) != 0)
{
@ -624,6 +642,7 @@ AM_Responder
return true;
}
#endif
if (!automapactive)
{
@ -784,14 +803,14 @@ void AM_changeWindowScale(void)
void AM_doFollowPlayer(void)
{
if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y)
if (f_oldloc.x != plr->mo->xy.x || f_oldloc.y != plr->mo->xy.y)
{
m_x = FTOM(MTOF(plr->mo->x)) - m_w/2;
m_y = FTOM(MTOF(plr->mo->y)) - m_h/2;
m_x = FTOM(MTOF(plr->mo->xy.x)) - m_w/2;
m_y = FTOM(MTOF(plr->mo->xy.y)) - m_h/2;
m_x2 = m_x + m_w;
m_y2 = m_y + m_h;
f_oldloc.x = plr->mo->x;
f_oldloc.y = plr->mo->y;
f_oldloc.x = plr->mo->xy.x;
f_oldloc.y = plr->mo->xy.y;
// m_x = FTOM(MTOF(plr->mo->x - m_w/2));
// m_y = FTOM(MTOF(plr->mo->y - m_h/2));
@ -805,6 +824,7 @@ void AM_doFollowPlayer(void)
//
//
//
#if !DOOM_TINY
void AM_updateLightLev(void)
{
static int nexttic = 0;
@ -821,7 +841,7 @@ void AM_updateLightLev(void)
}
}
#endif
//
// Updates on Game Tick
@ -856,6 +876,9 @@ void AM_Ticker (void)
//
void AM_clearFB(int color)
{
#if PICODOOM_RENDER_NEWHOPE
assert(f_h <= MAIN_VIEWHEIGHT);
#endif
memset(fb, color, f_w*f_h*sizeof(*fb));
}
@ -1017,7 +1040,8 @@ AM_drawFline
register int ax;
register int ay;
register int d;
#if !DOOM_TINY
static int fuck = 0;
// For debugging only
@ -1029,6 +1053,7 @@ AM_drawFline
DEH_fprintf(stderr, "fuck %d \r", fuck++);
return;
}
#endif
#define PUTDOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc)
@ -1148,37 +1173,38 @@ void AM_drawWalls(void)
int i;
static mline_t l;
for (i=0;i<numlines;i++)
line_t *li = lines;
for (i=0;i<numlines;i++,li += line_next_step(li))
{
l.a.x = lines[i].v1->x;
l.a.y = lines[i].v1->y;
l.b.x = lines[i].v2->x;
l.b.y = lines[i].v2->y;
if (cheating || (lines[i].flags & ML_MAPPED))
l.a.x = vertex_x(line_v1(li));
l.a.y = vertex_y(line_v1(li));
l.b.x = vertex_x(line_v2(li));
l.b.y = vertex_y(line_v2(li));
if (cheating || line_is_mapped(li))
{
if ((lines[i].flags & LINE_NEVERSEE) && !cheating)
if ((line_flags(li) & LINE_NEVERSEE) && !cheating)
continue;
if (!lines[i].backsector)
if (!line_backsector(li))
{
AM_drawMline(&l, WALLCOLORS+lightlev);
}
else
{
if (lines[i].special == 39)
if (line_special(li) == 39)
{ // teleporters
AM_drawMline(&l, WALLCOLORS+WALLRANGE/2);
}
else if (lines[i].flags & ML_SECRET) // secret door
else if (line_flags(li) & ML_SECRET) // secret door
{
if (cheating) AM_drawMline(&l, SECRETWALLCOLORS + lightlev);
else AM_drawMline(&l, WALLCOLORS+lightlev);
}
else if (lines[i].backsector->floorheight
!= lines[i].frontsector->floorheight) {
else if (line_backsector(li)->rawfloorheight
!= line_frontsector(li)->rawfloorheight) {
AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change
}
else if (lines[i].backsector->ceilingheight
!= lines[i].frontsector->ceilingheight) {
else if (line_backsector(li)->rawceilingheight
!= line_frontsector(li)->rawceilingheight) {
AM_drawMline(&l, CDWALLCOLORS+lightlev); // ceiling level change
}
else if (cheating) {
@ -1188,7 +1214,7 @@ void AM_drawWalls(void)
}
else if (plr->powers[pw_allmap])
{
if (!(lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3);
if (!(line_flags(li) & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3);
}
}
}
@ -1207,19 +1233,19 @@ AM_rotate
fixed_t tmpx;
tmpx =
FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT])
- FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]);
FixedMul(*x,finecosine(a>>ANGLETOFINESHIFT))
- FixedMul(*y,finesine(a>>ANGLETOFINESHIFT));
*y =
FixedMul(*x,finesine[a>>ANGLETOFINESHIFT])
+ FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]);
FixedMul(*x,finesine(a>>ANGLETOFINESHIFT))
+ FixedMul(*y,finecosine(a>>ANGLETOFINESHIFT));
*x = tmpx;
}
void
AM_drawLineCharacter
( mline_t* lineguy,
( const mline_t* lineguy,
int lineguylines,
fixed_t scale,
angle_t angle,
@ -1279,11 +1305,11 @@ void AM_drawPlayers(void)
if (cheating)
AM_drawLineCharacter
(cheat_player_arrow, arrlen(cheat_player_arrow), 0,
plr->mo->angle, WHITE, plr->mo->x, plr->mo->y);
mobj_angle(plr->mo), WHITE, plr->mo->xy.x, plr->mo->xy.y);
else
AM_drawLineCharacter
(player_arrow, arrlen(player_arrow), 0, plr->mo->angle,
WHITE, plr->mo->x, plr->mo->y);
(player_arrow, arrlen(player_arrow), 0, mobj_angle(plr->mo),
WHITE, plr->mo->xy.x, plr->mo->xy.y);
return;
}
@ -1304,8 +1330,8 @@ void AM_drawPlayers(void)
color = their_colors[their_color];
AM_drawLineCharacter
(player_arrow, arrlen(player_arrow), 0, p->mo->angle,
color, p->mo->x, p->mo->y);
(player_arrow, arrlen(player_arrow), 0, mobj_angle(p->mo),
color, p->mo->xy.x, p->mo->xy.y);
}
}
@ -1320,13 +1346,13 @@ AM_drawThings
for (i=0;i<numsectors;i++)
{
t = sectors[i].thinglist;
t = shortptr_to_mobj(sectors[i].thinglist);
while (t)
{
AM_drawLineCharacter
(thintriangle_guy, arrlen(thintriangle_guy),
16<<FRACBITS, t->angle, colors+lightlev, t->x, t->y);
t = t->snext;
16<<FRACBITS, mobj_angle(t), colors+lightlev, t->xy.x, t->xy.y);
t = mobj_snext(t);
}
}
}
@ -1345,8 +1371,14 @@ void AM_drawMarks(void)
h = 6; // because something's wrong with the wad, i guess
fx = CXMTOF(markpoints[i].x);
fy = CYMTOF(markpoints[i].y);
vpatch_handle_large_t patch;
#if USE_WHD
patch = VPATCH_AMMNUM0 + i;
#else
patch = marknums[i];
#endif
if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h)
V_DrawPatch(fx, fy, marknums[i]);
V_DrawPatch(fx, fy, patch);
}
}
@ -1361,6 +1393,7 @@ void AM_drawCrosshair(int color)
void AM_Drawer (void)
{
if (!automapactive) return;
fb = I_VideoBuffer;
AM_clearFB(BACKGROUND);
if (grid)

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -34,7 +35,7 @@
// atkstate, i.e. attack/fire/hit frame
// flashstate, muzzle flash
//
weaponinfo_t weaponinfo[NUMWEAPONS] =
should_be_const weaponinfo_t weaponinfo[NUMWEAPONS] =
{
{
// fist

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -28,14 +29,14 @@
typedef struct
{
ammotype_t ammo;
int upstate;
int downstate;
int readystate;
int atkstate;
int flashstate;
isb_int16_t upstate;
isb_int16_t downstate;
isb_int16_t readystate;
isb_int16_t atkstate;
isb_int16_t flashstate;
} weaponinfo_t;
extern weaponinfo_t weaponinfo[NUMWEAPONS];
extern should_be_const weaponinfo_t weaponinfo[NUMWEAPONS];
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -44,7 +45,8 @@ void D_StartTitle (void);
//
extern gameaction_t gameaction;
extern const char* pagename;
extern boolean advancedemo;
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -68,7 +69,6 @@ static void PlayerQuitGame(player_t *player)
static void RunTic(ticcmd_t *cmds, boolean *ingame)
{
extern boolean advancedemo;
unsigned int i;
// Check for player quits.
@ -143,7 +143,11 @@ static void SaveGameSettings(net_gamesettings_t *settings)
settings->episode = startepisode;
settings->map = startmap;
settings->skill = startskill;
#if !DOOM_TINY
settings->loadgame = startloadgame;
#else
settings->loadgame = -1;
#endif
settings->gameversion = gameversion;
settings->nomonsters = nomonsters;
settings->fast_monsters = fastparm;
@ -166,6 +170,7 @@ static void InitConnectData(net_connect_data_t *connect_data)
// Run as the left screen in three screen mode.
//
#if !DOOM_TINY
if (M_CheckParm("-left") > 0)
{
viewangleoffset = ANG90;
@ -183,16 +188,17 @@ static void InitConnectData(net_connect_data_t *connect_data)
viewangleoffset = ANG270;
connect_data->drone = true;
}
#endif
//
// Connect data
//
// Game type fields:
connect_data->gamemode = gamemode;
connect_data->_gamemode = gamemode;
connect_data->gamemission = gamemission;
#if !DOOM_TINY
// Are we recording a demo? Possibly set lowres turn mode
connect_data->lowres_turn = (M_ParmExists("-record")
@ -200,9 +206,14 @@ static void InitConnectData(net_connect_data_t *connect_data)
|| M_ParmExists("-shorttics");
// Read checksums of our WAD directory and dehacked information
#endif
#if !NO_USE_CHECKSUM
W_Checksum(connect_data->wad_sha1sum);
DEH_Checksum(connect_data->deh_sha1sum);
#else
// leave as garbage for now
#endif
// Are we playing with the Freedoom IWAD?
@ -214,6 +225,7 @@ void D_ConnectNetGame(void)
net_connect_data_t connect_data;
InitConnectData(&connect_data);
#if !NO_USE_NET
netgame = D_InitNetGame(&connect_data);
//!
@ -224,10 +236,13 @@ void D_ConnectNetGame(void)
// demos.
//
#if !NO_USE_ARGS
if (M_CheckParm("-solo-net") > 0)
{
netgame = true;
}
#endif
#endif
}
//
@ -249,6 +264,7 @@ void D_CheckNetGame (void)
D_StartNetGame(&settings, NULL);
LoadGameSettings(&settings);
#if !DOOM_TINY
DEH_printf("startskill %i deathmatch: %i startmap: %i startepisode: %i\n",
startskill, deathmatch, startmap, startepisode);
@ -274,5 +290,6 @@ void D_CheckNetGame (void)
printf(".\n");
}
}
#endif
}

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -111,13 +112,13 @@ typedef struct player_s
// Is wp_nochange if not changing.
weapontype_t pendingweapon;
int weaponowned[NUMWEAPONS];
int ammo[NUMAMMO];
int maxammo[NUMAMMO];
isb_int8_t weaponowned[NUMWEAPONS];
int ammo[NUMAMMO]; // int because pointed to by st_ stuff
int maxammo[NUMAMMO]; // int because pointed to by st_ stuff
// True if button down last tic.
int attackdown;
int usedown;
isb_int8_t attackdown;
isb_int8_t usedown;
// Bit flags, for cheats and debug.
// See cheat_t, above.
@ -142,21 +143,21 @@ typedef struct player_s
mobj_t* attacker;
// So gun flashes light up areas.
int extralight;
isb_int8_t extralight;
// Current PLAYPAL, ???
// can be set to REDCOLORMAP for pain, etc.
int fixedcolormap;
isb_int8_t fixedcolormap;
// Player skin colorshift,
// 0-3 for which color to draw player.
int colormap;
// Overlay view sprites (gun, etc).
pspdef_t psprites[NUMPSPRITES];
isb_int8_t colormap;
// True if secret level has been done.
boolean didsecret;
boolean didsecret;
// Overlay view sprites (gun, etc).
pspdef_t psprites[NUMPSPRITES];
} player_t;

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -51,17 +52,64 @@ typedef union
// Historically, "think_t" is yet another
// function pointer to a routine to handle
// an actor.
typedef actionf_t think_t;
typedef enum {
ThinkF_NULL = 0,
ThinkF_T_MoveCeiling,
ThinkF_T_VerticalDoor,
ThinkF_T_PlatRaise,
ThinkF_T_FireFlicker,
ThinkF_T_LightFlash,
ThinkF_T_StrobeFlash,
ThinkF_T_MoveFloor,
ThinkF_T_Glow,
ThinkF_P_MobjThinker,
ThinkF_INVALID, // only set during save game load, should be overwritten
ThinkF_REMOVED,
NUM_THINKF
} think_t_orig;
#include <assert.h>
static_assert(NUM_THINKF < 256, "");
typedef uint8_t think_t;
// Doubly linked list of actors.
// linked list of actors.
typedef struct thinker_s
{
struct thinker_s* prev;
struct thinker_s* next;
// todo graham this can be an array index into an active thinker array
shortptr_t /*struct thinker_s*/ sp_next;
think_t function;
} thinker_t;
#if DOOM_SMALL
uint8_t pool_info; // 0xff if not in memory pool
#endif
} __attribute__((aligned(4))) thinker_t; // must be aligned for shortptr
#if DOOM_SMALL
#if PICO_ON_DEVICE
static_assert(sizeof(thinker_t) == 4, ""); // note z_zone requires this too to zero out
#endif
#endif
#define thinker_next(t) ((thinker_t *)shortptr_to_ptr((t)->sp_next))
static inline shortptr_t thinker_to_shortptr(thinker_t *thinker) {
return ptr_to_shortptr(thinker);
}
#if !USE_THINKER_POOL
#include "z_zone.h"
static inline void *Z_ThinkMalloc(int size, int tag, void *user) {
thinker_t *t = Z_Malloc(size, tag, user);
memset(t, 0, size);
return t;
}
#define Z_ThinkFree Z_Free
#else
#include "z_zone.h"
thinker_t *Z_ThinkMallocImpl(int size);
void Z_ThinkFree(thinker_t *thinker);
static inline void *Z_ThinkMalloc(int size, int tag, void *user) {
assert(!user);
assert(tag == PU_LEVEL);
return Z_ThinkMallocImpl(size);
}
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -26,6 +27,7 @@
#include "deh_main.h"
#include "p_local.h"
#if !NO_USE_DEH
static void *DEH_AmmoStart(deh_context_t *context, char *line)
{
int ammo_number = 0;
@ -101,3 +103,4 @@ deh_section_t deh_section_ammo =
DEH_AmmoSHA1Hash,
};
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2014 Fabian Greffrath
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -25,6 +26,7 @@
#include "dstrings.h"
#if !NO_USE_DEH
typedef struct {
const char *macro;
const char *string;
@ -374,3 +376,4 @@ deh_section_t deh_section_bexstr =
NULL,
NULL,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -26,6 +27,7 @@
#include "am_map.h"
#include "st_stuff.h"
#if !NO_USE_DEH
typedef struct
{
const char *name;
@ -144,3 +146,4 @@ deh_section_t deh_section_cheat =
NULL,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -19,6 +20,8 @@
#include "deh_defs.h"
#include "deh_main.h"
#if !NO_USE_DEH
const char *deh_signatures[] =
{
"Patch File for DeHackEd v2.3",
@ -66,3 +69,4 @@ deh_section_t *deh_section_types[] =
NULL
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -27,6 +28,7 @@
#include "deh_main.h"
#include "deh_mapping.h"
#if !NO_USE_DEH
DEH_BEGIN_MAPPING(state_mapping, state_t)
DEH_MAPPING("Sprite number", sprite)
DEH_MAPPING("Sprite subnumber", frame)
@ -159,3 +161,4 @@ deh_section_t deh_section_frame =
DEH_FrameSHA1Sum,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -24,6 +25,7 @@
#include "deh_main.h"
#include "deh_misc.h"
#if !NO_USE_DEH
// Dehacked: "Initial Health"
// This is the initial health a player has when starting anew.
// See G_PlayerReborn in g_game.c
@ -226,3 +228,4 @@ deh_section_t deh_section_misc =
DEH_MiscSHA1Sum,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -35,6 +36,8 @@
#define DEH_DEFAULT_BFG_CELLS_PER_SHOT 40
#define DEH_DEFAULT_SPECIES_INFIGHTING 0
#if !NO_USE_DEH
extern int deh_initial_health;
extern int deh_initial_bullets;
extern int deh_max_health;
@ -52,7 +55,7 @@ extern int deh_idkfa_armor_class;
extern int deh_bfg_cells_per_shot;
extern int deh_species_infighting;
#if 0
#else
// To compile without dehacked, it's possible to use these:

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -26,6 +27,7 @@
#include "deh_io.h"
#include "deh_main.h"
#if !NO_USE_DEH
static actionf_t codeptrs[NUMSTATES];
static int CodePointerIndex(actionf_t *ptr)
@ -140,3 +142,4 @@ deh_section_t deh_section_pointer =
DEH_PointerSHA1Sum,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -24,6 +25,7 @@
#include "deh_mapping.h"
#include "sounds.h"
#if !NO_USE_DEH
DEH_BEGIN_MAPPING(sound_mapping, sfxinfo_t)
DEH_UNSUPPORTED_MAPPING("Offset")
DEH_UNSUPPORTED_MAPPING("Zero/One")
@ -100,3 +102,4 @@ deh_section_t deh_section_sound =
NULL,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -26,6 +27,7 @@
#include "info.h"
#if !NO_USE_DEH
DEH_BEGIN_MAPPING(thing_mapping, mobjinfo_t)
DEH_MAPPING("ID #", doomednum)
DEH_MAPPING("Initial frame", spawnstate)
@ -129,3 +131,4 @@ deh_section_t deh_section_thing =
DEH_ThingSHA1Sum,
};
#endif

View file

@ -1,5 +1,6 @@
//
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -27,6 +28,7 @@
#include "deh_main.h"
#include "deh_mapping.h"
#if !NO_USE_DEH
DEH_BEGIN_MAPPING(weapon_mapping, weaponinfo_t)
DEH_MAPPING("Ammo type", ammo)
DEH_MAPPING("Deselect frame", upstate)
@ -99,3 +101,4 @@ deh_section_t deh_section_weapon =
DEH_WeaponSHA1Sum,
};
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -131,9 +132,14 @@ typedef PACKED_STRUCT (
// Set if already seen, thus drawn in automap.
#define ML_MAPPED 256
#if USE_WHD && WHD_SUPER_TINY
#define ML_NO_PREDICT_SIDE 256 // can share with ML_MAPPED
#define ML_NO_PREDICT_V1 512
#define ML_NO_PREDICT_V2 1024
#define ML_HAS_SPECIAL 2048
#define ML_HAS_TAG 4096
#define ML_SIDE_MASK 0xe000u
#endif
// Sector definition, from editing.
typedef PACKED_STRUCT (
{
@ -206,8 +212,14 @@ typedef PACKED_STRUCT (
short options;
}) mapthing_t;
#if !WHD_SUPER_TINY
#define TAG_666 666
#define TAG_667 667
#else
// we only have one byte tags
#define TAG_666 254
#define TAG_667 255
#endif
#endif // __DOOMDATA__

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -39,7 +40,9 @@
// If rangecheck is undefined,
// most parameter validation debugging code will not be compiled
#ifndef NDEBUG
#define RANGECHECK
#endif
// The maximum number of players, multiplayer/networking.
#define MAXPLAYERS 4
@ -66,7 +69,15 @@ typedef enum
ga_completed,
ga_victory,
ga_worlddone,
ga_screenshot
#if !NO_SCREENSHOT
ga_screenshot,
#endif
#if USE_PICO_NET
ga_newgamenet,
#endif
#if DOOM_TINY
ga_deferredquit,
#endif
} gameaction_t;
//

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -22,14 +23,18 @@
// Game Mode - identify IWAD as shareware, retail etc.
#if !DEMO1_ONLY
GameMode_t gamemode = indetermined;
#endif
GameMission_t gamemission = doom;
GameVersion_t gameversion = exe_final2;
GameVariant_t gamevariant = vanilla;
const char *gamedescription;
#if !DOOM_TINY
// Set if homebrew PWAD stuff has been added.
boolean modifiedgame;
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -53,7 +54,11 @@ extern boolean devparm; // DEBUG: launched with -devparm
// -----------------------------------------------------
// Game Mode - identify IWAD as shareware, retail etc.
//
#if !DEMO1_ONLY
extern GameMode_t gamemode;
#else
#define gamemode shareware
#endif
extern GameMission_t gamemission;
extern GameVersion_t gameversion;
extern GameVariant_t gamevariant;
@ -65,11 +70,15 @@ extern const char *gamedescription;
// as the same most of the time.
#define logical_gamemission \
(gamemission == pack_chex ? doom : \
gamemission == pack_hacx ? doom2 : gamemission)
(gamemission_is_chex(gamemission) ? doom : \
gamemission_is_hacx(gamemission) ? doom2 : gamemission)
// Set if homebrew PWAD stuff has been added.
#if !DOOM_TINY
extern boolean modifiedgame;
#else
#define modifiedgame 0
#endif
// -------------------------------------------
@ -78,20 +87,20 @@ extern boolean modifiedgame;
// Defaults for menu, methinks.
extern skill_t startskill;
extern int startepisode;
extern int startmap;
extern isb_int8_t startepisode;
extern isb_int8_t startmap;
// Savegame slot to load on startup. This is the value provided to
// the -loadgame option. If this has not been provided, this is -1.
extern int startloadgame;
extern isb_int8_t startloadgame;
extern boolean autostart;
// Selected by user.
extern skill_t gameskill;
extern int gameepisode;
extern int gamemap;
extern isb_int8_t gameepisode;
extern isb_int8_t gamemap;
// If non-zero, exit the level after this number of minutes
extern int timelimit;
@ -100,10 +109,14 @@ extern int timelimit;
extern boolean respawnmonsters;
// Netgame? Only true if >1 player.
#if !NO_USE_NET || USE_PICO_NET
extern boolean netgame;
#else
#define netgame false
#endif
// 0=Cooperative; 1=Deathmatch; 2=Altdeath
extern int deathmatch;
extern isb_int8_t deathmatch;
// -------------------------
// Internal parameters for sound rendering.
@ -145,11 +158,20 @@ extern boolean paused; // Game Pause?
extern boolean viewactive;
#if !FORCE_NODRAW
extern boolean nodrawers;
#else
#define nodrawers true
#endif
#if !DOOM_TINY
extern boolean testcontrols;
extern int testcontrols_mousespeed;
#else
#define testcontrols false
#define testcontrols_mousespeed false
#endif
@ -159,8 +181,8 @@ extern int testcontrols_mousespeed;
extern int viewangleoffset;
// Player taking events, and displaying.
extern int consoleplayer;
extern int displayplayer;
extern isb_int8_t consoleplayer;
extern isb_int8_t displayplayer;
// -------------------------------------
@ -185,7 +207,11 @@ extern boolean usergame;
//?
extern boolean demoplayback;
#if !NO_DEMO_RECORDING
extern boolean demorecording;
#else
#define demorecording 0
#endif
// Round angleturn in ticcmds to the nearest 256. This is used when
// recording Vanilla demos in netgames.
@ -243,8 +269,10 @@ extern wbstartstruct_t wminfo;
// Internal parameters, used for engine.
//
#if !NO_FILE_ACCESS
// File handling stuff.
extern char *savegamedir;
#endif
// if true, load all graphics at level load
extern boolean precache;
@ -254,16 +282,16 @@ extern boolean precache;
// to force a wipe on the next draw
extern gamestate_t wipegamestate;
extern int mouseSensitivity;
extern isb_int8_t mouseSensitivity;
extern int bodyqueslot;
extern isb_uint8_t bodyqueslot;
// Needed to store the number of the dummy sky flat.
// Used for rendering,
// as well as tracking projectiles etc.
extern int skyflatnum;
extern flatnum_t skyflatnum;

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -20,7 +21,7 @@
#include "dstrings.h"
const char *doom1_endmsg[] =
const constcharstar doom1_endmsg[NUM_QUITMESSAGES] =
{
"are you sure you want to\nquit this great game?",
"please don't leave, there's more\ndemons to toast!",
@ -32,7 +33,7 @@ const char *doom1_endmsg[] =
"go ahead and leave. see if i care.",
};
const char *doom2_endmsg[] =
const constcharstar doom2_endmsg[NUM_QUITMESSAGES] =
{
// QuitDOOM II messages
"are you sure you want to\nquit this great game?",

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -34,8 +35,9 @@
// 8 per each game type
#define NUM_QUITMESSAGES 8
extern const char *doom1_endmsg[];
extern const char *doom2_endmsg[];
#include "doomtype.h"
extern const constcharstar doom1_endmsg[NUM_QUITMESSAGES];
extern const constcharstar doom2_endmsg[NUM_QUITMESSAGES];
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -35,19 +36,13 @@
#include "sounds.h"
#include "doomstat.h"
#include "r_data.h"
#include "r_state.h"
typedef enum
{
F_STAGE_TEXT,
F_STAGE_ARTSCREEN,
F_STAGE_CAST,
} finalestage_t;
// ?
//#include "doomstat.h"
//#include "r_local.h"
//#include "f_finale.h"
#include "f_finale.h"
// Stage of animation:
finalestage_t finalestage;
@ -67,7 +62,16 @@ typedef struct
static textscreen_t textscreens[] =
{
#if !HACK_FINALE_E1M1
{ doom, 1, 8, "FLOOR4_8", E1TEXT},
#else
#if HACK_FINALE_SHAREWARE
{ doom, 3, 1, "MFLR8_4", E3TEXT}, // for shareware
#else
{ doom, 1, 1, "FLOOR4_8", E1TEXT}, // for ultimate
#endif
#endif
#if !DEMO1_ONLY
{ doom, 2, 8, "SFLR6_1", E2TEXT},
{ doom, 3, 8, "MFLR8_4", E3TEXT},
{ doom, 4, 8, "MFLR8_3", E4TEXT},
@ -92,6 +96,7 @@ static textscreen_t textscreens[] =
{ pack_plut, 1, 30, "RROCK17", P4TEXT},
{ pack_plut, 1, 15, "RROCK13", P5TEXT},
{ pack_plut, 1, 31, "RROCK19", P6TEXT},
#endif
};
const char *finaletext;
@ -100,8 +105,10 @@ const char *finaleflat;
void F_StartCast (void);
void F_CastTicker (void);
boolean F_CastResponder (event_t *ev);
void F_CastDrawer (void);
#if DOOM_TINY
#include "doom/m_menu.h"
#endif
//
// F_StartFinale
//
@ -114,6 +121,9 @@ void F_StartFinale (void)
viewactive = false;
automapactive = false;
#if DOOM_TINY
M_ClearMenus(); // don't want menus in the finale... this way we can prevent help appearing which we don't draw correctly
#endif
if (logical_gamemission == doom)
{
S_ChangeMusic(mus_victor, true);
@ -131,7 +141,7 @@ void F_StartFinale (void)
// Hack for Chex Quest
if (gameversion == exe_chex && screen->mission == doom)
if (gameversion_is_chex(gameversion) && screen->mission == doom)
{
screen->level = 5;
}
@ -141,6 +151,9 @@ void F_StartFinale (void)
&& gamemap == screen->level)
{
finaletext = screen->text;
#if 0 && HACK_FINALE_E1M1
finaletext = "MUCH SHORTER!";
#endif
finaleflat = screen->background;
}
}
@ -159,9 +172,10 @@ void F_StartFinale (void)
boolean F_Responder (event_t *event)
{
#if !NO_USE_FINALE_CAST
if (finalestage == F_STAGE_CAST)
return F_CastResponder (event);
#endif
return false;
}
@ -171,47 +185,50 @@ boolean F_Responder (event_t *event)
//
void F_Ticker (void)
{
size_t i;
size_t i;
// check for skipping
if ( (gamemode == commercial)
&& ( finalecount > 50) )
{
// go on to the next level
for (i=0 ; i<MAXPLAYERS ; i++)
if (players[i].cmd.buttons)
break;
if (i < MAXPLAYERS)
{
if (gamemap == 30)
F_StartCast ();
else
gameaction = ga_worlddone;
}
if ((gamemode == commercial)
&& (finalecount > 50)) {
// go on to the next level
for (i = 0; i < MAXPLAYERS; i++)
if (players[i].cmd.buttons)
break;
if (i < MAXPLAYERS) {
#if !NO_USE_FINALE_CAST
if (gamemap == 30)
F_StartCast();
else
#endif
gameaction = ga_worlddone;
}
}
// advance animation
finalecount++;
if (finalestage == F_STAGE_CAST)
{
F_CastTicker ();
return;
#if !NO_USE_FINALE_CAST
if (finalestage == F_STAGE_CAST) {
F_CastTicker();
return;
}
if ( gamemode == commercial)
return;
#endif
if (gamemode == commercial)
return;
if (finalestage == F_STAGE_TEXT
&& finalecount>strlen (finaletext)*TEXTSPEED + TEXTWAIT)
{
finalecount = 0;
finalestage = F_STAGE_ARTSCREEN;
wipegamestate = -1; // force a wipe
if (gameepisode == 3)
S_StartMusic (mus_bunny);
&& finalecount > strlen(finaletext) * TEXTSPEED + TEXTWAIT) {
finalecount = 0;
finalestage = F_STAGE_ARTSCREEN;
wipegamestate = -1; // force a wipe
if (gameepisode == 3)
S_StartMusic(mus_bunny);
}
#if PICO_ON_DEVICE
I_UpdateSound();
#endif
}
@ -221,12 +238,12 @@ void F_Ticker (void)
//
#include "hu_stuff.h"
extern patch_t *hu_font[HU_FONTSIZE];
extern vpatch_sequence_t hu_font;
void F_TextWrite (void)
{
byte* src;
should_be_const byte* src;
pixel_t* dest;
int x,y,w;
@ -235,7 +252,8 @@ void F_TextWrite (void)
int c;
int cx;
int cy;
#if !DOOM_TINY
// erase the entire screen to a tiled background
src = W_CacheLumpName ( finaleflat , PU_CACHE);
dest = I_VideoBuffer;
@ -255,6 +273,7 @@ void F_TextWrite (void)
}
V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
#endif
// draw some of the text onto the screen
cx = 10;
@ -275,7 +294,7 @@ void F_TextWrite (void)
cy += 11;
continue;
}
c = toupper(c) - HU_FONTSTART;
if (c < 0 || c> HU_FONTSIZE)
{
@ -283,15 +302,22 @@ void F_TextWrite (void)
continue;
}
w = SHORT (hu_font[c]->width);
w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c)));
if (cx+w > SCREENWIDTH)
break;
V_DrawPatch(cx, cy, hu_font[c]);
#if DOOM_TINY
// since we don't clear the screen, only draw the last few character (otherwise we have too many patches)
// note we should only need to draw one, but we seem to skip some and I can't be bothered to figure out why
if (count < 3) V_DrawPatch(cx, cy, vpatch_n(hu_font,c));
#else
V_DrawPatch(cx, cy, vpatch_n(hu_font,c));
#endif
cx+=w;
}
}
#if !NO_USE_FINALE_CAST
//
// Final DOOM 2 animation
// Casting by id Software.
@ -327,7 +353,7 @@ castinfo_t castorder[] = {
int castnum;
int casttics;
state_t* caststate;
should_be_const state_t* caststate;
boolean castdeath;
int castframes;
int castonmelee;
@ -339,10 +365,11 @@ boolean castattacking;
//
void F_StartCast (void)
{
if (finalestage == F_STAGE_CAST) return; // seems to be a bug in general, but seems easier for us to start the cast wipe multiple times
wipegamestate = -1; // force a screen wipe
castnum = 0;
caststate = &states[mobjinfo[castorder[castnum].type].seestate];
casttics = caststate->tics;
casttics = state_tics(caststate);
castdeath = false;
finalestage = F_STAGE_CAST;
castframes = 0;
@ -363,7 +390,7 @@ void F_CastTicker (void)
if (--casttics > 0)
return; // not time to change state yet
if (caststate->tics == -1 || caststate->nextstate == S_NULL)
if (state_tics(caststate) == -1 || caststate->nextstate == S_NULL)
{
// switch from deathstate to next monster
castnum++;
@ -452,7 +479,7 @@ void F_CastTicker (void)
}
}
casttics = caststate->tics;
casttics = state_tics(caststate);
if (casttics == -1)
casttics = 15;
}
@ -473,7 +500,7 @@ boolean F_CastResponder (event_t* ev)
// go into death frame
castdeath = true;
caststate = &states[mobjinfo[castorder[castnum].type].deathstate];
casttics = caststate->tics;
casttics = state_tics(caststate);
castframes = 0;
castattacking = false;
if (mobjinfo[castorder[castnum].type].deathsound)
@ -507,7 +534,7 @@ void F_CastPrint (const char *text)
continue;
}
w = SHORT (hu_font[c]->width);
w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c)));
width += w;
}
@ -526,8 +553,8 @@ void F_CastPrint (const char *text)
continue;
}
w = SHORT (hu_font[c]->width);
V_DrawPatch(cx, 180, hu_font[c]);
w = vpatch_width(resolve_vpatch_handle(vpatch_n(hu_font,c)));
V_DrawPatch(cx, 180, vpatch_n(hu_font,c));
cx+=w;
}
@ -538,32 +565,44 @@ void F_CastPrint (const char *text)
// F_CastDrawer
//
int F_CastSprite(void) {
spriteframeref_t sprframe = sprite_frame(caststate->sprite, caststate->frame & FF_FRAMEMASK);
int lump;
boolean flip;
if (spriteframe_rotates(sprframe)) {
lump = spriteframe_rotated_pic(sprframe, 0);
flip = spriteframe_rotated_flipped(sprframe, 0);
} else {
// use single rotation for all views
lump = spriteframe_unrotated_pic(sprframe);
flip = spriteframe_unrotated_flipped(sprframe);
}
return flip ? -1 - lump : lump;
}
void F_CastDrawer (void)
{
spritedef_t* sprdef;
spriteframe_t* sprframe;
int lump;
boolean flip;
patch_t* patch;
#if !DOOM_TINY
int lump = F_CastSprite();
boolean flip = lump < 0;
if (flip) lump = -1 - lump;
// erase the entire screen to a background
V_DrawPatch (0, 0, W_CacheLumpName (DEH_String("BOSSBACK"), PU_CACHE));
F_CastPrint (DEH_String(castorder[castnum].name));
// draw the current frame in the middle of the screen
sprdef = &sprites[caststate->sprite];
sprframe = &sprdef->spriteframes[ caststate->frame & FF_FRAMEMASK];
lump = sprframe->lump[0];
flip = (boolean)sprframe->flip[0];
patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE);
if (flip)
V_DrawPatchFlipped(SCREENWIDTH/2, 170, patch);
else
V_DrawPatch(SCREENWIDTH/2, 170, patch);
}
should_be_const patch_t *patch = W_CacheLumpNum (lump + firstspritelump, PU_CACHE);
if (flip)
V_DrawPatchFlipped(SCREENWIDTH/2, 170, patch);
else
V_DrawPatch(SCREENWIDTH/2, 170, patch);
#else
F_CastPrint (DEH_String(castorder[castnum].name));
#endif
}
#endif
//
// F_DrawPatchCol
@ -571,7 +610,7 @@ void F_CastDrawer (void)
void
F_DrawPatchCol
( int x,
patch_t* patch,
should_be_const patch_t* patch,
int col )
{
column_t* column;
@ -580,7 +619,7 @@ F_DrawPatchCol
pixel_t* desttop;
int count;
column = (column_t *)((byte *)patch + LONG(patch->columnofs[col]));
column = (column_t *)((byte *)patch + patch_columnofs(patch, col));
desttop = I_VideoBuffer + x;
// step through the posts in a column
@ -599,31 +638,73 @@ F_DrawPatchCol
}
}
#if !NO_USE_FINALE_BUNNY
int F_BunnyScrollPos(void) {
int scrolled = (SCREENWIDTH - ((signed int) finalecount-230)/2);
if (scrolled > SCREENWIDTH)
scrolled = SCREENWIDTH;
if (scrolled < 0)
scrolled = 0;
return scrolled;
}
void F_BunnyDrawPatches(void) {
char name[10];
int stage;
static int laststage;
if (finalecount < 1130)
return;
if (finalecount < 1180)
{
#if !DOOM_TINY
V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
(SCREENHEIGHT - 8 * 8) / 2,
W_CacheLumpName ("END0",PU_CACHE));
#else
V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
(SCREENHEIGHT - 8 * 8) / 2,
VPATCH_NAME(END0));
#endif
laststage = 0;
return;
}
stage = (finalecount-1180) / 5;
if (stage > 6)
stage = 6;
if (stage > laststage)
{
S_StartSound (NULL, sfx_pistol);
laststage = stage;
}
#if !DOOM_TINY
DEH_snprintf(name, 10, "END%i", stage);
V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
(SCREENHEIGHT - 8 * 8) / 2,
W_CacheLumpName (name,PU_CACHE));
#else
V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
(SCREENHEIGHT - 8 * 8) / 2,
VPATCH_END0 + stage);
#endif
}
#if !DOOM_TINY
//
// F_BunnyScroll
//
void F_BunnyScroll (void)
{
signed int scrolled;
signed int scrolled = F_BunnyScrollPos();
int x;
patch_t* p1;
patch_t* p2;
char name[10];
int stage;
static int laststage;
should_be_const patch_t* p1;
should_be_const patch_t* p2;
p1 = W_CacheLumpName (DEH_String("PFUB2"), PU_LEVEL);
p2 = W_CacheLumpName (DEH_String("PFUB1"), PU_LEVEL);
V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
scrolled = (SCREENWIDTH - ((signed int) finalecount-230)/2);
if (scrolled > SCREENWIDTH)
scrolled = SCREENWIDTH;
if (scrolled < 0)
scrolled = 0;
for ( x=0 ; x<SCREENWIDTH ; x++)
{
if (x+scrolled < SCREENWIDTH)
@ -631,69 +712,56 @@ void F_BunnyScroll (void)
else
F_DrawPatchCol (x, p2, x+scrolled - SCREENWIDTH);
}
if (finalecount < 1130)
return;
if (finalecount < 1180)
F_BunnyDrawPatches();
}
#endif
#endif
const char *F_ArtScreenLumpName(void) {
const char* lumpname;
switch (gameepisode)
{
V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
(SCREENHEIGHT - 8 * 8) / 2,
W_CacheLumpName(DEH_String("END0"), PU_CACHE));
laststage = 0;
return;
case 1:
if (gameversion >= exe_ultimate)
{
lumpname = "CREDIT";
}
else
{
lumpname = "HELP2";
}
break;
case 2:
lumpname = "VICTORY2";
break;
case 3:
return NULL; // bunny
case 4:
lumpname = "ENDPIC";
break;
default:
assert(false);
lumpname = "CREDIT";
}
stage = (finalecount-1180) / 5;
if (stage > 6)
stage = 6;
if (stage > laststage)
{
S_StartSound (NULL, sfx_pistol);
laststage = stage;
}
DEH_snprintf(name, 10, "END%i", stage);
V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
(SCREENHEIGHT - 8 * 8) / 2,
W_CacheLumpName (name,PU_CACHE));
return DEH_String(lumpname);
}
static void F_ArtScreenDrawer(void)
{
const char *lumpname;
if (gameepisode == 3)
#if !DOOM_TINY
const char *lumpname = F_ArtScreenLumpName();
if (!lumpname)
{
#if !NO_USE_FINALE_BUNNY
F_BunnyScroll();
}
else
#endif
} else
{
switch (gameepisode)
{
case 1:
if (gameversion >= exe_ultimate)
{
lumpname = "CREDIT";
}
else
{
lumpname = "HELP2";
}
break;
case 2:
lumpname = "VICTORY2";
break;
case 4:
lumpname = "ENDPIC";
break;
default:
return;
}
lumpname = DEH_String(lumpname);
V_DrawPatch (0, 0, W_CacheLumpName(lumpname, PU_CACHE));
}
#endif
}
//
@ -704,8 +772,10 @@ void F_Drawer (void)
switch (finalestage)
{
case F_STAGE_CAST:
#if !DOOM_TINY && !NO_USE_FINALE_CAST
F_CastDrawer();
break;
#endif
case F_STAGE_TEXT:
F_TextWrite();
break;

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -39,7 +40,25 @@ void F_Drawer (void);
void F_StartFinale (void);
const char *F_ArtScreenLumpName(void);
typedef enum
{
F_STAGE_TEXT,
F_STAGE_ARTSCREEN,
F_STAGE_CAST,
} finalestage_t;
// Stage of animation:
extern finalestage_t finalestage;
extern const char *finaleflat;
#if DOOM_TINY
int F_BunnyScrollPos(void);
void F_BunnyDrawPatches(void);
#endif
// return the sprite to to draw or -1-x to draw x flipped
int F_CastSprite(void);
void F_CastDrawer (void);
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -234,8 +235,10 @@ wipe_StartScreen
int width,
int height )
{
wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_start), PU_STATIC, NULL);
#if !NO_USE_WIPE
wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_start), PU_STATIC, 0);
I_ReadScreen(wipe_scr_start);
#endif
return 0;
}
@ -246,9 +249,11 @@ wipe_EndScreen
int width,
int height )
{
wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_end), PU_STATIC, NULL);
#if !NO_USE_WIPE
wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT * sizeof(*wipe_scr_end), PU_STATIC, 0);
I_ReadScreen(wipe_scr_end);
V_DrawBlock(x, y, width, height, wipe_scr_start); // restore start scr.
#endif
return 0;
}
@ -261,6 +266,7 @@ wipe_ScreenWipe
int height,
int ticks )
{
#if !NO_USE_WIPE
int rc;
static int (*wipes[])(int, int, int) =
{
@ -290,5 +296,8 @@ wipe_ScreenWipe
}
return !go;
#else
return 1;
#endif
}

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -60,4 +61,16 @@ wipe_ScreenWipe
int height,
int ticks );
#if DOOM_TINY
typedef enum {
WIPESTATE_NONE,
WIPESTATE_SKIP1,
WIPESTATE_REDRAW1,
WIPESTATE_SKIP2,
WIPESTATE_REDRAW2,
WIPESTATE_SKIP3,
} wipestate_t;
extern wipestate_t wipestate;
extern volatile uint8_t wipe_min;
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -35,7 +36,7 @@ void G_InitNew (skill_t skill, int episode, int map);
// Can be called by the startup code or M_Responder.
// A normal game starts at map 1,
// but a warp test can start elsewhere
void G_DeferedInitNew (skill_t skill, int episode, int map);
void G_DeferedInitNew (skill_t skill, int episode, int map, boolean net);
void G_DeferedPlayDemo (const char* demo);
@ -48,10 +49,12 @@ void G_DoLoadGame (void);
// Called by M_Responder.
void G_SaveGame (int slot, char* description);
#if !NO_DEMO_RECORDING
// Only called by startup code.
void G_RecordDemo (char* name);
void G_BeginRecording (void);
#endif
void G_PlayDemo (char* name);
void G_TimeDemo (char* name);
@ -74,7 +77,11 @@ void G_ScreenShot (void);
void G_DrawMouseSpeedBox(void);
int G_VanillaVersionCode(void);
extern int vanilla_savegame_limit;
extern int vanilla_demo_limit;
extern isb_int8_t vanilla_savegame_limit;
extern isb_int8_t vanilla_demo_limit;
#if PICO_ON_DEVICE
extern uint8_t g_load_slot;
#endif
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -24,6 +25,7 @@
#include "v_video.h"
#include "i_swap.h"
#include "w_wad.h"
#include "hu_lib.h"
#include "r_local.h"
#include "r_draw.h"
@ -49,7 +51,7 @@ HUlib_initTextLine
( hu_textline_t* t,
int x,
int y,
patch_t** f,
vpatch_sequence_t f,
int sc )
{
t->x = x;
@ -110,10 +112,10 @@ HUlib_drawTextLine
&& c >= l->sc
&& c <= '_')
{
w = SHORT(l->f[c - l->sc]->width);
w = vpatch_width(resolve_vpatch_handle(vpatch_n(l->f,c - l->sc)));
if (x+w > SCREENWIDTH)
break;
V_DrawPatchDirect(x, l->y, l->f[c - l->sc]);
V_DrawPatchDirect(x, l->y, vpatch_n(l->f, c - l->sc));
x += w;
}
else
@ -126,9 +128,9 @@ HUlib_drawTextLine
// draw the cursor if requested
if (drawcursor
&& x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH)
&& x + vpatch_width(resolve_vpatch_handle(vpatch_n(l->f,'_' - l->sc))) <= SCREENWIDTH)
{
V_DrawPatchDirect(x, l->y, l->f['_' - l->sc]);
V_DrawPatchDirect(x, l->y, vpatch_n(l->f, '_' - l->sc));
}
}
@ -144,10 +146,11 @@ void HUlib_eraseTextLine(hu_textline_t* l)
// and the text must either need updating or refreshing
// (because of a recent change back from the automap)
#if !NO_RDRAW
if (!automapactive &&
viewwindowx && l->needsupdate)
{
lh = SHORT(l->f[0]->height) + 1;
lh = patch_height(l->f[0]) + 1;
for (y=l->y,yoffset=y*SCREENWIDTH ; y<l->y+lh ; y++,yoffset+=SCREENWIDTH)
{
if (y < viewwindowy || y >= viewwindowy + viewheight)
@ -160,6 +163,7 @@ void HUlib_eraseTextLine(hu_textline_t* l)
}
}
}
#endif
if (l->needsupdate) l->needsupdate--;
@ -171,7 +175,7 @@ HUlib_initSText
int x,
int y,
int h,
patch_t** font,
vpatch_sequence_t font,
int startchar,
boolean* on )
{
@ -182,9 +186,10 @@ HUlib_initSText
s->on = on;
s->laston = true;
s->cl = 0;
int pheight = vpatch_height(resolve_vpatch_handle(vpatch_n(font, 0)));
for (i=0;i<h;i++)
HUlib_initTextLine(&s->l[i],
x, y - i*(SHORT(font[0]->height)+1),
x, y - i*(pheight+1),
font, startchar);
}
@ -263,7 +268,7 @@ HUlib_initIText
( hu_itext_t* it,
int x,
int y,
patch_t** font,
vpatch_sequence_t font,
int startchar,
boolean* on )
{

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -39,7 +40,7 @@ typedef struct
int x;
int y;
patch_t** f; // font
vpatch_sequence_t f; // font
int sc; // start character
char l[HU_MAXLINELENGTH+1]; // line of text
int len; // current line length
@ -97,7 +98,7 @@ void HUlib_init(void);
// clear a line of text
void HUlib_clearTextLine(hu_textline_t *t);
void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc);
void HUlib_initTextLine(hu_textline_t *t, int x, int y, vpatch_sequence_t f, int sc);
// returns success
boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch);
@ -123,7 +124,7 @@ HUlib_initSText
int x,
int y,
int h,
patch_t** font,
vpatch_sequence_t font,
int startchar,
boolean* on );
@ -149,7 +150,7 @@ HUlib_initIText
( hu_itext_t* it,
int x,
int y,
patch_t** font,
vpatch_sequence_t font,
int startchar,
boolean* on );

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -41,6 +42,7 @@
// Data.
#include "dstrings.h"
#include "sounds.h"
#include "r_data.h"
//
// Locally used constants, shortcuts.
@ -52,17 +54,19 @@
#define HU_TITLE_CHEX (mapnames_chex[(gameepisode-1)*9+gamemap-1])
#define HU_TITLEHEIGHT 1
#define HU_TITLEX 0
#define HU_TITLEY (167 - SHORT(hu_font[0]->height))
#define HU_TITLEY (167 - vpatch_height(resolve_vpatch_handle(vpatch_n(hu_font,0))))
#define HU_INPUTTOGGLE 't'
#define HU_INPUTX HU_MSGX
#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(SHORT(hu_font[0]->height) +1))
#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(vpatch_height(resolve_vpatch_handle(vpatch_n(hu_font,0))) +1))
#define HU_INPUTWIDTH 64
#define HU_INPUTHEIGHT 1
char *chat_macros[10] =
#if !DOOM_TINY
const char *chat_macros[10] =
#else
const constcharstar chat_macros[10] =
#endif
{
HUSTR_CHATMACRO0,
HUSTR_CHATMACRO1,
@ -76,7 +80,7 @@ char *chat_macros[10] =
HUSTR_CHATMACRO9
};
const char *player_names[] =
const constcharstar player_names[] =
{
HUSTR_PLRGREEN,
HUSTR_PLRINDIGO,
@ -86,7 +90,12 @@ const char *player_names[] =
char chat_char; // remove later.
static player_t* plr;
patch_t* hu_font[HU_FONTSIZE];
#if !USE_WHD
vpatch_handle_large_t hu_fontx[HU_FONTSIZE];
vpatch_sequence_t hu_font = hu_fontx;
#else
vpatch_sequence_t hu_font;
#endif
static hu_textline_t w_title;
boolean chat_on;
static hu_itext_t w_chat;
@ -101,7 +110,7 @@ static boolean message_nottobefuckedwith;
static hu_stext_t w_message;
static int message_counter;
extern int showMessages;
extern isb_int8_t showMessages;
static boolean headsupactive = false;
@ -110,7 +119,7 @@ static boolean headsupactive = false;
// The actual names can be found in DStrings.h.
//
const char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names.
const constcharstar mapnames[] = // DOOM shareware/registered/retail (Ultimate) names.
{
HUSTR_E1M1,
@ -123,6 +132,7 @@ const char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names.
HUSTR_E1M8,
HUSTR_E1M9,
#if !DEMO1_ONLY
HUSTR_E2M1,
HUSTR_E2M2,
HUSTR_E2M3,
@ -162,6 +172,7 @@ const char *mapnames[] = // DOOM shareware/registered/retail (Ultimate) names.
"NEWLEVEL",
"NEWLEVEL",
"NEWLEVEL"
#endif
};
const char *mapnames_chex[] = // Chex Quest names.
@ -224,7 +235,7 @@ const char *mapnames_chex[] = // Chex Quest names.
// the layout in the Vanilla executable, where it is possible to
// overflow the end of one array into the next.
const char *mapnames_commercial[] =
const char * const mapnames_commercial[] =
{
// DOOM 2 map names.
@ -352,12 +363,19 @@ void HU_Init(void)
char buffer[9];
// load the heads-up font
#if !USE_WHD
j = HU_FONTSTART;
for (i=0;i<HU_FONTSIZE;i++)
{
DEH_snprintf(buffer, 9, "STCFN%.3d", j++);
hu_font[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC);
}
#else
static_assert(HU_FONTSTART == 33, "");
static_assert(HU_FONTSIZE == 95 - 33 + 1, "");
hu_font = VPATCH_STCFN033;
// todo what about STCFN121 !!?
#endif
}
@ -398,6 +416,7 @@ void HU_Start(void)
case doom:
s = HU_TITLE;
break;
#if !DEMO1_ONLY
case doom2:
s = HU_TITLE2;
// Pre-Final Doom compatibility: map33-map35 names don't spill over
@ -411,16 +430,19 @@ void HU_Start(void)
break;
case pack_tnt:
s = HU_TITLET;
#endif
break;
default:
s = "Unknown level";
break;
}
#if !DOOM_ONLY
if (logical_gamemission == doom && gameversion == exe_chex)
{
s = HU_TITLE_CHEX;
}
#endif
// dehacked substitution to get modified level name
@ -445,12 +467,12 @@ void HU_Start(void)
void HU_Drawer(void)
{
#if !NO_HU_DRAWER
HUlib_drawSText(&w_message);
HUlib_drawIText(&w_chat);
if (automapactive)
HUlib_drawTextLine(&w_title, false);
HUlib_drawTextLine(&w_title, false);
#endif
}
void HU_Erase(void)
@ -521,9 +543,9 @@ void HU_Ticker(void)
message_on = true;
message_counter = HU_MSGTIMEOUT;
if ( gamemode == commercial )
S_StartSound(0, sfx_radio);
S_StartUnpositionedSound( sfx_radio);
else
S_StartSound(0, sfx_tink);
S_StartUnpositionedSound( sfx_tink);
}
HUlib_resetIText(&w_inputbuffer[i]);
}
@ -535,6 +557,8 @@ void HU_Ticker(void)
}
#if !NO_USE_NET
#define QUEUESIZE 128
static char chatchars[QUEUESIZE];
@ -716,3 +740,4 @@ boolean HU_Responder(event_t *ev)
return eatkey;
}
#endif

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -53,7 +54,11 @@ void HU_Drawer(void);
char HU_dequeueChatChar(void);
void HU_Erase(void);
extern char *chat_macros[10];
#if !DOOM_TINY
extern const char *chat_macros[10];
#else
extern const constcharstar chat_macros[10];
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
// Copyright(C) 2021-2022 Graham Sanderson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
@ -21,6 +22,8 @@
#ifndef __INFO__
#define __INFO__
#include "doomdef.h"
// Needed for action function pointer handling.
#include "d_think.h"
@ -166,7 +169,11 @@ typedef enum
SPR_TLP2,
NUMSPRITES
} spritenum_t;
} spritenum_t_orig;
#include <assert.h>
static_assert(NUMSPRITES <= 256, "");
typedef uint8_t spritenum_t;
typedef enum
{
@ -1138,22 +1145,52 @@ typedef enum
S_TECH2LAMP3,
S_TECH2LAMP4,
NUMSTATES
} statenum_t;
} statenum_t_orig;
static_assert(NUMSTATES < 1024, ""); // just so we know
typedef uint16_t statenum_t;
#include <stdint.h>
typedef struct
{
#if !DOOM_SMALL
spritenum_t sprite;
int frame;
int tics;
// void (*action) ();
actionf_t action;
statenum_t nextstate;
#ifndef NO_USE_STATE_MISC
int misc1;
int misc2;
#endif
#else
uint8_t sprite;
#if !DOOM_SMALL
uint16_t frame; // todo highbit is a flag
#else
uint8_t frame;
#endif
int8_t xtics;
// void (*action) ();
actionf_t action; // could be trivially small
statenum_t nextstate;
#if !NO_USE_STATE_MISC
int misc1;
int misc2;
#endif
#endif
} state_t;
#if !DOOM_CONST
#define state_tics(s) ((s)->tics)
#else
int state_tics(const state_t *s);
extern boolean nightmare_speeds;
#endif
extern should_be_const state_t states[NUMSTATES];
extern state_t states[NUMSTATES];
extern const char *sprnames[];
typedef enum {
@ -1296,36 +1333,40 @@ typedef enum {
MT_MISC86,
NUMMOBJTYPES
} mobjtype_t;
} mobjtype_t_orig;
static_assert(NUMMOBJTYPES<256, "");
typedef uint8_t mobjtype_t;
typedef struct
{
int doomednum;
int spawnstate;
int spawnhealth;
int seestate;
int seesound;
int reactiontime;
int attacksound;
int painstate;
int painchance;
int painsound;
int meleestate;
int missilestate;
int deathstate;
int xdeathstate;
int deathsound;
isb_int16_t doomednum;
statenum_t spawnstate;
isb_int16_t spawnhealth;
statenum_t seestate;
isb_uint8_t seesound;
isb_int8_t reactiontime;
isb_uint8_t attacksound;
statenum_t painstate;
isb_int16_t painchance;
isb_uint8_t painsound;
statenum_t meleestate;
statenum_t missilestate;
statenum_t deathstate;
statenum_t xdeathstate;
isb_uint8_t deathsound;
int speed;
int radius;
int height;
int radius; // todo wasteful
int height; // todo wasteful
int mass;
int damage;
int activesound;
isb_int8_t damage;
isb_uint8_t activesound;
int flags;
int raisestate;
statenum_t raisestate;
} mobjinfo_t;
extern mobjinfo_t mobjinfo[NUMMOBJTYPES];
extern should_be_const mobjinfo_t mobjinfo[NUMMOBJTYPES];
#endif

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more